diff --git a/html/src/app.js b/html/src/app.js index f9f88c76..8e6d9174 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -6407,6 +6407,9 @@ speechSynthesis.getVoices(); case 'Event': this.speak(noty.data); break; + case 'External': + this.speak(noty.message); + break; case 'VideoPlay': this.speak(`Now playing: ${noty.notyName}`); break; @@ -6642,6 +6645,9 @@ speechSynthesis.getVoices(); case 'Event': AppApi.XSNotification('VRCX', noty.data, timeout, image); break; + case 'External': + AppApi.XSNotification('VRCX', noty.message, timeout, image); + break; case 'VideoPlay': AppApi.XSNotification( 'VRCX', @@ -6909,6 +6915,9 @@ speechSynthesis.getVoices(); case 'Event': AppApi.DesktopNotification('Event', noty.data, image); break; + case 'External': + AppApi.DesktopNotification('External', noty.message, image); + break; case 'VideoPlay': AppApi.DesktopNotification('Now playing', noty.notyName, image); break; @@ -9532,6 +9541,14 @@ speechSynthesis.getVoices(); return true; } return false; + case 'External': + if (String(row.message).toUpperCase().includes(value)) { + return true; + } + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + return false; case 'VideoPlay': if (String(row.displayName).toUpperCase().includes(value)) { return true; @@ -14445,6 +14462,7 @@ speechSynthesis.getVoices(); 'group.queueReady': 'On', PortalSpawn: 'Everyone', Event: 'On', + External: 'On', VideoPlay: 'Off', BlockedOnPlayerJoined: 'Off', BlockedOnPlayerLeft: 'Off', @@ -14482,6 +14500,7 @@ speechSynthesis.getVoices(); 'group.queueReady': 'On', PortalSpawn: 'Everyone', Event: 'On', + External: 'On', VideoPlay: 'On', BlockedOnPlayerJoined: 'Off', BlockedOnPlayerLeft: 'Off', @@ -14525,6 +14544,10 @@ speechSynthesis.getVoices(); $app.data.sharedFeedFilters.noty['group.queueReady'] = 'On'; $app.data.sharedFeedFilters.wrist['group.queueReady'] = 'On'; } + if (!$app.data.sharedFeedFilters.noty.External) { + $app.data.sharedFeedFilters.noty.External = 'On'; + $app.data.sharedFeedFilters.wrist.External = 'On'; + } $app.data.trustColor = JSON.parse( configRepository.getString( @@ -23330,7 +23353,8 @@ speechSynthesis.getVoices(); joinLeave: await database.getJoinLeaveTableSize(), portalSpawn: await database.getPortalSpawnTableSize(), videoPlay: await database.getVideoPlayTableSize(), - event: await database.getEventTableSize() + event: await database.getEventTableSize(), + external: await database.getExternalTableSize() }; }; @@ -23418,14 +23442,11 @@ speechSynthesis.getVoices(); this.photonLoggingEnabled = true; configRepository.setBool('VRCX_photonLoggingEnabled', true); } - if (!this.companionUpdateReminder && data.version < '1.1.3') { - // check version - this.promptCompanionUpdateReminder(); - } this.ipcEnabled = true; this.ipcTimeout = 60; // 30secs break; case 'MsgPing': + this.externalNotifierVersion = data.version; break; case 'LaunchCommand': AppApi.FocusWindow(); @@ -23439,16 +23460,7 @@ speechSynthesis.getVoices(); } }; - $app.data.companionUpdateReminder = false; - - $app.methods.promptCompanionUpdateReminder = function () { - this.$alert( - 'An update is required for it to function properly.', - 'VRCX Companion mod is out of date' - ); - this.companionUpdateReminder = true; - }; - + $app.data.externalNotifierVersion = 0; $app.data.photonEventCount = 0; $app.data.photonEventIcon = false; $app.data.customUserTags = new Map(); @@ -23493,6 +23505,13 @@ speechSynthesis.getVoices(); }); break; case 'Noty': + if ( + this.photonLoggingEnabled || + (this.externalNotifierVersion && + this.externalNotifierVersion > 21) + ) { + return; + } var entry = { created_at: new Date().toJSON(), type: 'Event', @@ -23502,6 +23521,20 @@ speechSynthesis.getVoices(); this.queueGameLogNoty(entry); this.addGameLog(entry); break; + case 'External': + var displayName = data.DisplayName ?? ''; + var entry = { + created_at: new Date().toJSON(), + type: 'External', + message: data.Data, + displayName, + userId: data.UserId, + location: this.lastLocation.location + }; + database.addGamelogExternalToDatabase(entry); + this.queueGameLogNoty(entry); + this.addGameLog(entry); + break; default: console.log('VRCXMessage:', data); break; diff --git a/html/src/index.pug b/html/src/index.pug index 9a076349..98186067 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -1646,6 +1646,11 @@ html el-radio-group(v-model="sharedFeedFilters.noty.Event" size="mini") el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }} el-radio-button(label="On") {{ $t('dialog.shared_feed_filters.on') }} + .toggle-item + span.toggle-name External + el-radio-group(v-model="sharedFeedFilters.noty.External" size="mini") + el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }} + el-radio-button(label="On") {{ $t('dialog.shared_feed_filters.on') }} .toggle-item span.toggle-name Blocked Player Joins el-radio-group(v-model="sharedFeedFilters.noty.BlockedOnPlayerJoined" size="mini") @@ -1863,6 +1868,11 @@ html el-radio-group(v-model="sharedFeedFilters.wrist.Event" size="mini") el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }} el-radio-button(label="On") {{ $t('dialog.shared_feed_filters.on') }} + .toggle-item + span.toggle-name External + el-radio-group(v-model="sharedFeedFilters.wrist.External" size="mini") + el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }} + el-radio-button(label="On") {{ $t('dialog.shared_feed_filters.on') }} .toggle-item span.toggle-name Blocked Player Joins el-radio-group(v-model="sharedFeedFilters.wrist.BlockedOnPlayerJoined" size="mini") diff --git a/html/src/mixins/tabs/gameLog.pug b/html/src/mixins/tabs/gameLog.pug index 67c59da5..cb46ac91 100644 --- a/html/src/mixins/tabs/gameLog.pug +++ b/html/src/mixins/tabs/gameLog.pug @@ -4,7 +4,7 @@ mixin gameLogTab() template(#tool) div(style="margin:0 0 10px;display:flex;align-items:center") el-select(v-model="gameLogTable.filter" @change="gameLogTableLookup" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.game_log.filter_placeholder')") - el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'Event', 'VideoPlay', 'StringLoad', 'ImageLoad']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'VideoPlay', 'Event', 'External', 'StringLoad', 'ImageLoad']" :key="type" :label="type" :value="type") el-input(v-model="gameLogTable.search" :placeholder="$t('view.game_log.search_placeholder')" @keyup.native.13="gameLogTableLookup" @change="gameLogTableLookup" clearable style="flex:none;width:150px;margin:0 10px") //- el-tooltip(placement="bottom" content="Reload game log" :disabled="hideTooltips") //- el-button(type="default" @click="resetGameLog" icon="el-icon-refresh" circle style="flex:none") @@ -34,6 +34,8 @@ mixin gameLogTab() location(v-else-if="scope.row.type === 'PortalSpawn'" :location="scope.row.instanceId" :hint="scope.row.worldName" :grouphint="scope.row.groupName") template(v-else-if="scope.row.type === 'Event'") span(v-text="scope.row.data") + template(v-else-if="scope.row.type === 'External'") + span(v-text="scope.row.message") template(v-else-if="scope.row.type === 'VideoPlay'") span(v-if="scope.row.videoId" style="margin-right:5px") {{ scope.row.videoId }}: span(v-if="scope.row.videoId === 'LSMedia'" v-text="scope.row.videoName") diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 41cadc2a..5f13f780 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -65,6 +65,9 @@ class Database { await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS gamelog_event (id INTEGER PRIMARY KEY, created_at TEXT, data TEXT, UNIQUE(created_at, data))` ); + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS gamelog_external (id INTEGER PRIMARY KEY, created_at TEXT, message TEXT, display_name TEXT, user_id TEXT, location TEXT, UNIQUE(created_at, message))` + ); await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS cache_avatar (id TEXT PRIMARY KEY, added_at TEXT, author_id TEXT, author_name TEXT, created_at TEXT, description TEXT, image_url TEXT, name TEXT, release_status TEXT, thumbnail_image_url TEXT, updated_at TEXT, version INTEGER)` ); @@ -565,6 +568,18 @@ class Database { }; gamelogDatabase.unshift(row); }, `SELECT * FROM gamelog_event WHERE created_at >= date('${dateOffset}') ORDER BY id DESC`); + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: 'External', + message: dbRow[2], + displayName: dbRow[3], + userId: dbRow[4], + location: dbRow[5] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_external WHERE created_at >= date('${dateOffset}') ORDER BY id DESC`); var compareByCreatedAt = function (a, b) { var A = a.created_at; var B = b.created_at; @@ -701,6 +716,19 @@ class Database { ); } + addGamelogExternalToDatabase(entry) { + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO gamelog_external (created_at, message, display_name, user_id, location) VALUES (@created_at, @message, @display_name, @user_id, @location)`, + { + '@created_at': entry.created_at, + '@message': entry.message, + '@display_name': entry.displayName, + '@user_id': entry.userId, + '@location': entry.location + } + ); + } + async getNotifications() { var notifications = []; await sqliteService.execute((dbRow) => { @@ -906,6 +934,14 @@ class Database { return size; } + async getExternalTableSize() { + var size = 0; + await sqliteService.execute((row) => { + size = row[0]; + }, `SELECT COUNT(*) FROM gamelog_external`); + return size; + } + async getLastVisit(worldId, currentWorldMatch) { if (currentWorldMatch) { var count = 2; @@ -1552,6 +1588,7 @@ class Database { var onplayerleft = true; var portalspawn = true; var msgevent = true; + var external = true; var videoplay = true; var resourceload_string = true; var resourceload_image = true; @@ -1561,6 +1598,7 @@ class Database { onplayerleft = false; portalspawn = false; msgevent = false; + external = false; videoplay = false; resourceload_string = false; resourceload_image = false; @@ -1581,6 +1619,9 @@ class Database { case 'Event': msgevent = true; break; + case 'External': + external = true; + break; case 'VideoPlay': videoplay = true; break; @@ -1657,6 +1698,20 @@ class Database { gamelogDatabase.unshift(row); }, `SELECT * FROM gamelog_event WHERE data LIKE '%${search}%' ORDER BY id DESC LIMIT ${Database.maxTableSize}`); } + if (external) { + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: 'External', + message: dbRow[2], + displayName: dbRow[3], + userId: dbRow[4], + location: dbRow[5] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_external WHERE (display_name LIKE '%${search}%' OR message LIKE '%${search}%') ORDER BY id DESC LIMIT ${Database.maxTableSize}`); + } if (videoplay) { await sqliteService.execute((dbRow) => { var row = { @@ -1855,6 +1910,9 @@ class Database { case 'Event': this.deleteGameLogEvent(input); break; + case 'External': + this.deleteGameLogExternal(input); + break; case 'StringLoad': case 'ImageLoad': this.deleteGameLogResourceLoad(input); @@ -1883,6 +1941,16 @@ class Database { ); } + deleteGameLogExternal(input) { + sqliteService.executeNonQuery( + `DELETE FROM gamelog_external WHERE created_at = @created_at AND message = @message`, + { + '@created_at': input.created_at, + '@message': input.message + } + ); + } + deleteGameLogResourceLoad(input) { sqliteService.executeNonQuery( `DELETE FROM gamelog_resource_load WHERE created_at = @created_at AND resource_url = @resource_url AND location = @location`, diff --git a/html/src/vr.js b/html/src/vr.js index 5cd50588..dc366c05 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -609,6 +609,9 @@ Vue.component('marquee-text', MarqueeText); case 'Event': text = escapeTag(noty.data); break; + case 'External': + text = escapeTag(noty.message); + break; case 'VideoPlay': text = `Now playing: ${escapeTag( noty.notyName diff --git a/html/src/vr.pug b/html/src/vr.pug index 76ce5c85..af83536b 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -175,6 +175,11 @@ html span.extra span.time {{ feed.created_at | formatDate }} | 🛑 #[span.name(v-text="feed.data")] + div(v-else-if="feed.type === 'External'" class="x-friend-item") + .detail + span.extra + span.time {{ feed.created_at | formatDate }} + | 🟠 #[span.name(v-text="feed.message")] #[span.name(v-text="feed.displayName")] div(v-else-if="feed.type === 'BlockedOnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -381,6 +386,11 @@ html span.extra span.time {{ feed.created_at | formatDate }} | Event: #[span.name(v-text="feed.data")] + div(v-else-if="feed.type === 'External'" class="x-friend-item") + .detail + span.extra + span.time {{ feed.created_at | formatDate }} + | External: #[span.name(v-text="feed.message")] #[span.name(v-text="feed.displayName")] div(v-else-if="feed.type === 'BlockedOnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra