diff --git a/LogWatcher.cs b/LogWatcher.cs index 5c28e92a..e2ecafdb 100644 --- a/LogWatcher.cs +++ b/LogWatcher.cs @@ -224,7 +224,9 @@ namespace VRCX ParseLogUsharpVideoSync(fileInfo, logContext, line, offset) || ParseLogWorldVRCX(fileInfo, logContext, line, offset) || ParseLogOnAudioConfigurationChanged(fileInfo, logContext, line, offset) || - ParseLogScreenshot(fileInfo, logContext, line, offset)) + ParseLogScreenshot(fileInfo, logContext, line, offset) || + ParseLogStringDownload(fileInfo, logContext, line, offset) || + ParseLogImageDownload(fileInfo, logContext, line, offset)) { } } @@ -928,6 +930,40 @@ namespace VRCX return true; } + private bool ParseLogStringDownload(FileInfo fileInfo, LogContext logContext, string line, int offset) + { + // 2023.03.23 11:37:21 Log - [String Download] Attempting to load String from URL 'https://pastebin.com/raw/BaW6NL2L' + string check = "] Attempting to load String from URL '"; + if (!line.Contains(check)) + return false; + + var lineOffset = line.LastIndexOf(check); + if (lineOffset < 0) + return true; + + var stringData = line.Substring(lineOffset + check.Length); + stringData = stringData.Remove(stringData.Length - 1); + AppendLog(new[] { fileInfo.Name, ConvertLogTimeToISO8601(line), "resource-load", stringData, "string" }); + return true; + } + + private bool ParseLogImageDownload(FileInfo fileInfo, LogContext logContext, string line, int offset) + { + // 2023.03.23 11:32:25 Log - [Image Download] Attempting to load image from URL 'https://i.imgur.com/lCfUMX0.jpeg' + string check = "] Attempting to load image from URL '"; + if (!line.Contains(check)) + return false; + + var lineOffset = line.LastIndexOf(check); + if (lineOffset < 0) + return true; + + var imageData = line.Substring(lineOffset + check.Length); + imageData = imageData.Remove(imageData.Length - 1); + AppendLog(new[] { fileInfo.Name, ConvertLogTimeToISO8601(line), "resource-load", imageData, "image" }); + return true; + } + public string[][] Get() { Update(); diff --git a/html/src/app.js b/html/src/app.js index 999b6802..67d5cee0 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -9378,6 +9378,19 @@ speechSynthesis.getVoices(); this.nowPlaying.offset = parseInt(timestamp, 10); } break; + case 'resource-load': + if (!this.logResourceLoad) { + break; + } + var entry = { + created_at: gameLog.dt, + type: gameLog.resourceType === 'string' ? 'StringLoad' : 'ImageLoad', + resourceUrl: gameLog.resourceUrl, + resourceType: gameLog.resourceType, + location + }; + database.addGamelogResourceLoadToDatabase(entry); + break; case 'screenshot': if (!this.screenshotHelper) { break; @@ -13359,6 +13372,10 @@ speechSynthesis.getVoices(); AppApi.ExecuteVrOverlayFunction('updateHudTimeout', '[]'); } }; + $app.data.logResourceLoad = configRepository.getBool('VRCX_logResourceLoad', false); + $app.methods.saveGameLogOptions = function() { + configRepository.setBool('VRCX_logResourceLoad', this.logResourceLoad); + }; // setting defaults if (!configRepository.getString('VRCX_notificationPosition')) { diff --git a/html/src/index.pug b/html/src/index.pug index 5e20f077..00d1fba0 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -451,7 +451,7 @@ html 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']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'Event', 'VideoPlay', '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") @@ -486,6 +486,10 @@ html span(v-if="scope.row.videoId === 'LSMedia'" v-text="scope.row.videoName") span.x-link(v-else-if="scope.row.videoName" @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoName") span.x-link(v-else @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoUrl") + template(v-else-if="scope.row.type === 'ImageLoad'") + span.x-link(@click="openExternalLink(scope.row.resourceUrl)" v-text="scope.row.resourceUrl") + template(v-else-if="scope.row.type === 'StringLoad'") + span.x-link(@click="openExternalLink(scope.row.resourceUrl)" v-text="scope.row.resourceUrl") template(v-else-if="scope.row.type === 'Notification' || scope.row.type === 'OnPlayerJoined' || scope.row.type === 'OnPlayerLeft'") span.x-link(v-else v-text="scope.row.data") @@ -1150,6 +1154,12 @@ html span.name {{ $t("view.settings.general.application.tray") }} el-switch(v-model="isCloseToTray") div.options-container + div.options-container + span.header {{ $t("view.settings.general.game_log.header") }} + div.options-container-item + span.name {{ $t("view.settings.general.game_log.resource_load") }} + el-switch(v-model="logResourceLoad" @change="saveGameLogOptions") + div.options-container div.options-container(style="margin-top:45px;border-top:1px solid #eee;padding-top:30px") span.header {{ $t("view.settings.general.legal_notice.header" )}} div.options-container-item diff --git a/html/src/localization/strings/en.json b/html/src/localization/strings/en.json index 4a0cc15a..96e356bd 100644 --- a/html/src/localization/strings/en.json +++ b/html/src/localization/strings/en.json @@ -202,6 +202,10 @@ "minimized": "Start as minimized state", "tray": "Close to tray" }, + "game_log": { + "header": "Game Log", + "resource_load": "Log Resource Load entries" + }, "legal_notice": { "header": "Legal Notice", "info": "VRCX is an assistant application for VRChat that provides information about and managing friendship. This application makes use of the unofficial VRChat API SDK.", diff --git a/html/src/localization/strings/ko.json b/html/src/localization/strings/ko.json index 8e080ff8..5706dcd6 100644 --- a/html/src/localization/strings/ko.json +++ b/html/src/localization/strings/ko.json @@ -201,6 +201,9 @@ "minimized": "최소화 상태로 시작", "tray": "닫지 않고 트레이로 최소화하기" }, + "game_log": { + "header": "게임 기록" + }, "legal_notice": { "header": "법적 공지", "info": "VRCX는 친구 관계에 도움을 줄 만한 정보를 제공하는 보조 프로그램입니다. 비공식 VRChat 응용프로그램 프로그래밍 인터페이스를 사용합니다 (VRCSDK).", diff --git a/html/src/localization/strings/zh_CN.json b/html/src/localization/strings/zh_CN.json index f5b6f1a7..18bc8fe9 100644 --- a/html/src/localization/strings/zh_CN.json +++ b/html/src/localization/strings/zh_CN.json @@ -202,6 +202,9 @@ "minimized": "以最小化的形式启动", "tray": "最小化到系统托盘" }, + "game_log": { + "header": "游戏日志" + }, "legal_notice": { "header": "法律声明", "info": "VRCX 是一个提供VRChat好友管理的辅助应用程序。这个程序使用非官方的 VRChat API (VRCSDK)。", diff --git a/html/src/localization/strings/zh_TW.json b/html/src/localization/strings/zh_TW.json index c6f386f8..c9a854fc 100644 --- a/html/src/localization/strings/zh_TW.json +++ b/html/src/localization/strings/zh_TW.json @@ -202,6 +202,9 @@ "minimized": "以最小化啟動", "tray": "最小化到系統列" }, + "game_log": { + "header": "遊戲紀錄" + }, "legal_notice": { "header": "法律聲明", "info": "VRCX 是一個提供好友管理的輔助應用程式。這個程式使用非官方的 VRChat API (VRCSDK)。", diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 6771cff2..788ed331 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -56,6 +56,9 @@ class Database { await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS gamelog_video_play (id INTEGER PRIMARY KEY, created_at TEXT, video_url TEXT, video_name TEXT, video_id TEXT, location TEXT, display_name TEXT, user_id TEXT, UNIQUE(created_at, video_url))` ); + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS gamelog_resource_load (id INTEGER PRIMARY KEY, created_at TEXT, resource_url TEXT, resource_type TEXT, location TEXT, UNIQUE(created_at, resource_url))` + ); await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS gamelog_event (id INTEGER PRIMARY KEY, created_at TEXT, data TEXT, UNIQUE(created_at, data))` ); @@ -498,6 +501,17 @@ class Database { }; gamelogDatabase.unshift(row); }, `SELECT * FROM gamelog_video_play WHERE created_at >= date('${dateOffset}') ORDER BY id DESC`); + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: dbRow[3] === 'string' ? 'StringLoad' : 'ImageLoad', + resourceUrl: dbRow[2], + resourceType: dbRow[3], + location: dbRow[4] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_resource_load WHERE created_at >= date('${dateOffset}') ORDER BY id DESC`); await sqliteService.execute((dbRow) => { var row = { rowId: dbRow[0], @@ -621,6 +635,18 @@ class Database { ); } + addGamelogResourceLoadToDatabase(entry) { + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO gamelog_resource_load (created_at, resource_url, resource_type, location) VALUES (@created_at, @resource_url, @resource_type, @location)`, + { + '@created_at': entry.created_at, + '@resource_url': entry.resourceUrl, + '@resource_type': entry.resourceType, + '@location': entry.location + } + ); + } + addGamelogEventToDatabase(entry) { sqliteService.executeNonQuery( `INSERT OR IGNORE INTO gamelog_event (created_at, data) VALUES (@created_at, @data)`, @@ -816,6 +842,14 @@ class Database { return size; } + async getResourceLoadTableSize() { + var size = 0; + await sqliteService.execute((row) => { + size = row[0]; + }, `SELECT COUNT(*) FROM gamelog_resource_load`); + return size; + } + async getEventTableSize() { var size = 0; await sqliteService.execute((row) => { @@ -1176,6 +1210,8 @@ class Database { var portalspawn = true; var msgevent = true; var videoplay = true; + var resourceload_string = true; + var resourceload_image = true; if (filters.length > 0) { location = false; onplayerjoined = false; @@ -1183,6 +1219,8 @@ class Database { portalspawn = false; msgevent = false; videoplay = false; + resourceload_string = false; + resourceload_image = false; filters.forEach((filter) => { switch (filter) { case 'Location': @@ -1203,6 +1241,12 @@ class Database { case 'VideoPlay': videoplay = true; break; + case 'StringLoad': + resourceload_string = true; + break; + case 'ImageLoad': + resourceload_image = true; + break; } }); } @@ -1286,6 +1330,27 @@ class Database { gamelogDatabase.unshift(row); }, `SELECT * FROM gamelog_video_play WHERE video_url LIKE '%${search}%' OR video_name LIKE '%${search}%' OR display_name LIKE '%${search}%' ORDER BY id DESC LIMIT ${Database.maxTableSize}`); } + if (resourceload_string || resourceload_image) { + var checkString = ''; + var checkImage = ''; + if (!resourceload_string) { + checkString = `AND resource_type != 'string'`; + } + if (!resourceload_image) { + checkString = `AND resource_type != 'image'`; + } + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: dbRow[3] === 'string' ? 'StringLoad' : 'ImageLoad', + resourceUrl: dbRow[2], + resourceType: dbRow[3], + location: dbRow[4] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_resource_load WHERE resource_url LIKE '%${search}%' ${checkString} ${checkImage} ORDER BY id DESC LIMIT ${Database.maxTableSize}`); + } var compareByCreatedAt = function (a, b) { var A = a.created_at; var B = b.created_at; @@ -1324,6 +1389,9 @@ class Database { await sqliteService.execute((dbRow) => { gamelogDatabase.unshift(dbRow[0]); }, 'SELECT created_at FROM gamelog_video_play ORDER BY id DESC LIMIT 1'); + await sqliteService.execute((dbRow) => { + gamelogDatabase.unshift(dbRow[0]); + }, 'SELECT created_at FROM gamelog_resource_load ORDER BY id DESC LIMIT 1'); if (gamelogDatabase.length > 0) { gamelogDatabase.sort(); var newDate = gamelogDatabase[gamelogDatabase.length - 1]; diff --git a/html/src/service/gamelog.js b/html/src/service/gamelog.js index df3e7015..b638079d 100644 --- a/html/src/service/gamelog.js +++ b/html/src/service/gamelog.js @@ -41,6 +41,11 @@ class GameLogService { gameLog.displayName = args[1]; break; + case 'resource-load': + gameLog.resourceUrl = args[0]; + gameLog.resourceType = args[1]; + break; + case 'video-sync': gameLog.timestamp = args[0]; break;