From 2418d902cfa608676cb977f12040e5ffc8b22011 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Mon, 30 Aug 2021 12:05:42 +1200 Subject: [PATCH 01/25] Massive commit that will break everything! --- AppApi.cs | 17 +- LogWatcher.cs | 89 ++- VRCXVR.cs | 4 +- html/src/app.js | 892 ++++++++++++++++++--------- html/src/index.pug | 55 +- html/src/repository/database.js | 176 +++++- html/src/service/gamelog.js | 131 ++-- html/src/vr.js | 1001 +++++++------------------------ html/src/vr.pug | 80 ++- html/src/vr.scss | 19 +- 10 files changed, 1252 insertions(+), 1212 deletions(-) diff --git a/AppApi.cs b/AppApi.cs index 2a0afb4c..e8aee4a3 100644 --- a/AppApi.cs +++ b/AppApi.cs @@ -314,13 +314,28 @@ namespace VRCX System.Environment.Exit(0); } - public bool checkForUpdateZip() + public bool CheckForUpdateZip() { if (File.Exists(Path.Combine(Program.AppDataDirectory, "update.exe"))) return true; return false; } + public void ExecuteAppFunction(string function, string json) + { + MainForm.Instance.Browser.ExecuteScriptAsync($"$app.{function}", json); + } + + public void ExecuteVrFeedFunction(string function, string json) + { + VRCXVR._browser1.ExecuteScriptAsync($"$app.{function}", json); + } + + public void ExecuteVrOverlayFunction(string function, string json) + { + VRCXVR._browser2.ExecuteScriptAsync($"$app.{function}", json); + } + public void SetStartup(bool enabled) { try diff --git a/LogWatcher.cs b/LogWatcher.cs index b7f23005..9a929e34 100644 --- a/LogWatcher.cs +++ b/LogWatcher.cs @@ -3,6 +3,7 @@ // This work is licensed under the terms of the MIT license. // For a copy, see . +using CefSharp; using System; using System.Collections.Generic; using System.Globalization; @@ -30,6 +31,8 @@ namespace VRCX private readonly List m_LogList; private Thread m_Thread; private bool m_ResetLog; + private bool m_FirstRun = true; + private static DateTime tillDate = DateTime.Now; // NOTE // FileSystemWatcher() is unreliable @@ -64,6 +67,17 @@ namespace VRCX thread.Interrupt(); thread.Join(); } + + public void Reset() + { + m_ResetLog = true; + m_Thread?.Interrupt(); + } + + public void SetDateTill(string date) + { + tillDate = DateTime.Parse(date); + } private void ThreadLoop() { @@ -86,6 +100,7 @@ namespace VRCX { if (m_ResetLog == true) { + m_FirstRun = true; m_ResetLog = false; m_LogContextMap.Clear(); m_LogListLock.EnterWriteLock(); @@ -109,26 +124,17 @@ namespace VRCX // sort by creation time Array.Sort(fileInfos, (a, b) => a.CreationTimeUtc.CompareTo(b.CreationTimeUtc)); - var utcNow = DateTime.UtcNow; - var minLimitDateTime = utcNow.AddDays(-7d); - var minRefreshDateTime = utcNow.AddMinutes(-3d); - foreach (var fileInfo in fileInfos) { - var lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; - - if (lastWriteTimeUtc < minLimitDateTime) + fileInfo.Refresh(); + if (fileInfo.Exists == false) { continue; } - if (lastWriteTimeUtc >= minRefreshDateTime) + if (DateTime.Compare(fileInfo.LastWriteTime, tillDate) < 0) { - fileInfo.Refresh(); - if (fileInfo.Exists == false) - { - continue; - } + continue; } if (m_LogContextMap.TryGetValue(fileInfo.Name, out LogContext logContext) == true) @@ -155,6 +161,8 @@ namespace VRCX { m_LogContextMap.Remove(name); } + + m_FirstRun = false; } private void ParseLog(FileInfo fileInfo, LogContext logContext) @@ -184,6 +192,20 @@ namespace VRCX continue; } + if (DateTime.TryParseExact( + line.Substring(0, 19), + "yyyy.MM.dd HH:mm:ss", + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeLocal, + out DateTime lineDate + )) + { + if (DateTime.Compare(lineDate, tillDate) <= 0) + { + continue; + } + } + var offset = 34; if (line[offset] == '[') { @@ -194,7 +216,8 @@ namespace VRCX ParseLogJoinBlocked(fileInfo, logContext, line, offset) == true || ParseLogAvatarPedestalChange(fileInfo, logContext, line, offset) == true || ParseLogVideoError(fileInfo, logContext, line, offset) == true || - ParseLogVideoPlay(fileInfo, logContext, line, offset) == true) + ParseLogVideoPlay(fileInfo, logContext, line, offset) == true || + ParseLogWorldVRCX(fileInfo, logContext, line, offset) == true) { continue; } @@ -221,6 +244,12 @@ namespace VRCX m_LogListLock.EnterWriteLock(); try { + if (!m_FirstRun) + { + var logLine = System.Text.Json.JsonSerializer.Serialize(item); + if (MainForm.Instance != null && MainForm.Instance.Browser != null) + MainForm.Instance.Browser.ExecuteScriptAsync("$app.addGameLogEvent", logLine); + } m_LogList.Add(item); } finally @@ -244,7 +273,7 @@ namespace VRCX dt = DateTime.UtcNow; } - return $"{dt:yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'}"; + return $"{dt:yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'}"; } private bool ParseLogLocation(FileInfo fileInfo, LogContext logContext, string line, int offset) @@ -515,6 +544,28 @@ namespace VRCX return true; } + private bool ParseLogWorldVRCX(FileInfo fileInfo, LogContext logContext, string line, int offset) + { + // [VRCX] VideoPlay(PyPyDance) "https://jd.pypy.moe/api/v1/videos/-Q3pdlsQxOk.mp4",0.5338666,260.6938,"1339 : Le Freak (Random)" + + if (string.Compare(line, offset, "[VRCX] ", 0, 7, StringComparison.Ordinal) == 0) + { + var data = line.Substring(offset + 7); + + AppendLog(new[] + { + fileInfo.Name, + ConvertLogTimeToISO8601(line), + "vrcx", + data + }); + + return true; + } + + return false; + } + private bool ParseLogSDK2VideoPlay(FileInfo fileInfo, LogContext logContext, string line, int offset) { // 2021.04.23 13:12:25 Log - User Natsumi-sama added URL https://www.youtube.com/watch?v=dQw4w9WgXcQ @@ -579,14 +630,10 @@ namespace VRCX return true; } - public void Reset() - { - m_ResetLog = true; - m_Thread?.Interrupt(); - } - public string[][] Get() { + Update(); + if (m_ResetLog == false && m_LogList.Count > 0) { diff --git a/VRCXVR.cs b/VRCXVR.cs index a8e38694..8d5a9dd3 100644 --- a/VRCXVR.cs +++ b/VRCXVR.cs @@ -34,8 +34,8 @@ namespace VRCX private Device _device; private Texture2D _texture1; private Texture2D _texture2; - private OffScreenBrowser _browser1; - private OffScreenBrowser _browser2; + public static OffScreenBrowser _browser1; + public static OffScreenBrowser _browser2; private bool _active; static VRCXVR() diff --git a/html/src/app.js b/html/src/app.js index 194a3c6d..958f41ac 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -15,7 +15,6 @@ import locale from 'element-ui/lib/locale/lang/en'; import {v4 as uuidv4} from 'uuid'; import {appVersion} from './constants.js'; -import sharedRepository from './repository/shared.js'; import configRepository from './repository/config.js'; import webApiService from './service/webapi.js'; import gameLogService from './service/gamelog.js'; @@ -463,6 +462,9 @@ speechSynthesis.getVoices(); if (init.method === 'GET' && status === 404) { this.failedGetRequests.set(endpoint, Date.now()); } + if (status === 404 && endpoint.substring(0, 6) === 'users/') { + throw new Error("404: Can't find user!"); + } if (data.error === Object(data.error)) { this.$throw( data.error.status_code || status, @@ -1273,7 +1275,6 @@ speechSynthesis.getVoices(); ref }); } - sharedRepository.setString('current_user_status', ref.status); return ref; }; @@ -1494,6 +1495,24 @@ speechSynthesis.getVoices(); }); }; + /* + params: { + username: string + } + */ + API.getUserByUsername = function (params) { + return this.call(`users/${params.username}/name`, { + method: 'GET' + }).then((json) => { + var args = { + json, + params + }; + this.$emit('USER', args); + return args; + }); + }; + /* params: { status: string ('active', 'offline', 'busy', 'ask me', 'join me'), @@ -3441,7 +3460,7 @@ speechSynthesis.getVoices(); }); API.$on('USER:CURRENT', function () { - if (this.webSocket === null) { + if ($app.friendLogInitStatus && this.webSocket === null) { this.getAuth(); } }); @@ -3897,7 +3916,7 @@ speechSynthesis.getVoices(); API.$on('SHOW_WORLD_DIALOG', (tag) => this.showWorldDialog(tag)); API.$on('SHOW_LAUNCH_DIALOG', (tag) => this.showLaunchDialog(tag)); this.updateLoop(); - this.updateGameLogLoop(); + this.getGameLogTable(); this.$nextTick(function () { this.$el.style.display = ''; if (!this.enablePrimaryPassword) { @@ -3969,8 +3988,12 @@ speechSynthesis.getVoices(); if (isGameRunning !== this.isGameRunning) { this.isGameRunning = isGameRunning; Discord.SetTimestamps(Date.now(), 0); + this.updateVRConfigVars(); + } + if (isGameNoVR !== this.isGameNoVR) { + this.isGameNoVR = isGameNoVR; + this.updateVRConfigVars(); } - this.isGameNoVR = isGameNoVR; this.updateDiscord(); this.updateOpenVR(); } @@ -3986,6 +4009,8 @@ speechSynthesis.getVoices(); $app.data.debugWebRequests = false; $app.data.debugWebSocket = false; + $app.data.APILastOnline = new Map(); + $app.data.sharedFeed = { gameLog: { wrist: [], @@ -4010,22 +4035,8 @@ speechSynthesis.getVoices(); pendingUpdate: false }; - $app.data.appInit = false; - $app.data.notyInit = false; - - API.$on('LOGIN', function () { - sharedRepository.setArray('wristFeed', []); - sharedRepository.setArray('notyFeed', []); - setTimeout(function () { - $app.appInit = true; - $app.updateSharedFeed(true); - $app.notyInit = true; - sharedRepository.setBool('VRInit', true); - }, 10000); - }); - $app.methods.updateSharedFeed = function (forceUpdate) { - if (!this.appInit) { + if (!this.friendLogInitStatus) { return; } this.updateSharedFeedGameLog(forceUpdate); @@ -4085,7 +4096,7 @@ speechSynthesis.getVoices(); } } var playersInInstance = this.lastLocation.playerList; - if (playersInInstance.includes(ctx.displayName)) { + if (playersInInstance.has(ctx.displayName)) { continue; } var joining = true; @@ -4103,7 +4114,7 @@ speechSynthesis.getVoices(); } if ( gameLogItem.type === 'OnPlayerJoined' && - gameLogItem.data === ctx.displayName + gameLogItem.displayName === ctx.displayName ) { joining = false; break; @@ -4161,15 +4172,18 @@ speechSynthesis.getVoices(); } return 0; }); - notyFeed.splice(1); - sharedRepository.setArray('wristFeed', wristFeed); - sharedRepository.setArray('notyFeed', notyFeed); + notyFeed.splice(5); + AppApi.ExecuteVrFeedFunction( + 'wristFeedUpdate', + JSON.stringify(wristFeed) + ); if (this.userDialog.visible) { this.applyUserDialogLocation(); } if (this.worldDialog.visible) { this.applyWorldDialogInstances(); } + this.getCurrentInstanceUserList(); this.playNoty(notyFeed); feeds.pendingUpdate = false; }; @@ -4197,9 +4211,6 @@ speechSynthesis.getVoices(); var n = 0; var wristFilter = this.sharedFeedFilters.wrist; var notyFilter = this.sharedFeedFilters.noty; - var playerCountIndex = 0; - var playerList = []; - var friendList = []; var currentUserJoinTime = ''; var currentUserLeaveTime = ''; for (var i = data.length - 1; i > -1; i--) { @@ -4210,15 +4221,12 @@ speechSynthesis.getVoices(); if (ctx.type === 'Notification') { continue; } - if (playerCountIndex === 0 && ctx.type === 'Location') { - playerCountIndex = i; - } // on Location change remove OnPlayerLeft if (ctx.type === 'OnPlayerLeft') { if (ctx.created_at.slice(0, -4) === currentUserLeaveTime) { continue; } - if (ctx.data === API.currentUser.displayName) { + if (ctx.displayName === API.currentUser.displayName) { var {created_at} = ctx; currentUserLeaveTime = created_at.slice(0, -4); for (var k = w - 1; k > -1; k--) { @@ -4251,7 +4259,7 @@ speechSynthesis.getVoices(); if (ctx.created_at.slice(0, -4) === currentUserJoinTime) { continue; } - if (ctx.data === API.currentUser.displayName) { + if (ctx.displayName === API.currentUser.displayName) { var {created_at} = ctx; currentUserJoinTime = created_at.slice(0, -4); for (var k = w - 1; k > -1; k--) { @@ -4284,7 +4292,7 @@ speechSynthesis.getVoices(); (ctx.type === 'OnPlayerJoined' || ctx.type === 'OnPlayerLeft' || ctx.type === 'PortalSpawn') && - ctx.data === API.currentUser.displayName + ctx.displayName === API.currentUser.displayName ) { continue; } @@ -4293,20 +4301,28 @@ speechSynthesis.getVoices(); if ( ctx.type === 'OnPlayerJoined' || ctx.type === 'OnPlayerLeft' || - ctx.type === 'PortalSpawn' + ctx.type === 'PortalSpawn' || + ctx.type === 'AvatarChange' ) { - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === ctx.data) { - isFriend = this.friends.has(ref.id); - isFavorite = API.cachedFavoritesByObjectId.has(ref.id); - break; + if (ctx.userId) { + isFriend = this.friends.has(ctx.userId); + isFavorite = API.cachedFavoritesByObjectId.has(ctx.userId); + } else { + for (var ref of API.cachedUsers.values()) { + if (ref.displayName === ctx.displayName) { + isFriend = this.friends.has(ref.id); + isFavorite = API.cachedFavoritesByObjectId.has( + ref.id + ); + break; + } } } } // BlockedOnPlayerJoined, BlockedOnPlayerLeft, MutedOnPlayerJoined, MutedOnPlayerLeft if (ctx.type === 'OnPlayerJoined' || ctx.type === 'OnPlayerLeft') { for (var ref of this.playerModerationTable.data) { - if (ref.targetDisplayName === ctx.data) { + if (ref.targetDisplayName === ctx.displayName) { if (ref.type === 'block') { var type = `Blocked${ctx.type}`; } else if (ref.type === 'mute') { @@ -4350,6 +4366,22 @@ speechSynthesis.getVoices(); } } } + if (ctx.type === 'VideoPlay' && ctx.displayName) { + if (ctx.userId) { + isFriend = this.friends.has(ctx.userId); + isFavorite = API.cachedFavoritesByObjectId.has(ctx.userId); + } else { + for (var ref of API.cachedUsers.values()) { + if (ref.displayName === ctx.displayName) { + isFriend = this.friends.has(ref.id); + isFavorite = API.cachedFavoritesByObjectId.has( + ref.id + ); + break; + } + } + } + } if ( w < 20 && wristFilter[ctx.type] && @@ -4366,7 +4398,7 @@ speechSynthesis.getVoices(); ++w; } if ( - n < 1 && + n < 5 && notyFilter[ctx.type] && (notyFilter[ctx.type] === 'On' || notyFilter[ctx.type] === 'Everyone' || @@ -4381,41 +4413,6 @@ speechSynthesis.getVoices(); ++n; } } - // instance player list - for (var i = playerCountIndex + 1; i < data.length; i++) { - var ctx = data[i]; - if (ctx.type === 'OnPlayerJoined') { - playerList.push(ctx.data); - var isFriend = false; - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === ctx.data) { - isFriend = this.friends.has(ref.id); - break; - } - } - if (ctx.data === API.currentUser.displayName) { - isFriend = true; - } - if (isFriend) { - friendList.push(ctx.data); - } - } - if (ctx.type === 'OnPlayerLeft') { - var index = playerList.indexOf(ctx.data); - if (index > -1) { - playerList.splice(index, 1); - } - var index = friendList.indexOf(ctx.data); - if (index > -1) { - friendList.splice(index, 1); - } - } - } - if (this.isGameRunning) { - this.lastLocation.playerList = playerList; - this.lastLocation.friendList = friendList; - sharedRepository.setObject('last_location', this.lastLocation); - } this.sharedFeed.gameLog.wrist = wristArr; this.sharedFeed.gameLog.noty = notyArr; this.sharedFeed.pendingUpdate = true; @@ -4476,7 +4473,7 @@ speechSynthesis.getVoices(); ++w; } if ( - n < 1 && + n < 5 && notyFilter[ctx.type] && (notyFilter[ctx.type] === 'Friends' || (notyFilter[ctx.type] === 'VIP' && isFavorite)) @@ -4545,7 +4542,7 @@ speechSynthesis.getVoices(); ++w; } if ( - n < 1 && + n < 5 && notyFilter[ctx.type] && (notyFilter[ctx.type] === 'On' || notyFilter[ctx.type] === 'Friends' || @@ -4613,7 +4610,7 @@ speechSynthesis.getVoices(); ++w; } if ( - n < 1 && + n < 5 && notyFilter[ctx.type] && (notyFilter[ctx.type] === 'On' || notyFilter[ctx.type] === 'Friends' || @@ -4661,7 +4658,15 @@ speechSynthesis.getVoices(); if (this.xsNotifications && this.isGameRunning && !this.isGameNoVR) { playXSNotification = true; } - if (this.currentUserStatus === 'busy' || !this.notyInit) { + var playOverlayNotification = false; + if ( + this.overlayNotifications && + !this.isGameNoVR && + this.isGameRunning + ) { + playOverlayNotification = true; + } + if (API.currentUser.status === 'busy' || !this.notyInit) { return; } var notyToPlay = []; @@ -4675,8 +4680,6 @@ speechSynthesis.getVoices(); displayName = feed.sourceDisplayName; } else if (feed.data) { displayName = feed.data; - } else { - console.error('missing displayName'); } if ( (displayName && !this.notyMap[displayName]) || @@ -4712,8 +4715,13 @@ speechSynthesis.getVoices(); if (playNotificationTTS) { this.playNotyTTS(noty, message); } + if (playOverlayNotification) { + this.notyGetImage(noty).then((imageUrl) => { + this.displayOverlayNotification(noty, message, imageUrl); + }); + } if (playDesktopToast || playXSNotification) { - this.notyGetImage(noty).then((image) => { + this.notySaveImage(noty).then((image) => { if (playXSNotification) { this.displayXSNotification(noty, message, image); } @@ -4726,7 +4734,7 @@ speechSynthesis.getVoices(); }; $app.methods.notyGetImage = async function (noty) { - var imageURL = ''; + var imageUrl = ''; var userId = ''; if (noty.userId) { userId = noty.userId; @@ -4734,23 +4742,25 @@ speechSynthesis.getVoices(); userId = noty.senderUserId; } else if (noty.sourceUserId) { userId = noty.sourceUserId; - } else if (noty.data) { + } else if (noty.displayName) { for (var ref of API.cachedUsers.values()) { - if (ref.displayName === noty.data) { + if (ref.displayName === noty.displayName) { userId = ref.id; break; } } } - if (noty.details && noty.details.imageUrl) { - imageURL = noty.details.imageUrl; + if (noty.thumbnailImageUrl) { + imageUrl = noty.thumbnailImageUrl; + } else if (noty.details && noty.details.imageUrl) { + imageUrl = noty.details.imageURL; } else if (userId) { - imageURL = await API.getCachedUser({ + imageUrl = await API.getCachedUser({ userId }) .catch((err) => { console.error(err); - return false; + return ''; }) .then((args) => { if ( @@ -4759,14 +4769,19 @@ speechSynthesis.getVoices(); ) { return args.json.userIcon; } + if (args.json.profilePicOverride) { + return args.json.profilePicOverride; + } return args.json.currentAvatarThumbnailImageUrl; }); } - if (!imageURL) { - return false; - } + return imageUrl; + }; + + $app.methods.notySaveImage = async function (noty) { + var imageUrl = await this.notyGetImage(noty); try { - await fetch(imageURL, { + await fetch(imageUrl, { method: 'GET', redirect: 'follow', headers: { @@ -4791,13 +4806,24 @@ speechSynthesis.getVoices(); } }; + $app.methods.displayOverlayNotification = function ( + noty, + message, + imageUrl + ) { + AppApi.ExecuteVrOverlayFunction( + 'playNoty', + JSON.stringify({noty, message, imageUrl}) + ); + }; + $app.methods.playNotyTTS = function (noty, message) { switch (noty.type) { case 'OnPlayerJoined': - this.speak(`${noty.data} has joined`); + this.speak(`${noty.displayName} has joined`); break; case 'OnPlayerLeft': - this.speak(`${noty.data} has left`); + this.speak(`${noty.displayName} has left`); break; case 'OnPlayerJoining': this.speak(`${noty.displayName} is joining`); @@ -4868,13 +4894,31 @@ speechSynthesis.getVoices(); ); break; case 'PortalSpawn': - this.speak(`${noty.data} has spawned a portal`); + var locationName = ''; + if (noty.worldName) { + locationName = ` to ${this.displayLocation( + noty.instanceId, + noty.worldName + )}`; + } + this.speak( + `${noty.displayName} has spawned a portal${locationName}` + ); + break; + case 'AvatarChange': + this.speak( + `${noty.displayName} changed into avatar ${noty.name}` + ); break; case 'Event': this.speak(noty.data); break; case 'VideoPlay': - this.speak(`Now playing: ${noty.data}`); + var videoName = ''; + if (noty.videoName) { + videoName = `: ${noty.videoName}`; + } + this.speak(`Now playing video${videoName}`); break; case 'BlockedOnPlayerJoined': this.speak(`Blocked user ${noty.displayName} has joined`); @@ -4897,7 +4941,7 @@ speechSynthesis.getVoices(); case 'OnPlayerJoined': AppApi.XSNotification( 'VRCX', - `${noty.data} has joined`, + `${noty.displayName} has joined`, timeout, image ); @@ -4905,7 +4949,7 @@ speechSynthesis.getVoices(); case 'OnPlayerLeft': AppApi.XSNotification( 'VRCX', - `${noty.data} has left`, + `${noty.displayName} has left`, timeout, image ); @@ -5031,9 +5075,24 @@ speechSynthesis.getVoices(); ); break; case 'PortalSpawn': + var locationName = ''; + if (noty.worldName) { + locationName = ` to ${this.displayLocation( + noty.instanceId, + noty.worldName + )}`; + } AppApi.XSNotification( 'VRCX', - `${noty.data} has spawned a portal`, + `${noty.displayName} has spawned a portal${locationName}`, + timeout, + image + ); + break; + case 'AvatarChange': + AppApi.XSNotification( + 'VRCX', + `${noty.displayName} changed into avatar ${noty.name}`, timeout, image ); @@ -5042,9 +5101,13 @@ speechSynthesis.getVoices(); AppApi.XSNotification('VRCX', noty.data, timeout, image); break; case 'VideoPlay': + var videoName = noty.videoUrl; + if (noty.videoName) { + videoName = noty.videoName; + } AppApi.XSNotification( 'VRCX', - `Now playing: ${noty.data}`, + `Now playing: ${videoName}`, timeout, image ); @@ -5087,10 +5150,14 @@ speechSynthesis.getVoices(); $app.methods.displayDesktopToast = function (noty, message, image) { switch (noty.type) { case 'OnPlayerJoined': - AppApi.DesktopNotification(noty.data, 'has joined', image); + AppApi.DesktopNotification( + noty.displayName, + 'has joined', + image + ); break; case 'OnPlayerLeft': - AppApi.DesktopNotification(noty.data, 'has left', image); + AppApi.DesktopNotification(noty.displayName, 'has left', image); break; case 'OnPlayerJoining': AppApi.DesktopNotification( @@ -5197,9 +5264,23 @@ speechSynthesis.getVoices(); ); break; case 'PortalSpawn': + var locationName = ''; + if (noty.worldName) { + locationName = ` to ${this.displayLocation( + noty.instanceId, + noty.worldName + )}`; + } AppApi.DesktopNotification( - noty.data, - `has spawned a portal`, + noty.displayName, + `has spawned a portal${locationName}`, + image + ); + break; + case 'AvatarChange': + AppApi.DesktopNotification( + noty.displayName, + `changed into avatar ${noty.name}`, image ); break; @@ -5207,7 +5288,11 @@ speechSynthesis.getVoices(); AppApi.DesktopNotification('Event', noty.data, image); break; case 'VideoPlay': - AppApi.DesktopNotification('Now playing', noty.data, image); + var videoName = noty.videoUrl; + if (noty.videoName) { + videoName = noty.videoName; + } + AppApi.DesktopNotification('Now playing', videoName, image); break; case 'BlockedOnPlayerJoined': AppApi.DesktopNotification( @@ -5441,7 +5526,6 @@ speechSynthesis.getVoices(); )}!` }).show(); $app.$refs.menu.activeIndex = 'feed'; - $app.resetGameLog(); }); API.$on('LOGIN', function (args) { @@ -5955,7 +6039,10 @@ speechSynthesis.getVoices(); }); $app.methods.checkActiveFriends = function (ref) { - if (Array.isArray(ref.activeFriends) === false || !this.appInit) { + if ( + Array.isArray(ref.activeFriends) === false || + !this.friendLogInitStatus + ) { return; } for (var userId of ref.activeFriends) { @@ -6013,6 +6100,9 @@ speechSynthesis.getVoices(); }); API.$on('FRIEND:STATE', function (args) { + if (args.json.state === 'online') { + $app.APILastOnline.set(args.params.userId, Date.now()); + } $app.updateFriend(args.params.userId, args.json.state); }); @@ -6149,7 +6239,7 @@ speechSynthesis.getVoices(); // AddFriend (CurrentUser) 이후, // 서버에서 오는 순서라고 보면 될 듯. if (ctx.state === 'online') { - if (this.appInit) { + if (this.friendLogInitStatus) { API.getUser({ userId: id }); @@ -6212,6 +6302,8 @@ speechSynthesis.getVoices(); ) { API.getUser({ userId: id + }).catch(() => { + this.updateFriendInProgress.delete(id); }); } } else { @@ -6224,6 +6316,22 @@ speechSynthesis.getVoices(); ) { var {location, $location_at} = ref; } + // prevent status flapping + if ( + ctx.state === 'online' && + (stateInput === 'active' || stateInput === 'offline') + ) { + await new Promise((resolve) => { + setTimeout(resolve, 50000); + }); + if (this.APILastOnline.has(id)) { + var date = this.APILastOnline.get(id); + if (date > Date.now() - 60000) { + this.updateFriendInProgress.delete(id); + return; + } + } + } try { var args = await API.getUser({ userId: id @@ -6421,6 +6529,10 @@ speechSynthesis.getVoices(); return $app.sortStatus(a.ref.status, b.ref.status); }; + $app.methods.sortByStatus = function (a, b, field) { + return this.sortStatus(a[field], b[field]); + }; + $app.methods.sortStatus = function (a, b) { switch (b) { case 'join me': @@ -6759,14 +6871,23 @@ speechSynthesis.getVoices(); }; API.$on('LOGIN', async function (args) { + $app.feedTable.data = []; $app.friendLogInitStatus = false; - await database.init(args.json.id); + await database.initUserTables(args.json.id); $app.feedTable.data = await database.getFeedDatabase(); $app.sweepFeed(); if (configRepository.getBool(`friendLogInit_${args.json.id}`)) { - $app.getFriendLog(); + await $app.getFriendLog(); } else { - $app.initFriendLog(args.json.id); + await $app.initFriendLog(args.json.id); + } + this.getAuth(); + + $app.updateSharedFeed(true); + $app.notyInit = true; + + if ($app.isGameRunning) { + $app.loadPlayerList(); } // remove old data from json file and migrate to SQLite if (VRCXStorage.Get(`${args.json.id}_friendLogUpdatedAt`)) { @@ -6776,6 +6897,63 @@ speechSynthesis.getVoices(); } }); + $app.methods.loadPlayerList = function () { + var {data} = this.gameLogTable; + if (data.length === 0) { + return; + } + var length = 0; + for (var i = data.length - 1; i > -1; i--) { + var ctx = data[i]; + if (ctx.type === 'Location') { + this.lastLocation = { + date: Date.parse(ctx.created_at), + location: ctx.location, + name: ctx.worldName, + playerList: new Map(), + friendList: new Map() + }; + length = i; + break; + } + } + if (length > 0) { + for (var i = length + 1; i < data.length; i++) { + var ctx = data[i]; + if (ctx.type === 'OnPlayerJoined') { + if (!ctx.userId) { + for (var ref of API.cachedUsers.values()) { + if (ref.displayName === ctx.displayName) { + ctx.userId = ref.id; + break; + } + } + } + var userMap = { + displayName: ctx.displayName, + userId: ctx.userId, + joinTime: Date.parse(ctx.created_at) + }; + this.lastLocation.playerList.set(ctx.displayName, userMap); + if ( + this.friends.has(ctx.userId) || + API.currentUser.displayName === ctx.displayName + ) { + this.lastLocation.friendList.set( + ctx.displayName, + userMap + ); + } + } + if (ctx.type === 'OnPlayerLeft') { + this.lastLocation.playerList.delete(ctx.displayName); + this.lastLocation.friendList.delete(ctx.displayName); + } + } + this.updateVRLastLocation(); + } + }; + API.$on('USER:UPDATE', async function (args) { var {ref, props} = args; if ($app.friends.has(ref.id) === false) { @@ -6973,9 +7151,42 @@ speechSynthesis.getVoices(); date: 0, location: '', name: '', - playerList: [], - friendList: [] + playerList: new Map(), + friendList: new Map() }; + + $app.methods.lastLocationReset = function () { + var playerList = Array.from(this.lastLocation.playerList.values()); + for (var ref of playerList) { + var time = new Date().getTime() - ref.joinTime; + var entry = { + created_at: new Date().toJSON(), + type: 'OnPlayerLeft', + displayName: ref.displayName, + location: this.lastLocation.location, + userId: ref.userId, + time + }; + this.addGameLog(entry); + database.addGamelogJoinLeaveToDatabase(entry); + } + if (this.lastLocation.date !== 0) { + var timeLocation = new Date().getTime() - this.lastLocation.date; + var update = { + time: timeLocation, + created_at: new Date(this.lastLocation.date).toJSON() + }; + database.updateGamelogLocationTimeToDatabase(update); + } + this.lastLocation = { + date: 0, + location: '', + name: '', + playerList: new Map(), + friendList: new Map() + }; + }; + $app.data.lastLocation$ = {}; $app.data.discordActive = configRepository.getBool('discordActive'); $app.data.discordInstance = configRepository.getBool('discordInstance'); @@ -6997,13 +7208,14 @@ speechSynthesis.getVoices(); filter.value.some((v) => v === row.type) }, { - prop: 'data', + prop: 'displayName', value: '' }, { - prop: 'data', + prop: 'displayName', value: true, - filterFn: (row) => row.data !== API.currentUser.displayName + filterFn: (row) => + row.displayName !== API.currentUser.displayName }, { prop: 'type', @@ -7029,117 +7241,200 @@ speechSynthesis.getVoices(); $app.methods.resetGameLog = async function () { await gameLogService.reset(); - await gameLogService.poll(); this.gameLogTable.data = []; - this.lastLocation = { - date: 0, - location: '', - name: '', - playerList: [], - friendList: [] - }; + this.lastLocationReset(); }; - $app.methods.updateGameLogLoop = async function () { - try { - if (API.isLoggedIn === true) { - await this.updateGameLog(); - this.sweepGameLog(); - var length = this.gameLogTable.data.length; - if (length > 0) { - if ( - this.gameLogTable.data[length - 1].created_at !== - this.gameLogTable.lastEntryDate - ) { - this.notifyMenu('gameLog'); - } - this.gameLogTable.lastEntryDate = - this.gameLogTable.data[length - 1].created_at; - } - this.updateSharedFeed(false); + $app.methods.refreshEntireGameLog = async function () { + await gameLogService.setDateTill('1970-01-01'); + await database.initTables(); + await this.resetGameLog(); + var location = ''; + var pushToTable = false; + for (var gameLog of await gameLogService.getAll()) { + if (gameLog.type === 'location') { + location = gameLog.location; } - } catch (err) { - console.error(err); + this.addGameLogEntry(gameLog, location, pushToTable); } - setTimeout(() => this.updateGameLogLoop(), 500); + this.getGameLogTable(); }; - $app.methods.updateGameLog = async function () { - for (var gameLog of await gameLogService.poll()) { - var tableData = null; + $app.methods.getGameLogTable = async function () { + await database.initTables(); + this.gameLogTable.data = await database.getGamelogDatabase(); + this.sweepGameLog(); + var length = this.gameLogTable.data.length; + if (length > 1) { + this.updateGameLog(this.gameLogTable.data[length - 1].created_at); + } + }; - switch (gameLog.type) { - case 'location': - if (this.isGameRunning) { - this.lastLocation = { - date: Date.parse(gameLog.dt), - location: gameLog.location, - name: gameLog.worldName, - playerList: [], - friendList: [] - }; - } - tableData = { - created_at: gameLog.dt, - type: 'Location', - data: [gameLog.location, gameLog.worldName] - }; - break; - - case 'player-joined': - tableData = { - created_at: gameLog.dt, - type: 'OnPlayerJoined', - data: gameLog.userDisplayName - }; - break; - - case 'player-left': - tableData = { - created_at: gameLog.dt, - type: 'OnPlayerLeft', - data: gameLog.userDisplayName - }; - break; - - case 'notification': - tableData = { - created_at: gameLog.dt, - type: 'Notification', - data: gameLog.json - }; - break; - - case 'portal-spawn': - tableData = { - created_at: gameLog.dt, - type: 'PortalSpawn', - data: gameLog.userDisplayName - }; - break; - - case 'event': - tableData = { - created_at: gameLog.dt, - type: 'Event', - data: gameLog.event - }; - break; - - case 'video-play': - tableData = { - created_at: gameLog.dt, - type: 'VideoPlay', - data: gameLog.videoURL, - displayName: gameLog.displayName - }; - break; + $app.methods.updateGameLog = async function (dateTill) { + await gameLogService.setDateTill(dateTill); + await gameLogService.reset(); + await new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + var location = ''; + var pushToTable = true; + for (var gameLog of await gameLogService.getAll()) { + if (gameLog.type === 'location') { + location = gameLog.location; } + this.addGameLogEntry(gameLog, location, pushToTable); + } + }; - if (tableData !== null) { - this.gameLogTable.data.push(tableData); + $app.methods.addGameLogEvent = async function (json) { + var rawLogs = JSON.parse(json); + var gameLog = gameLogService.parseRawGameLog( + rawLogs[1], + rawLogs[2], + rawLogs.slice(3) + ); + var pushToTable = true; + await this.addGameLogEntry( + gameLog, + this.lastLocation.location, + pushToTable + ); + this.updateSharedFeed(false); + this.notifyMenu('gameLog'); + this.sweepGameLog(); + }; + + $app.methods.addGameLogEntry = function ( + gameLog, + location, + pushToTable + ) { + var userId = ''; + if (gameLog.userDisplayName) { + for (var ref of API.cachedUsers.values()) { + if (ref.displayName === gameLog.userDisplayName) { + userId = ref.id; + break; + } } } + switch (gameLog.type) { + case 'location': + if (this.isGameRunning) { + this.lastLocationReset(); + this.lastLocation = { + date: Date.parse(gameLog.dt), + location: gameLog.location, + name: gameLog.worldName, + playerList: new Map(), + friendList: new Map() + }; + this.updateVRLastLocation(); + this.checkVRChatCacheDownload(this.lastLocation.location); + } + var L = API.parseLocation(gameLog.location); + var entry = { + created_at: gameLog.dt, + type: 'Location', + location: gameLog.location, + worldId: L.worldId, + worldName: gameLog.worldName, + time: 0 + }; + database.addGamelogLocationToDatabase(entry); + break; + case 'player-joined': + var userMap = { + displayName: gameLog.userDisplayName, + userId, + joinTime: Date.parse(gameLog.dt) + }; + this.lastLocation.playerList.set( + gameLog.userDisplayName, + userMap + ); + if (this.friends.has(userId)) { + this.lastLocation.friendList.set( + gameLog.userDisplayName, + userMap + ); + } + this.updateVRLastLocation(); + var entry = { + created_at: gameLog.dt, + type: 'OnPlayerJoined', + displayName: gameLog.userDisplayName, + location, + userId, + time: 0 + }; + database.addGamelogJoinLeaveToDatabase(entry); + break; + case 'player-left': + var time = 0; + var ref = this.lastLocation.playerList.get( + gameLog.userDisplayName + ); + if (typeof ref !== 'undefined') { + time = new Date().getTime() - ref.joinTime; + this.lastLocation.playerList.delete( + gameLog.userDisplayName + ); + this.lastLocation.friendList.delete( + gameLog.userDisplayName + ); + } + this.updateVRLastLocation(); + var entry = { + created_at: gameLog.dt, + type: 'OnPlayerLeft', + displayName: gameLog.userDisplayName, + location, + userId, + time + }; + database.addGamelogJoinLeaveToDatabase(entry); + break; + case 'portal-spawn': + var entry = { + created_at: gameLog.dt, + type: 'PortalSpawn', + displayName: gameLog.userDisplayName, + location, + userId, + instanceId: '', + worldName: '' + }; + database.addGamelogPortalSpawnToDatabase(entry); + break; + case 'video-play': + var entry = { + created_at: gameLog.dt, + type: 'VideoPlay', + data: gameLog.videoUrl, + displayName: gameLog.displayName + }; + database.addGamelogVideoPlayToDatabase(entry); + break; + case 'notification': + var entry = { + created_at: gameLog.dt, + type: 'Notification', + data: gameLog.json + }; + break; + case 'event': + var entry = { + created_at: gameLog.dt, + type: 'Event', + data: gameLog.event + }; + database.addGamelogEventToDatabase(entry); + break; + } + if (pushToTable && entry) { + this.gameLogTable.data.push(entry); + } }; $app.methods.sweepGameLog = function () { @@ -7208,20 +7503,32 @@ speechSynthesis.getVoices(); Discord.SetActive(this.discordActive); }; - $app.methods.lookupUser = async function (name) { - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === name) { - this.showUserDialog(ref.id); + $app.methods.lookupUser = async function (ref) { + if (ref.userId) { + this.showUserDialog(ref.userId); + return; + } + for (var ctx of API.cachedUsers.values()) { + if (ctx.displayName === ref.displayName) { + this.showUserDialog(ctx.id); return; } } - this.searchText = name; + try { + var username = encodeURIComponent(ref.displayName.toLowerCase()); + var args = await API.getUserByUsername({username}); + if (args.ref.displayName === ref.displayName) { + this.showUserDialog(args.ref.id); + return; + } + } catch (err) {} + this.searchText = ref.displayName; await this.searchUser(); - for (var ref of this.searchUserResults) { - if (ref.displayName === name) { + for (var ctx of this.searchUserResults) { + if (ctx.displayName === ref.displayName) { this.searchText = ''; this.clearSearch(); - this.showUserDialog(ref.id); + this.showUserDialog(ctx.id); return; } } @@ -7797,6 +8104,7 @@ speechSynthesis.getVoices(); } this.friendLogTable.data = []; this.friendLogTable.data = await database.getFriendLogHistory(); + await API.refreshFriends(); this.friendLogInitStatus = true; }; @@ -8448,6 +8756,7 @@ speechSynthesis.getVoices(); 'VRCX_vrBackgroundEnabled', this.vrBackgroundEnabled ); + this.updateSharedFeed(true); this.updateVRConfigVars(); }; $app.data.TTSvoices = speechSynthesis.getVoices(); @@ -8631,6 +8940,7 @@ speechSynthesis.getVoices(); DisplayName: 'VIP', TrustLevel: 'VIP', PortalSpawn: 'Everyone', + AvatarChange: 'Off', Event: 'On', VideoPlay: 'Off', BlockedOnPlayerJoined: 'Off', @@ -8657,6 +8967,7 @@ speechSynthesis.getVoices(); DisplayName: 'Friends', TrustLevel: 'Friends', PortalSpawn: 'Everyone', + AvatarChange: 'Everyone', Event: 'On', VideoPlay: 'On', BlockedOnPlayerJoined: 'Off', @@ -8775,16 +9086,8 @@ speechSynthesis.getVoices(); this.updateVRConfigVars(); }; - sharedRepository.setBool('is_game_running', false); var isGameRunningStateChange = function () { - sharedRepository.setBool('is_game_running', this.isGameRunning); - this.lastLocation = { - date: 0, - location: '', - name: '', - playerList: [], - friendList: [] - }; + this.lastLocationReset(); if (this.isGameRunning) { API.currentUser.$online_for = Date.now(); API.currentUser.$offline_for = ''; @@ -8796,23 +9099,15 @@ speechSynthesis.getVoices(); }; $app.watch.isGameRunning = isGameRunningStateChange; - sharedRepository.setBool('is_Game_No_VR', false); - var isGameNoVRStateChange = function () { - sharedRepository.setBool('is_Game_No_VR', this.isGameNoVR); + var downloadProgressStateChange = function () { + this.updateVRConfigVars(); }; - $app.watch.isGameNoVR = isGameNoVRStateChange; - - var lastLocationStateChange = function () { - sharedRepository.setObject('last_location', $app.lastLocation); - $app.checkVRChatCacheDownload($app.lastLocation.location); - }; - $app.watch['lastLocation.location'] = lastLocationStateChange; + $app.watch.downloadProgress = downloadProgressStateChange; $app.methods.updateVRConfigVars = function () { - if (configRepository.getBool('isDarkMode')) { - var notificationTheme = 'sunset'; - } else { - var notificationTheme = 'relax'; + var notificationTheme = 'relax'; + if (this.isDarkMode) { + notificationTheme = 'sunset'; } var VRConfigVars = { notificationTTS: this.notificationTTS, @@ -8823,15 +9118,34 @@ speechSynthesis.getVoices(); notificationPosition: this.notificationPosition, notificationTimeout: this.notificationTimeout, notificationTheme, - backgroundEnabled: this.vrBackgroundEnabled + backgroundEnabled: this.vrBackgroundEnabled, + isGameRunning: this.isGameRunning, + isGameNoVR: this.isGameNoVR, + downloadProgress: this.downloadProgress }; - sharedRepository.setObject('VRConfigVars', VRConfigVars); - this.updateSharedFeed(true); + var json = JSON.stringify(VRConfigVars); + AppApi.ExecuteVrFeedFunction('configUpdate', json); + AppApi.ExecuteVrOverlayFunction('configUpdate', json); }; - API.$on('LOGIN', function () { - $app.updateVRConfigVars(); - }); + $app.methods.updateVRLastLocation = function () { + var lastLocation = { + date: this.lastLocation.date, + location: this.lastLocation.location, + name: this.lastLocation.name, + playerList: Array.from(this.lastLocation.playerList.values()), + friendList: Array.from(this.lastLocation.friendList.values()) + }; + var json = JSON.stringify(lastLocation); + AppApi.ExecuteVrFeedFunction('lastLocationUpdate', json); + AppApi.ExecuteVrOverlayFunction('lastLocationUpdate', json); + }; + + $app.methods.vrInit = function () { + this.updateVRConfigVars(); + this.updateVRLastLocation(); + this.updateSharedFeed(true); + }; API.$on('LOGIN', function () { $app.currentUserTreeData = []; @@ -9567,33 +9881,28 @@ speechSynthesis.getVoices(); var playersInInstance = this.lastLocation.playerList; if ( this.lastLocation.location === L.tag && - playersInInstance.length > 0 + playersInInstance.size > 0 ) { var ref = API.cachedUsers.get(API.currentUser.id); if (typeof ref === 'undefined') { ref = API.currentUser; } - if (playersInInstance.includes(ref.displayName)) { - users.push(ref); + if (playersInInstance.has(ref.displayName)) { + users.push(ref); // add self } var friendsInInstance = this.lastLocation.friendList; - for (var i = 0; i < friendsInInstance.length; i++) { + for (var friend of friendsInInstance.values()) { + // if friend isn't in instance add them var addUser = true; - var player = friendsInInstance[i]; for (var k = 0; k < users.length; k++) { var user = users[k]; - if (user.displayName === player) { + if (friend.displayName === user.displayName) { addUser = false; break; } } - if (addUser) { - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === player) { - users.push(ref); - break; - } - } + if (addUser && API.cachedUsers.has(friend.userId)) { + users.push(API.cachedUsers.get(friend.userId)); } } } else if (L.isOffline === false) { @@ -9617,7 +9926,7 @@ speechSynthesis.getVoices(); if (L.worldId && this.lastLocation.location === D.ref.location) { D.instance = { id: D.ref.location, - occupants: this.lastLocation.playerList.length + occupants: this.lastLocation.playerList.size }; } if (L.isOffline || L.isPrivate || L.worldId === '') { @@ -10156,7 +10465,17 @@ speechSynthesis.getVoices(); D.isFavorite = API.cachedFavoritesByObjectId.has(D.id); this.updateVRChatWorldCache(); if (args.cache) { - API.getWorld(args.params); + API.getWorld(args.params) + .catch((err) => { + throw err; + }) + .then((args1) => { + if (D.id === args1.ref.id) { + D.ref = args1.ref; + this.updateVRChatWorldCache(); + } + return args1; + }); } } return args; @@ -10186,7 +10505,7 @@ speechSynthesis.getVoices(); if (lastLocation$.worldId === D.id) { var instance = { id: lastLocation$.instanceId, - occupants: playersInInstance.length, + occupants: playersInInstance.size, users: [] }; instances[instance.id] = instance; @@ -10194,26 +10513,24 @@ speechSynthesis.getVoices(); if (typeof ref === 'undefined') { ref = API.currentUser; } - if (playersInInstance.includes(ref.displayName)) { - instance.users.push(ref); + if (playersInInstance.has(ref.displayName)) { + instance.users.push(ref); // add self } var friendsInInstance = this.lastLocation.friendList; - for (var i = 0; i < friendsInInstance.length; i++) { + for (var friend of friendsInInstance.values()) { + // if friend isn't in instance add them var addUser = true; - var player = friendsInInstance[i]; for (var k = 0; k < instance.users.length; k++) { var user = instance.users[k]; - if (user.displayName === player) { + if (friend.displayName === user.displayName) { addUser = false; break; } } if (addUser) { - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === player) { - instance.users.push(ref); - break; - } + var ref = API.cachedUsers.get(friend.userId); + if (typeof ref !== 'undefined') { + instance.users.push(ref); } } } @@ -12282,6 +12599,10 @@ speechSynthesis.getVoices(); if (val === null) { return; } + if (!val.id) { + this.lookupUser(val); + return; + } this.showUserDialog(val.id); }; @@ -13869,11 +14190,6 @@ speechSynthesis.getVoices(); $app.data.downloadQueue = new Map(); $app.data.downloadCurrent = {}; - var downloadProgressUpdateWrist = function () { - sharedRepository.setInt('downloadProgress', this.downloadProgress); - }; - $app.watch.downloadProgress = downloadProgressUpdateWrist; - $app.methods.downloadVRChatCacheProgress = async function () { var downloadProgress = await AssetBundleCacher.CheckDownloadProgress(); switch (downloadProgress) { @@ -14480,7 +14796,7 @@ speechSynthesis.getVoices(); this.$nextTick(() => adjustDialogZ(this.$refs.VRCXUpdateDialog.$el)); var D = this.VRCXUpdateDialog; D.visible = true; - D.updatePending = await AppApi.checkForUpdateZip(); + D.updatePending = await AppApi.CheckForUpdateZip(); this.loadBranchVersions(); }; @@ -14571,7 +14887,7 @@ speechSynthesis.getVoices(); }; $app.methods.checkForVRCXUpdate = async function () { - if (await AppApi.checkForUpdateZip()) { + if (await AppApi.CheckForUpdateZip()) { return; } var url = this.branches[this.branch].urlLatest; diff --git a/html/src/index.pug b/html/src/index.pug index 0286b08c..8bf3a478 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -185,10 +185,10 @@ html template(#tool) div(style="margin:0 0 10px;display:flex;align-items:center") el-select(v-model="gameLogTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" placeholder="Filter") - 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', 'AvatarChange', 'Event', 'VideoPlay']" :key="type" :label="type" :value="type") el-input(v-model="gameLogTable.filters[1].value" placeholder="Search" style="flex:none;width:150px;margin:0 10px") el-tooltip(placement="bottom" content="Reset game log" :disabled="hideTooltips") - el-button(type="default" @click="resetGameLog()" icon="el-icon-refresh" circle style="flex:none") + el-button(type="default" @click="refreshGameLog()" icon="el-icon-refresh" circle style="flex:none") el-table-column(label="Date" prop="created_at" sortable="custom" width="90") template(v-once #default="scope") el-tooltip(placement="right") @@ -196,17 +196,28 @@ html span {{ scope.row.created_at | formatDate('YYYY-MM-DD HH24:MI:SS') }} span {{ scope.row.created_at | formatDate('MM-DD HH24:MI') }} el-table-column(label="Type" prop="type" width="120") + template(v-once #default="scope") + span.x-link(v-if="scope.row.location && scope.row.type !== 'Location'" v-text="scope.row.type" @click="showWorldDialog(scope.row.location)") + span(v-else v-text="scope.row.type") + el-table-column(label="User" prop="displayName" width="160") + template(v-once #default="scope") + span.x-link(v-text="scope.row.displayName" @click="lookupUser(scope.row)") el-table-column(label="Detail" prop="data") template(v-once #default="scope") - location(v-if="scope.row.type === 'Location'" :location="scope.row.data[0]" :hint="scope.row.data[1]") + location(v-if="scope.row.type === 'Location'" :location="scope.row.location" :hint="scope.row.worldName") + location(v-else-if="scope.row.type === 'PortalSpawn'" :location="scope.row.instanceId" :hint="scope.row.worldName") + template(v-else-if="scope.row.type === 'AvatarChange'") + span.x-link(@click="showUserDialog(scope.row.authorId)" v-text="scope.row.name") + template(v-if="scope.row.description && scope.row.name !== scope.row.description") + | - {{ scope.row.description }} template(v-else-if="scope.row.type === 'Event'") span(v-text="scope.row.data") template(v-else-if="scope.row.type === 'VideoPlay'") - span.x-link(v-text="scope.row.data" @click="openExternalLink(scope.row.data)") - template(v-if="scope.row.displayName") - span.x-link(@click="lookupUser(scope.row.displayName)") ({{ scope.row.displayName }}) - template(v-else-if="scope.row.type === 'Notification'") - span.x-link(v-else v-text="scope.row.data" @click="lookupUser(scope.row.data)") + span(v-if="scope.row.videoId") {{ scope.row.videoId }}: + span.x-link(v-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 === 'Notification' || scope.row.type === 'OnPlayerJoined' || scope.row.type === 'OnPlayerLeft'") + span.x-link(v-else v-text="scope.row.data") //- search .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'search'") @@ -1924,15 +1935,22 @@ html el-radio-button(label="Friends") el-radio-button(label="Everyone") .toggle-item - span.toggle-name Events - el-radio-group(v-model="sharedFeedFilters.noty.Event" size="mini") + span.toggle-name Avatar Change + el-radio-group(v-model="sharedFeedFilters.noty.AvatarChange" size="mini") el-radio-button(label="Off") - el-radio-button(label="On") + el-radio-button(label="VIP") + el-radio-button(label="Friends") + el-radio-button(label="Everyone") .toggle-item span.toggle-name Video Play el-radio-group(v-model="sharedFeedFilters.noty.VideoPlay" size="mini") el-radio-button(label="Off") el-radio-button(label="On") + .toggle-item + span.toggle-name Events + el-radio-group(v-model="sharedFeedFilters.noty.Event" size="mini") + el-radio-button(label="Off") + el-radio-button(label="On") .toggle-item span.toggle-name Blocked Player Joins el-radio-group(v-model="sharedFeedFilters.noty.BlockedOnPlayerJoined" size="mini") @@ -2076,13 +2094,20 @@ html el-radio-button(label="Friends") el-radio-button(label="Everyone") .toggle-item - span.toggle-name Events - el-radio-group(v-model="sharedFeedFilters.wrist.Event" size="mini") + span.toggle-name Avatar Change + el-radio-group(v-model="sharedFeedFilters.wrist.AvatarChange" size="mini") + el-radio-button(label="Off") + el-radio-button(label="VIP") + el-radio-button(label="Friends") + el-radio-button(label="Everyone") + .toggle-item + span.toggle-name Video Play + el-radio-group(v-model="sharedFeedFilters.wrist.VideoPlay" size="mini") el-radio-button(label="Off") el-radio-button(label="On") .toggle-item - span.toggle-name Video Play - el-radio-group(v-model="sharedFeedFilters.noty.VideoPlay" size="mini") + span.toggle-name Events + el-radio-group(v-model="sharedFeedFilters.wrist.Event" size="mini") el-radio-button(label="Off") el-radio-button(label="On") .toggle-item diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 0ec42f4a..0ff1042a 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -1,7 +1,7 @@ import sqliteService from '../service/sqlite.js'; class Database { - async init(userId) { + async initUserTables(userId) { Database.userId = userId.replaceAll('-', '').replaceAll('_', ''); await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS ${Database.userId}_feed_gps (id INTEGER PRIMARY KEY, created_at TEXT, user_id TEXT, display_name TEXT, location TEXT, world_name TEXT, previous_location TEXT, time INTEGER)` @@ -26,6 +26,24 @@ class Database { ); } + async initTables() { + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS gamelog_location (id INTEGER PRIMARY KEY, created_at TEXT, location TEXT, world_id TEXT, world_name TEXT, time INTEGER, UNIQUE(created_at, location))` + ); + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS gamelog_join_leave (id INTEGER PRIMARY KEY, created_at TEXT, type TEXT, display_name TEXT, location TEXT, user_id TEXT, time INTEGER, UNIQUE(created_at, type, display_name))` + ); + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS gamelog_portal_spawn (id INTEGER PRIMARY KEY, created_at TEXT, display_name TEXT, location TEXT, user_id TEXT, instance_id TEXT, world_name TEXT, UNIQUE(created_at, display_name))` + ); + 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_event (id INTEGER PRIMARY KEY, created_at TEXT, data TEXT, UNIQUE(created_at, data))` + ); + } + async getFeedDatabase() { var feedDatabase = []; var date = new Date(); @@ -349,6 +367,162 @@ class Database { } ); } + + async getGamelogDatabase() { + var gamelogDatabase = []; + var date = new Date(); + date.setDate(date.getDate() - 3); // 3 day limit + var dateOffset = date.toJSON(); + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: 'Location', + location: dbRow[2], + worldId: dbRow[3], + worldName: dbRow[4], + time: dbRow[5] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_location WHERE created_at >= date('${dateOffset}')`); + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: dbRow[2], + displayName: dbRow[3], + location: dbRow[4], + userId: dbRow[5], + time: dbRow[6] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_join_leave WHERE created_at >= date('${dateOffset}')`); + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: 'PortalSpawn', + displayName: dbRow[2], + location: dbRow[3], + userId: dbRow[4], + instanceId: dbRow[5], + worldName: dbRow[6] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_portal_spawn WHERE created_at >= date('${dateOffset}')`); + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: 'VideoPlay', + videoUrl: dbRow[2], + videoName: dbRow[3], + videoId: dbRow[4], + location: dbRow[5], + displayName: dbRow[6], + userId: dbRow[7] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_video_play WHERE created_at >= date('${dateOffset}')`); + await sqliteService.execute((dbRow) => { + var row = { + rowId: dbRow[0], + created_at: dbRow[1], + type: 'Event', + data: dbRow[2] + }; + gamelogDatabase.unshift(row); + }, `SELECT * FROM gamelog_event WHERE created_at >= date('${dateOffset}')`); + var compareByCreatedAt = function (a, b) { + var A = a.created_at; + var B = b.created_at; + if (A < B) { + return -1; + } + if (A > B) { + return 1; + } + return 0; + }; + gamelogDatabase.sort(compareByCreatedAt); + return gamelogDatabase; + } + + addGamelogLocationToDatabase(entry) { + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO gamelog_location (created_at, location, world_id, world_name, time) VALUES (@created_at, @location, @world_id, @world_name, @time)`, + { + '@created_at': entry.created_at, + '@location': entry.location, + '@world_id': entry.worldId, + '@world_name': entry.worldName, + '@time': entry.time + } + ); + } + + updateGamelogLocationTimeToDatabase(entry) { + sqliteService.executeNonQuery( + `UPDATE gamelog_location SET time = @time WHERE created_at = @created_at`, + { + '@created_at': entry.created_at, + '@time': entry.time + } + ); + } + + addGamelogJoinLeaveToDatabase(entry) { + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO gamelog_join_leave (created_at, type, display_name, location, user_id, time) VALUES (@created_at, @type, @display_name, @location, @user_id, @time)`, + { + '@created_at': entry.created_at, + '@type': entry.type, + '@display_name': entry.displayName, + '@location': entry.location, + '@user_id': entry.userId, + '@time': entry.time + } + ); + } + + addGamelogPortalSpawnToDatabase(entry) { + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO gamelog_portal_spawn (created_at, display_name, location, user_id, instance_id, world_name) VALUES (@created_at, @display_name, @location, @user_id, @instance_id, @world_name)`, + { + '@created_at': entry.created_at, + '@display_name': entry.displayName, + '@location': entry.location, + '@user_id': entry.userId, + '@instance_id': entry.instanceId, + '@world_name': entry.worldName + } + ); + } + + addGamelogVideoPlayToDatabase(entry) { + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO gamelog_video_play (created_at, video_url, video_name, video_id, location, display_name, user_id) VALUES (@created_at, @video_url, @video_name, @video_id, @location, @display_name, @user_id)`, + { + '@created_at': entry.created_at, + '@video_url': entry.videoUrl, + '@video_name': entry.videoName, + '@video_id': entry.videoId, + '@location': entry.location, + '@display_name': entry.displayName, + '@user_id': entry.userId + } + ); + } + + addGamelogEventToDatabase(entry) { + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO gamelog_event (created_at, data) VALUES (@created_at, @data)`, + { + '@created_at': entry.created_at, + '@data': entry.data + } + ); + } } var self = new Database(); diff --git a/html/src/service/gamelog.js b/html/src/service/gamelog.js index 598e435d..20c4c015 100644 --- a/html/src/service/gamelog.js +++ b/html/src/service/gamelog.js @@ -1,93 +1,78 @@ // requires binding of LogWatcher -// -var contextMap = new Map(); - -function parseRawGameLog(dt, type, args) { - var gameLog = { - dt, - type - }; - - switch (type) { - case 'location': - gameLog.location = args[0]; - gameLog.worldName = args[1]; - break; - - case 'player-joined': - gameLog.userDisplayName = args[0]; - gameLog.userType = args[1]; - break; - - case 'player-left': - gameLog.userDisplayName = args[0]; - break; - - case 'notification': - gameLog.json = args[0]; - break; - - case 'portal-spawn': - gameLog.userDisplayName = args[0]; - break; - - case 'event': - gameLog.event = args[0]; - break; - - case 'video-play': - gameLog.videoURL = args[0]; - gameLog.displayName = args[1]; - break; - - default: - break; - } - - return gameLog; -} - class GameLogService { - async poll() { - var rawGameLogs = await LogWatcher.Get(); - var gameLogs = []; - var now = Date.now(); + parseRawGameLog(dt, type, args) { + var gameLog = { + dt, + type + }; - for (var [fileName, dt, type, ...args] of rawGameLogs) { - var context = contextMap.get(fileName); - if (typeof context === 'undefined') { - context = { - updatedAt: null, + switch (type) { + case 'location': + gameLog.location = args[0]; + gameLog.worldName = args[1]; + break; - // location - location: null - }; - contextMap.set(fileName, context); - } + case 'player-joined': + gameLog.userDisplayName = args[0]; + gameLog.userType = args[1]; + break; - var gameLog = parseRawGameLog(dt, type, args); + case 'player-left': + gameLog.userDisplayName = args[0]; + break; - switch (gameLog.type) { - case 'location': - context.location = gameLog.location; - break; + case 'notification': + gameLog.json = args[0]; + break; - default: - break; - } + case 'portal-spawn': + gameLog.userDisplayName = args[0]; + break; - context.updatedAt = now; + case 'event': + gameLog.event = args[0]; + break; - gameLogs.push(gameLog); + case 'video-play': + gameLog.videoUrl = args[0]; + gameLog.displayName = args[1]; + break; + + case 'vrcx': + gameLog.data = args[0]; + break; + + default: + break; } + return gameLog; + } + + async getAll() { + var gameLogs = []; + var done = false; + while (!done) { + var rawGameLogs = await LogWatcher.Get(); + // eslint-disable-next-line no-unused-vars + for (var [fileName, dt, type, ...args] of rawGameLogs) { + var gameLog = this.parseRawGameLog(dt, type, args); + gameLogs.push(gameLog); + } + if (rawGameLogs.length === 0) { + done = true; + } + } return gameLogs; } + async setDateTill(dateTill) { + await LogWatcher.SetDateTill(dateTill); + } + async reset() { await LogWatcher.Reset(); - contextMap.clear(); } } diff --git a/html/src/vr.js b/html/src/vr.js index 84684d3e..87117845 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -9,10 +9,7 @@ import Vue from 'vue'; import ElementUI from 'element-ui'; import locale from 'element-ui/lib/locale/lang/en'; -import {appVersion} from './constants.js'; -import sharedRepository from './repository/shared.js'; import configRepository from './repository/config.js'; -import webApiService from './service/webapi.js'; speechSynthesis.getVoices(); @@ -109,273 +106,104 @@ speechSynthesis.getVoices(); }; Vue.filter('timeToText', timeToText); - // - // API - // - - var API = {}; - - API.eventHandlers = new Map(); - - API.$emit = function (name, ...args) { - // console.log(name, ...args); - var handlers = this.eventHandlers.get(name); - if (typeof handlers === 'undefined') { - return; - } - try { - for (var handler of handlers) { - handler.apply(this, args); + Vue.component('location', { + template: + '{{ text }}', + props: { + location: String, + hint: { + type: String, + default: '' } - } catch (err) { - console.error(err); - } - }; - - API.$on = function (name, handler) { - var handlers = this.eventHandlers.get(name); - if (typeof handlers === 'undefined') { - handlers = []; - this.eventHandlers.set(name, handlers); - } - handlers.push(handler); - }; - - API.$off = function (name, handler) { - var handlers = this.eventHandlers.get(name); - if (typeof handlers === 'undefined') { - return; - } - var {length} = handlers; - for (var i = 0; i < length; ++i) { - if (handlers[i] === handler) { - if (length > 1) { - handlers.splice(i, 1); - } else { - this.eventHandlers.delete(name); - } - break; - } - } - }; - - API.pendingGetRequests = new Map(); - - API.call = function (endpoint, options) { - var init = { - url: `https://api.vrchat.cloud/api/1/${endpoint}`, - method: 'GET', - ...options - }; - var {params} = init; - var isGetRequest = init.method === 'GET'; - if (isGetRequest === true) { - // transform body to url - if (params === Object(params)) { - var url = new URL(init.url); - var {searchParams} = url; - for (var key in params) { - searchParams.set(key, params[key]); - } - init.url = url.toString(); - } - // merge requests - var req = this.pendingGetRequests.get(init.url); - if (typeof req !== 'undefined') { - return req; - } - } else { - init.headers = { - 'Content-Type': 'application/json;charset=utf-8', - ...init.headers + }, + data() { + return { + text: this.location, + region: this.region }; - init.body = - params === Object(params) ? JSON.stringify(params) : '{}'; - } - init.headers = { - 'User-Agent': appVersion, - ...init.headers - }; - var req = webApiService - .execute(init) - .catch((err) => { - this.$throw(0, err); - }) - .then((response) => { - try { - response.data = JSON.parse(response.data); - return response; - } catch (e) {} - if (response.status === 200) { - this.$throw(0, 'Invalid JSON response'); - } - this.$throw(response.status); - return {}; - }) - .then(({data, status}) => { - if (data === Object(data)) { - if (status === 200) { - if (data.success === Object(data.success)) { - new Noty({ - type: 'success', - text: escapeTag(data.success.message) - }).show(); - } - return data; + }, + methods: { + parse() { + this.text = this.location; + var L = $app.parseLocation(this.location); + if (L.isOffline) { + this.text = 'Offline'; + } else if (L.isPrivate) { + this.text = 'Private'; + } else if (typeof this.hint === 'string' && this.hint !== '') { + if (L.instanceId) { + this.text = `${this.hint} #${L.instanceName} ${L.accessType}`; + } else { + this.text = this.hint; } - if (data.error === Object(data.error)) { - this.$throw( - data.error.status_code || status, - data.error.message, - data.error.data - ); - } else if (typeof data.error === 'string') { - this.$throw(data.status_code || status, data.error); + } else if (L.worldId) { + if (L.instanceId) { + this.text = ` #${L.instanceName} ${L.accessType}`; + } else { + this.text = this.location; + } + } + this.region = ''; + if ( + this.location !== '' && + L.instanceId && + !L.isOffline && + !L.isPrivate + ) { + if (L.region === 'eu') { + this.region = 'europeanunion'; + } else if (L.region === 'jp') { + this.region = 'jp'; + } else { + this.region = 'us'; } } - this.$throw(status, data); - return data; - }); - if (isGetRequest === true) { - req.finally(() => { - this.pendingGetRequests.delete(init.url); - }); - this.pendingGetRequests.set(init.url, req); - } - return req; - }; - - API.statusCodes = { - 100: 'Continue', - 101: 'Switching Protocols', - 102: 'Processing', - 103: 'Early Hints', - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-Authoritative Information', - 204: 'No Content', - 205: 'Reset Content', - 206: 'Partial Content', - 207: 'Multi-Status', - 208: 'Already Reported', - 226: 'IM Used', - 300: 'Multiple Choices', - 301: 'Moved Permanently', - 302: 'Found', - 303: 'See Other', - 304: 'Not Modified', - 305: 'Use Proxy', - 306: 'Switch Proxy', - 307: 'Temporary Redirect', - 308: 'Permanent Redirect', - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Failed', - 413: 'Payload Too Large', - 414: 'URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Range Not Satisfiable', - 417: 'Expectation Failed', - 418: "I'm a teapot", - 421: 'Misdirected Request', - 422: 'Unprocessable Entity', - 423: 'Locked', - 424: 'Failed Dependency', - 425: 'Too Early', - 426: 'Upgrade Required', - 428: 'Precondition Required', - 429: 'Too Many Requests', - 431: 'Request Header Fields Too Large', - 451: 'Unavailable For Legal Reasons', - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported', - 506: 'Variant Also Negotiates', - 507: 'Insufficient Storage', - 508: 'Loop Detected', - 510: 'Not Extended', - 511: 'Network Authentication Required', - // CloudFlare Error - 520: 'Web server returns an unknown error', - 521: 'Web server is down', - 522: 'Connection timed out', - 523: 'Origin is unreachable', - 524: 'A timeout occurred', - 525: 'SSL handshake failed', - 526: 'Invalid SSL certificate', - 527: 'Railgun Listener to origin error' - }; - - API.$throw = function (code, error) { - var text = []; - if (code > 0) { - var status = this.statusCodes[code]; - if (typeof status === 'undefined') { - text.push(`${code}`); - } else { - text.push(`${code} ${status}`); } + }, + watch: { + location() { + this.parse(); + } + }, + created() { + this.parse(); } - if (typeof error !== 'undefined') { - text.push(JSON.stringify(error)); - } - text = text.map((s) => escapeTag(s)).join('
'); - if (text.length) { - new Noty({ - type: 'error', - text - }).show(); - } - throw new Error(text); - }; - - // API: Config - - API.cachedConfig = {}; - - API.$on('CONFIG', function (args) { - args.ref = this.applyConfig(args.json); }); - API.applyConfig = function (json) { - var ref = { - clientApiKey: '', - ...json - }; - this.cachedConfig = ref; - return ref; + var $app = { + data: { + // 1 = 대시보드랑 손목에 보이는거 + // 2 = 항상 화면에 보이는 거 + appType: location.href.substr(-1), + currentTime: new Date().toJSON(), + cpuUsage: 0, + config: {}, + lastLocation: { + date: 0, + location: '', + name: '', + playerList: [], + friendList: [] + }, + lastLocationTimer: '', + wristFeed: [], + devices: [] + }, + computed: {}, + methods: {}, + watch: {}, + el: '#x-app', + mounted() { + setTimeout(function () { + AppApi.ExecuteAppFunction('vrInit', ''); + }, 1000); + if (this.appType === '1') { + this.updateStatsLoop(); + } + } }; - API.getConfig = function () { - return this.call('config', { - method: 'GET' - }).then((json) => { - var args = { - ref: null, - json - }; - this.$emit('CONFIG', args); - return args; - }); - }; - - // API: Location - - API.parseLocation = function (tag) { + $app.methods.parseLocation = function (tag) { var _tag = String(tag || ''); var ctx = { tag: _tag, @@ -448,380 +276,32 @@ speechSynthesis.getVoices(); return ctx; }; - Vue.component('location', { - template: - '{{ text }}', - props: { - location: String, - hint: { - type: String, - default: '' - } - }, - data() { - return { - text: this.location, - region: this.region - }; - }, - methods: { - parse() { - this.text = this.location; - var L = API.parseLocation(this.location); - if (L.isOffline) { - this.text = 'Offline'; - } else if (L.isPrivate) { - this.text = 'Private'; - } else if (typeof this.hint === 'string' && this.hint !== '') { - if (L.instanceId) { - this.text = `${this.hint} #${L.instanceName} ${L.accessType}`; - } else { - this.text = this.hint; - } - } else if (L.worldId) { - if (L.instanceId) { - this.text = ` #${L.instanceName} ${L.accessType}`; - } else { - this.text = this.location; - } - } - this.region = ''; - if ( - this.location !== '' && - L.instanceId && - !L.isOffline && - !L.isPrivate - ) { - if (L.region === 'eu') { - this.region = 'europeanunion'; - } else if (L.region === 'jp') { - this.region = 'jp'; - } else { - this.region = 'us'; - } - } - } - }, - watch: { - location() { - this.parse(); - } - }, - created() { - this.parse(); - } - }); - - // API: World - - API.cachedWorlds = new Map(); - - API.$on('WORLD', function (args) { - args.ref = this.applyWorld(args.json); - }); - - API.applyWorld = function (json) { - var ref = this.cachedWorlds.get(json.id); - if (typeof ref === 'undefined') { - ref = { - id: '', - name: '', - description: '', - authorId: '', - authorName: '', - capacity: 0, - tags: [], - releaseStatus: '', - imageUrl: '', - thumbnailImageUrl: '', - assetUrl: '', - assetUrlObject: {}, - pluginUrl: '', - pluginUrlObject: {}, - unityPackageUrl: '', - unityPackageUrlObject: {}, - unityPackages: [], - version: 0, - favorites: 0, - created_at: '', - updated_at: '', - publicationDate: '', - labsPublicationDate: '', - visits: 0, - popularity: 0, - heat: 0, - publicOccupants: 0, - privateOccupants: 0, - occupants: 0, - instances: [], - // VRCX - $isLabs: false, - // - ...json - }; - this.cachedWorlds.set(ref.id, ref); - } else { - Object.assign(ref, json); - } - ref.$isLabs = ref.tags.includes('system_labs'); - return ref; + $app.methods.configUpdate = function (json) { + this.config = JSON.parse(json); }; - /* - params: { - worldId: string - } - */ - API.getWorld = function (params) { - return this.call(`worlds/${params.worldId}`, { - method: 'GET' - }).then((json) => { - var args = { - ref: null, - json, - params - }; - this.$emit('WORLD', args); - return args; - }); + $app.methods.lastLocationUpdate = function (json) { + this.lastLocation = JSON.parse(json); }; - // API: User - - API.cachedUsers = new Map(); - - API.$on('USER', function (args) { - args.ref = this.applyUser(args.json); - }); - - API.applyUser = function (json) { - var ref = this.cachedUsers.get(json.id); - if (typeof ref === 'undefined') { - ref = { - id: '', - username: '', - displayName: '', - userIcon: '', - bio: '', - bioLinks: [], - currentAvatarImageUrl: '', - currentAvatarThumbnailImageUrl: '', - status: '', - statusDescription: '', - state: '', - tags: [], - developerType: '', - last_login: '', - last_platform: '', - allowAvatarCopying: false, - isFriend: false, - location: '', - worldId: '', - instanceId: '', - // VRCX - ...json - }; - this.cachedUsers.set(ref.id, ref); - } else { - var props = {}; - for (var prop in ref) { - if (ref[prop] !== Object(ref[prop])) { - props[prop] = true; - } - } - var $ref = {...ref}; - Object.assign(ref, json); - for (var prop in ref) { - if (ref[prop] !== Object(ref[prop])) { - props[prop] = true; - } - } - for (var prop in props) { - var asis = $ref[prop]; - var tobe = ref[prop]; - if (asis === tobe) { - delete props[prop]; - } else { - props[prop] = [tobe, asis]; - } - } - } - return ref; + $app.methods.wristFeedUpdate = function (json) { + this.wristFeed = JSON.parse(json); }; - /* - params: { - userId: string - } - */ - API.getUser = function (params) { - return this.call(`users/${params.userId}`, { - method: 'GET' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('USER', args); - return args; - }); - }; + $app.methods.updateStatsLoop = async function () { + try { + this.currentTime = new Date().toJSON(); + var cpuUsage = await AppApi.CpuUsage(); + this.cpuUsage = cpuUsage.toFixed(0); - /* - params: { - userId: string - } - */ - API.getCachedUser = function (params) { - return new Promise((resolve, reject) => { - var ref = this.cachedUsers.get(params.userId); - if (typeof ref === 'undefined') { - this.getUser(params).catch(reject).then(resolve); - } else { - resolve({ - cache: true, - json: ref, - params, - ref - }); - } - }); - }; - - var $app = { - data: { - API, - // 1 = 대시보드랑 손목에 보이는거 - // 2 = 항상 화면에 보이는 거 - appType: location.href.substr(-1), - currentTime: new Date().toJSON(), - currentUserStatus: null, - cpuUsage: 0, - config: {}, - isGameRunning: false, - isGameNoVR: false, - downloadProgress: 0, - lastLocation: { - date: 0, - location: '', - name: '', - playerList: [], - friendList: [] - }, - lastLocationTimer: '', - wristFeedLastEntry: '', - notyFeedLastEntry: '', - wristFeed: [], - notyMap: [], - devices: [] - }, - computed: {}, - methods: {}, - watch: {}, - el: '#x-app', - mounted() { - // https://media.discordapp.net/attachments/581757976625283083/611170278218924033/unknown.png - // 현재 날짜 시간 - // 컨트롤러 배터리 상황 - // -- - // OO is in Let's Just H!!!!! [GPS] - // OO has logged in [Online] -> TODO: location - // OO has logged out [Offline] -> TODO: location - // OO has joined [OnPlayerJoined] - // OO has left [OnPlayerLeft] - // [Moderation] - // OO has blocked you - // OO has muted you - // OO has hidden you - // -- - API.getConfig() - .catch((err) => { - // FIXME: 어케 복구하냐 이건 - throw err; - }) - .then((args) => { - if (this.appType === '1') { - this.updateCpuUsageLoop(); - } - this.initLoop(); - return args; - }); - } - }; - - $app.methods.updateVRConfigVars = function () { - this.currentUserStatus = sharedRepository.getString( - 'current_user_status' - ); - this.isGameRunning = sharedRepository.getBool('is_game_running'); - this.isGameNoVR = sharedRepository.getBool('is_Game_No_VR'); - this.downloadProgress = sharedRepository.getInt('downloadProgress'); - var lastLocation = sharedRepository.getObject('last_location'); - if (lastLocation) { - this.lastLocation = lastLocation; + this.lastLocationTimer = ''; if (this.lastLocation.date !== 0) { this.lastLocationTimer = timeToText( Date.now() - this.lastLocation.date ); - } else { - this.lastLocationTimer = ''; } - } - var newConfig = sharedRepository.getObject('VRConfigVars'); - if (newConfig) { - if (JSON.stringify(newConfig) !== JSON.stringify(this.config)) { - this.config = newConfig; - this.notyFeedLastEntry = ''; - this.wristFeedLastEntry = ''; - if (this.appType === '2') { - this.initNotyMap(); - } - } - } else { - throw 'config not set'; - } - }; - $app.methods.initNotyMap = function () { - var notyFeed = sharedRepository.getArray('notyFeed'); - if (notyFeed === null) { - return; - } - notyFeed.forEach((feed) => { - var displayName = ''; - if (feed.displayName) { - displayName = feed.displayName; - } else if (feed.senderUsername) { - displayName = feed.senderUsername; - } else if (feed.sourceDisplayName) { - displayName = feed.sourceDisplayName; - } else if (feed.data) { - displayName = feed.data; - } else { - console.error('missing displayName'); - } - if ( - (displayName && !this.notyMap[displayName]) || - this.notyMap[displayName] < feed.created_at - ) { - this.notyMap[displayName] = feed.created_at; - } - }); - }; - - $app.methods.initLoop = function () { - if (!sharedRepository.getBool('VRInit')) { - setTimeout(this.initLoop, 500); - } else { - this.updateLoop(); - } - }; - - $app.methods.updateLoop = async function () { - try { - this.currentTime = new Date().toJSON(); - await this.updateVRConfigVars(); - if (!this.config.hideDevicesFromFeed && this.appType === '1') { + if (!this.config.hideDevicesFromFeed) { AppApi.GetVRDevices().then((devices) => { devices.forEach((device) => { device[2] = parseInt(device[2], 10); @@ -831,183 +311,124 @@ speechSynthesis.getVoices(); } else { this.devices = ''; } - await this.updateSharedFeeds(); } catch (err) { console.error(err); } - setTimeout(() => this.updateLoop(), 500); + setTimeout(() => this.updateStatsLoop(), 500); }; - $app.methods.updateCpuUsageLoop = async function () { - try { - var cpuUsage = await AppApi.CpuUsage(); - this.cpuUsage = cpuUsage.toFixed(0); - } catch (err) { - console.error(err); + $app.methods.playNoty = function (json) { + var {noty, message, imageUrl} = JSON.parse(json); + var text = ''; + var img = ''; + if (imageUrl) { + img = ``; } - setTimeout(() => this.updateCpuUsageLoop(), 1000); - }; - - $app.methods.updateSharedFeeds = function () { - if (this.appType === '1') { - this.wristFeed = sharedRepository.getArray('wristFeed'); - } - if (this.appType === '2') { - var notyFeed = sharedRepository.getArray('notyFeed'); - this.updateSharedFeedNoty(notyFeed); - } - }; - - $app.methods.updateSharedFeedNoty = function (notyFeed) { - var notyToPlay = []; - notyFeed.forEach((feed) => { - var displayName = ''; - if (feed.displayName) { - displayName = feed.displayName; - } else if (feed.senderUsername) { - displayName = feed.senderUsername; - } else if (feed.sourceDisplayName) { - displayName = feed.sourceDisplayName; - } else if (feed.data) { - displayName = feed.data; - } else { - console.error('missing displayName'); - } - if ( - (displayName && !this.notyMap[displayName]) || - this.notyMap[displayName] < feed.created_at - ) { - this.notyMap[displayName] = feed.created_at; - notyToPlay.push(feed); - } - }); - // disable notifications when busy - if (this.currentUserStatus === 'busy') { - return; - } - var bias = new Date(Date.now() - 60000).toJSON(); - var noty = {}; - var messageList = [ - 'inviteMessage', - 'requestMessage', - 'responseMessage' - ]; - for (var i = 0; i < notyToPlay.length; i++) { - noty = notyToPlay[i]; - if (noty.created_at < bias) { - continue; - } - var message = ''; - for (var k = 0; k < messageList.length; k++) { - if ( - typeof noty.details !== 'undefined' && - typeof noty.details[messageList[k]] !== 'undefined' - ) { - message = noty.details[messageList[k]]; + switch (noty.type) { + case 'OnPlayerJoined': + text = `${noty.displayName} has joined`; + break; + case 'OnPlayerLeft': + text = `${noty.displayName} has left`; + break; + case 'OnPlayerJoining': + text = `${noty.displayName} is joining`; + break; + case 'GPS': + text = `${ + noty.displayName + } is in ${this.displayLocation( + noty.location, + noty.worldName + )}`; + break; + case 'Online': + text = `${noty.displayName} has logged in`; + break; + case 'Offline': + text = `${noty.displayName} has logged out`; + break; + case 'Status': + text = `${noty.displayName} status is now ${noty.status} ${noty.statusDescription}`; + break; + case 'invite': + text = `${ + noty.senderUsername + } has invited you to ${this.displayLocation( + noty.details.worldId, + noty.details.worldName + )}${message}`; + break; + case 'requestInvite': + text = `${noty.senderUsername} has requested an invite ${message}`; + break; + case 'inviteResponse': + text = `${noty.senderUsername} has responded to your invite ${message}`; + break; + case 'requestInviteResponse': + text = `${noty.senderUsername} has responded to your invite request ${message}`; + break; + case 'friendRequest': + text = `${noty.senderUsername} has sent you a friend request`; + break; + case 'Friend': + text = `${noty.displayName} is now your friend`; + break; + case 'Unfriend': + text = `${noty.displayName} is no longer your friend`; + break; + case 'TrustLevel': + text = `${noty.displayName} trust level is now ${noty.trustLevel}`; + break; + case 'DisplayName': + text = `${noty.previousDisplayName} changed their name to ${noty.displayName}`; + break; + case 'PortalSpawn': + var locationName = ''; + if (noty.worldName) { + locationName = ` to ${this.displayLocation( + noty.instanceId, + noty.worldName + )}`; } - } - if (message) { - message = `, ${message}`; - } - if ( - this.config.overlayNotifications && - !this.isGameNoVR && - this.isGameRunning - ) { - var text = ''; - switch (noty.type) { - case 'OnPlayerJoined': - text = `${noty.data} has joined`; - break; - case 'OnPlayerLeft': - text = `${noty.data} has left`; - break; - case 'OnPlayerJoining': - text = `${noty.displayName} is joining`; - break; - case 'GPS': - text = `${ - noty.displayName - } is in ${this.displayLocation( - noty.location, - noty.worldName - )}`; - break; - case 'Online': - text = `${noty.displayName} has logged in`; - break; - case 'Offline': - text = `${noty.displayName} has logged out`; - break; - case 'Status': - text = `${noty.displayName} status is now ${noty.status} ${noty.statusDescription}`; - break; - case 'invite': - text = `${ - noty.senderUsername - } has invited you to ${this.displayLocation( - noty.details.worldId, - noty.details.worldName - )}${message}`; - break; - case 'requestInvite': - text = `${noty.senderUsername} has requested an invite ${message}`; - break; - case 'inviteResponse': - text = `${noty.senderUsername} has responded to your invite ${message}`; - break; - case 'requestInviteResponse': - text = `${noty.senderUsername} has responded to your invite request ${message}`; - break; - case 'friendRequest': - text = `${noty.senderUsername} has sent you a friend request`; - break; - case 'Friend': - text = `${noty.displayName} is now your friend`; - break; - case 'Unfriend': - text = `${noty.displayName} is no longer your friend`; - break; - case 'TrustLevel': - text = `${noty.displayName} trust level is now ${noty.trustLevel}`; - break; - case 'DisplayName': - text = `${noty.previousDisplayName} changed their name to ${noty.displayName}`; - break; - case 'PortalSpawn': - text = `${noty.data} has spawned a portal`; - break; - case 'Event': - text = noty.data; - break; - case 'VideoPlay': - text = `Now playing: ${noty.data}`; - break; - case 'BlockedOnPlayerJoined': - text = `Blocked user ${noty.displayName} has joined`; - break; - case 'BlockedOnPlayerLeft': - text = `Blocked user ${noty.displayName} has left`; - break; - case 'MutedOnPlayerJoined': - text = `Muted user ${noty.displayName} has joined`; - break; - case 'MutedOnPlayerLeft': - text = `Muted user ${noty.displayName} has left`; - break; - default: - break; + text = `${noty.displayName} has spawned a portal${locationName}`; + break; + case 'AvatarChange': + text = `${noty.displayName} changed into avatar ${noty.name}`; + break; + case 'Event': + text = noty.data; + break; + case 'VideoPlay': + var videoName = noty.videoUrl; + if (noty.videoName) { + videoName = noty.videoName; } - if (text) { - new Noty({ - type: 'alert', - theme: this.config.notificationTheme, - timeout: this.config.notificationTimeout, - layout: this.config.notificationPosition, - text - }).show(); - } - } + text = `Now playing: ${videoName}`; + break; + case 'BlockedOnPlayerJoined': + text = `Blocked user ${noty.displayName} has joined`; + break; + case 'BlockedOnPlayerLeft': + text = `Blocked user ${noty.displayName} has left`; + break; + case 'MutedOnPlayerJoined': + text = `Muted user ${noty.displayName} has joined`; + break; + case 'MutedOnPlayerLeft': + text = `Muted user ${noty.displayName} has left`; + break; + default: + break; + } + if (text) { + new Noty({ + type: 'alert', + theme: this.config.notificationTheme, + timeout: this.config.notificationTimeout, + layout: this.config.notificationPosition, + text: `${img}
${text}
` + }).show(); } }; @@ -1033,7 +454,7 @@ speechSynthesis.getVoices(); $app.methods.displayLocation = function (location, worldName) { var text = ''; - var L = API.parseLocation(location); + var L = this.parseLocation(location); if (L.isOffline) { text = 'Offline'; } else if (L.isPrivate) { diff --git a/html/src/vr.pug b/html/src/vr.pug index 516220b2..64b7580a 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -36,17 +36,23 @@ html .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | #[span.name(v-text="feed.displayName")] #[i.x-user-status(:class="statusClass(feed.status)")] {{feed.statusDescription}} + | #[span.name(v-text="feed.displayName")] + template(v-if="feed.statusDescription === feed.previousStatusDescription") + i.x-user-status(:class="statusClass(feed.previousStatus)") + i.el-icon-right + i.x-user-status(:class="statusClass(feed.status)") + template(v-else) + | #[i.x-user-status(:class="statusClass(feed.status)")] {{feed.statusDescription}} div(v-else-if="feed.type === 'OnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | ▶️ #[span.name(v-text="feed.data")] + | ▶️ #[span.name(v-text="feed.displayName")] div(v-else-if="feed.type === 'OnPlayerLeft'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | ◀️ #[span.name(v-text="feed.data")] + | ◀️ #[span.name(v-text="feed.displayName")] div(v-else-if="feed.type === 'OnPlayerJoining'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -57,7 +63,16 @@ html .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - location(:location="feed.data[0]" :hint="feed.data[1]") + location(:location="feed.location" :hint="feed.worldName") + div(v-else-if="feed.type === 'VideoPlay'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | 🎵 #[span.name(v-text="feed.displayName")] + template(v-if="feed.videoName") + | #[span(v-text="feed.videoName")] + template(v-else) + | #[span(v-text="feed.videoUrl")] div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -107,7 +122,16 @@ html .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | ✨ #[span.name(v-text="feed.data")] + | ✨ #[span.name(v-text="feed.displayName")] + template(v-if="feed.worldName") + | #[location(:location="feed.instanceId" :hint="feed.worldName")] + div(v-else-if="feed.type === 'AvatarChange'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | 🧍 #[span.name(v-text="feed.displayName")] {{ feed.name }} + template(v-if="feed.description && feed.description !== feed.name") + | - {{ feed.description }} div(v-else-if="feed.type === 'Event'" class="x-friend-item") .detail span.extra @@ -159,17 +183,23 @@ html .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | #[span.name(v-text="feed.displayName")] is #[i.x-user-status(:class="statusClass(feed.status)")] {{feed.statusDescription}} + | #[span.name(v-text="feed.displayName")] + template(v-if="feed.statusDescription === feed.previousStatusDescription") + i.x-user-status(:class="statusClass(feed.previousStatus)") + i.el-icon-right + i.x-user-status(:class="statusClass(feed.status)") + template(v-else) + | #[i.x-user-status(:class="statusClass(feed.status)")] {{feed.statusDescription}} div(v-else-if="feed.type === 'OnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | #[span.name(v-text="feed.data")] has joined + | #[span.name(v-text="feed.displayName")] has joined div(v-else-if="feed.type === 'OnPlayerLeft'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | #[span.name(v-text="feed.data")] has left + | #[span.name(v-text="feed.displayName")] has left div(v-else-if="feed.type === 'OnPlayerJoining'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -179,7 +209,16 @@ html .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - location(:location="feed.data[0]" :hint="feed.data[1]") + location(:location="feed.location" :hint="feed.worldName") + div(v-else-if="feed.type === 'VideoPlay'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | #[span.name(v-text="feed.displayName")] changed video to + template(v-if="feed.videoName") + | #[span(v-text="feed.videoName")] + template(v-else) + | #[span(v-text="feed.videoUrl")] div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -229,7 +268,16 @@ html .detail span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} - | #[span.name(v-text="feed.data")] has spawned a portal + | #[span.name(v-text="feed.displayName")] has spawned a portal + template(v-if="feed.worldName") + | to #[location(:location="feed.instanceId" :hint="feed.worldName")] + div(v-else-if="feed.type === 'AvatarChange'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate('HH:MI') }} + | #[span.name(v-text="feed.displayName")] changed into avatar {{ feed.name }} + template(v-if="feed.description && feed.description !== feed.name") + | - {{ feed.description }} div(v-else-if="feed.type === 'Event'" class="x-friend-item") .detail span.extra @@ -302,19 +350,19 @@ html span {{ device[2] }}% .x-containerbottom template(v-if="config && config.minimalFeed") - template(v-if="downloadProgress === 100") + template(v-if="config.downloadProgress === 100") span(style="display:inline-block;margin-right:5px") #[i.el-icon-loading] - template(v-else-if="downloadProgress > 0") - span(style="display:inline-block;margin-right:5px") {{ downloadProgress }}% + template(v-else-if="config.downloadProgress > 0") + span(style="display:inline-block;margin-right:5px") {{ config.downloadProgress }}% template(v-if="lastLocation.date != 0") span(style="float:right") {{ lastLocationTimer }} span(style="display:inline-block") {{ lastLocation.playerList.length }} span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? `‎‎‎‎‎‎‎‎‏‏‎ ‎(${lastLocation.friendList.length})` : ''}} template(v-else) - template(v-if="downloadProgress === 100") + template(v-if="config.downloadProgress === 100") span(style="display:inline-block;margin-right:5px") Downloading: #[i.el-icon-loading] - template(v-else-if="downloadProgress > 0") - span(style="display:inline-block;margin-right:5px") Downloading: {{ downloadProgress }}% + template(v-else-if="config.downloadProgress > 0") + span(style="display:inline-block;margin-right:5px") Downloading: {{ config.downloadProgress }}% template(v-if="lastLocation.date != 0") span(style="float:right") Timer: {{ lastLocationTimer }} span(style="display:inline-block") Players: {{ lastLocation.playerList.length }} diff --git a/html/src/vr.scss b/html/src/vr.scss index ad8fd3b2..f1b2495e 100644 --- a/html/src/vr.scss +++ b/html/src/vr.scss @@ -22,9 +22,6 @@ .noty_body { display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } .noty_layout { @@ -34,6 +31,7 @@ .noty_theme__relax.noty_bar, .noty_theme__sunset.noty_bar { + height: 42px; position: relative; margin: 4px 0; overflow: hidden; @@ -42,9 +40,7 @@ .noty_theme__relax.noty_bar .noty_body, .noty_theme__sunset.noty_bar .noty_body { - padding: 5px 10px 10px; font-size: 15px; - text-align: center; text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); } @@ -143,6 +139,19 @@ opacity: 0.6; } +.noty-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 8px 8px 0 11px; +} + +.noty-img { + height: 42px; + float: left; + border-radius: 4px; +} + ::-webkit-scrollbar { width: 8px; height: 8px; From a19c90623f56334e44a4dcf0cde3a407d09548a4 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Thu, 2 Sep 2021 01:11:24 +1200 Subject: [PATCH 02/25] Another big one --- LogWatcher.cs | 24 ++ html/src/app.js | 606 ++++++++++++++++++++------------------------- html/src/index.pug | 2 +- 3 files changed, 289 insertions(+), 343 deletions(-) diff --git a/LogWatcher.cs b/LogWatcher.cs index 9a929e34..10fa67eb 100644 --- a/LogWatcher.cs +++ b/LogWatcher.cs @@ -211,6 +211,7 @@ namespace VRCX { if (ParseLogOnPlayerJoinedOrLeft(fileInfo, logContext, line, offset) == true || ParseLogLocation(fileInfo, logContext, line, offset) == true || + ParseLogLocationDestination(fileInfo, logContext, line, offset) == true || ParseLogPortalSpawn(fileInfo, logContext, line, offset) == true || ParseLogNotification(fileInfo, logContext, line, offset) == true || ParseLogJoinBlocked(fileInfo, logContext, line, offset) == true || @@ -313,6 +314,29 @@ namespace VRCX return false; } + private bool ParseLogLocationDestination(FileInfo fileInfo, LogContext logContext, string line, int offset) + { + // 2021.09.02 00:02:12 Log - [Behaviour] Destination set: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd:15609~private(usr_032383a7-748c-4fb2-94e4-bcb928e5de6b)~nonce(72CC87D420C1D49AEFFBEE8824C84B2DF0E38678E840661E) + // 2021.09.02 00:49:15 Log - [Behaviour] Destination fetching: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd + + if (string.Compare(line, offset, "[Behaviour] Destination fetching: ", 0, 34, StringComparison.Ordinal) == 0) + { + var location = line.Substring(offset + 34); + + AppendLog(new[] + { + fileInfo.Name, + ConvertLogTimeToISO8601(line), + "location-destination", + location + }); + + return true; + } + + return false; + } + private bool ParseLogOnPlayerJoinedOrLeft(FileInfo fileInfo, LogContext logContext, string line, int offset) { // 2020.10.31 23:36:58 Log - [NetworkManager] OnPlayerJoined pypy diff --git a/html/src/app.js b/html/src/app.js index 958f41ac..696019a6 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4014,22 +4014,18 @@ speechSynthesis.getVoices(); $app.data.sharedFeed = { gameLog: { wrist: [], - noty: [], lastEntryDate: '' }, feedTable: { wrist: [], - noty: [], lastEntryDate: '' }, notificationTable: { wrist: [], - noty: [], lastEntryDate: '' }, friendLogTable: { wrist: [], - noty: [], lastEntryDate: '' }, pendingUpdate: false @@ -4054,13 +4050,6 @@ speechSynthesis.getVoices(); feeds.notificationTable.wrist, feeds.friendLogTable.wrist ); - var notyFeed = []; - notyFeed = notyFeed.concat( - feeds.gameLog.noty, - feeds.feedTable.noty, - feeds.notificationTable.noty, - feeds.friendLogTable.noty - ); // OnPlayerJoining var L = API.parseLocation(this.lastLocation.location); // WebSocket dosen't update friend only instances var locationBias = Date.now() - 30000; // 30 seconds @@ -4103,9 +4092,6 @@ speechSynthesis.getVoices(); var gameLogTable = this.gameLogTable.data; for (var k = gameLogTable.length - 1; k > -1; k--) { var gameLogItem = gameLogTable[k]; - if (gameLogItem.type === 'Notification') { - continue; - } if ( gameLogItem.type === 'Location' || gameLogItem.created_at < bias @@ -4140,15 +4126,7 @@ speechSynthesis.getVoices(); ) { wristFeed.unshift(onPlayerJoining); } - if ( - this.sharedFeedFilters.noty.OnPlayerJoining === - 'Friends' || - (this.sharedFeedFilters.noty.OnPlayerJoining === - 'VIP' && - isFavorite) - ) { - notyFeed.unshift(onPlayerJoining); - } + this.queueFeedNoty(onPlayerJoining); } } } @@ -4163,16 +4141,6 @@ speechSynthesis.getVoices(); return 0; }); wristFeed.splice(20); - notyFeed.sort(function (a, b) { - if (a.created_at < b.created_at) { - return 1; - } - if (a.created_at > b.created_at) { - return -1; - } - return 0; - }); - notyFeed.splice(5); AppApi.ExecuteVrFeedFunction( 'wristFeedUpdate', JSON.stringify(wristFeed) @@ -4183,8 +4151,6 @@ speechSynthesis.getVoices(); if (this.worldDialog.visible) { this.applyWorldDialogInstances(); } - this.getCurrentInstanceUserList(); - this.playNoty(notyFeed); feeds.pendingUpdate = false; }; @@ -4206,13 +4172,10 @@ speechSynthesis.getVoices(); } var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours var wristArr = []; - var notyArr = []; var w = 0; - var n = 0; var wristFilter = this.sharedFeedFilters.wrist; - var notyFilter = this.sharedFeedFilters.noty; - var currentUserJoinTime = ''; - var currentUserLeaveTime = ''; + var currentUserLeaveTime = 0; + var locationJoinTime = 0; for (var i = data.length - 1; i > -1; i--) { var ctx = data[i]; if (ctx.created_at < bias) { @@ -4222,69 +4185,36 @@ speechSynthesis.getVoices(); continue; } // on Location change remove OnPlayerLeft - if (ctx.type === 'OnPlayerLeft') { - if (ctx.created_at.slice(0, -4) === currentUserLeaveTime) { - continue; - } - if (ctx.displayName === API.currentUser.displayName) { - var {created_at} = ctx; - currentUserLeaveTime = created_at.slice(0, -4); - for (var k = w - 1; k > -1; k--) { - var feedItem = wristArr[k]; - if ( - feedItem.created_at.slice(0, -4) === - currentUserLeaveTime && - feedItem.type === 'OnPlayerLeft' - ) { - wristArr.splice(k, 1); - w--; - } + if (ctx.type === 'LocationDestination') { + currentUserLeaveTime = Date.parse(ctx.created_at); + for (var k = w - 1; k > -1; k--) { + var feedItem = wristArr[k]; + if ( + feedItem.type === 'OnPlayerLeft' && + Date.parse(feedItem.created_at) >= + currentUserLeaveTime && + Date.parse(feedItem.created_at) <= + currentUserLeaveTime + 5 * 1000 + ) { + wristArr.splice(k, 1); + w--; } - for (var k = n - 1; k > -1; k--) { - var feedItem = notyArr[k]; - if ( - feedItem.created_at.slice(0, -4) === - currentUserLeaveTime && - feedItem.type === 'OnPlayerLeft' - ) { - notyArr.splice(k, 1); - n--; - } - } - continue; } } // on Location change remove OnPlayerJoined - if (ctx.type === 'OnPlayerJoined') { - if (ctx.created_at.slice(0, -4) === currentUserJoinTime) { - continue; - } - if (ctx.displayName === API.currentUser.displayName) { - var {created_at} = ctx; - currentUserJoinTime = created_at.slice(0, -4); - for (var k = w - 1; k > -1; k--) { - var feedItem = wristArr[k]; - if ( - feedItem.created_at.slice(0, -4) === - currentUserJoinTime && - feedItem.type === 'OnPlayerJoined' - ) { - wristArr.splice(k, 1); - w--; - } + if (ctx.type === 'Location') { + locationJoinTime = Date.parse(ctx.created_at); + for (var k = w - 1; k > -1; k--) { + var feedItem = wristArr[k]; + if ( + feedItem.type === 'OnPlayerJoined' && + Date.parse(feedItem.created_at) >= locationJoinTime && + Date.parse(feedItem.created_at) <= + locationJoinTime + 20 * 1000 + ) { + wristArr.splice(k, 1); + w--; } - for (var k = n - 1; k > -1; k--) { - var feedItem = notyArr[k]; - if ( - feedItem.created_at.slice(0, -4) === - currentUserJoinTime && - feedItem.type === 'OnPlayerJoined' - ) { - notyArr.splice(k, 1); - n--; - } - } - continue; } } // remove current user @@ -4298,24 +4228,15 @@ speechSynthesis.getVoices(); } var isFriend = false; var isFavorite = false; - if ( - ctx.type === 'OnPlayerJoined' || - ctx.type === 'OnPlayerLeft' || - ctx.type === 'PortalSpawn' || - ctx.type === 'AvatarChange' - ) { - if (ctx.userId) { - isFriend = this.friends.has(ctx.userId); - isFavorite = API.cachedFavoritesByObjectId.has(ctx.userId); - } else { - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === ctx.displayName) { - isFriend = this.friends.has(ref.id); - isFavorite = API.cachedFavoritesByObjectId.has( - ref.id - ); - break; - } + if (ctx.userId) { + isFriend = this.friends.has(ctx.userId); + isFavorite = API.cachedFavoritesByObjectId.has(ctx.userId); + } else if (ctx.displayName) { + for (var ref of API.cachedUsers.values()) { + if (ref.displayName === ctx.displayName) { + isFriend = this.friends.has(ref.id); + isFavorite = API.cachedFavoritesByObjectId.has(ref.id); + break; } } } @@ -4330,55 +4251,23 @@ speechSynthesis.getVoices(); } else { continue; } - var displayName = ref.targetDisplayName; - var userId = ref.targetUserId; - var created_at = ctx.created_at; + var entry = { + created_at: ctx.created_at, + type, + displayName: ref.targetDisplayName, + userId: ref.targetUserId, + isFriend, + isFavorite + }; if ( wristFilter[type] && (wristFilter[type] === 'Everyone' || (wristFilter[type] === 'Friends' && isFriend) || (wristFilter[type] === 'VIP' && isFavorite)) ) { - wristArr.unshift({ - created_at, - type, - displayName, - userId, - isFriend, - isFavorite - }); - } - if ( - notyFilter[type] && - (notyFilter[type] === 'Everyone' || - (notyFilter[type] === 'Friends' && isFriend) || - (notyFilter[type] === 'VIP' && isFavorite)) - ) { - notyArr.unshift({ - created_at, - type, - displayName, - userId, - isFriend, - isFavorite - }); - } - } - } - } - if (ctx.type === 'VideoPlay' && ctx.displayName) { - if (ctx.userId) { - isFriend = this.friends.has(ctx.userId); - isFavorite = API.cachedFavoritesByObjectId.has(ctx.userId); - } else { - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === ctx.displayName) { - isFriend = this.friends.has(ref.id); - isFavorite = API.cachedFavoritesByObjectId.has( - ref.id - ); - break; + wristArr.unshift(entry); } + this.queueFeedNoty(entry); } } } @@ -4397,27 +4286,61 @@ speechSynthesis.getVoices(); }); ++w; } - if ( - n < 5 && - notyFilter[ctx.type] && - (notyFilter[ctx.type] === 'On' || - notyFilter[ctx.type] === 'Everyone' || - (notyFilter[ctx.type] === 'Friends' && isFriend) || - (notyFilter[ctx.type] === 'VIP' && isFavorite)) - ) { - notyArr.push({ - ...ctx, - isFriend, - isFavorite - }); - ++n; - } } this.sharedFeed.gameLog.wrist = wristArr; - this.sharedFeed.gameLog.noty = notyArr; this.sharedFeed.pendingUpdate = true; }; + $app.methods.queueGameLogNoty = function (noty) { + // remove join/leave notifications when switching worlds + if (noty.type === 'OnPlayerJoined') { + var bias = this.lastLocation.date + 30 * 1000; // 30 secs + if (Date.parse(noty.created_at) <= bias) { + return; + } + } + if (noty.type === 'OnPlayerLeft') { + var bias = this.lastLocationDestinationTime + 5 * 1000; // 5 secs + if (Date.parse(noty.created_at) <= bias) { + return; + } + } + if (noty.type === 'Notification' || noty.type === 'LocationDestination') { + return; + } + // remove current user + if ( + noty.type !== 'VideoPlay' && + noty.displayName === API.currentUser.displayName + ) { + return; + } + noty.isFriend = false; + noty.isFavorite = false; + if (noty.userId) { + noty.isFriend = this.friends.has(noty.userId); + noty.isFavorite = API.cachedFavoritesByObjectId.has(noty.userId); + } else if (noty.displayName) { + for (var ref of API.cachedUsers.values()) { + if (ref.displayName === noty.displayName) { + noty.isFriend = this.friends.has(ref.id); + noty.isFavorite = API.cachedFavoritesByObjectId.has(ref.id); + break; + } + } + } + var notyFilter = this.sharedFeedFilters.noty; + if ( + notyFilter[noty.type] && + (notyFilter[noty.type] === 'On' || + notyFilter[noty.type] === 'Everyone' || + (notyFilter[noty.type] === 'Friends' && noty.isFriend) || + (notyFilter[noty.type] === 'VIP' && noty.isFavorite)) + ) { + this.playNoty(noty); + } + }; + $app.methods.updateSharedFeedFeedTable = function (forceUpdate) { // GPS, Online, Offline, Status, Avatar var {data} = this.feedTable; @@ -4436,11 +4359,8 @@ speechSynthesis.getVoices(); } var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours var wristArr = []; - var notyArr = []; var w = 0; - var n = 0; var wristFilter = this.sharedFeedFilters.wrist; - var notyFilter = this.sharedFeedFilters.noty; for (var i = data.length - 1; i > -1; i--) { var ctx = data[i]; if (ctx.created_at < bias) { @@ -4449,7 +4369,7 @@ speechSynthesis.getVoices(); if (ctx.type === 'Avatar') { continue; } - // hide private worlds from feeds + // hide private worlds from feed if ( this.hidePrivateFromFeed && ctx.type === 'GPS' && @@ -4472,25 +4392,35 @@ speechSynthesis.getVoices(); }); ++w; } - if ( - n < 5 && - notyFilter[ctx.type] && - (notyFilter[ctx.type] === 'Friends' || - (notyFilter[ctx.type] === 'VIP' && isFavorite)) - ) { - notyArr.push({ - ...ctx, - isFriend, - isFavorite - }); - ++n; - } } this.sharedFeed.feedTable.wrist = wristArr; - this.sharedFeed.feedTable.noty = notyArr; this.sharedFeed.pendingUpdate = true; }; + $app.methods.queueFeedNoty = function (noty) { + if (noty.type === 'Avatar') { + return; + } + // hide private worlds from feed + if ( + this.hidePrivateFromFeed && + noty.type === 'GPS' && + noty.location === 'private' + ) { + return; + } + noty.isFriend = this.friends.has(noty.userId); + noty.isFavorite = API.cachedFavoritesByObjectId.has(noty.userId); + var notyFilter = this.sharedFeedFilters.noty; + if ( + notyFilter[noty.type] && + (notyFilter[noty.type] === 'Friends' || + (notyFilter[noty.type] === 'VIP' && noty.isFavorite)) + ) { + this.playNoty(noty); + } + }; + $app.methods.updateSharedFeedNotificationTable = function (forceUpdate) { // invite, requestInvite, requestInviteResponse, inviteResponse, friendRequest var {data} = this.notificationTable; @@ -4510,11 +4440,8 @@ speechSynthesis.getVoices(); } var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours var wristArr = []; - var notyArr = []; var w = 0; - var n = 0; var wristFilter = this.sharedFeedFilters.wrist; - var notyFilter = this.sharedFeedFilters.noty; for (var i = data.length - 1; i > -1; i--) { var ctx = data[i]; if (ctx.created_at < bias) { @@ -4541,26 +4468,28 @@ speechSynthesis.getVoices(); }); ++w; } - if ( - n < 5 && - notyFilter[ctx.type] && - (notyFilter[ctx.type] === 'On' || - notyFilter[ctx.type] === 'Friends' || - (notyFilter[ctx.type] === 'VIP' && isFavorite)) - ) { - notyArr.push({ - ...ctx, - isFriend, - isFavorite - }); - ++n; - } } this.sharedFeed.notificationTable.wrist = wristArr; - this.sharedFeed.notificationTable.noty = notyArr; this.sharedFeed.pendingUpdate = true; }; + $app.methods.queueNotificationNoty = function (noty) { + if (noty.senderUserId === API.currentUser.id) { + return; + } + noty.isFriend = this.friends.has(noty.senderUserId); + noty.isFavorite = API.cachedFavoritesByObjectId.has(noty.senderUserId); + var notyFilter = this.sharedFeedFilters.noty; + if ( + notyFilter[noty.type] && + (notyFilter[noty.type] === 'On' || + notyFilter[noty.type] === 'Friends' || + (notyFilter[noty.type] === 'VIP' && noty.isFavorite)) + ) { + this.playNoty(noty); + } + }; + $app.methods.updateSharedFeedFriendLogTable = function (forceUpdate) { // TrustLevel, Friend, FriendRequest, Unfriend, DisplayName var {data} = this.friendLogTable; @@ -4580,11 +4509,8 @@ speechSynthesis.getVoices(); } var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours var wristArr = []; - var notyArr = []; var w = 0; - var n = 0; var wristFilter = this.sharedFeedFilters.wrist; - var notyFilter = this.sharedFeedFilters.noty; for (var i = data.length - 1; i > -1; i--) { var ctx = data[i]; if (ctx.created_at < bias) { @@ -4609,29 +4535,58 @@ speechSynthesis.getVoices(); }); ++w; } - if ( - n < 5 && - notyFilter[ctx.type] && - (notyFilter[ctx.type] === 'On' || - notyFilter[ctx.type] === 'Friends' || - (notyFilter[ctx.type] === 'VIP' && isFavorite)) - ) { - notyArr.push({ - ...ctx, - isFriend, - isFavorite - }); - ++n; - } } this.sharedFeed.friendLogTable.wrist = wristArr; - this.sharedFeed.friendLogTable.noty = notyArr; this.sharedFeed.pendingUpdate = true; }; + $app.methods.queueFriendLogNoty = function (noty) { + if (noty.type === 'FriendRequest') { + return; + } + noty.isFriend = this.friends.has(noty.userId); + noty.isFavorite = API.cachedFavoritesByObjectId.has(noty.userId); + var notyFilter = this.sharedFeedFilters.noty; + if ( + notyFilter[noty.type] && + (notyFilter[noty.type] === 'On' || + notyFilter[noty.type] === 'Friends' || + (notyFilter[noty.type] === 'VIP' && noty.isFavorite)) + ) { + this.playNoty(noty); + } + }; + $app.data.notyMap = []; - $app.methods.playNoty = function (notyFeed) { + $app.methods.playNoty = function (noty) { + if (API.currentUser.status === 'busy' || !this.friendLogInitStatus) { + return; + } + var displayName = ''; + if (noty.displayName) { + displayName = noty.displayName; + } else if (noty.senderUsername) { + displayName = noty.senderUsername; + } else if (noty.sourceDisplayName) { + displayName = noty.sourceDisplayName; + } + if (displayName) { + // don't play noty twice + if ( + this.notyMap[displayName] && + this.notyMap[displayName] > noty.created_at + ) { + return; + } + this.notyMap[displayName] = noty.created_at; + } + var bias = new Date(Date.now() - 60000).toJSON(); + if (noty.created_at < bias) { + // don't play noty if it's over 1min old + return; + } + var playNotificationTTS = false; if ( this.notificationTTS === 'Always' || @@ -4666,70 +4621,37 @@ speechSynthesis.getVoices(); ) { playOverlayNotification = true; } - if (API.currentUser.status === 'busy' || !this.notyInit) { - return; - } - var notyToPlay = []; - notyFeed.forEach((feed) => { - var displayName = ''; - if (feed.displayName) { - displayName = feed.displayName; - } else if (feed.senderUsername) { - displayName = feed.senderUsername; - } else if (feed.sourceDisplayName) { - displayName = feed.sourceDisplayName; - } else if (feed.data) { - displayName = feed.data; - } - if ( - (displayName && !this.notyMap[displayName]) || - this.notyMap[displayName] < feed.created_at - ) { - this.notyMap[displayName] = feed.created_at; - notyToPlay.push(feed); - } - }); - var bias = new Date(Date.now() - 60000).toJSON(); var messageList = [ 'inviteMessage', 'requestMessage', 'responseMessage' ]; - for (var i = 0; i < notyToPlay.length; i++) { - let noty = notyToPlay[i]; - if (noty.created_at < bias) { - continue; + let message = ''; + for (var k = 0; k < messageList.length; k++) { + if ( + typeof noty.details !== 'undefined' && + typeof noty.details[messageList[k]] !== 'undefined' + ) { + message = `, ${noty.details[messageList[k]]}`; } - let message = ''; - for (var k = 0; k < messageList.length; k++) { - if ( - typeof noty.details !== 'undefined' && - typeof noty.details[messageList[k]] !== 'undefined' - ) { - message = noty.details[messageList[k]]; + } + if (playNotificationTTS) { + this.playNotyTTS(noty, message); + } + if (playOverlayNotification) { + this.notyGetImage(noty).then((imageUrl) => { + this.displayOverlayNotification(noty, message, imageUrl); + }); + } + if (playDesktopToast || playXSNotification) { + this.notySaveImage(noty).then((image) => { + if (playXSNotification) { + this.displayXSNotification(noty, message, image); } - } - if (message) { - message = `, ${message}`; - } - if (playNotificationTTS) { - this.playNotyTTS(noty, message); - } - if (playOverlayNotification) { - this.notyGetImage(noty).then((imageUrl) => { - this.displayOverlayNotification(noty, message, imageUrl); - }); - } - if (playDesktopToast || playXSNotification) { - this.notySaveImage(noty).then((image) => { - if (playXSNotification) { - this.displayXSNotification(noty, message, image); - } - if (playDesktopToast) { - this.displayDesktopToast(noty, message, image); - } - }); - } + if (playDesktopToast) { + this.displayDesktopToast(noty, message, image); + } + }); } }; @@ -7078,6 +7000,7 @@ speechSynthesis.getVoices(); this.feedTable.data.push(feed); this.sweepFeed(); this.updateSharedFeed(false); + this.queueFeedNoty(feed); this.notifyMenu('feed'); }; @@ -7185,6 +7108,7 @@ speechSynthesis.getVoices(); playerList: new Map(), friendList: new Map() }; + this.updateVRLastLocation(); }; $app.data.lastLocation$ = {}; @@ -7220,7 +7144,7 @@ speechSynthesis.getVoices(); { prop: 'type', value: true, - filterFn: (row) => row.type !== 'Notification' + filterFn: (row) => row.type !== 'Notification' || row.type !== 'LocationDestination' } ], tableProps: { @@ -7304,11 +7228,9 @@ speechSynthesis.getVoices(); this.sweepGameLog(); }; - $app.methods.addGameLogEntry = function ( - gameLog, - location, - pushToTable - ) { + $app.lastLocationDestinationTime = 0; + + $app.methods.addGameLogEntry = function (gameLog, location, pushToTable) { var userId = ''; if (gameLog.userDisplayName) { for (var ref of API.cachedUsers.values()) { @@ -7319,6 +7241,17 @@ speechSynthesis.getVoices(); } } switch (gameLog.type) { + case 'location-destination': + if (this.isGameRunning) { + this.cancelVRChatCacheDownload(gameLog.location); + } + this.lastLocationDestinationTime = Date.parse(gameLog.dt); + var entry = { + created_at: gameLog.dt, + type: 'LocationDestination', + location: gameLog.location + }; + break; case 'location': if (this.isGameRunning) { this.lastLocationReset(); @@ -7330,7 +7263,7 @@ speechSynthesis.getVoices(); friendList: new Map() }; this.updateVRLastLocation(); - this.checkVRChatCacheDownload(this.lastLocation.location); + this.cancelVRChatCacheDownload(gameLog.location); } var L = API.parseLocation(gameLog.location); var entry = { @@ -7433,6 +7366,7 @@ speechSynthesis.getVoices(); break; } if (pushToTable && entry) { + this.queueGameLogNoty(entry); this.gameLogTable.data.push(entry); } }; @@ -8129,6 +8063,7 @@ speechSynthesis.getVoices(); }; this.friendLogTable.data.push(friendLogHistory); database.addFriendLogHistory(friendLogHistory); + this.queueFriendLogNoty(friendLogHistory); var friendLogCurrent = { userId: id, displayName: ctx.displayName, @@ -8153,6 +8088,7 @@ speechSynthesis.getVoices(); }; this.friendLogTable.data.push(friendLogHistory); database.addFriendLogHistory(friendLogHistory); + this.queueFriendLogNoty(friendLogHistory); this.friendLog.delete(id); database.deleteFriendLogCurrent(id); this.notifyMenu('friendLog'); @@ -8185,18 +8121,17 @@ speechSynthesis.getVoices(); displayName: ref.displayName, previousDisplayName: ctx.displayName }; - this.friendLogTable.data.push(friendLogHistory); - database.addFriendLogHistory(friendLogHistory); - } else if (ctx.displayName === null) { + } else { var friendLogHistory = { created_at: new Date().toJSON(), type: 'Friend', userId: ref.id, displayName: ref.displayName }; - this.friendLogTable.data.push(friendLogHistory); - database.addFriendLogHistory(friendLogHistory); } + this.friendLogTable.data.push(friendLogHistory); + database.addFriendLogHistory(friendLogHistory); + this.queueFriendLogNoty(friendLogHistory); var friendLogCurrent = { userId: ref.id, displayName: ref.displayName, @@ -8207,35 +8142,32 @@ speechSynthesis.getVoices(); ctx.displayName = ref.displayName; this.notifyMenu('friendLog'); } - if (ref.$trustLevel && ctx.trustLevel !== ref.$trustLevel) { - if ( - ctx.trustLevel && - ctx.trustLevel !== 'Legendary User' && - ctx.trustLevel !== 'VRChat Team' && - ctx.trustLevel !== 'Nuisance' - ) { - // TODO: remove - var friendLogHistory = { - created_at: new Date().toJSON(), - type: 'TrustLevel', - userId: ref.id, - displayName: ref.displayName, - trustLevel: ref.$trustLevel, - previousTrustLevel: ctx.trustLevel - }; - this.friendLogTable.data.push(friendLogHistory); - database.addFriendLogHistory(friendLogHistory); - var friendLogCurrent = { - userId: ref.id, - displayName: ref.displayName, - trustLevel: ref.$trustLevel - }; - this.friendLog.set(ref.id, friendLogCurrent); - database.setFriendLogCurrent(friendLogCurrent); - this.notifyMenu('friendLog'); - } - ctx.trustLevel = ref.$trustLevel; + if ( + ref.$trustLevel && + ctx.trustLevel && + ctx.trustLevel !== ref.$trustLevel + ) { + var friendLogHistory = { + created_at: new Date().toJSON(), + type: 'TrustLevel', + userId: ref.id, + displayName: ref.displayName, + trustLevel: ref.$trustLevel, + previousTrustLevel: ctx.trustLevel + }; + this.friendLogTable.data.push(friendLogHistory); + database.addFriendLogHistory(friendLogHistory); + this.queueFriendLogNoty(friendLogHistory); + var friendLogCurrent = { + userId: ref.id, + displayName: ref.displayName, + trustLevel: ref.$trustLevel + }; + this.friendLog.set(ref.id, friendLogCurrent); + database.setFriendLogCurrent(friendLogCurrent); + this.notifyMenu('friendLog'); } + ctx.trustLevel = ref.$trustLevel; }; $app.methods.deleteFriendLog = function (row) { @@ -8398,6 +8330,7 @@ speechSynthesis.getVoices(); $app.notifyMenu('notification'); $app.unseenNotifications.push(ref.id); } + $app.queueNotificationNoty(ref); } $app.updateSharedFeed(true); }); @@ -14070,12 +14003,13 @@ speechSynthesis.getVoices(); this.downloadVRChatCacheProgress(); }; - $app.methods.checkVRChatCacheDownload = function (lastLocation) { - var L = API.parseLocation(lastLocation); + $app.methods.cancelVRChatCacheDownload = function (location) { + var L = API.parseLocation(location); if (L.worldId) { if (this.downloadCurrent.id === L.worldId) { - this.cancelVRChatCacheDownload(L.worldId); - } else if (this.downloadQueue.has(L.worldId)) { + AssetBundleCacher.CancelDownload(); + } + if (this.downloadQueue.has(L.worldId)) { this.downloadQueue.delete(L.worldId); this.downloadQueueTable.data = Array.from( this.downloadQueue.values() @@ -14084,18 +14018,6 @@ speechSynthesis.getVoices(); } }; - $app.methods.cancelVRChatCacheDownload = function (worldId) { - if (this.downloadCurrent.id === worldId) { - AssetBundleCacher.CancelDownload(); - } - if (this.downloadQueue.has(worldId)) { - this.downloadQueue.delete(worldId); - this.downloadQueueTable.data = Array.from( - this.downloadQueue.values() - ); - } - }; - $app.methods.cancelAllVRChatCacheDownload = function () { if (typeof this.downloadCurrent.id !== 'undefined') { this.cancelVRChatCacheDownload(this.downloadCurrent.id); diff --git a/html/src/index.pug b/html/src/index.pug index 8bf3a478..f37ff754 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -1110,7 +1110,7 @@ html el-dropdown-item(v-else icon="el-icon-plus" command="Send Friend Request") Send Friend Request el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author" divided) Show Avatar Author el-dropdown-item(icon="el-icon-s-custom" command="Show Fallback Avatar Details") Show Fallback Avatar Details - el-dropdown-item(v-if="userDialog.currentAvatarImageUrl !== 'https://assets.vrchat.com/system/defaultAvatar.png'" icon="el-icon-picture-outline" command="Previous Images") Show Avatar Previous Images + el-dropdown-item(v-if="userDialog.ref.currentAvatarImageUrl !== 'https://assets.vrchat.com/system/defaultAvatar.png'" icon="el-icon-picture-outline" command="Previous Images") Show Avatar Previous Images el-dropdown-item(v-if="userDialog.isBlock" icon="el-icon-circle-check" command="Unblock" divided style="color:#F56C6C") Unblock el-dropdown-item(v-else icon="el-icon-circle-close" command="Block" divided :disabled="userDialog.ref.$isModerator") Block el-dropdown-item(v-if="userDialog.isMute" icon="el-icon-microphone" command="Unmute" style="color:#F56C6C") Unmute From babc6e21f7da4e59dda50c173f140dd5febaf364 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Fri, 3 Sep 2021 16:51:16 +1200 Subject: [PATCH 03/25] Base64 notification images --- AppApi.cs | 25 ++++++----- html/src/app.js | 88 ++++++++++++++++++------------------- html/src/index.pug | 51 +++++++++++---------- html/src/service/gamelog.js | 4 ++ html/src/vr.js | 6 +-- 5 files changed, 90 insertions(+), 84 deletions(-) diff --git a/AppApi.cs b/AppApi.cs index e8aee4a3..f2f0b088 100644 --- a/AppApi.cs +++ b/AppApi.cs @@ -229,20 +229,15 @@ namespace VRCX return CpuMonitor.Instance.CpuUsage; } - public void CacheImage(string Base64File) - { - String Icon = Path.Combine(Program.AppDataDirectory, "cache\\toast"); - File.WriteAllBytes(Icon, Convert.FromBase64String(Base64File)); - } - - public void DesktopNotification(string BoldText, string Text, bool Image) + public void DesktopNotification(string BoldText, string Text, string Image) { XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02); XmlNodeList stringElements = toastXml.GetElementsByTagName("text"); String imagePath = Path.Combine(Program.BaseDirectory, "VRCX.ico"); - if (Image) + if (!String.IsNullOrEmpty(Image)) { imagePath = Path.Combine(Program.AppDataDirectory, "cache\\toast"); + File.WriteAllBytes(imagePath, Convert.FromBase64String(Image)); } stringElements[0].AppendChild(toastXml.CreateTextNode(BoldText)); stringElements[1].AppendChild(toastXml.CreateTextNode(Text)); @@ -268,14 +263,20 @@ namespace VRCX public string sourceApp { get; set; } } - public void XSNotification(string Title, string Content, int Timeout, bool Image) + public void XSNotification(string Title, string Content, int Timeout, string Image) { - bool UseBase64Icon = true; - String Icon = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHaGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1NTE2MWIyMi1hYzgxLTY3NDYtODAyYi1kODIzYWFmN2RjYjciIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3ZjJjNTA2ZS02YTVhLWRhNGEtOTg5Mi02NDZiMzQ0MGQxZTgiPiA8cGhvdG9zaG9wOkRvY3VtZW50QW5jZXN0b3JzPiA8cmRmOkJhZz4gPHJkZjpsaT5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NmJmOGE5MTgtY2QzZS03OTRjLTk3NzktMzM0YjYwZWJiNTYyPC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6N2YyYzUwNmUtNmE1YS1kYTRhLTk4OTItNjQ2YjM0NDBkMWU4IiBzdEV2dDp3aGVuPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhM2ZjODI3LTM0ZjQtYjU0OC05ZGFiLTZhMTZlZmQzZjAxMSIgc3RFdnQ6d2hlbj0iMjAyMS0wNC0wOFQxNTowMTozMSsxMjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHN0RXZ0OndoZW49IjIwMjEtMDQtMDhUMTY6MzM6MTArMTI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4XAd9sAAAFM0lEQVR42u2aWUhjVxjHjVpf3Iraoh3c4ksFx7ZYahV8EHEBqdQHFdsHQRRxpcyDIDNFpdSK+iBKUcTpmy/iglVrtT4oYsEq7hP3RGXcqqY6invy9Xy3OdPEE5PY5pKb5P7hTyA5y/1+Ofc7y70OAOBgz3YQAYgARAAiABGACEAEIAIQAYgADBT6V4HErcRbxCAwy4nriN/DC+UDADb8swADv++fiN3MDeAJ8be0k9HRUbi4uACUWq22qFFvzt5AZ1enNoSvzJ4DiJ5j412dXSBUVf9QTQH08gHgF2x8b2/P0nGqNGa0ML9AAazyAeA3bPzg4MDoFV5fX8PZ2RlcXl7qGL83JjKsVeT2UpHyaqxzdXXFtUVvOVpMYx3JFfK3CZEPAL9i4/v7+0aDwDL5+fmQl5cHBQUFnHNzc6GsrAzW19cNBQ8dHR3q7OxsFamvxnrFxcWQnp4O4+PjRvtdW1ujANYtCgBVWlqqN0vn5ORw/6o+TU1Nga+vL1MnMTERtre3rQvA3d0dZGZmMsG4ublBW1sbU/7k5ATi4+OZ8uHh4bC5uWlSn4ICQC/I39+fCSo0NBRWV1d1M3h1NVPOw8MDenp6HtWfoACg8N92dnZmgisqKuISI2pkZAS8vLyYMngb3dzcWDcAvBUKCwuZ4FxdXWFwcJDLB1FRUczvcXFxcHx8/Ki+BAkAtbW1BZGRkUyQsbGx3Gzh5OSk831QUJBJWd9qAKD6+/vB29tbJ1CJRMIE7+7uDk1NTf+pD0EDwFuhoqKCC9rQZiYrKwtub29tDwBqZ2cHUlNTwdHRkQkcwURHRxtcKFk9ANTAwAB4enoyAHCmqKys/F9tCx4ATnuY9B4a/mFhYTA3N2e7AFpaWoweaKSkpHCbH5sDMDMzw01vxgC4uLhAfX29oAHo3Yoa0vn5OSQnJzPBZmRkQFpaGjMz+Pn5wdjYmGAB3D0WQG1tLRM8Bjk7OwsKhQICAwOZ3xMSEkw6e7AEANVjAAwPD3ObmvsBVlVVgUr1z8FOQ0MD8zsukMrLyx+1JhBcDtjd3YWIiAgmOLwdtP9dTHpJSUl6d4M4bVolADzdKSkpYYIKCAjgdn/3NT8/Dz4+Pkz5mJgYkw5DBAUAh3ZzczOzDcYVYE1NzYNL5bq6Or1LZVw7nJ6eWg8APMHBRQ0ehkilUggODuaSHp4QGdriHh0dcTMDlsV6ISEhXF0cGb29vRYHMGTqqTCWmZiYgKWlJVheXgaZTMatAw4PD43WVSqVMD09zdVD48kRtiWXy98mzYe0Id+gADb4ADCMjSuPlYJ9MKLYVFAAm3wAaMbGFxcXBQugu7ubAviDDwCfY+N4Ro/DVGjCmUIrcX7P1+PxfdpJ68tWGBoagr7+PrMZH3DiwglnBGPCtQOWxeSIM45W8IvEUr4AfEG8xPcj7sbGRqMAVpZX9NWdIv6Ur/cDqD4k/o64j/h34jEzeUTTHhdMX2+fQQCyVzIa9KXmwe0z4hB6kXwCQL2DLyEQ+xK/byZ7EfsRN1AICwsLDwLAKVZTDkfkZ8RO2hfINwA+9YQ+iUYf/nloDADe80/vN2LNAFCRxGsYx4vnL/QmRS0Ar4g/sjUAqC/pKGhvb7dLAKhyCmFyctIuAbxL3EEhaL+eowVARvyxrQJASYlnKAS6IbInAKg44lMKAYU7Ra1p8BNbB4D6hvgGY8MlMG6PNQBWiCPsAYAL8Y96lr+4ivzAHgDQpPiS+EwikfxFPl8Tf00s4RWA+Lq8CEAEIAIQAYgARAA26b8BaVJkoY+4rDoAAAAASUVORK5CYII="; - if (Image) + bool UseBase64Icon; + string Icon; + if (!String.IsNullOrEmpty(Image)) { UseBase64Icon = false; + Icon = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHaGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1NTE2MWIyMi1hYzgxLTY3NDYtODAyYi1kODIzYWFmN2RjYjciIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3ZjJjNTA2ZS02YTVhLWRhNGEtOTg5Mi02NDZiMzQ0MGQxZTgiPiA8cGhvdG9zaG9wOkRvY3VtZW50QW5jZXN0b3JzPiA8cmRmOkJhZz4gPHJkZjpsaT5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NmJmOGE5MTgtY2QzZS03OTRjLTk3NzktMzM0YjYwZWJiNTYyPC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6N2YyYzUwNmUtNmE1YS1kYTRhLTk4OTItNjQ2YjM0NDBkMWU4IiBzdEV2dDp3aGVuPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhM2ZjODI3LTM0ZjQtYjU0OC05ZGFiLTZhMTZlZmQzZjAxMSIgc3RFdnQ6d2hlbj0iMjAyMS0wNC0wOFQxNTowMTozMSsxMjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHN0RXZ0OndoZW49IjIwMjEtMDQtMDhUMTY6MzM6MTArMTI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4XAd9sAAAFM0lEQVR42u2aWUhjVxjHjVpf3Iraoh3c4ksFx7ZYahV8EHEBqdQHFdsHQRRxpcyDIDNFpdSK+iBKUcTpmy/iglVrtT4oYsEq7hP3RGXcqqY6invy9Xy3OdPEE5PY5pKb5P7hTyA5y/1+Ofc7y70OAOBgz3YQAYgARAAiABGACEAEIAIQAYgADBT6V4HErcRbxCAwy4nriN/DC+UDADb8swADv++fiN3MDeAJ8be0k9HRUbi4uACUWq22qFFvzt5AZ1enNoSvzJ4DiJ5j412dXSBUVf9QTQH08gHgF2x8b2/P0nGqNGa0ML9AAazyAeA3bPzg4MDoFV5fX8PZ2RlcXl7qGL83JjKsVeT2UpHyaqxzdXXFtUVvOVpMYx3JFfK3CZEPAL9i4/v7+0aDwDL5+fmQl5cHBQUFnHNzc6GsrAzW19cNBQ8dHR3q7OxsFamvxnrFxcWQnp4O4+PjRvtdW1ujANYtCgBVWlqqN0vn5ORw/6o+TU1Nga+vL1MnMTERtre3rQvA3d0dZGZmMsG4ublBW1sbU/7k5ATi4+OZ8uHh4bC5uWlSn4ICQC/I39+fCSo0NBRWV1d1M3h1NVPOw8MDenp6HtWfoACg8N92dnZmgisqKuISI2pkZAS8vLyYMngb3dzcWDcAvBUKCwuZ4FxdXWFwcJDLB1FRUczvcXFxcHx8/Ki+BAkAtbW1BZGRkUyQsbGx3Gzh5OSk831QUJBJWd9qAKD6+/vB29tbJ1CJRMIE7+7uDk1NTf+pD0EDwFuhoqKCC9rQZiYrKwtub29tDwBqZ2cHUlNTwdHRkQkcwURHRxtcKFk9ANTAwAB4enoyAHCmqKys/F9tCx4ATnuY9B4a/mFhYTA3N2e7AFpaWoweaKSkpHCbH5sDMDMzw01vxgC4uLhAfX29oAHo3Yoa0vn5OSQnJzPBZmRkQFpaGjMz+Pn5wdjYmGAB3D0WQG1tLRM8Bjk7OwsKhQICAwOZ3xMSEkw6e7AEANVjAAwPD3ObmvsBVlVVgUr1z8FOQ0MD8zsukMrLyx+1JhBcDtjd3YWIiAgmOLwdtP9dTHpJSUl6d4M4bVolADzdKSkpYYIKCAjgdn/3NT8/Dz4+Pkz5mJgYkw5DBAUAh3ZzczOzDcYVYE1NzYNL5bq6Or1LZVw7nJ6eWg8APMHBRQ0ehkilUggODuaSHp4QGdriHh0dcTMDlsV6ISEhXF0cGb29vRYHMGTqqTCWmZiYgKWlJVheXgaZTMatAw4PD43WVSqVMD09zdVD48kRtiWXy98mzYe0Id+gADb4ADCMjSuPlYJ9MKLYVFAAm3wAaMbGFxcXBQugu7ubAviDDwCfY+N4Ro/DVGjCmUIrcX7P1+PxfdpJ68tWGBoagr7+PrMZH3DiwglnBGPCtQOWxeSIM45W8IvEUr4AfEG8xPcj7sbGRqMAVpZX9NWdIv6Ur/cDqD4k/o64j/h34jEzeUTTHhdMX2+fQQCyVzIa9KXmwe0z4hB6kXwCQL2DLyEQ+xK/byZ7EfsRN1AICwsLDwLAKVZTDkfkZ8RO2hfINwA+9YQ+iUYf/nloDADe80/vN2LNAFCRxGsYx4vnL/QmRS0Ar4g/sjUAqC/pKGhvb7dLAKhyCmFyctIuAbxL3EEhaL+eowVARvyxrQJASYlnKAS6IbInAKg44lMKAYU7Ra1p8BNbB4D6hvgGY8MlMG6PNQBWiCPsAYAL8Y96lr+4ivzAHgDQpPiS+EwikfxFPl8Tf00s4RWA+Lq8CEAEIAIQAYgARAA26b8BaVJkoY+4rDoAAAAASUVORK5CYII="; + } + else + { + UseBase64Icon = true; Icon = Path.Combine(Program.AppDataDirectory, "cache\\toast"); + File.WriteAllBytes(Icon, Convert.FromBase64String(Image)); } IPAddress broadcastIP = IPAddress.Parse("127.0.0.1"); diff --git a/html/src/app.js b/html/src/app.js index 696019a6..81d4ae85 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4305,7 +4305,10 @@ speechSynthesis.getVoices(); return; } } - if (noty.type === 'Notification' || noty.type === 'LocationDestination') { + if ( + noty.type === 'Notification' || + noty.type === 'LocationDestination' + ) { return; } // remove current user @@ -4638,20 +4641,30 @@ speechSynthesis.getVoices(); if (playNotificationTTS) { this.playNotyTTS(noty, message); } - if (playOverlayNotification) { - this.notyGetImage(noty).then((imageUrl) => { - this.displayOverlayNotification(noty, message, imageUrl); - }); - } - if (playDesktopToast || playXSNotification) { - this.notySaveImage(noty).then((image) => { + if (playDesktopToast || playXSNotification || playOverlayNotification) { + if (this.imageNotifications) { + this.notySaveImage(noty).then((image) => { + if (playXSNotification) { + this.displayXSNotification(noty, message, image); + } + if (playDesktopToast) { + this.displayDesktopToast(noty, message, image); + } + if (playOverlayNotification) { + this.displayOverlayNotification(noty, message, image); + } + }); + } else { if (playXSNotification) { - this.displayXSNotification(noty, message, image); + this.displayXSNotification(noty, message, ''); } if (playDesktopToast) { - this.displayDesktopToast(noty, message, image); + this.displayDesktopToast(noty, message, ''); } - }); + if (playOverlayNotification) { + this.displayOverlayNotification(noty, message, ''); + } + } } }; @@ -4702,8 +4715,9 @@ speechSynthesis.getVoices(); $app.methods.notySaveImage = async function (noty) { var imageUrl = await this.notyGetImage(noty); + var base64Image = ''; try { - await fetch(imageUrl, { + base64Image = await fetch(imageUrl, { method: 'GET', redirect: 'follow', headers: { @@ -4718,24 +4732,18 @@ speechSynthesis.getVoices(); for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } - var imageData = btoa(binary); - AppApi.CacheImage(imageData); + return btoa(binary); }); - return true; } catch (err) { console.error(err); - return false; } + return base64Image; }; - $app.methods.displayOverlayNotification = function ( - noty, - message, - imageUrl - ) { + $app.methods.displayOverlayNotification = function (noty, message, image) { AppApi.ExecuteVrOverlayFunction( 'playNoty', - JSON.stringify({noty, message, imageUrl}) + JSON.stringify({noty, message, image}) ); }; @@ -6806,7 +6814,6 @@ speechSynthesis.getVoices(); this.getAuth(); $app.updateSharedFeed(true); - $app.notyInit = true; if ($app.isGameRunning) { $app.loadPlayerList(); @@ -7144,7 +7151,9 @@ speechSynthesis.getVoices(); { prop: 'type', value: true, - filterFn: (row) => row.type !== 'Notification' || row.type !== 'LocationDestination' + filterFn: (row) => + row.type !== 'Notification' && + row.type !== 'LocationDestination' } ], tableProps: { @@ -8602,6 +8611,9 @@ speechSynthesis.getVoices(); $app.data.xsNotifications = configRepository.getBool( 'VRCX_xsNotifications' ); + $app.data.imageNotifications = configRepository.getBool( + 'VRCX_imageNotifications' + ); $app.data.desktopToast = configRepository.getString('VRCX_desktopToast'); $app.data.minimalFeed = configRepository.getBool('VRCX_minimalFeed'); $app.data.displayVRCPlusIconsAsAvatar = configRepository.getBool( @@ -8640,7 +8652,7 @@ speechSynthesis.getVoices(); 'VRCX_autoUpdateVRCX' ); $app.data.branch = configRepository.getString('VRCX_branch'); - var saveOpenVROption = function () { + $app.methods.saveOpenVROption = function () { configRepository.setBool('openVR', this.openVR); configRepository.setBool('openVRAlways', this.openVRAlways); configRepository.setBool('VRCX_overlaybutton', this.overlaybutton); @@ -8658,6 +8670,10 @@ speechSynthesis.getVoices(); ); configRepository.setBool('VRCX_overlayWrist', this.overlayWrist); configRepository.setBool('VRCX_xsNotifications', this.xsNotifications); + configRepository.setBool( + 'VRCX_imageNotifications', + this.imageNotifications + ); configRepository.setString('VRCX_desktopToast', this.desktopToast); configRepository.setBool('VRCX_minimalFeed', this.minimalFeed); configRepository.setBool( @@ -8693,7 +8709,7 @@ speechSynthesis.getVoices(); this.updateVRConfigVars(); }; $app.data.TTSvoices = speechSynthesis.getVoices(); - var saveNotificationTTS = function () { + $app.methods.saveNotificationTTS = function () { speechSynthesis.cancel(); if ( configRepository.getString('VRCX_notificationTTS') === 'Never' && @@ -8707,25 +8723,6 @@ speechSynthesis.getVoices(); ); this.updateVRConfigVars(); }; - $app.watch.openVR = saveOpenVROption; - $app.watch.openVRAlways = saveOpenVROption; - $app.watch.overlaybutton = saveOpenVROption; - $app.watch.hidePrivateFromFeed = saveOpenVROption; - $app.watch.hideDevicesFromFeed = saveOpenVROption; - $app.watch.overlayNotifications = saveOpenVROption; - $app.watch.overlayWrist = saveOpenVROption; - $app.watch.xsNotifications = saveOpenVROption; - $app.watch.desktopToast = saveOpenVROption; - $app.watch.minimalFeed = saveOpenVROption; - $app.watch.displayVRCPlusIconsAsAvatar = saveOpenVROption; - $app.watch.hideTooltips = saveOpenVROption; - $app.watch.worldAutoCacheInvite = saveOpenVROption; - $app.watch.worldAutoCacheGPS = saveOpenVROption; - $app.watch.worldAutoCacheInviteFilter = saveOpenVROption; - $app.watch.worldAutoCacheGPSFilter = saveOpenVROption; - $app.watch.autoSweepVRChatCache = saveOpenVROption; - $app.watch.vrBackgroundEnabled = saveOpenVROption; - $app.watch.notificationTTS = saveNotificationTTS; $app.data.themeMode = configRepository.getString('VRCX_ThemeMode'); if (!$app.data.themeMode) { $app.data.themeMode = 'system'; @@ -9342,6 +9339,7 @@ speechSynthesis.getVoices(); 'VRCX_notificationTimeout', this.notificationTimeout ); + this.updateVRConfigVars(); } } }); diff --git a/html/src/index.pug b/html/src/index.pug index f37ff754..3f8553ee 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -719,10 +719,10 @@ html el-radio-button(label="dark") Dark div.options-container-item span.name VRCPlus Profile Icons - el-switch(v-model="displayVRCPlusIconsAsAvatar") + el-switch(v-model="displayVRCPlusIconsAsAvatar" @change="saveOpenVROption") div.options-container-item span.name Disable Tooltips - el-switch(v-model="hideTooltips") + el-switch(v-model="hideTooltips" @change="saveOpenVROption") div.options-container span.header Side Panel br @@ -805,61 +805,64 @@ html br div.options-container-item span.name Enable - el-switch(v-model="openVR") + el-switch(v-model="openVR" @change="saveOpenVROption") div.options-container-item span.name Force Run (Opens SteamVR) - el-switch(v-model="openVRAlways" :disabled="!openVR") + el-switch(v-model="openVRAlways" @change="saveOpenVROption" :disabled="!openVR") div.options-container-item span.name Hide Private Worlds - el-switch(v-model="hidePrivateFromFeed") + el-switch(v-model="hidePrivateFromFeed" @change="saveOpenVROption") br span.sub-header Wrist Feed div.options-container-item span.name Wrist Feed Overlay - el-switch(v-model="overlayWrist" :disabled="!openVR") + el-switch(v-model="overlayWrist" @change="saveOpenVROption" :disabled="!openVR") div.options-container-item span.name(style="min-width:137px") Overlay Button - el-switch(v-model="overlaybutton" inactive-text="Grip" active-text="Menu" :disabled="!openVR || !overlayWrist") + el-switch(v-model="overlaybutton" @change="saveOpenVROption" inactive-text="Grip" active-text="Menu" :disabled="!openVR || !overlayWrist") div.options-container-item span.name Background Color - el-switch(v-model="vrBackgroundEnabled" :disabled="!openVR || !overlayWrist") + el-switch(v-model="vrBackgroundEnabled" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist") div.options-container-item span.name Minimal Feed Icons - el-switch(v-model="minimalFeed" :disabled="!openVR || !overlayWrist") + el-switch(v-model="minimalFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist") div.options-container-item span.name Hide VR Devices - el-switch(v-model="hideDevicesFromFeed" :disabled="!openVR || !overlayWrist") + el-switch(v-model="hideDevicesFromFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist") div.options-container-item - el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog()" :disabled="!openVR || !overlayWrist") Wrist Feed Filters + el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog" :disabled="!openVR || !overlayWrist") Wrist Feed Filters br span.sub-header Notifications div.options-container-item span.name Overlay Notifications - el-switch(v-model="overlayNotifications" :disabled="!openVR") + el-switch(v-model="overlayNotifications" @change="saveOpenVROption" :disabled="!openVR") div.options-container-item - el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog()" :disabled="!overlayNotifications || !openVR") Notification Position + el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog" :disabled="!overlayNotifications || !openVR") Notification Position div.options-container-item span.name XSOverlay Notifications - el-switch(v-model="xsNotifications") + el-switch(v-model="xsNotifications" @change="saveOpenVROption") div.options-container-item - el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout()" :disabled="(!overlayNotifications || !openVR) && !xsNotifications") Notification Timeout + span.name User images (slower) + el-switch(v-model="imageNotifications" @change="saveOpenVROption") + div.options-container-item + el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout" :disabled="(!overlayNotifications || !openVR) && !xsNotifications") Notification Timeout div.options-container-item span.name Desktop Notifications, When to display: br - el-radio-group(v-model="desktopToast" size="mini") + el-radio-group(v-model="desktopToast" @change="saveOpenVROption" size="mini") el-radio-button(label="Never") el-radio-button(label="Inside VR") el-radio-button(label="Game Closed") el-radio-button(label="Game Running") el-radio-button(label="Always") div.options-container-item - el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog()") Notification Filters + el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog") Notification Filters br span.sub-header Text-To-Speech Options div.options-container-item span.name Notification TTS, When to play: br - el-radio-group(v-model="notificationTTS" size="mini") + el-radio-group(v-model="notificationTTS" @change="saveNotificationTTS" size="mini") el-radio-button(label="Never") el-radio-button(label="Inside VR") el-radio-button(label="Game Closed") @@ -880,31 +883,31 @@ html div.options-container-item span.name Download on invite: br - el-radio-group(v-model="worldAutoCacheInvite" size="mini") + el-radio-group(v-model="worldAutoCacheInvite" @change="saveOpenVROption" size="mini") el-radio-button(label="Never") el-radio-button(label="Game Closed") el-radio-button(label="Game Running") el-radio-button(label="Always") div.options-container-item - el-switch(v-model="worldAutoCacheInviteFilter" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheInvite == 'Never'") + el-switch(v-model="worldAutoCacheInviteFilter" @change="saveOpenVROption" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheInvite === 'Never'") div.options-container-item span.name Download on GPS: br - el-radio-group(v-model="worldAutoCacheGPS" size="mini") + el-radio-group(v-model="worldAutoCacheGPS" @change="saveOpenVROption" size="mini") el-radio-button(label="Never") el-radio-button(label="Game Closed") el-radio-button(label="Game Running") el-radio-button(label="Always") div.options-container-item - el-switch(v-model="worldAutoCacheGPSFilter" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheGPS == 'Never'") + el-switch(v-model="worldAutoCacheGPSFilter" @change="saveOpenVROption" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheGPS === 'Never'") div.options-container-item el-button-group - el-button(size="small" icon="el-icon-download" @click="showDownloadDialog()") Download History + el-button(size="small" icon="el-icon-download" @click="showDownloadDialog") Download History br span.sub-header Automatically Manage Cache When Closing VRChat div.options-container-item span.name(style="min-width:300px") Auto delete old versions from cache - el-switch(v-model="autoSweepVRChatCache") + el-switch(v-model="autoSweepVRChatCache" @change="saveOpenVROption") div.options-container span.header Application div.options-container-item diff --git a/html/src/service/gamelog.js b/html/src/service/gamelog.js index 20c4c015..e1e56af4 100644 --- a/html/src/service/gamelog.js +++ b/html/src/service/gamelog.js @@ -13,6 +13,10 @@ class GameLogService { gameLog.worldName = args[1]; break; + case 'location-destination': + gameLog.location = args[0]; + break; + case 'player-joined': gameLog.userDisplayName = args[0]; gameLog.userType = args[1]; diff --git a/html/src/vr.js b/html/src/vr.js index 87117845..c33ad7dd 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -318,11 +318,11 @@ speechSynthesis.getVoices(); }; $app.methods.playNoty = function (json) { - var {noty, message, imageUrl} = JSON.parse(json); + var {noty, message, image} = JSON.parse(json); var text = ''; var img = ''; - if (imageUrl) { - img = ``; + if (image) { + img = ``; } switch (noty.type) { case 'OnPlayerJoined': From 181210b7b0beb43d6aff3a785e254c1193701c2f Mon Sep 17 00:00:00 2001 From: Natsumi Date: Fri, 3 Sep 2021 16:54:56 +1200 Subject: [PATCH 04/25] Stop auto cacher from starting after being canceled from joining a world --- html/src/app.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/html/src/app.js b/html/src/app.js index 81d4ae85..e3559506 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -13908,6 +13908,8 @@ speechSynthesis.getVoices(); }); }; + $app.data.cacheAutoDownloadHistory = new Set(); + $app.methods.downloadVRChatCache = async function () { if (this.downloadQueue.size === 0) { return; @@ -13957,6 +13959,16 @@ speechSynthesis.getVoices(); this.downloadVRChatCache(); return; } + if ( + this.downloadCurrent.type === 'Auto' && + this.cacheAutoDownloadHistory.has(assetUrl) + ) { + this.downloadCurrent = {}; + this.downloadInProgress = false; + this.downloadVRChatCache(); + return; + } + this.cacheAutoDownloadHistory.add(assetUrl); try { var args = await API.getBundles(fileId); } catch (err) { From a32798d0bf565206a2722b448eb506458ce9c749 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Sun, 5 Sep 2021 17:39:52 +1200 Subject: [PATCH 05/25] Fixes --- AppApi.cs | 6 +++--- html/src/app.js | 49 +++++++++++++++++++++---------------------------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/AppApi.cs b/AppApi.cs index f2f0b088..24b37ea5 100644 --- a/AppApi.cs +++ b/AppApi.cs @@ -267,14 +267,14 @@ namespace VRCX { bool UseBase64Icon; string Icon; - if (!String.IsNullOrEmpty(Image)) + if (String.IsNullOrEmpty(Image)) { - UseBase64Icon = false; + UseBase64Icon = true; Icon = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHaGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1NTE2MWIyMi1hYzgxLTY3NDYtODAyYi1kODIzYWFmN2RjYjciIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3ZjJjNTA2ZS02YTVhLWRhNGEtOTg5Mi02NDZiMzQ0MGQxZTgiPiA8cGhvdG9zaG9wOkRvY3VtZW50QW5jZXN0b3JzPiA8cmRmOkJhZz4gPHJkZjpsaT5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NmJmOGE5MTgtY2QzZS03OTRjLTk3NzktMzM0YjYwZWJiNTYyPC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6N2YyYzUwNmUtNmE1YS1kYTRhLTk4OTItNjQ2YjM0NDBkMWU4IiBzdEV2dDp3aGVuPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhM2ZjODI3LTM0ZjQtYjU0OC05ZGFiLTZhMTZlZmQzZjAxMSIgc3RFdnQ6d2hlbj0iMjAyMS0wNC0wOFQxNTowMTozMSsxMjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHN0RXZ0OndoZW49IjIwMjEtMDQtMDhUMTY6MzM6MTArMTI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4XAd9sAAAFM0lEQVR42u2aWUhjVxjHjVpf3Iraoh3c4ksFx7ZYahV8EHEBqdQHFdsHQRRxpcyDIDNFpdSK+iBKUcTpmy/iglVrtT4oYsEq7hP3RGXcqqY6invy9Xy3OdPEE5PY5pKb5P7hTyA5y/1+Ofc7y70OAOBgz3YQAYgARAAiABGACEAEIAIQAYgADBT6V4HErcRbxCAwy4nriN/DC+UDADb8swADv++fiN3MDeAJ8be0k9HRUbi4uACUWq22qFFvzt5AZ1enNoSvzJ4DiJ5j412dXSBUVf9QTQH08gHgF2x8b2/P0nGqNGa0ML9AAazyAeA3bPzg4MDoFV5fX8PZ2RlcXl7qGL83JjKsVeT2UpHyaqxzdXXFtUVvOVpMYx3JFfK3CZEPAL9i4/v7+0aDwDL5+fmQl5cHBQUFnHNzc6GsrAzW19cNBQ8dHR3q7OxsFamvxnrFxcWQnp4O4+PjRvtdW1ujANYtCgBVWlqqN0vn5ORw/6o+TU1Nga+vL1MnMTERtre3rQvA3d0dZGZmMsG4ublBW1sbU/7k5ATi4+OZ8uHh4bC5uWlSn4ICQC/I39+fCSo0NBRWV1d1M3h1NVPOw8MDenp6HtWfoACg8N92dnZmgisqKuISI2pkZAS8vLyYMngb3dzcWDcAvBUKCwuZ4FxdXWFwcJDLB1FRUczvcXFxcHx8/Ki+BAkAtbW1BZGRkUyQsbGx3Gzh5OSk831QUJBJWd9qAKD6+/vB29tbJ1CJRMIE7+7uDk1NTf+pD0EDwFuhoqKCC9rQZiYrKwtub29tDwBqZ2cHUlNTwdHRkQkcwURHRxtcKFk9ANTAwAB4enoyAHCmqKys/F9tCx4ATnuY9B4a/mFhYTA3N2e7AFpaWoweaKSkpHCbH5sDMDMzw01vxgC4uLhAfX29oAHo3Yoa0vn5OSQnJzPBZmRkQFpaGjMz+Pn5wdjYmGAB3D0WQG1tLRM8Bjk7OwsKhQICAwOZ3xMSEkw6e7AEANVjAAwPD3ObmvsBVlVVgUr1z8FOQ0MD8zsukMrLyx+1JhBcDtjd3YWIiAgmOLwdtP9dTHpJSUl6d4M4bVolADzdKSkpYYIKCAjgdn/3NT8/Dz4+Pkz5mJgYkw5DBAUAh3ZzczOzDcYVYE1NzYNL5bq6Or1LZVw7nJ6eWg8APMHBRQ0ehkilUggODuaSHp4QGdriHh0dcTMDlsV6ISEhXF0cGb29vRYHMGTqqTCWmZiYgKWlJVheXgaZTMatAw4PD43WVSqVMD09zdVD48kRtiWXy98mzYe0Id+gADb4ADCMjSuPlYJ9MKLYVFAAm3wAaMbGFxcXBQugu7ubAviDDwCfY+N4Ro/DVGjCmUIrcX7P1+PxfdpJ68tWGBoagr7+PrMZH3DiwglnBGPCtQOWxeSIM45W8IvEUr4AfEG8xPcj7sbGRqMAVpZX9NWdIv6Ur/cDqD4k/o64j/h34jEzeUTTHhdMX2+fQQCyVzIa9KXmwe0z4hB6kXwCQL2DLyEQ+xK/byZ7EfsRN1AICwsLDwLAKVZTDkfkZ8RO2hfINwA+9YQ+iUYf/nloDADe80/vN2LNAFCRxGsYx4vnL/QmRS0Ar4g/sjUAqC/pKGhvb7dLAKhyCmFyctIuAbxL3EEhaL+eowVARvyxrQJASYlnKAS6IbInAKg44lMKAYU7Ra1p8BNbB4D6hvgGY8MlMG6PNQBWiCPsAYAL8Y96lr+4ivzAHgDQpPiS+EwikfxFPl8Tf00s4RWA+Lq8CEAEIAIQAYgARAA26b8BaVJkoY+4rDoAAAAASUVORK5CYII="; } else { - UseBase64Icon = true; + UseBase64Icon = false; Icon = Path.Combine(Program.AppDataDirectory, "cache\\toast"); File.WriteAllBytes(Icon, Convert.FromBase64String(Image)); } diff --git a/html/src/app.js b/html/src/app.js index e3559506..89c98f33 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4716,26 +4716,25 @@ speechSynthesis.getVoices(); $app.methods.notySaveImage = async function (noty) { var imageUrl = await this.notyGetImage(noty); var base64Image = ''; - try { - base64Image = await fetch(imageUrl, { - method: 'GET', - redirect: 'follow', - headers: { - 'User-Agent': appVersion - } - }) - .then((response) => response.arrayBuffer()) - .then((buffer) => { - var binary = ''; - var bytes = new Uint8Array(buffer); - var length = bytes.byteLength; - for (var i = 0; i < length; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); - }); - } catch (err) { - console.error(err); + if (imageUrl) { + try { + base64Image = await fetch(imageUrl, { + method: 'GET', + redirect: 'follow' + }) + .then((response) => response.arrayBuffer()) + .then((buffer) => { + var binary = ''; + var bytes = new Uint8Array(buffer); + var length = bytes.byteLength; + for (var i = 0; i < length; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); + }); + } catch (err) { + console.error(err); + } } return base64Image; }; @@ -5693,10 +5692,7 @@ speechSynthesis.getVoices(); var user = $app.loginForm.savedCredentials[$app.loginForm.lastUserLoggedIn]; if (typeof user !== 'undefined') { - $app.relogin({ - username: user.loginParmas.username, - password: user.loginParmas.password - }).then(() => { + $app.relogin(user).then(() => { new Noty({ type: 'success', text: 'Automatically logged in.' @@ -13440,10 +13436,7 @@ speechSynthesis.getVoices(); if (image.file && image.file.url) { var response = await fetch(image.file.url, { method: 'HEAD', - redirect: 'follow', - headers: { - 'User-Agent': appVersion - } + redirect: 'follow' }).catch((error) => { console.log(error); }); From f419f0732c80c2698f25c40058252c36a596b1ac Mon Sep 17 00:00:00 2001 From: Natsumi Date: Sun, 5 Sep 2021 17:44:23 +1200 Subject: [PATCH 06/25] Discord RPC rework --- Discord.cs | 33 +++++++-- html/src/app.js | 166 ++++++++++++++++++++++++++++++--------------- html/src/index.pug | 9 ++- 3 files changed, 145 insertions(+), 63 deletions(-) diff --git a/Discord.cs b/Discord.cs index fca55100..e451a04a 100644 --- a/Discord.cs +++ b/Discord.cs @@ -1,4 +1,4 @@ -// Copyright(c) 2019 pypy. All rights reserved. +// Copyright(c) 2019 pypy. All rights reserved. // // This work is licensed under the terms of the MIT license. // For a copy, see . @@ -17,6 +17,7 @@ namespace VRCX private DiscordRpcClient m_Client; private Timer m_Timer; private bool m_Active; + public static string DiscordAppId; static Discord() { @@ -78,7 +79,7 @@ namespace VRCX { if (m_Client == null) { - m_Client = new DiscordRpcClient("525953831020920832"); + m_Client = new DiscordRpcClient(DiscordAppId); if (m_Client.Initialize() == false) { m_Client.Dispose(); @@ -126,7 +127,7 @@ namespace VRCX } } - public void SetAssets(string largeKey, string largeText, string smallKey, string smallText) + public void SetAssets(string largeKey, string largeText, string smallKey, string smallText, string partyId, int partySize, int partyMax, string buttonText, string buttonUrl, string appId) { m_Lock.EnterWriteLock(); try @@ -139,13 +140,35 @@ namespace VRCX else { if (m_Presence.Assets == null) - { m_Presence.Assets = new Assets(); - } + if (m_Presence.Party == null) + m_Presence.Party = new Party(); m_Presence.Assets.LargeImageKey = largeKey; m_Presence.Assets.LargeImageText = largeText; m_Presence.Assets.SmallImageKey = smallKey; m_Presence.Assets.SmallImageText = smallText; + m_Presence.Party.ID = partyId; + m_Presence.Party.Size = partySize; + m_Presence.Party.Max = partyMax; + Button[] Buttons = { }; + if (!string.IsNullOrEmpty(buttonUrl)) + { + Buttons = new Button[] + { + new Button() { Label = buttonText, Url = buttonUrl } + }; + } + m_Presence.Buttons = Buttons; + if (DiscordAppId != appId) + { + DiscordAppId = appId; + if (m_Client != null) + { + m_Client.Dispose(); + m_Client = null; + } + Update(); + } } } finally diff --git a/html/src/app.js b/html/src/app.js index 89c98f33..87e2d7ec 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -244,6 +244,17 @@ speechSynthesis.getVoices(); $appDarkStyle.href = `app.dark.css?_=${Date.now()}`; document.head.appendChild($appDarkStyle); + var getLaunchURL = function (worldId, instanceId) { + if (instanceId) { + return `https://vrchat.com/home/launch?worldId=${encodeURIComponent( + worldId + )}&instanceId=${encodeURIComponent(instanceId)}`; + } + return `https://vrchat.com/home/launch?worldId=${encodeURIComponent( + worldId + )}`; + }; + // // Languages // @@ -3987,15 +3998,25 @@ speechSynthesis.getVoices(); ([isGameRunning, isGameNoVR]) => { if (isGameRunning !== this.isGameRunning) { this.isGameRunning = isGameRunning; - Discord.SetTimestamps(Date.now(), 0); + if (isGameRunning) { + API.currentUser.$online_for = Date.now(); + API.currentUser.$offline_for = ''; + } else { + API.currentUser.$online_for = ''; + API.currentUser.$offline_for = Date.now(); + Discord.SetActive(false); + this.autoVRChatCacheManagement(); + } + this.lastLocationReset(); this.updateVRConfigVars(); + this.updateOpenVR(); } if (isGameNoVR !== this.isGameNoVR) { this.isGameNoVR = isGameNoVR; this.updateVRConfigVars(); + this.updateOpenVR(); } this.updateDiscord(); - this.updateOpenVR(); } ); } @@ -7114,15 +7135,30 @@ speechSynthesis.getVoices(); this.updateVRLastLocation(); }; - $app.data.lastLocation$ = {}; + $app.data.lastLocation$ = { + tag: '', + instanceId: '', + accessType: '', + worldName: '', + worldCapacity: 0, + joinUrl: '', + statusName: '', + statusImage: '' + }; $app.data.discordActive = configRepository.getBool('discordActive'); $app.data.discordInstance = configRepository.getBool('discordInstance'); - var saveDiscordOption = function () { + $app.data.discordJoinButton = configRepository.getBool('discordJoinButton'); + $app.methods.saveDiscordOption = function () { configRepository.setBool('discordActive', this.discordActive); configRepository.setBool('discordInstance', this.discordInstance); + configRepository.setBool('discordJoinButton', this.discordJoinButton); + if (!this.discordActive) { + Discord.SetText('', ''); + Discord.SetActive(false); + } + this.lastLocation$.tag = ''; + this.updateDiscord(); }; - $app.watch.discordActive = saveDiscordOption; - $app.watch.discordInstance = saveDiscordOption; $app.data.gameLogTable = { data: [], @@ -7393,53 +7429,97 @@ speechSynthesis.getVoices(); }; $app.methods.updateDiscord = function () { - var ref = API.cachedUsers.get(API.currentUser.id); - if (typeof ref !== 'undefined') { - var myLocation = this.lastLocation.location; - if (ref.location !== myLocation) { - API.applyUser({ - id: ref.id, - location: myLocation - }); - } - } - if (this.isGameRunning === false || this.lastLocation.location === '') { - Discord.SetActive(false); + if (!this.discordActive || !this.isGameRunning) { return; } + var L = this.lastLocation$; if (this.lastLocation.location !== this.lastLocation$.tag) { - var L = API.parseLocation(this.lastLocation.location); - L.worldName = L.worldId; - this.lastLocation$ = L; + if (this.lastLocation.location) { + Discord.SetActive(true); + } + Discord.SetTimestamps(this.lastLocation.date, 0); + L = API.parseLocation(this.lastLocation.location); + L.worldName = ''; + L.worldCapacity = 0; + L.joinUrl = ''; if (L.worldId) { var ref = API.cachedWorlds.get(L.worldId); if (ref) { L.worldName = ref.name; + L.worldCapacity = ref.capacity; } else { API.getWorld({ worldId: L.worldId }).then((args) => { L.worldName = args.ref.name; + L.worldCapacity = args.ref.capacity; return args; }); } + switch (L.accessType) { + case 'public': + L.joinUrl = getLaunchURL(L.worldId, L.instanceId); + L.accessType = 'Public'; + break; + case 'invite+': + L.accessType = 'Invite+'; + break; + case 'invite': + L.accessType = 'Invite'; + break; + case 'friends': + L.accessType = 'Friends'; + break; + case 'friends+': + L.accessType = 'Friends+'; + break; + } } + this.lastLocation$ = L; } + switch (API.currentUser.status) { + case 'active': + L.statusName = 'Active'; + L.statusImage = 'active'; + break; + case 'join me': + L.statusName = 'Join Me'; + L.statusImage = 'joinme'; + break; + case 'ask me': + L.statusName = 'Ask Me'; + L.statusImage = 'askme'; + break; + case 'busy': + L.statusName = 'Do Not Disturb'; + L.statusImage = 'busy'; + break; + } + Discord.SetAssets( + 'vrchat', // big icon + 'Powered by VRCX', // big icon hover text + L.statusImage, // small icon + L.statusName, // small icon hover text + L.instanceId, // party id + this.lastLocation.playerList.size, // party size + L.worldCapacity, // party max size + 'Join', // button text + L.joinUrl, // button url + '883308884863901717' // app id + ); // NOTE // 글자 수가 짧으면 업데이트가 안된다.. - var LL = this.lastLocation$; - if (LL.worldName.length < 2) { - LL.worldName += '\uFFA0'.repeat(2 - LL.worldName.length); + if (L.worldName.length < 2) { + L.worldName += '\uFFA0'.repeat(2 - L.worldName.length); } - if (this.discordInstance) { - Discord.SetText( - LL.worldName, - `#${LL.instanceName} ${LL.accessType}` - ); + if (API.currentUser.status === 'busy') { + Discord.SetText('Do Not Disturb', ''); + Discord.SetTimestamps(0, 0); + } else if (this.discordInstance) { + Discord.SetText(L.worldName, L.accessType); } else { - Discord.SetText(LL.worldName, ''); + Discord.SetText(L.worldName, ''); } - Discord.SetActive(this.discordActive); }; $app.methods.lookupUser = async function (ref) { @@ -9012,19 +9092,6 @@ speechSynthesis.getVoices(); this.updateVRConfigVars(); }; - var isGameRunningStateChange = function () { - this.lastLocationReset(); - if (this.isGameRunning) { - API.currentUser.$online_for = Date.now(); - API.currentUser.$offline_for = ''; - } else { - API.currentUser.$online_for = ''; - API.currentUser.$offline_for = Date.now(); - this.autoVRChatCacheManagement(); - } - }; - $app.watch.isGameRunning = isGameRunningStateChange; - var downloadProgressStateChange = function () { this.updateVRConfigVars(); }; @@ -11327,17 +11394,6 @@ speechSynthesis.getVoices(); }); }; - var getLaunchURL = function (worldId, instanceId) { - if (instanceId) { - return `https://vrchat.com/home/launch?worldId=${encodeURIComponent( - worldId - )}&instanceId=${encodeURIComponent(instanceId)}`; - } - return `https://vrchat.com/home/launch?worldId=${encodeURIComponent( - worldId - )}`; - }; - var updateLocationURL = function () { var D = $app.newInstanceDialog; if (D.instanceId) { diff --git a/html/src/index.pug b/html/src/index.pug index 3f8553ee..a9be5fd1 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -789,10 +789,13 @@ html span * Only works when VRChat is running. div.options-container-item span.name Enable - el-switch(v-model="discordActive") + el-switch(v-model="discordActive" @change="saveDiscordOption") div.options-container-item - span.name Instance details - el-switch(v-model="discordInstance" :disabled="!discordActive") + span.name Instance type + el-switch(v-model="discordInstance" @change="saveDiscordOption" :disabled="!discordActive") + div.options-container-item + span.name Join button (public only) + el-switch(v-model="discordJoinButton" @change="saveDiscordOption" :disabled="!discordActive") div.options-container span.header SteamVR Overlay div.options-container-item From 1673a0745cc4a8ca1c44b07194812dbd6428f536 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Sun, 5 Sep 2021 23:47:26 +1200 Subject: [PATCH 07/25] YouTube API --- WebApi.cs | 4 + html/src/app.js | 177 +++++++++++++++++++++++++++++++++++++++------ html/src/index.pug | 20 ++++- html/src/vr.js | 6 +- 4 files changed, 176 insertions(+), 31 deletions(-) diff --git a/WebApi.cs b/WebApi.cs index 24d4fac0..11d88a94 100644 --- a/WebApi.cs +++ b/WebApi.cs @@ -147,6 +147,10 @@ namespace VRCX { request.UserAgent = value; } + else if (string.Compare(key, "Referer", StringComparison.OrdinalIgnoreCase) == 0) + { + request.Referer = value; + } else { request.Headers.Add(key, value); diff --git a/html/src/app.js b/html/src/app.js index 87e2d7ec..671994b9 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4329,14 +4329,19 @@ speechSynthesis.getVoices(); if ( noty.type === 'Notification' || noty.type === 'LocationDestination' + // skip unused entries ) { return; } - // remove current user + if (noty.type === 'VideoPlay' && !noty.videoName) { + // skip videos without names + return; + } if ( noty.type !== 'VideoPlay' && noty.displayName === API.currentUser.displayName ) { + // remove current user return; } noty.isFriend = false; @@ -4864,11 +4869,7 @@ speechSynthesis.getVoices(); this.speak(noty.data); break; case 'VideoPlay': - var videoName = ''; - if (noty.videoName) { - videoName = `: ${noty.videoName}`; - } - this.speak(`Now playing video${videoName}`); + this.speak(`Now playing: ${noty.videoName}`); break; case 'BlockedOnPlayerJoined': this.speak(`Blocked user ${noty.displayName} has joined`); @@ -5051,13 +5052,9 @@ speechSynthesis.getVoices(); AppApi.XSNotification('VRCX', noty.data, timeout, image); break; case 'VideoPlay': - var videoName = noty.videoUrl; - if (noty.videoName) { - videoName = noty.videoName; - } AppApi.XSNotification( 'VRCX', - `Now playing: ${videoName}`, + `Now playing: ${noty.videoName}`, timeout, image ); @@ -5238,11 +5235,11 @@ speechSynthesis.getVoices(); AppApi.DesktopNotification('Event', noty.data, image); break; case 'VideoPlay': - var videoName = noty.videoUrl; - if (noty.videoName) { - videoName = noty.videoName; - } - AppApi.DesktopNotification('Now playing', videoName, image); + AppApi.DesktopNotification( + 'Now playing', + noty.videoName, + image + ); break; case 'BlockedOnPlayerJoined': AppApi.DesktopNotification( @@ -7382,14 +7379,8 @@ speechSynthesis.getVoices(); database.addGamelogPortalSpawnToDatabase(entry); break; case 'video-play': - var entry = { - created_at: gameLog.dt, - type: 'VideoPlay', - data: gameLog.videoUrl, - displayName: gameLog.displayName - }; - database.addGamelogVideoPlayToDatabase(entry); - break; + this.addGameLogVideo(gameLog, location, userId, pushToTable); + return; case 'notification': var entry = { created_at: gameLog.dt, @@ -7412,6 +7403,90 @@ speechSynthesis.getVoices(); } }; + $app.methods.addGameLogVideo = async function ( + gameLog, + location, + userId, + pushToTable + ) { + var videoUrl = gameLog.videoUrl; + var youtubeVideoId = ''; + var videoId = ''; + var videoName = ''; + var videoLength = ''; + var displayName = ''; + if (typeof gameLog.displayName !== 'undefined') { + displayName = gameLog.displayName; + } + try { + var url = new URL(videoUrl); + var id1 = url.pathname; + var id2 = url.searchParams.get('v'); + if (id1 && id1.length === 12) { + youtubeVideoId = id2.substring(1, 12); + } + if (id2 && id2.length === 11) { + youtubeVideoId = id2; + } + if (this.youTubeApi && youtubeVideoId) { + var data = await this.lookupYouTubeVideo(youtubeVideoId); + if ( + data || + (data.status === 200 && data.pageInfo.totalResults !== 0) + ) { + videoId = 'YouTube'; + videoName = data.items[0].snippet.title; + videoLength = this.convertYoutubeTime( + data.items[0].contentDetails.duration + ); + } else { + console.error(`YouTube video lookup failed status: ${status}`); + } + } + } catch { + console.error(`Invalid URL: ${url}`); + } + var entry = { + created_at: gameLog.dt, + type: 'VideoPlay', + videoUrl, + videoId, + videoName, + videoLength, + location, + displayName, + userId + }; + if (pushToTable) { + this.queueGameLogNoty(entry); + this.gameLogTable.data.push(entry); + } + database.addGamelogVideoPlayToDatabase(entry); + }; + + $app.methods.lookupYouTubeVideo = async function (videoId) { + var data = {}; + var apiKey = 'AIzaSyA-iUQCpWf5afEL3NanEOSxbzziPMU3bxY'; + if (this.youTubeApiKey) { + apiKey = this.youTubeApiKey; + } + try { + var response = await webApiService.execute({ + url: `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&part=snippet,contentDetails&key=${apiKey}`, + method: 'GET', + headers: { + 'User-Agent': appVersion, + Referer: 'https://vrcx.pypy.moe' + } + }); + data = JSON.parse(response.data); + data.status = response.status; + } catch { + console.error(`YouTube video lookup failed for ${videoId}`); + } + return data; + }; + $app.methods.sweepGameLog = function () { var {data} = this.gameLogTable; // 로그는 7일까지만 남김 @@ -9092,6 +9167,9 @@ speechSynthesis.getVoices(); this.updateVRConfigVars(); }; + $app.data.youTubeApi = configRepository.getBool('VRCX_youtubeAPI'); + $app.data.youTubeApiKey = configRepository.getString('VRCX_youtubeAPIKey'); + var downloadProgressStateChange = function () { this.updateVRConfigVars(); }; @@ -13872,6 +13950,57 @@ speechSynthesis.getVoices(); this.VRChatConfigFile.screenshot_res_width = res.width; }; + // YouTube API + + $app.data.youTubeApiKey = ''; + + $app.data.youTubeApiDialog = { + visible: false + }; + + API.$on('LOGOUT', function () { + $app.youTubeApiDialog.visible = false; + }); + + $app.methods.testYouTubeApiKey = async function () { + if (!this.youTubeApiKey) { + this.$message({ + message: 'YouTube API key removed', + type: 'success' + }); + this.youTubeApiDialog.visible = false; + return; + } + var data = await this.lookupYouTubeVideo('dQw4w9WgXcQ'); + if (!data || data.status !== 200) { + this.youTubeApiKey = ''; + this.$message({ + message: `Invalid YouTube API key, error code: ${data.status}`, + type: 'error' + }); + } else { + configRepository.setString( + 'VRCX_youtubeAPIKey', + this.youTubeApiKey + ); + this.$message({ + message: 'YouTube API key valid!', + type: 'success' + }); + } + this.youTubeApiDialog.visible = false; + }; + + $app.methods.changeYouTubeApi = function () { + configRepository.setBool('VRCX_youtubeAPI', this.youTubeApi); + }; + + $app.methods.showYouTubeApiDialog = function () { + this.$nextTick(() => adjustDialogZ(this.$refs.youTubeApiDialog.$el)); + var D = this.youTubeApiDialog; + D.visible = true; + }; + // Asset Bundle Cacher $app.methods.updateVRChatWorldCache = function () { diff --git a/html/src/index.pug b/html/src/index.pug index a9be5fd1..faae7ee7 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -904,8 +904,7 @@ html div.options-container-item el-switch(v-model="worldAutoCacheGPSFilter" @change="saveOpenVROption" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheGPS === 'Never'") div.options-container-item - el-button-group - el-button(size="small" icon="el-icon-download" @click="showDownloadDialog") Download History + el-button(size="small" icon="el-icon-download" @click="showDownloadDialog") Download History br span.sub-header Automatically Manage Cache When Closing VRChat div.options-container-item @@ -929,6 +928,13 @@ html el-button-group el-button(size="small" icon="el-icon-s-operation" @click="showLaunchOptions()") Launch Options el-button(size="small" icon="el-icon-s-operation" @click="showVRChatConfig()") VRChat config.json + div.options-container + span.header YouTube API + div.options-container-item + span.name Enabled + el-switch(v-model="youTubeApi" @change="changeYouTubeApi") + div.options-container-item + el-button(size="small" icon="el-icon-caret-right" @click="showYouTubeApiDialog") YouTube API Key div.options-container(style="margin-top:45px;border-top:1px solid #eee;padding-top:30px") span.header Legal Notice div.options-container-item @@ -1732,6 +1738,16 @@ html el-button(size="small" @click="VRChatConfigDialog.visible = false") Cancel el-button(type="primary" size="small" :disabled="VRChatConfigDialog.loading" @click="saveVRChatConfigFile") Save + //- dialog: YouTube Api Dialog + el-dialog.x-dialog(ref="youTubeApiDialog" :visible.sync="youTubeApiDialog.visible" title="YouTube API" width="400px") + div(style='font-size:12px;') + | Enter your YouTube API Key #[br] + el-input(type="textarea" v-model="youTubeApiKey" placeholder="YouTube API Key" maxlength="39" show-word-limit style="dispaly:block;margin-top:10px") + template(#footer) + div(style="display:flex") + el-button(size="small" @click="openExternalLink('https://rapidapi.com/blog/how-to-get-youtube-api-key/')") Guide + el-button(type="primary" size="small" @click="testYouTubeApiKey" style="margin-left:auto") Save + //- dialog: Cache Download el-dialog.x-dialog(ref="downloadDialog" :visible.sync="downloadDialog.visible" title="Download History" width="770px") div(v-if="downloadInProgress && downloadCurrent.ref") diff --git a/html/src/vr.js b/html/src/vr.js index c33ad7dd..7fd90882 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -400,11 +400,7 @@ speechSynthesis.getVoices(); text = noty.data; break; case 'VideoPlay': - var videoName = noty.videoUrl; - if (noty.videoName) { - videoName = noty.videoName; - } - text = `Now playing: ${videoName}`; + text = `Now playing: ${noty.videoName}`; break; case 'BlockedOnPlayerJoined': text = `Blocked user ${noty.displayName} has joined`; From 405bc2caf98e1d7f5c89a28d9f9eaead0d2ae4f9 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Tue, 7 Sep 2021 09:04:14 +1200 Subject: [PATCH 08/25] YouTube API 2 --- html/src/app.js | 124 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index 671994b9..fb685347 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4604,7 +4604,7 @@ speechSynthesis.getVoices(); // don't play noty twice if ( this.notyMap[displayName] && - this.notyMap[displayName] > noty.created_at + this.notyMap[displayName] >= noty.created_at ) { return; } @@ -7324,7 +7324,10 @@ speechSynthesis.getVoices(); gameLog.userDisplayName, userMap ); - if (this.friends.has(userId)) { + if ( + this.friends.has(userId) || + API.currentUser.displayName === gameLog.userDisplayName + ) { this.lastLocation.friendList.set( gameLog.userDisplayName, userMap @@ -7381,6 +7384,13 @@ speechSynthesis.getVoices(); case 'video-play': this.addGameLogVideo(gameLog, location, userId, pushToTable); return; + case 'vrcx': + // VideoPlay(PyPyDance) "https://jd.pypy.moe/api/v1/videos/jr1NX4Jo8GE.mp4",0.1001,239.606,"0905 : [J-POP] 【まなこ】金曜日のおはよう 踊ってみた (vernities)" + var type = gameLog.data.substr(0, gameLog.data.indexOf(' ')); + if (type === 'VideoPlay(PyPyDance)') { + this.addGameLogPyPyDance(gameLog, location, pushToTable); + } + return; case 'notification': var entry = { created_at: gameLog.dt, @@ -7418,15 +7428,46 @@ speechSynthesis.getVoices(); if (typeof gameLog.displayName !== 'undefined') { displayName = gameLog.displayName; } - try { - var url = new URL(videoUrl); - var id1 = url.pathname; - var id2 = url.searchParams.get('v'); - if (id1 && id1.length === 12) { - youtubeVideoId = id2.substring(1, 12); + var L = API.parseLocation(location); + if (L.worldId !== 'wrld_f20326da-f1ac-45fc-a062-609723b097b1') { + // skip PyPyDance videos + try { + var url = new URL(videoUrl); + var id1 = url.pathname; + var id2 = url.searchParams.get('v'); + if (id1 && id1.length === 12) { + youtubeVideoId = id1.substring(1, 12); + } + if (id2 && id2.length === 11) { + youtubeVideoId = id2; + } + if (this.youTubeApi && youtubeVideoId) { + var data = await this.lookupYouTubeVideo(youtubeVideoId); + if (data || data.pageInfo.totalResults !== 0) { + videoId = 'YouTube'; + videoName = data.items[0].snippet.title; + videoLength = this.convertYoutubeTime( + data.items[0].contentDetails.duration + ); + } + } + } catch { + console.error(`Invalid URL: ${url}`); } - if (id2 && id2.length === 11) { - youtubeVideoId = id2; + var entry = { + created_at: gameLog.dt, + type: 'VideoPlay', + videoUrl, + videoId, + videoName, + videoLength, + location, + displayName, + userId + }; + if (pushToTable) { + this.queueGameLogNoty(entry); + this.gameLogTable.data.push(entry); } if (this.youTubeApi && youtubeVideoId) { var data = await this.lookupYouTubeVideo(youtubeVideoId); @@ -7443,29 +7484,36 @@ speechSynthesis.getVoices(); console.error(`YouTube video lookup failed status: ${status}`); } } - } catch { - console.error(`Invalid URL: ${url}`); } - var entry = { - created_at: gameLog.dt, - type: 'VideoPlay', - videoUrl, - videoId, - videoName, - videoLength, - location, - displayName, - userId - }; - if (pushToTable) { - this.queueGameLogNoty(entry); - this.gameLogTable.data.push(entry); + if (videoId === 'URL') { + var entry = { + dt: gameLog.dt, + videoUrl, + displayName + }; + this.addGameLogVideo(entry, location, userId, pushToTable); + } else { + var entry = { + created_at: gameLog.dt, + type: 'VideoPlay', + videoUrl, + videoId, + videoName, + videoLength, + location, + displayName, + userId + }; + if (pushToTable) { + this.queueGameLogNoty(entry); + this.gameLogTable.data.push(entry); + } + database.addGamelogVideoPlayToDatabase(entry); } - database.addGamelogVideoPlayToDatabase(entry); }; $app.methods.lookupYouTubeVideo = async function (videoId) { - var data = {}; + var data = null; var apiKey = 'AIzaSyA-iUQCpWf5afEL3NanEOSxbzziPMU3bxY'; if (this.youTubeApiKey) { apiKey = this.youTubeApiKey; @@ -7479,8 +7527,12 @@ speechSynthesis.getVoices(); Referer: 'https://vrcx.pypy.moe' } }); - data = JSON.parse(response.data); - data.status = response.status; + var json = JSON.parse(response.data); + if (response.status === 200) { + data = json; + } else { + throw new Error(`Error: ${response.data}`); + } } catch { console.error(`YouTube video lookup failed for ${videoId}`); } @@ -7521,13 +7573,13 @@ speechSynthesis.getVoices(); var ref = API.cachedWorlds.get(L.worldId); if (ref) { L.worldName = ref.name; - L.worldCapacity = ref.capacity; + L.worldCapacity = ref.capacity * 2; } else { API.getWorld({ worldId: L.worldId }).then((args) => { L.worldName = args.ref.name; - L.worldCapacity = args.ref.capacity; + L.worldCapacity = args.ref.capacity * 2; return args; }); } @@ -7554,7 +7606,7 @@ speechSynthesis.getVoices(); } switch (API.currentUser.status) { case 'active': - L.statusName = 'Active'; + L.statusName = 'Online'; L.statusImage = 'active'; break; case 'join me': @@ -13972,10 +14024,10 @@ speechSynthesis.getVoices(); return; } var data = await this.lookupYouTubeVideo('dQw4w9WgXcQ'); - if (!data || data.status !== 200) { + if (!data) { this.youTubeApiKey = ''; this.$message({ - message: `Invalid YouTube API key, error code: ${data.status}`, + message: 'Invalid YouTube API key', type: 'error' }); } else { @@ -13987,8 +14039,8 @@ speechSynthesis.getVoices(); message: 'YouTube API key valid!', type: 'success' }); + this.youTubeApiDialog.visible = false; } - this.youTubeApiDialog.visible = false; }; $app.methods.changeYouTubeApi = function () { From 9540e145dd1f5684dcf7a6f1dbc74eaaf5869d18 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Tue, 7 Sep 2021 09:08:30 +1200 Subject: [PATCH 09/25] chore(npm): update deps --- html/package-lock.json | 195 ++++++++++++++++++++++------------------- html/package.json | 8 +- html/src/app.dark.scss | 9 +- html/src/app.scss | 4 + 4 files changed, 116 insertions(+), 100 deletions(-) diff --git a/html/package-lock.json b/html/package-lock.json index 32b47370..6ebfd28c 100644 --- a/html/package-lock.json +++ b/html/package-lock.json @@ -9,26 +9,26 @@ "animate.css": "^4.1.1", "copy-webpack-plugin": "^9.0.1", "css-loader": "^6.2.0", - "element-ui": "^2.15.5", + "element-ui": "^2.15.6", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "famfamfam-flags": "^1.0.0", "html-webpack-plugin": "^5.3.2", - "mini-css-extract-plugin": "^2.2.0", + "mini-css-extract-plugin": "^2.2.2", "normalize.css": "^8.0.1", "noty": "^3.2.0-beta-deprecated", "prettier": "^2.3.2", "pug": "^3.0.2", "pug-plain-loader": "^1.1.0", "raw-loader": "^4.0.2", - "sass": "^1.38.2", + "sass": "^1.39.0", "sass-loader": "^12.1.0", "uuid": "^8.3.2", "vue": "^2.6.14", "vue-data-tables": "^3.4.5", "vue-lazyload": "^1.3.3", "vue-swatches": "^2.1.1", - "webpack": "^5.51.1", + "webpack": "^5.52.0", "webpack-cli": "^4.8.0" } }, @@ -136,9 +136,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "version": "7.15.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.5.tgz", + "integrity": "sha512-2hQstc6I7T6tQsWzlboMh3SgMRPaS4H6H7cPQsJkdzTzEGqQrpLDsE2BGASU5sBPoEQyHzeqU6C8uKbFeEk6sg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -148,9 +148,9 @@ } }, "node_modules/@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", + "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.14.9", @@ -283,9 +283,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.6.tgz", - "integrity": "sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg==", + "version": "16.7.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", + "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", "dev": true }, "node_modules/@webassemblyjs/ast": { @@ -709,14 +709,14 @@ } }, "node_modules/browserslist": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.8.tgz", - "integrity": "sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.0.tgz", + "integrity": "sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==", "dev": true, "dependencies": { - "caniuse-lite": "^1.0.30001251", + "caniuse-lite": "^1.0.30001254", "colorette": "^1.3.0", - "electron-to-chromium": "^1.3.811", + "electron-to-chromium": "^1.3.830", "escalade": "^3.1.1", "node-releases": "^1.1.75" }, @@ -770,9 +770,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001252", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz", - "integrity": "sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw==", + "version": "1.0.30001255", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001255.tgz", + "integrity": "sha512-F+A3N9jTZL882f/fg/WWVnKSu6IOo3ueLz4zwaOPbPYHNmM/ZaDUyzyJwS1mZhX7Ex5jqTyW599Gdelh5PDYLQ==", "dev": true, "funding": { "type": "opencollective", @@ -1052,9 +1052,9 @@ } }, "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "node_modules/deepmerge": { @@ -1171,15 +1171,15 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.822", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.822.tgz", - "integrity": "sha512-k7jG5oYYHxF4jx6PcqwHX3JVME/OjzolqOZiIogi9xtsfsmTjTdie4x88OakYFPEa8euciTgCCzvVNwvmjHb1Q==", + "version": "1.3.830", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", + "integrity": "sha512-gBN7wNAxV5vl1430dG+XRcQhD4pIeYeak6p6rjdCtlz5wWNwDad8jwvphe5oi1chL5MV6RNRikfffBBiFuj+rQ==", "dev": true }, "node_modules/element-ui": { - "version": "2.15.5", - "resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.5.tgz", - "integrity": "sha512-B/YCdz2aRY2WnFXzbTRTHPKZHBD/2KV6u88EBnkaARC/Lyxnap+7vpvrcW5UNTyVwjItS5Fj1eQyRy6236lbXg==", + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.6.tgz", + "integrity": "sha512-rcYXEKd/j2G0AgficAOk1Zd1AsnHRkhmrK4yLHmNOiimU2JfsywgfKUjMoFuT6pQx0luhovj8lFjpE4Fnt58Iw==", "dev": true, "dependencies": { "async-validator": "~1.8.1", @@ -2422,9 +2422,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.2.0.tgz", - "integrity": "sha512-91HeVHbq7PUJ4TwOuMTlFWfVWrLqf3SF0PlEDPV+wtgsfxrMebN9LLzflyQqdKLp4/H3PexRB1WLKsCqpWKkxQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.2.2.tgz", + "integrity": "sha512-eUjQ/q1rQIeHWgIx7ny/DNgXHcMXHdBwgrZQK7Ev8dbR+HxhroFM2Cb6kMiswOYaq05IRJhPuQqXWUABIjjA3g==", "dev": true, "dependencies": { "schema-utils": "^3.1.0" @@ -3335,9 +3335,9 @@ ] }, "node_modules/sass": { - "version": "1.38.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.38.2.tgz", - "integrity": "sha512-Bz1fG6qiyF0FX6m/I+VxtdVKz1Dfmg/e9kfDy2PhWOkq3T384q2KxwIfP0fXpeI+EyyETdOauH+cRHQDFASllA==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.0.tgz", + "integrity": "sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0" @@ -3649,17 +3649,17 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz", - "integrity": "sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.3.tgz", + "integrity": "sha512-eDbuaDlXhVaaoKuLD3DTNTozKqln6xOG6Us0SzlKG5tNlazG+/cdl8pm9qiF1Di89iWScTI0HcO+CDcf2dkXiw==", "dev": true, "dependencies": { - "jest-worker": "^27.0.2", + "jest-worker": "^27.0.6", "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", + "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.0", "source-map": "^0.6.1", - "terser": "^5.7.0" + "terser": "^5.7.2" }, "engines": { "node": ">= 10.13.0" @@ -3670,6 +3670,17 @@ }, "peerDependencies": { "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, "node_modules/terser-webpack-plugin/node_modules/commander": { @@ -3874,9 +3885,9 @@ } }, "node_modules/webpack": { - "version": "5.51.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.51.1.tgz", - "integrity": "sha512-xsn3lwqEKoFvqn4JQggPSRxE4dhsRcysWTqYABAZlmavcoTmwlOb9b1N36Inbt/eIispSkuHa80/FJkDTPos1A==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.0.tgz", + "integrity": "sha512-yRZOat8jWGwBwHpco3uKQhVU7HYaNunZiJ4AkAVQkPCUGoZk/tiIXiwG+8HIy/F+qsiZvSOa+GLQOj3q5RKRYg==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.0", @@ -3996,9 +4007,9 @@ } }, "node_modules/webpack/node_modules/acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -4172,15 +4183,15 @@ } }, "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "version": "7.15.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.5.tgz", + "integrity": "sha512-2hQstc6I7T6tQsWzlboMh3SgMRPaS4H6H7cPQsJkdzTzEGqQrpLDsE2BGASU5sBPoEQyHzeqU6C8uKbFeEk6sg==", "dev": true }, "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", + "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.9", @@ -4292,9 +4303,9 @@ "dev": true }, "@types/node": { - "version": "16.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.6.tgz", - "integrity": "sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg==", + "version": "16.7.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", + "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", "dev": true }, "@webassemblyjs/ast": { @@ -4658,14 +4669,14 @@ } }, "browserslist": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.8.tgz", - "integrity": "sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.0.tgz", + "integrity": "sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001251", + "caniuse-lite": "^1.0.30001254", "colorette": "^1.3.0", - "electron-to-chromium": "^1.3.811", + "electron-to-chromium": "^1.3.830", "escalade": "^3.1.1", "node-releases": "^1.1.75" } @@ -4703,9 +4714,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001252", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz", - "integrity": "sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw==", + "version": "1.0.30001255", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001255.tgz", + "integrity": "sha512-F+A3N9jTZL882f/fg/WWVnKSu6IOo3ueLz4zwaOPbPYHNmM/ZaDUyzyJwS1mZhX7Ex5jqTyW599Gdelh5PDYLQ==", "dev": true }, "chalk": { @@ -4906,9 +4917,9 @@ } }, "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "deepmerge": { @@ -4998,15 +5009,15 @@ } }, "electron-to-chromium": { - "version": "1.3.822", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.822.tgz", - "integrity": "sha512-k7jG5oYYHxF4jx6PcqwHX3JVME/OjzolqOZiIogi9xtsfsmTjTdie4x88OakYFPEa8euciTgCCzvVNwvmjHb1Q==", + "version": "1.3.830", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", + "integrity": "sha512-gBN7wNAxV5vl1430dG+XRcQhD4pIeYeak6p6rjdCtlz5wWNwDad8jwvphe5oi1chL5MV6RNRikfffBBiFuj+rQ==", "dev": true }, "element-ui": { - "version": "2.15.5", - "resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.5.tgz", - "integrity": "sha512-B/YCdz2aRY2WnFXzbTRTHPKZHBD/2KV6u88EBnkaARC/Lyxnap+7vpvrcW5UNTyVwjItS5Fj1eQyRy6236lbXg==", + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.6.tgz", + "integrity": "sha512-rcYXEKd/j2G0AgficAOk1Zd1AsnHRkhmrK4yLHmNOiimU2JfsywgfKUjMoFuT6pQx0luhovj8lFjpE4Fnt58Iw==", "dev": true, "requires": { "async-validator": "~1.8.1", @@ -5942,9 +5953,9 @@ "dev": true }, "mini-css-extract-plugin": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.2.0.tgz", - "integrity": "sha512-91HeVHbq7PUJ4TwOuMTlFWfVWrLqf3SF0PlEDPV+wtgsfxrMebN9LLzflyQqdKLp4/H3PexRB1WLKsCqpWKkxQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.2.2.tgz", + "integrity": "sha512-eUjQ/q1rQIeHWgIx7ny/DNgXHcMXHdBwgrZQK7Ev8dbR+HxhroFM2Cb6kMiswOYaq05IRJhPuQqXWUABIjjA3g==", "dev": true, "requires": { "schema-utils": "^3.1.0" @@ -6622,9 +6633,9 @@ "dev": true }, "sass": { - "version": "1.38.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.38.2.tgz", - "integrity": "sha512-Bz1fG6qiyF0FX6m/I+VxtdVKz1Dfmg/e9kfDy2PhWOkq3T384q2KxwIfP0fXpeI+EyyETdOauH+cRHQDFASllA==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.0.tgz", + "integrity": "sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0" @@ -6845,17 +6856,17 @@ } }, "terser-webpack-plugin": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz", - "integrity": "sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.3.tgz", + "integrity": "sha512-eDbuaDlXhVaaoKuLD3DTNTozKqln6xOG6Us0SzlKG5tNlazG+/cdl8pm9qiF1Di89iWScTI0HcO+CDcf2dkXiw==", "dev": true, "requires": { - "jest-worker": "^27.0.2", + "jest-worker": "^27.0.6", "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", + "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.0", "source-map": "^0.6.1", - "terser": "^5.7.0" + "terser": "^5.7.2" }, "dependencies": { "commander": { @@ -7018,9 +7029,9 @@ } }, "webpack": { - "version": "5.51.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.51.1.tgz", - "integrity": "sha512-xsn3lwqEKoFvqn4JQggPSRxE4dhsRcysWTqYABAZlmavcoTmwlOb9b1N36Inbt/eIispSkuHa80/FJkDTPos1A==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.0.tgz", + "integrity": "sha512-yRZOat8jWGwBwHpco3uKQhVU7HYaNunZiJ4AkAVQkPCUGoZk/tiIXiwG+8HIy/F+qsiZvSOa+GLQOj3q5RKRYg==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", @@ -7050,9 +7061,9 @@ }, "dependencies": { "acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", "dev": true }, "acorn-import-assertions": { diff --git a/html/package.json b/html/package.json index 7632845d..0ef45a32 100644 --- a/html/package.json +++ b/html/package.json @@ -25,26 +25,26 @@ "animate.css": "^4.1.1", "copy-webpack-plugin": "^9.0.1", "css-loader": "^6.2.0", - "element-ui": "^2.15.5", + "element-ui": "^2.15.6", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "famfamfam-flags": "^1.0.0", "html-webpack-plugin": "^5.3.2", - "mini-css-extract-plugin": "^2.2.0", + "mini-css-extract-plugin": "^2.2.2", "normalize.css": "^8.0.1", "noty": "^3.2.0-beta-deprecated", "prettier": "^2.3.2", "pug": "^3.0.2", "pug-plain-loader": "^1.1.0", "raw-loader": "^4.0.2", - "sass": "^1.38.2", + "sass": "^1.39.0", "sass-loader": "^12.1.0", "uuid": "^8.3.2", "vue": "^2.6.14", "vue-data-tables": "^3.4.5", "vue-lazyload": "^1.3.3", "vue-swatches": "^2.1.1", - "webpack": "^5.51.1", + "webpack": "^5.52.0", "webpack-cli": "^4.8.0" } } diff --git a/html/src/app.dark.scss b/html/src/app.dark.scss index 6e9f01d1..1f444996 100644 --- a/html/src/app.dark.scss +++ b/html/src/app.dark.scss @@ -51,8 +51,9 @@ button { border: #555; } -.el-table td, -.el-table th.is-leaf { +.el-table tr, +.el-table td.el-table__cell, +.el-table th.el-table__cell { background-color: #292929; border-bottom: 1px solid #5f5f5f; } @@ -63,11 +64,11 @@ button { background-color: #5f5f5f; } -.el-table--striped .el-table__body tr.el-table__row--striped td { +.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell { background-color: #202020; } -.el-table--enable-row-hover .el-table__body tr:hover > td { +.el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell { background-color: #323232; } diff --git a/html/src/app.scss b/html/src/app.scss index 01bdc707..5ea84ed7 100644 --- a/html/src/app.scss +++ b/html/src/app.scss @@ -82,6 +82,10 @@ margin-top: 15px; } +.el-table__expanded-cell[class*=cell] { + padding: 20px 50px; +} + .el-dialog__body { padding: 20px; } From 6587f66a114a56775a8db92570bde20593c936a7 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Tue, 7 Sep 2021 09:10:51 +1200 Subject: [PATCH 10/25] YouTube API 1 --- html/src/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/src/app.js b/html/src/app.js index fb685347..3cbfc063 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -7481,7 +7481,7 @@ speechSynthesis.getVoices(); data.items[0].contentDetails.duration ); } else { - console.error(`YouTube video lookup failed status: ${status}`); + console.error(`YouTube video lookup failed, error code: ${data.status}`); } } } From e227e94c1452ebcc5af16589b69026774dc49f23 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Wed, 8 Sep 2021 07:15:11 +1200 Subject: [PATCH 11/25] Random changes --- html/src/app.js | 64 ++++++++++++++++++++++++++++++------------------- html/src/vr.js | 2 +- html/src/vr.pug | 8 +++---- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index 3cbfc063..f9c5d790 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4333,9 +4333,16 @@ speechSynthesis.getVoices(); ) { return; } - if (noty.type === 'VideoPlay' && !noty.videoName) { - // skip videos without names - return; + if (noty.type === 'VideoPlay') { + if (!noty.videoName) { + // skip videos without names + return; + } + noty.notyName = noty.videoName; + if (noty.displayName) { + // add requester's name to noty + noty.notyName = `${noty.videoName} (${noty.displayName})`; + } } if ( noty.type !== 'VideoPlay' && @@ -4869,7 +4876,7 @@ speechSynthesis.getVoices(); this.speak(noty.data); break; case 'VideoPlay': - this.speak(`Now playing: ${noty.videoName}`); + this.speak(`Now playing: ${noty.notyName}`); break; case 'BlockedOnPlayerJoined': this.speak(`Blocked user ${noty.displayName} has joined`); @@ -5054,7 +5061,7 @@ speechSynthesis.getVoices(); case 'VideoPlay': AppApi.XSNotification( 'VRCX', - `Now playing: ${noty.videoName}`, + `Now playing: ${noty.notyName}`, timeout, image ); @@ -5237,7 +5244,7 @@ speechSynthesis.getVoices(); case 'VideoPlay': AppApi.DesktopNotification( 'Now playing', - noty.videoName, + noty.notyName, image ); break; @@ -7248,7 +7255,7 @@ speechSynthesis.getVoices(); } }; - $app.methods.addGameLogEvent = async function (json) { + $app.methods.addGameLogEvent = function (json) { var rawLogs = JSON.parse(json); var gameLog = gameLogService.parseRawGameLog( rawLogs[1], @@ -7256,14 +7263,11 @@ speechSynthesis.getVoices(); rawLogs.slice(3) ); var pushToTable = true; - await this.addGameLogEntry( + this.addGameLogEntry( gameLog, this.lastLocation.location, pushToTable ); - this.updateSharedFeed(false); - this.notifyMenu('gameLog'); - this.sweepGameLog(); }; $app.lastLocationDestinationTime = 0; @@ -7410,6 +7414,9 @@ speechSynthesis.getVoices(); if (pushToTable && entry) { this.queueGameLogNoty(entry); this.gameLogTable.data.push(entry); + this.updateSharedFeed(false); + this.notifyMenu('gameLog'); + this.sweepGameLog(); } }; @@ -7468,6 +7475,9 @@ speechSynthesis.getVoices(); if (pushToTable) { this.queueGameLogNoty(entry); this.gameLogTable.data.push(entry); + this.updateSharedFeed(false); + this.notifyMenu('gameLog'); + this.sweepGameLog(); } if (this.youTubeApi && youtubeVideoId) { var data = await this.lookupYouTubeVideo(youtubeVideoId); @@ -7507,6 +7517,9 @@ speechSynthesis.getVoices(); if (pushToTable) { this.queueGameLogNoty(entry); this.gameLogTable.data.push(entry); + this.updateSharedFeed(false); + this.notifyMenu('gameLog'); + this.sweepGameLog(); } database.addGamelogVideoPlayToDatabase(entry); } @@ -14190,21 +14203,22 @@ speechSynthesis.getVoices(); return; } if ( - this.downloadCurrent.type === 'Auto' && - this.cacheAutoDownloadHistory.has(assetUrl) + this.downloadCurrent.type !== 'Auto' || + !this.cacheAutoDownloadHistory.has(assetUrl) ) { - this.downloadCurrent = {}; - this.downloadInProgress = false; - this.downloadVRChatCache(); - return; - } - this.cacheAutoDownloadHistory.add(assetUrl); - try { - var args = await API.getBundles(fileId); - } catch (err) { - this.downloadCurrent.status = 'API request failed'; - this.downloadCurrent.date = Date.now(); - this.downloadHistoryTable.data.unshift(this.downloadCurrent); + this.cacheAutoDownloadHistory.add(assetUrl); + try { + var args = await API.getBundles(fileId); + } catch (err) { + this.downloadCurrent.status = 'API request failed'; + this.downloadCurrent.date = Date.now(); + this.downloadHistoryTable.data.unshift(this.downloadCurrent); + this.downloadCurrent = {}; + this.downloadInProgress = false; + this.downloadVRChatCache(); + return; + } + } else { this.downloadCurrent = {}; this.downloadInProgress = false; this.downloadVRChatCache(); diff --git a/html/src/vr.js b/html/src/vr.js index 7fd90882..acf972db 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -400,7 +400,7 @@ speechSynthesis.getVoices(); text = noty.data; break; case 'VideoPlay': - text = `Now playing: ${noty.videoName}`; + text = `Now playing: ${noty.notyName}`; break; case 'BlockedOnPlayerJoined': text = `Blocked user ${noty.displayName} has joined`; diff --git a/html/src/vr.pug b/html/src/vr.pug index 64b7580a..be5e6c35 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -69,8 +69,8 @@ html span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} | 🎵 #[span.name(v-text="feed.displayName")] - template(v-if="feed.videoName") - | #[span(v-text="feed.videoName")] + template(v-if="feed.notyName") + | #[span(v-text="feed.notyName")] template(v-else) | #[span(v-text="feed.videoUrl")] div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") @@ -215,8 +215,8 @@ html span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} | #[span.name(v-text="feed.displayName")] changed video to - template(v-if="feed.videoName") - | #[span(v-text="feed.videoName")] + template(v-if="feed.notyName") + | #[span(v-text="feed.notyName")] template(v-else) | #[span(v-text="feed.videoUrl")] div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") From de3ad943057d90e7e11531f1e12a29176149c8ef Mon Sep 17 00:00:00 2001 From: Natsumi Date: Wed, 8 Sep 2021 07:13:35 +1200 Subject: [PATCH 12/25] Small changes --- html/src/app.js | 31 ++++++++++++++++++++++--------- html/src/index.pug | 18 ++++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index f9c5d790..427a28e3 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -3695,14 +3695,12 @@ speechSynthesis.getVoices(); this.$emit('PIPELINE', { json }); - if ( - $app.debugWebSocket && - json.content && - this.cachedUsers.has(json.content.userId) - ) { + if ($app.debugWebSocket && json.content) { var displayName = ''; var user = this.cachedUsers.get(json.content.userId); - displayName = user.displayName; + if (user) { + displayName = user.displayName; + } console.log( 'WebSocket', json.type, @@ -7815,7 +7813,7 @@ speechSynthesis.getVoices(); params.featured = 'false'; break; default: - params.sort = 'popularity'; + params.sort = 'relevance'; params.search = this.searchText; break; } @@ -9376,7 +9374,7 @@ speechSynthesis.getVoices(); this.currentUserTreeData = buildTreeData(API.currentUser); }; - $app.methods.promptUserDialog = function () { + $app.methods.promptUserIdDialog = function () { this.$prompt('Enter a User URL or ID (UUID)', 'Direct Access', { distinguishCancelAndClose: true, confirmButtonText: 'OK', @@ -9404,6 +9402,21 @@ speechSynthesis.getVoices(); }); }; + $app.methods.promptUsernameDialog = function () { + this.$prompt('Enter a Username', 'Direct Access', { + distinguishCancelAndClose: true, + confirmButtonText: 'OK', + cancelButtonText: 'Cancel', + inputPattern: /\S+/, + inputErrorMessage: 'Username is required', + callback: (action, instance) => { + if (action === 'confirm' && instance.inputValue) { + this.lookupUser({displayName: instance.inputValue}); + } + } + }); + }; + $app.methods.promptWorldDialog = function () { this.$prompt('Enter a World URL or ID (UUID)', 'Direct Access', { distinguishCancelAndClose: true, @@ -9435,7 +9448,7 @@ speechSynthesis.getVoices(); }; $app.methods.promptAvatarDialog = function () { - this.$prompt('Enter a Avatar ID (UUID)', 'Direct Access', { + this.$prompt('Enter a Avatar URL or ID (UUID)', 'Direct Access', { distinguishCancelAndClose: true, confirmButtonText: 'OK', cancelButtonText: 'Cancel', diff --git a/html/src/index.pug b/html/src/index.pug index faae7ee7..630e0227 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -257,13 +257,13 @@ html el-button-group(style="margin-top:15px") el-button(v-if="searchWorldParams.offset" @click="moreSearchWorld(-1)" icon="el-icon-back" size="small") Prev el-button(v-if="searchWorldResults.length" @click="moreSearchWorld(1)" icon="el-icon-right" size="small") Next - el-tab-pane(label="Avatar" v-loading="isSearchAvatarLoading" style="min-height:60px") - el-dropdown(@command="(command) => searchAvatar(command)" size="small" trigger="click" style="margin-bottom:15px") - el-button(size="small") Search by Category #[i.el-icon-arrow-down.el-icon--right] - el-dropdown-menu(#default="dropdown") - el-dropdown-item(command="updated") Updated Recently - el-dropdown-item(command="created") New - el-dropdown-item(command="mine") Mine + //- el-tab-pane(label="Avatar" v-loading="isSearchAvatarLoading" style="min-height:60px") + //- el-dropdown(@command="(command) => searchAvatar(command)" size="small" trigger="click" style="margin-bottom:15px") + //- el-button(size="small") Search by Category #[i.el-icon-arrow-down.el-icon--right] + //- el-dropdown-menu(#default="dropdown") + //- el-dropdown-item(command="updated") Updated Recently + //- el-dropdown-item(command="created") New + //- el-dropdown-item(command="mine") Mine span(style="margin-left:10px;font-size:12px;color:#909399") Avatar search is not possible. .x-friend-list .x-friend-item(v-for="avatar in searchAvatarResults" :key="avatar.id" @click="showAvatarDialog(avatar.id)") @@ -522,7 +522,9 @@ html span.header Direct Access div(style="margin-top:10px") el-button-group - el-button(size="small" @click="promptUserDialog()") User + + el-button(size="small" @click="promptUsernameDialog()") Username + el-button(size="small" @click="promptUserIdDialog()") User ID el-button(size="small" @click="promptWorldDialog()") World/Instance el-button(size="small" @click="promptAvatarDialog()") Avatar div.options-container From 584f0df0ed139d15d576ebef27390cce0471af7e Mon Sep 17 00:00:00 2001 From: Natsumi Date: Sun, 12 Sep 2021 06:18:28 +1200 Subject: [PATCH 13/25] userIcon has priority over profilePicOverride --- html/src/app.js | 20 +++++++++ html/src/index.pug | 104 ++++++++++++--------------------------------- 2 files changed, 46 insertions(+), 78 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index 427a28e3..3e4824d0 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -15148,6 +15148,26 @@ speechSynthesis.getVoices(); return false; }; + $app.methods.userImage = function (user) { + if (this.displayVRCPlusIconsAsAvatar && user.userIcon) { + return user.userIcon; + } + if (user.profilePicOverride) { + return user.profilePicOverride; + } + return user.currentAvatarThumbnailImageUrl; + }; + + $app.methods.userImageFull = function (user) { + if (this.displayVRCPlusIconsAsAvatar && user.userIcon) { + return user.userIcon; + } + if (user.profilePicOverride) { + return user.profilePicOverride; + } + return user.currentAvatarImageUrl; + }; + $app = new Vue($app); window.$app = $app; })(); diff --git a/html/src/index.pug b/html/src/index.pug index 630e0227..1b723c79 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -22,9 +22,7 @@ html .x-friend-item(v-for="user in loginForm.savedCredentials" :key="user.user.id") .x-friend-item(@click="relogin(user)" style="width:202px;padding:0") .avatar - img(v-if="user.user.profilePicOverride" v-lazy="user.user.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && user.user.userIcon" v-lazy="user.user.userIcon") - img(v-else v-lazy="user.user.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(user.user)") .detail span.name(v-text="user.user.displayName") span.extra(v-text="user.user.username") @@ -231,8 +229,7 @@ html .x-friend-item(v-for="user in searchUserResults" :key="user.id" @click="showUserDialog(user.id)") template(v-once) .avatar - img(v-if="user.userIcon" v-lazy="user.userIcon") - img(v-lazy="user.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(user)") .detail span.name(v-text="user.displayName" :class="user.trustClass") span.extra(v-text="user.username" style="font-family:monospace") @@ -297,9 +294,7 @@ html .x-friend-item template(v-if="favorite.ref") .avatar(:class="userStatusClass(favorite.ref)") - img(v-if="favorite.ref.profilePicOverride" v-lazy="favorite.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && favorite.ref.userIcon" v-lazy="favorite.ref.userIcon") - img(v-else v-lazy="favorite.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(favorite.ref)") .detail span.name(v-text="favorite.ref.displayName" :class="favorite.ref.$trustColor") location.extra(v-if="favorite.ref.location !== 'offline'" :location="favorite.ref.location" :link="false") @@ -491,9 +486,7 @@ html .x-friend-list(style="margin-top:10px") .x-friend-item(@click="showUserDialog(API.currentUser.id)") .avatar - img(v-if="API.currentUser.profilePicOverride" v-lazy="API.currentUser.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && API.currentUser.userIcon" v-lazy="API.currentUser.userIcon") - img(v-else v-lazy="API.currentUser.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(API.currentUser)") .detail span.name(v-text="API.currentUser.displayName") span.extra(v-text="API.currentUser.username") @@ -522,7 +515,6 @@ html span.header Direct Access div(style="margin-top:10px") el-button-group - el-button(size="small" @click="promptUsernameDialog()") Username el-button(size="small" @click="promptUserIdDialog()") User ID el-button(size="small" @click="promptWorldDialog()") World/Instance @@ -645,15 +637,9 @@ html el-table-column(label="No." width="70" prop="$friendNum" sortable="custom") el-table-column(label="Avatar" width="70" prop="photo") template(v-once #default="scope") - el-popover(v-if="scope.row.profilePicOverride" placement="right" height="500px" trigger="hover") - img.friends-list-avatar(slot="reference" v-lazy="scope.row.profilePicOverride") - img.friends-list-avatar(v-lazy="scope.row.profilePicOverride" style="height:500px;cursor:pointer" @click="openExternalLink(scope.row.profilePicOverride)") - el-popover(v-else-if="displayVRCPlusIconsAsAvatar && scope.row.userIcon" placement="right" height="500px" trigger="hover") - img.friends-list-avatar(slot="reference" v-lazy="scope.row.userIcon") - img.friends-list-avatar(v-lazy="scope.row.userIcon" style="height:500px;cursor:pointer" @click="openExternalLink(scope.row.userIcon)") - el-popover(v-else placement="right" height="500px" trigger="hover") - img.friends-list-avatar(slot="reference" v-lazy="scope.row.currentAvatarThumbnailImageUrl") - img.friends-list-avatar(v-lazy="scope.row.currentAvatarImageUrl" style="height:500px;cursor:pointer" @click="openExternalLink(scope.row.currentAvatarImageUrl)") + el-popover(placement="right" height="500px" trigger="hover") + img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row)") + img.friends-list-avatar(v-lazy="userImageFull(scope.row)" style="height:500px;cursor:pointer" @click="openExternalLink(userImageFull(scope.row))") el-table-column(label="Display Name" min-width="130" prop="displayName" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'displayName')") el-table-column(label="User Name" min-width="120" prop="username" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'username')") el-table-column(label="Rank" width="110" prop="$trustSortNum" sortable="custom") @@ -957,9 +943,7 @@ html .detail span.name(v-text="item.ref.displayName" :class="item.ref.$trustColor") location.extra(:location="item.ref.location" :link="false") - img.avatar(v-if="item.ref.profilePicOverride" v-lazy="item.ref.profilePicOverride") - img.avatar(v-else-if="displayVRCPlusIconsAsAvatar && item.ref.userIcon" v-lazy="item.ref.userIcon") - img.avatar(v-else v-lazy="item.ref.currentAvatarThumbnailImageUrl") + img.avatar(v-lazy="userImage(item.ref)") span(v-else) Search More: #[span(v-text="item.label" style="font-weight:bold")] el-tooltip(placement="bottom" content="Direct access ID/URL" :disabled="hideTooltips") el-button(type="default" @click="promptOmniDirectDialog()" size="mini" icon="el-icon-discover" circle) @@ -972,9 +956,7 @@ html div(v-show="isFriendsGroupMe") .x-friend-item(:key="API.currentUser.id" @click="showUserDialog(API.currentUser.id)") .avatar(:class="userStatusClass(API.currentUser)") - img(v-if="API.currentUser.profilePicOverride" v-lazy="API.currentUser.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && API.currentUser.userIcon" v-lazy="API.currentUser.userIcon") - img(v-else v-lazy="API.currentUser.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(API.currentUser)") .detail span.name(v-text="API.currentUser.displayName" :class="API.currentUser.$trustColor") location.extra(v-if="isGameRunning === true" :location="lastLocation.location" :link="false") @@ -986,9 +968,7 @@ html .x-friend-item(v-for="friend in friendsGroup0" :key="friend.id" @click="showUserDialog(friend.id)") template(v-if="friend.ref") .avatar(:class="userStatusClass(friend.ref)") - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-if="friend.memo" :class="friend.ref.$trustColor") {{ friend.ref.displayName }} ({{ friend.memo }}) span.name(v-else v-text="friend.ref.displayName" :class="friend.ref.$trustColor") @@ -1003,9 +983,7 @@ html .x-friend-item(v-for="friend in friendsGroup1" :key="friend.id" @click="showUserDialog(friend.id)") template(v-if="friend.ref") .avatar(:class="userStatusClass(friend.ref)") - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-if="friend.memo" :class="friend.ref.$trustColor") {{ friend.ref.displayName }} ({{ friend.memo }}) span.name(v-else v-text="friend.ref.displayName" :class="friend.ref.$trustColor") @@ -1020,9 +998,7 @@ html .x-friend-item(v-for="friend in friendsGroup2" :key="friend.id" @click="showUserDialog(friend.id)") template(v-if="friend.ref") .avatar - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-if="friend.memo" :class="friend.ref.$trustColor") {{ friend.ref.displayName }} ({{ friend.memo }}) span.name(v-else v-text="friend.ref.displayName" :class="friend.ref.$trustColor") @@ -1037,9 +1013,7 @@ html .x-friend-item(v-for="friend in friendsGroup3" :key="friend.id" @click="showUserDialog(friend.id)") template(v-if="friend.ref") .avatar - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-if="friend.memo" :class="friend.ref.$trustColor") {{ friend.ref.displayName }} ({{ friend.memo }}) span.name(v-else v-text="friend.ref.displayName" :class="friend.ref.$trustColor") @@ -1152,18 +1126,14 @@ html .x-friend-item(v-if="userDialog.$location.userId" @click="showUserDialog(userDialog.$location.userId)") template(v-if="userDialog.$location.user") .avatar(:class="userStatusClass(userDialog.$location.user)") - img(v-if="userDialog.$location.user.profilePicOverride" v-lazy="userDialog.$location.user.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && userDialog.$location.user.userIcon" v-lazy="userDialog.$location.user.userIcon") - img(v-else v-lazy="userDialog.$location.user.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(userDialog.$location.user)") .detail span.name(v-text="userDialog.$location.user.displayName" :class="userDialog.$location.user.$trustColor") span.extra Instance Creator span(v-else v-text="userDialog.$location.userId") .x-friend-item(v-for="user in userDialog.users" :key="user.id" @click="showUserDialog(user.id)") .avatar(:class="userStatusClass(user)") - img(v-if="user.profilePicOverride" v-lazy="user.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && user.userIcon" v-lazy="user.userIcon") - img(v-else v-lazy="user.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(user)") .detail span.name(v-text="user.displayName" :class="user.$trustColor") span.extra @@ -1341,18 +1311,14 @@ html .x-friend-item(v-if="room.$location.userId" @click="showUserDialog(room.$location.userId)") template(v-if="room.$location.user") .avatar(:class="userStatusClass(room.$location.user)") - img(v-if="room.$location.user.profilePicOverride" v-lazy="room.$location.user.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && room.$location.user.userIcon" v-lazy="room.$location.user.userIcon") - img(v-else v-lazy="room.$location.user.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(room.$location.user)") .detail span.name(v-text="room.$location.user.displayName" :class="room.$location.user.$trustColor") span.extra Instance Creator span(v-else v-text="room.$location.userId") .x-friend-item(v-for="user in room.users" :key="user.id" @click="showUserDialog(user.id)") .avatar(:class="userStatusClass(user)") - img(v-if="user.profilePicOverride" v-lazy="user.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && user.userIcon" v-lazy="user.userIcon") - img(v-else v-lazy="user.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(user)") .detail span.name(v-text="user.displayName" :class="user.$trustColor") span.extra @@ -1511,18 +1477,14 @@ html el-option-group(v-if="API.currentUser" label="ME") el-option.x-friend-item(:label="API.currentUser.displayName" :value="API.currentUser.id" style="height:auto") .avatar(:class="userStatusClass(API.currentUser)") - img(v-if="API.currentUser.profilePicOverride" v-lazy="API.currentUser.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && API.currentUser.userIcon" v-lazy="API.currentUser.userIcon") - img(v-else v-lazy="API.currentUser.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(API.currentUser)") .detail span.name(v-text="API.currentUser.displayName") el-option-group(v-if="friendsGroup0.length" label="VIP") el-option.x-friend-item(v-for="friend in friendsGroup0" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto") template(v-if="friend.ref") .avatar(:class="userStatusClass(friend.ref)") - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-text="friend.ref.displayName" :class="friend.ref.$trustColor") span(v-else v-text="friend.id") @@ -1530,9 +1492,7 @@ html el-option.x-friend-item(v-for="friend in friendsGroup1" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto") template(v-if="friend.ref") .avatar(:class="userStatusClass(friend.ref)") - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-text="friend.ref.displayName" :class="friend.ref.$trustColor") span(v-else v-text="friend.id") @@ -1540,9 +1500,7 @@ html el-option.x-friend-item(v-for="friend in friendsGroup2" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto") template(v-if="friend.ref") .avatar - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-text="friend.ref.displayName" :class="friend.ref.$trustColor") span(v-else v-text="friend.id") @@ -1627,18 +1585,14 @@ html el-option-group(v-if="API.currentUser" label="ME") el-option.x-friend-item(:label="API.currentUser.displayName" :value="API.currentUser.id" style="height:auto") .avatar(:class="userStatusClass(API.currentUser)") - img(v-if="API.currentUser.profilePicOverride" v-lazy="API.currentUser.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && API.currentUser.userIcon" v-lazy="API.currentUser.userIcon") - img(v-else v-lazy="API.currentUser.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(API.currentUser)") .detail span.name(v-text="API.currentUser.displayName") el-option-group(v-if="friendsGroup0.length" label="VIP") el-option.x-friend-item(v-for="friend in friendsGroup0" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto") template(v-if="friend.ref") .avatar(:class="userStatusClass(friend.ref)") - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-text="friend.ref.displayName" :class="friend.ref.$trustColor") span(v-else v-text="friend.id") @@ -1646,9 +1600,7 @@ html el-option.x-friend-item(v-for="friend in friendsGroup1" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto") template(v-if="friend.ref") .avatar(:class="userStatusClass(friend.ref)") - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-text="friend.ref.displayName" :class="friend.ref.$trustColor") span(v-else v-text="friend.id") @@ -1656,9 +1608,7 @@ html el-option.x-friend-item(v-for="friend in friendsGroup2" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto") template(v-if="friend.ref") .avatar - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-text="friend.ref.displayName" :class="friend.ref.$trustColor") span(v-else v-text="friend.id") @@ -1666,9 +1616,7 @@ html el-option.x-friend-item(v-for="friend in friendsGroup3" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto") template(v-if="friend.ref") .avatar - img(v-if="friend.ref.profilePicOverride" v-lazy="friend.ref.profilePicOverride") - img(v-else-if="displayVRCPlusIconsAsAvatar && friend.ref.userIcon" v-lazy="friend.ref.userIcon") - img(v-else v-lazy="friend.ref.currentAvatarThumbnailImageUrl") + img(v-lazy="userImage(friend.ref)") .detail span.name(v-text="friend.ref.displayName" :class="friend.ref.$trustColor") span(v-else v-text="friend.id") From b43eb982ee09d7b85d38ec8252e858598f0eb26d Mon Sep 17 00:00:00 2001 From: Natsumi Date: Mon, 13 Sep 2021 04:42:55 +1200 Subject: [PATCH 14/25] PyPyDance Discord RPC --- html/package-lock.json | 216 ++++++++++++++++++++-------------- html/package.json | 9 +- html/src/app.js | 258 +++++++++++++++++++++++++++++++++-------- html/src/app.scss | 2 +- html/src/vr.js | 15 +++ html/src/vr.pug | 15 ++- html/src/vr.scss | 7 +- 7 files changed, 374 insertions(+), 148 deletions(-) diff --git a/html/package-lock.json b/html/package-lock.json index 6ebfd28c..e0caa3a8 100644 --- a/html/package-lock.json +++ b/html/package-lock.json @@ -14,21 +14,22 @@ "eslint-config-prettier": "^8.3.0", "famfamfam-flags": "^1.0.0", "html-webpack-plugin": "^5.3.2", - "mini-css-extract-plugin": "^2.2.2", + "mini-css-extract-plugin": "^2.3.0", "normalize.css": "^8.0.1", "noty": "^3.2.0-beta-deprecated", - "prettier": "^2.3.2", + "prettier": "^2.4.0", "pug": "^3.0.2", "pug-plain-loader": "^1.1.0", "raw-loader": "^4.0.2", - "sass": "^1.39.0", + "sass": "^1.39.2", "sass-loader": "^12.1.0", "uuid": "^8.3.2", "vue": "^2.6.14", "vue-data-tables": "^3.4.5", "vue-lazyload": "^1.3.3", + "vue-marquee-text-component": "^1.2.0", "vue-swatches": "^2.1.1", - "webpack": "^5.52.0", + "webpack": "^5.52.1", "webpack-cli": "^4.8.0" } }, @@ -136,9 +137,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.15.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.5.tgz", - "integrity": "sha512-2hQstc6I7T6tQsWzlboMh3SgMRPaS4H6H7cPQsJkdzTzEGqQrpLDsE2BGASU5sBPoEQyHzeqU6C8uKbFeEk6sg==", + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.6.tgz", + "integrity": "sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -148,9 +149,9 @@ } }, "node_modules/@babel/types": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", - "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.14.9", @@ -283,9 +284,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.7.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", - "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", "dev": true }, "node_modules/@webassemblyjs/ast": { @@ -770,9 +771,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001255", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001255.tgz", - "integrity": "sha512-F+A3N9jTZL882f/fg/WWVnKSu6IOo3ueLz4zwaOPbPYHNmM/ZaDUyzyJwS1mZhX7Ex5jqTyW599Gdelh5PDYLQ==", + "version": "1.0.30001256", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001256.tgz", + "integrity": "sha512-QirrvMLmB4txNnxiaG/xbm6FSzv9LqOZ3Jp9VtCYb3oPIfCHpr/oGn38pFq0udwlkctvXQgPthaXqJ76DaYGnA==", "dev": true, "funding": { "type": "opencollective", @@ -891,9 +892,9 @@ "dev": true }, "node_modules/colorette": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", - "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "dev": true }, "node_modules/commander": { @@ -1171,9 +1172,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.830", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", - "integrity": "sha512-gBN7wNAxV5vl1430dG+XRcQhD4pIeYeak6p6rjdCtlz5wWNwDad8jwvphe5oi1chL5MV6RNRikfffBBiFuj+rQ==", + "version": "1.3.836", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.836.tgz", + "integrity": "sha512-Ney3pHOJBWkG/AqYjrW0hr2AUCsao+2uvq9HUlRP8OlpSdk/zOHOUJP7eu0icDvePC9DlgffuelP4TnOJmMRUg==", "dev": true }, "node_modules/element-ui": { @@ -1598,9 +1599,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", - "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2159,9 +2160,9 @@ } }, "node_modules/jest-worker": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.1.0.tgz", - "integrity": "sha512-mO4PHb2QWLn9yRXGp7rkvXLAYuxwhq1ZYUo0LoDhg8wqvv4QizP1ZWEJOeolgbEgAWZLIEU0wsku8J+lGWfBhg==", + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.1.1.tgz", + "integrity": "sha512-XJKCL7tu+362IUYTWvw8+3S75U7qMiYiRU6u5yqscB48bTvzwN6i8L/7wVTXiFLwkRsxARNM7TISnTvcgv9hxA==", "dev": true, "dependencies": { "@types/node": "*", @@ -2422,9 +2423,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.2.2.tgz", - "integrity": "sha512-eUjQ/q1rQIeHWgIx7ny/DNgXHcMXHdBwgrZQK7Ev8dbR+HxhroFM2Cb6kMiswOYaq05IRJhPuQqXWUABIjjA3g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.3.0.tgz", + "integrity": "sha512-uzWaOwC+gJrnKbr23J1ZRWx/Wd9W9Ce1mKPlsBGBV/r8zG7/G7oKMxGmxbI65pVGbae2cR7CUx9Ulk0HQt8BfQ==", "dev": true, "dependencies": { "schema-utils": "^3.1.0" @@ -2861,9 +2862,9 @@ } }, "node_modules/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz", + "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -3335,9 +3336,9 @@ ] }, "node_modules/sass": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.0.tgz", - "integrity": "sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg==", + "version": "1.39.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.2.tgz", + "integrity": "sha512-4/6Vn2RPc+qNwSclUSKvssh7dqK1Ih3FfHBW16I/GfH47b3scbYeOw65UIrYG7PkweFiKbpJjgkf5CV8EMmvzw==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0" @@ -3509,9 +3510,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -3649,9 +3650,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.3.tgz", - "integrity": "sha512-eDbuaDlXhVaaoKuLD3DTNTozKqln6xOG6Us0SzlKG5tNlazG+/cdl8pm9qiF1Di89iWScTI0HcO+CDcf2dkXiw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", + "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", "dev": true, "dependencies": { "jest-worker": "^27.0.6", @@ -3865,6 +3866,27 @@ "integrity": "sha512-uHnq0FTEeNmqnbBC2aRKlmtd9LofMZ6Q3mWvgfLa+i9vhxU8fDK+nGs9c1iVT85axSua/AUnMttIq3xPaU9G3A==", "dev": true }, + "node_modules/vue-marquee-text-component": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vue-marquee-text-component/-/vue-marquee-text-component-1.2.0.tgz", + "integrity": "sha512-wMqr7AnyCF1VjXFAnAXavAD2yCeiBULy6AbLet/YIGaI2KzuaUmi+Q3UyFYJOM2gWt994jl2PWilnkh9heLqAA==", + "dev": true, + "dependencies": { + "core-js": "^3.6.5", + "vue": "^2.5.17" + } + }, + "node_modules/vue-marquee-text-component/node_modules/core-js": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.17.3.tgz", + "integrity": "sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/vue-swatches": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/vue-swatches/-/vue-swatches-2.1.1.tgz", @@ -3885,9 +3907,9 @@ } }, "node_modules/webpack": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.0.tgz", - "integrity": "sha512-yRZOat8jWGwBwHpco3uKQhVU7HYaNunZiJ4AkAVQkPCUGoZk/tiIXiwG+8HIy/F+qsiZvSOa+GLQOj3q5RKRYg==", + "version": "5.52.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.1.tgz", + "integrity": "sha512-wkGb0hLfrS7ML3n2xIKfUIwHbjB6gxwQHyLmVHoAqEQBw+nWo+G6LoHL098FEXqahqximsntjBLuewStrnJk0g==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.0", @@ -4183,15 +4205,15 @@ } }, "@babel/parser": { - "version": "7.15.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.5.tgz", - "integrity": "sha512-2hQstc6I7T6tQsWzlboMh3SgMRPaS4H6H7cPQsJkdzTzEGqQrpLDsE2BGASU5sBPoEQyHzeqU6C8uKbFeEk6sg==", + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.6.tgz", + "integrity": "sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==", "dev": true }, "@babel/types": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", - "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.9", @@ -4303,9 +4325,9 @@ "dev": true }, "@types/node": { - "version": "16.7.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", - "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", "dev": true }, "@webassemblyjs/ast": { @@ -4714,9 +4736,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001255", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001255.tgz", - "integrity": "sha512-F+A3N9jTZL882f/fg/WWVnKSu6IOo3ueLz4zwaOPbPYHNmM/ZaDUyzyJwS1mZhX7Ex5jqTyW599Gdelh5PDYLQ==", + "version": "1.0.30001256", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001256.tgz", + "integrity": "sha512-QirrvMLmB4txNnxiaG/xbm6FSzv9LqOZ3Jp9VtCYb3oPIfCHpr/oGn38pFq0udwlkctvXQgPthaXqJ76DaYGnA==", "dev": true }, "chalk": { @@ -4807,9 +4829,9 @@ "dev": true }, "colorette": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", - "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "dev": true }, "commander": { @@ -5009,9 +5031,9 @@ } }, "electron-to-chromium": { - "version": "1.3.830", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", - "integrity": "sha512-gBN7wNAxV5vl1430dG+XRcQhD4pIeYeak6p6rjdCtlz5wWNwDad8jwvphe5oi1chL5MV6RNRikfffBBiFuj+rQ==", + "version": "1.3.836", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.836.tgz", + "integrity": "sha512-Ney3pHOJBWkG/AqYjrW0hr2AUCsao+2uvq9HUlRP8OlpSdk/zOHOUJP7eu0icDvePC9DlgffuelP4TnOJmMRUg==", "dev": true }, "element-ui": { @@ -5337,9 +5359,9 @@ "dev": true }, "fastq": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", - "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -5739,9 +5761,9 @@ "dev": true }, "jest-worker": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.1.0.tgz", - "integrity": "sha512-mO4PHb2QWLn9yRXGp7rkvXLAYuxwhq1ZYUo0LoDhg8wqvv4QizP1ZWEJOeolgbEgAWZLIEU0wsku8J+lGWfBhg==", + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.1.1.tgz", + "integrity": "sha512-XJKCL7tu+362IUYTWvw8+3S75U7qMiYiRU6u5yqscB48bTvzwN6i8L/7wVTXiFLwkRsxARNM7TISnTvcgv9hxA==", "dev": true, "requires": { "@types/node": "*", @@ -5953,9 +5975,9 @@ "dev": true }, "mini-css-extract-plugin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.2.2.tgz", - "integrity": "sha512-eUjQ/q1rQIeHWgIx7ny/DNgXHcMXHdBwgrZQK7Ev8dbR+HxhroFM2Cb6kMiswOYaq05IRJhPuQqXWUABIjjA3g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.3.0.tgz", + "integrity": "sha512-uzWaOwC+gJrnKbr23J1ZRWx/Wd9W9Ce1mKPlsBGBV/r8zG7/G7oKMxGmxbI65pVGbae2cR7CUx9Ulk0HQt8BfQ==", "dev": true, "requires": { "schema-utils": "^3.1.0" @@ -6275,9 +6297,9 @@ "dev": true }, "prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz", + "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==", "dev": true }, "pretty-error": { @@ -6633,9 +6655,9 @@ "dev": true }, "sass": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.0.tgz", - "integrity": "sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg==", + "version": "1.39.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.2.tgz", + "integrity": "sha512-4/6Vn2RPc+qNwSclUSKvssh7dqK1Ih3FfHBW16I/GfH47b3scbYeOw65UIrYG7PkweFiKbpJjgkf5CV8EMmvzw==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0" @@ -6740,9 +6762,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -6856,9 +6878,9 @@ } }, "terser-webpack-plugin": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.3.tgz", - "integrity": "sha512-eDbuaDlXhVaaoKuLD3DTNTozKqln6xOG6Us0SzlKG5tNlazG+/cdl8pm9qiF1Di89iWScTI0HcO+CDcf2dkXiw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", + "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", "dev": true, "requires": { "jest-worker": "^27.0.6", @@ -7012,6 +7034,24 @@ "integrity": "sha512-uHnq0FTEeNmqnbBC2aRKlmtd9LofMZ6Q3mWvgfLa+i9vhxU8fDK+nGs9c1iVT85axSua/AUnMttIq3xPaU9G3A==", "dev": true }, + "vue-marquee-text-component": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vue-marquee-text-component/-/vue-marquee-text-component-1.2.0.tgz", + "integrity": "sha512-wMqr7AnyCF1VjXFAnAXavAD2yCeiBULy6AbLet/YIGaI2KzuaUmi+Q3UyFYJOM2gWt994jl2PWilnkh9heLqAA==", + "dev": true, + "requires": { + "core-js": "^3.6.5", + "vue": "^2.5.17" + }, + "dependencies": { + "core-js": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.17.3.tgz", + "integrity": "sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw==", + "dev": true + } + } + }, "vue-swatches": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/vue-swatches/-/vue-swatches-2.1.1.tgz", @@ -7029,9 +7069,9 @@ } }, "webpack": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.0.tgz", - "integrity": "sha512-yRZOat8jWGwBwHpco3uKQhVU7HYaNunZiJ4AkAVQkPCUGoZk/tiIXiwG+8HIy/F+qsiZvSOa+GLQOj3q5RKRYg==", + "version": "5.52.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.1.tgz", + "integrity": "sha512-wkGb0hLfrS7ML3n2xIKfUIwHbjB6gxwQHyLmVHoAqEQBw+nWo+G6LoHL098FEXqahqximsntjBLuewStrnJk0g==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", diff --git a/html/package.json b/html/package.json index 0ef45a32..0d4413f4 100644 --- a/html/package.json +++ b/html/package.json @@ -30,21 +30,22 @@ "eslint-config-prettier": "^8.3.0", "famfamfam-flags": "^1.0.0", "html-webpack-plugin": "^5.3.2", - "mini-css-extract-plugin": "^2.2.2", + "mini-css-extract-plugin": "^2.3.0", "normalize.css": "^8.0.1", "noty": "^3.2.0-beta-deprecated", - "prettier": "^2.3.2", + "prettier": "^2.4.0", "pug": "^3.0.2", "pug-plain-loader": "^1.1.0", "raw-loader": "^4.0.2", - "sass": "^1.39.0", + "sass": "^1.39.2", "sass-loader": "^12.1.0", "uuid": "^8.3.2", "vue": "^2.6.14", "vue-data-tables": "^3.4.5", "vue-lazyload": "^1.3.3", + "vue-marquee-text-component": "^1.2.0", "vue-swatches": "^2.1.1", - "webpack": "^5.52.0", + "webpack": "^5.52.1", "webpack-cli": "^4.8.0" } } diff --git a/html/src/app.js b/html/src/app.js index 3e4824d0..a442b107 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -3994,6 +3994,7 @@ speechSynthesis.getVoices(); } AppApi.CheckGameRunning().then( ([isGameRunning, isGameNoVR]) => { + this.updateOpenVR(isGameRunning, isGameNoVR); if (isGameRunning !== this.isGameRunning) { this.isGameRunning = isGameRunning; if (isGameRunning) { @@ -4006,13 +4007,12 @@ speechSynthesis.getVoices(); this.autoVRChatCacheManagement(); } this.lastLocationReset(); + this.clearNowPlaying(); this.updateVRConfigVars(); - this.updateOpenVR(); } if (isGameNoVR !== this.isGameNoVR) { this.isGameNoVR = isGameNoVR; this.updateVRConfigVars(); - this.updateOpenVR(); } this.updateDiscord(); } @@ -5240,11 +5240,7 @@ speechSynthesis.getVoices(); AppApi.DesktopNotification('Event', noty.data, image); break; case 'VideoPlay': - AppApi.DesktopNotification( - 'Now playing', - noty.notyName, - image - ); + AppApi.DesktopNotification('Now playing', noty.notyName, image); break; case 'BlockedOnPlayerJoined': AppApi.DesktopNotification( @@ -7212,6 +7208,22 @@ speechSynthesis.getVoices(); this.lastLocationReset(); }; + $app.methods.sweepGameLog = function () { + var {data} = this.gameLogTable; + // 로그는 7일까지만 남김 + var limit = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toJSON(); + var i = 0; + var j = data.length; + while (i < j && data[i].created_at < limit) { + ++i; + } + if (i === j) { + this.gameLogTable.data = []; + } else if (i) { + data.splice(0, i); + } + }; + $app.methods.refreshEntireGameLog = async function () { await gameLogService.setDateTill('1970-01-01'); await database.initTables(); @@ -7241,7 +7253,7 @@ speechSynthesis.getVoices(); await gameLogService.setDateTill(dateTill); await gameLogService.reset(); await new Promise((resolve) => { - setTimeout(resolve, 1000); + setTimeout(resolve, 10000); }); var location = ''; var pushToTable = true; @@ -7261,11 +7273,7 @@ speechSynthesis.getVoices(); rawLogs.slice(3) ); var pushToTable = true; - this.addGameLogEntry( - gameLog, - this.lastLocation.location, - pushToTable - ); + this.addGameLogEntry(gameLog, this.lastLocation.location, pushToTable); }; $app.lastLocationDestinationTime = 0; @@ -7284,6 +7292,7 @@ speechSynthesis.getVoices(); case 'location-destination': if (this.isGameRunning) { this.cancelVRChatCacheDownload(gameLog.location); + this.clearNowPlaying(); } this.lastLocationDestinationTime = Date.parse(gameLog.dt); var entry = { @@ -7295,6 +7304,7 @@ speechSynthesis.getVoices(); case 'location': if (this.isGameRunning) { this.lastLocationReset(); + this.clearNowPlaying(); this.lastLocation = { date: Date.parse(gameLog.dt), location: gameLog.location, @@ -7430,11 +7440,18 @@ speechSynthesis.getVoices(); var videoName = ''; var videoLength = ''; var displayName = ''; + var videoPos = 5; // video loading delay if (typeof gameLog.displayName !== 'undefined') { displayName = gameLog.displayName; } + if (typeof gameLog.videoPos !== 'undefined') { + videoPos = gameLog.videoPos; + } var L = API.parseLocation(location); - if (L.worldId !== 'wrld_f20326da-f1ac-45fc-a062-609723b097b1') { + if ( + L.worldId !== 'wrld_f20326da-f1ac-45fc-a062-609723b097b1' || + gameLog.videoId === 'YouTube' + ) { // skip PyPyDance videos try { var url = new URL(videoUrl); @@ -7468,36 +7485,61 @@ speechSynthesis.getVoices(); videoLength, location, displayName, - userId + userId, + videoPos }; if (pushToTable) { + this.setNowPlaying(entry); this.queueGameLogNoty(entry); this.gameLogTable.data.push(entry); this.updateSharedFeed(false); this.notifyMenu('gameLog'); this.sweepGameLog(); } - if (this.youTubeApi && youtubeVideoId) { - var data = await this.lookupYouTubeVideo(youtubeVideoId); - if ( - data || - (data.status === 200 && data.pageInfo.totalResults !== 0) - ) { - videoId = 'YouTube'; - videoName = data.items[0].snippet.title; - videoLength = this.convertYoutubeTime( - data.items[0].contentDetails.duration - ); - } else { - console.error(`YouTube video lookup failed, error code: ${data.status}`); + database.addGamelogVideoPlayToDatabase(entry); + } + }; + + $app.methods.addGameLogPyPyDance = function ( + gameLog, + location, + pushToTable + ) { + var data = + /VideoPlay\(PyPyDance\) "(.+?)",([\d.]+),([\d.]+),"(.+?)\s*(?:)?"/g.exec( + gameLog.data + ); + var videoUrl = data[1]; + var videoPos = Number(data[2]); + var videoLength = Number(data[3]); + var title = data[4]; + var bracketArray = title.split('('); + var text1 = bracketArray.pop(); + var displayName = text1.slice(0, -1); + var text2 = bracketArray.join('('); + if (text2 === 'URL ') { + var videoId = 'YouTube'; + } else { + var videoId = text2.substr(0, text2.indexOf(':') - 1); + text2 = text2.substr(text2.indexOf(':') + 2); + } + var videoName = text2.slice(0, -1); + var userId = ''; + if (displayName && displayName !== 'Random') { + for (var ref of API.cachedUsers.values()) { + if (ref.displayName === displayName) { + userId = ref.id; + break; } } } - if (videoId === 'URL') { + if (videoId === 'YouTube') { var entry = { dt: gameLog.dt, videoUrl, - displayName + displayName, + videoPos, + videoId }; this.addGameLogVideo(entry, location, userId, pushToTable); } else { @@ -7510,9 +7552,11 @@ speechSynthesis.getVoices(); videoLength, location, displayName, - userId + userId, + videoPos }; if (pushToTable) { + this.setNowPlaying(entry); this.queueGameLogNoty(entry); this.gameLogTable.data.push(entry); this.updateSharedFeed(false); @@ -7550,20 +7594,127 @@ speechSynthesis.getVoices(); return data; }; - $app.methods.sweepGameLog = function () { - var {data} = this.gameLogTable; - // 로그는 7일까지만 남김 - var limit = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toJSON(); - var i = 0; - var j = data.length; - while (i < j && data[i].created_at < limit) { - ++i; + $app.data.nowPlaying = { + url: '', + name: '', + length: 0, + startTime: 0, + elapsed: 0, + percentage: 0, + remainingText: '', + playing: false + }; + + $app.methods.clearNowPlaying = function () { + this.nowPlaying = { + url: '', + name: '', + length: 0, + startTime: 0, + elapsed: 0, + percentage: 0, + remainingText: '', + playing: false + }; + this.updateVrNowPlaying(); + }; + + $app.methods.setNowPlaying = function (ctx) { + var videoId = ''; + var displayName = ''; + if (ctx.videoId) { + videoId = `${ctx.videoId} : `; } - if (i === j) { - this.gameLogTable.data = []; - } else if (i) { - data.splice(0, i); + if (ctx.displayName) { + displayName = ` (${ctx.displayName})`; } + var name = `${videoId}${ctx.videoName}${displayName} ‎`; + this.nowPlaying = { + url: ctx.videoUrl, + name, + length: ctx.videoLength, + startTime: Date.parse(ctx.created_at) / 1000 - ctx.videoPos, + elapsed: 0, + percentage: 0, + remainingText: '' + }; + if (!this.nowPlaying.playing) { + this.nowPlaying.playing = true; + this.updateNowPlaying(); + } + }; + + $app.methods.updateNowPlaying = function () { + var np = this.nowPlaying; + if (!np.url) { + this.nowPlaying.playing = false; + return; + } + var now = Date.now() / 1000; + np.elapsed = Math.round((now - np.startTime) * 10) / 10; + if (np.elapsed >= np.length) { + this.clearNowPlaying(); + return; + } + np.remainingText = this.formatSeconds(np.length - np.elapsed); + np.percentage = Math.round(((np.elapsed * 100) / np.length) * 10) / 10; + this.updateVrNowPlaying(); + setTimeout(() => this.updateNowPlaying(), 1000); + }; + + $app.methods.updateVrNowPlaying = function () { + var json = JSON.stringify(this.nowPlaying); + AppApi.ExecuteVrFeedFunction('nowPlayingUpdate', json); + }; + + $app.methods.formatSeconds = function (duration) { + var pad = function (num, size) { + return `000${num}`.slice(size * -1); + }, + time = parseFloat(duration).toFixed(3), + hours = Math.floor(time / 60 / 60), + minutes = Math.floor(time / 60) % 60, + seconds = Math.floor(time - minutes * 60); + var hoursOut = ''; + if (hours > '0') { + hoursOut = `${pad(hours, 2)}:`; + } + return `${hoursOut + pad(minutes, 2)}:${pad(seconds, 2)}`; + }; + + $app.methods.convertYoutubeTime = function (duration) { + var a = duration.match(/\d+/g); + if ( + duration.indexOf('M') >= 0 && + duration.indexOf('H') === -1 && + duration.indexOf('S') === -1 + ) { + a = [0, a[0], 0]; + } + if (duration.indexOf('H') >= 0 && duration.indexOf('M') === -1) { + a = [a[0], 0, a[1]]; + } + if ( + duration.indexOf('H') >= 0 && + duration.indexOf('M') === -1 && + duration.indexOf('S') === -1 + ) { + a = [a[0], 0, 0]; + } + var length = 0; + if (a.length === 3) { + length += parseInt(a[0], 10) * 3600; + length += parseInt(a[1], 10) * 60; + length += parseInt(a[2], 10); + } + if (a.length === 2) { + length += parseInt(a[0], 10) * 60; + length += parseInt(a[1], 10); + } + if (a.length === 1) { + length += parseInt(a[0], 10); + } + return length; }; $app.methods.updateDiscord = function () { @@ -7633,8 +7784,19 @@ speechSynthesis.getVoices(); L.statusImage = 'busy'; break; } + var appId = '883308884863901717'; + var bigIcon = 'vrchat'; + if (L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1') { + L.worldName = this.nowPlaying.name; + appId = '784094509008551956'; + bigIcon = 'pypy'; + Discord.SetTimestamps( + Date.now(), + (this.nowPlaying.startTime + this.nowPlaying.length) * 1000 + ); + } Discord.SetAssets( - 'vrchat', // big icon + bigIcon, // big icon 'Powered by VRCX', // big icon hover text L.statusImage, // small icon L.statusName, // small icon hover text @@ -7643,7 +7805,7 @@ speechSynthesis.getVoices(); L.worldCapacity, // party max size 'Join', // button text L.joinUrl, // button url - '883308884863901717' // app id + appId // app id ); // NOTE // 글자 수가 짧으면 업데이트가 안된다.. @@ -9330,11 +9492,11 @@ speechSynthesis.getVoices(); }); }; - $app.methods.updateOpenVR = function () { + $app.methods.updateOpenVR = function (isGameRunning, isGameNoVR) { if ( this.openVR && - this.isGameNoVR === false && - (this.isGameRunning || this.openVRAlways) + !isGameNoVR && + (isGameRunning || this.openVRAlways) ) { AppApi.StartVR(); } else { diff --git a/html/src/app.scss b/html/src/app.scss index 5ea84ed7..4a4e3b00 100644 --- a/html/src/app.scss +++ b/html/src/app.scss @@ -82,7 +82,7 @@ margin-top: 15px; } -.el-table__expanded-cell[class*=cell] { +.el-table__expanded-cell[class*='cell'] { padding: 20px 50px; } diff --git a/html/src/vr.js b/html/src/vr.js index acf972db..2bcb9763 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -8,6 +8,8 @@ import Noty from 'noty'; import Vue from 'vue'; import ElementUI from 'element-ui'; import locale from 'element-ui/lib/locale/lang/en'; +import MarqueeText from 'vue-marquee-text-component'; +Vue.component('marquee-text', MarqueeText); import configRepository from './repository/config.js'; @@ -178,6 +180,15 @@ speechSynthesis.getVoices(); currentTime: new Date().toJSON(), cpuUsage: 0, config: {}, + nowPlaying: { + url: '', + name: '', + length: 0, + startTime: 0, + elapsed: 0, + percentage: 0, + remainingText: '' + }, lastLocation: { date: 0, location: '', @@ -280,6 +291,10 @@ speechSynthesis.getVoices(); this.config = JSON.parse(json); }; + $app.methods.nowPlayingUpdate = function (json) { + this.nowPlaying = JSON.parse(json); + }; + $app.methods.lastLocationUpdate = function (json) { this.lastLocation = JSON.parse(json); }; diff --git a/html/src/vr.pug b/html/src/vr.pug index be5e6c35..96bd5394 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -69,8 +69,8 @@ html span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} | 🎵 #[span.name(v-text="feed.displayName")] - template(v-if="feed.notyName") - | #[span(v-text="feed.notyName")] + template(v-if="feed.videoName") + | #[span(v-text="feed.videoName")] template(v-else) | #[span(v-text="feed.videoUrl")] div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") @@ -215,8 +215,8 @@ html span.extra span.time {{ feed.created_at | formatDate('HH:MI') }} | #[span.name(v-text="feed.displayName")] changed video to - template(v-if="feed.notyName") - | #[span(v-text="feed.notyName")] + template(v-if="feed.videoName") + | #[span(v-text="feed.videoName")] template(v-else) | #[span(v-text="feed.videoUrl")] div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") @@ -349,12 +349,15 @@ html br span {{ device[2] }}% .x-containerbottom + span(style="float:right;padding-left:10px") {{ nowPlaying.remainingText }} + marquee-text {{ nowPlaying.name }} + div.np-progress-bar(:style="{ width: nowPlaying.percentage + '%' }") template(v-if="config && config.minimalFeed") template(v-if="config.downloadProgress === 100") span(style="display:inline-block;margin-right:5px") #[i.el-icon-loading] template(v-else-if="config.downloadProgress > 0") span(style="display:inline-block;margin-right:5px") {{ config.downloadProgress }}% - template(v-if="lastLocation.date != 0") + template(v-if="lastLocation.date !== 0") span(style="float:right") {{ lastLocationTimer }} span(style="display:inline-block") {{ lastLocation.playerList.length }} span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? `‎‎‎‎‎‎‎‎‏‏‎ ‎(${lastLocation.friendList.length})` : ''}} @@ -363,7 +366,7 @@ html span(style="display:inline-block;margin-right:5px") Downloading: #[i.el-icon-loading] template(v-else-if="config.downloadProgress > 0") span(style="display:inline-block;margin-right:5px") Downloading: {{ config.downloadProgress }}% - template(v-if="lastLocation.date != 0") + template(v-if="lastLocation.date !== 0") span(style="float:right") Timer: {{ lastLocationTimer }} span(style="display:inline-block") Players: {{ lastLocation.playerList.length }} span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? `‎‎‎‎‎‎‎‎‏‏‎ ‎(${lastLocation.friendList.length})` : ''}} diff --git a/html/src/vr.scss b/html/src/vr.scss index f1b2495e..a685d9ae 100644 --- a/html/src/vr.scss +++ b/html/src/vr.scss @@ -211,11 +211,16 @@ button { } .x-containerbottom span { - padding: 0px; display: block; overflow: hidden; } +.np-progress-bar { + width: 0%; + height: 2px; + background-color: white; +} + .x-friend-item { box-sizing: border-box; display: flex; From 9e1d6abc49f40f8e6f342ea9fc46ef452b1a7a10 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Mon, 13 Sep 2021 05:17:01 +1200 Subject: [PATCH 15/25] Adjust alignment --- html/src/vr.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/html/src/vr.scss b/html/src/vr.scss index a685d9ae..8ebd7eeb 100644 --- a/html/src/vr.scss +++ b/html/src/vr.scss @@ -226,6 +226,7 @@ button { display: flex; align-items: center; font-size: 18px; + height: 27.1px; } .x-friend-item .time { From 62c846bb4d5a404b368cea9c7a186d8055b0c9d9 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Mon, 13 Sep 2021 12:38:02 +1200 Subject: [PATCH 16/25] PyPyDance Discord RPC 1 --- html/src/app.js | 30 +++++++++++++++++++----------- html/src/index.pug | 1 + html/src/vr.pug | 7 ++++--- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index a442b107..7ba6895b 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4332,8 +4332,9 @@ speechSynthesis.getVoices(); return; } if (noty.type === 'VideoPlay') { - if (!noty.videoName) { - // skip videos without names + if (!noty.videoName || noty.videoUrl === this.nowPlaying.url) { + // skip video without name + // skip video already playing return; } noty.notyName = noty.videoName; @@ -7112,7 +7113,7 @@ speechSynthesis.getVoices(); userId: ref.userId, time }; - this.addGameLog(entry); + $app.addGameLog(entry); database.addGamelogJoinLeaveToDatabase(entry); } if (this.lastLocation.date !== 0) { @@ -7620,15 +7621,11 @@ speechSynthesis.getVoices(); }; $app.methods.setNowPlaying = function (ctx) { - var videoId = ''; var displayName = ''; - if (ctx.videoId) { - videoId = `${ctx.videoId} : `; - } if (ctx.displayName) { displayName = ` (${ctx.displayName})`; } - var name = `${videoId}${ctx.videoName}${displayName} ‎`; + var name = `${ctx.videoName}${displayName}`; this.nowPlaying = { url: ctx.videoUrl, name, @@ -7786,10 +7783,20 @@ speechSynthesis.getVoices(); } var appId = '883308884863901717'; var bigIcon = 'vrchat'; - if (L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1') { + if ( + L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1' || + L.worldId === 'wrld_42377cf1-c54f-45ed-8996-5875b0573a83' + ) { + if (L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1') { + appId = '784094509008551956'; + bigIcon = 'pypy'; + } else if ( + L.worldId === 'wrld_42377cf1-c54f-45ed-8996-5875b0573a83' + ) { + appId = '846232616054030376'; + bigIcon = 'vr_dancing'; + } L.worldName = this.nowPlaying.name; - appId = '784094509008551956'; - bigIcon = 'pypy'; Discord.SetTimestamps( Date.now(), (this.nowPlaying.startTime + this.nowPlaying.length) * 1000 @@ -9440,6 +9447,7 @@ speechSynthesis.getVoices(); $app.methods.vrInit = function () { this.updateVRConfigVars(); this.updateVRLastLocation(); + this.updateVrNowPlaying(); this.updateSharedFeed(true); }; diff --git a/html/src/index.pug b/html/src/index.pug index 1b723c79..deb376ce 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -173,6 +173,7 @@ html span(v-else-if="scope.row.status === 'busy'") Do Not Disturb span(v-else) Offline i.x-user-status(:class="statusClass(scope.row.status)") + span ‎ span(v-text="scope.row.statusDescription") template(v-else-if="scope.row.type === 'Avatar'") avatar-info(:imageurl="scope.row.currentAvatarImageUrl" :userid="scope.row.userId" :hintownerid="scope.row.ownerId" :hintavatarname="scope.row.avatarName") diff --git a/html/src/vr.pug b/html/src/vr.pug index 96bd5394..ee69bd60 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -349,9 +349,10 @@ html br span {{ device[2] }}% .x-containerbottom - span(style="float:right;padding-left:10px") {{ nowPlaying.remainingText }} - marquee-text {{ nowPlaying.name }} - div.np-progress-bar(:style="{ width: nowPlaying.percentage + '%' }") + template(v-if="nowPlaying.playing") + span(style="float:right;padding-left:10px") {{ nowPlaying.remainingText }} + marquee-text {{ nowPlaying.name }} ‎ + div.np-progress-bar(:style="{ width: nowPlaying.percentage + '%' }") template(v-if="config && config.minimalFeed") template(v-if="config.downloadProgress === 100") span(style="display:inline-block;margin-right:5px") #[i.el-icon-loading] From 13b6a168042aaf310accda2ba10c029732580876 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Mon, 13 Sep 2021 12:38:54 +1200 Subject: [PATCH 17/25] PyPyDance Discord RPC 2 --- html/src/app.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index 7ba6895b..9d90e346 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -7441,7 +7441,7 @@ speechSynthesis.getVoices(); var videoName = ''; var videoLength = ''; var displayName = ''; - var videoPos = 5; // video loading delay + var videoPos = 10; // video loading delay if (typeof gameLog.displayName !== 'undefined') { displayName = gameLog.displayName; } @@ -7796,11 +7796,13 @@ speechSynthesis.getVoices(); appId = '846232616054030376'; bigIcon = 'vr_dancing'; } - L.worldName = this.nowPlaying.name; - Discord.SetTimestamps( - Date.now(), - (this.nowPlaying.startTime + this.nowPlaying.length) * 1000 - ); + if (this.nowPlaying.playing) { + L.worldName = this.nowPlaying.name; + Discord.SetTimestamps( + Date.now(), + (this.nowPlaying.startTime + this.nowPlaying.length) * 1000 + ); + } } Discord.SetAssets( bigIcon, // big icon From db00248c1dff9b9dfdfd0623e4f0ba65449579ea Mon Sep 17 00:00:00 2001 From: Natsumi Date: Mon, 13 Sep 2021 13:28:26 +1200 Subject: [PATCH 18/25] Bug fix --- html/src/app.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/html/src/app.js b/html/src/app.js index 9d90e346..9b1825b9 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -7113,8 +7113,13 @@ speechSynthesis.getVoices(); userId: ref.userId, time }; - $app.addGameLog(entry); database.addGamelogJoinLeaveToDatabase(entry); + this.gameLogTable.data.push(entry); + } + if (playerList.length > 0) { + this.updateSharedFeed(false); + this.notifyMenu('gameLog'); + this.sweepGameLog(); } if (this.lastLocation.date !== 0) { var timeLocation = new Date().getTime() - this.lastLocation.date; From e39491a6c42beddae3ff5360c294bb6de6352568 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Fri, 17 Sep 2021 21:30:35 +1200 Subject: [PATCH 19/25] Fixes --- html/src/app.js | 23 ++++++++++++++++------- html/src/index.pug | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index 9b1825b9..a70bc67e 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -4332,9 +4332,8 @@ speechSynthesis.getVoices(); return; } if (noty.type === 'VideoPlay') { - if (!noty.videoName || noty.videoUrl === this.nowPlaying.url) { + if (!noty.videoName) { // skip video without name - // skip video already playing return; } noty.notyName = noty.videoName; @@ -7252,6 +7251,8 @@ speechSynthesis.getVoices(); var length = this.gameLogTable.data.length; if (length > 1) { this.updateGameLog(this.gameLogTable.data[length - 1].created_at); + } else { + this.refreshEntireGameLog(); } }; @@ -14053,32 +14054,40 @@ speechSynthesis.getVoices(); $app.data.VRChatConfigList = { cache_size: { - name: 'Max Cache Size [GB] (minimum 20)', + name: 'Max Cache Size [GB] (min 20)', default: '20', type: 'number', min: 20 }, cache_expiry_delay: { - name: 'Cache Expiry [Days] (minimum 30)', + name: 'Cache Expiry [Days] (30 - 150)', default: '30', type: 'number', - min: 30 + min: 30, + max: 150 }, cache_directory: { name: 'Custom Cache Folder Location', default: '%AppData%\\..\\LocalLow\\VRChat\\vrchat' }, dynamic_bone_max_affected_transform_count: { - name: 'Dynamic Bones Limit Max Transforms (0 always disable transforms)', + name: 'Dynamic Bones Limit Max Transforms (0 disable all transforms)', default: '32', type: 'number', min: 0 }, dynamic_bone_max_collider_check_count: { - name: 'Dynamic Bones Limit Max Collider Collisions (0 always disable colliders)', + name: 'Dynamic Bones Limit Max Collider Collisions (0 disable all colliders)', default: '8', type: 'number', min: 0 + }, + fpv_steadycam_fov: { + name: 'First-Person Steadycam FOV', + default: '50', + type: 'number', + min: 30, + max: 110 } }; diff --git a/html/src/index.pug b/html/src/index.pug index deb376ce..d5b57ebc 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -187,7 +187,7 @@ html el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'AvatarChange', 'Event', 'VideoPlay']" :key="type" :label="type" :value="type") el-input(v-model="gameLogTable.filters[1].value" placeholder="Search" style="flex:none;width:150px;margin:0 10px") el-tooltip(placement="bottom" content="Reset game log" :disabled="hideTooltips") - el-button(type="default" @click="refreshGameLog()" icon="el-icon-refresh" circle style="flex:none") + el-button(type="default" @click="refreshEntireGameLog" icon="el-icon-refresh" circle style="flex:none") el-table-column(label="Date" prop="created_at" sortable="custom" width="90") template(v-once #default="scope") el-tooltip(placement="right") From 75b856aa8a4fb6aa6788d2b10bacfc6c66114505 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Sat, 18 Sep 2021 20:06:33 +1200 Subject: [PATCH 20/25] Fix feed and gameLog search --- html/src/app.js | 114 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index a70bc67e..070de93f 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -6778,6 +6778,57 @@ speechSynthesis.getVoices(); // App: Feed + $app.methods.feedSearch = function (row, filter) { + var { value } = filter; + if (!value) { + return true; + } + value = value.toUpperCase(); + switch (row.type) { + case 'GPS': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + if (String(row.worldName).toUpperCase().includes(value)) { + return true; + } + return false; + case 'Online': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + if (String(row.worldName).toUpperCase().includes(value)) { + return true; + } + return false; + case 'Offline': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + if (String(row.worldName).toUpperCase().includes(value)) { + return true; + } + return false; + case 'Status': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + if (String(row.statusDescription).toUpperCase().includes(value)) { + return true; + } + return false; + case 'Avatar': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + if (String(row.avatarName).toUpperCase().includes(value)) { + return true; + } + return false; + } + return true; + }; + $app.data.feedTable = { data: [], filters: [ @@ -6789,7 +6840,9 @@ speechSynthesis.getVoices(); }, { prop: 'displayName', - value: '' + value: '', + filterFn: (row, filter) => + $app.feedSearch(row, filter) }, { prop: 'userId', @@ -7163,6 +7216,61 @@ speechSynthesis.getVoices(); this.updateDiscord(); }; + $app.methods.gameLogSearch = function (row, filter) { + var { value } = filter; + if (!value) { + return true; + } + value = value.toUpperCase(); + switch (row.type) { + case 'Location': + if (String(row.worldName).toUpperCase().includes(value)) { + return true; + } + return false; + case 'OnPlayerJoined': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + return false; + case 'OnPlayerLeft': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + return false; + case 'PortalSpawn': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + if (String(row.worldName).toUpperCase().includes(value)) { + return true; + } + return false; + case 'AvatarChange': + if (String(row.name).toUpperCase().includes(value)) { + return true; + } + return false; + case 'Event': + if (String(row.data).toUpperCase().includes(value)) { + return true; + } + return false; + case 'VideoPlay': + if (String(row.displayName).toUpperCase().includes(value)) { + return true; + } + if (String(row.videoName).toUpperCase().includes(value)) { + return true; + } + if (String(row.videoUrl).toUpperCase().includes(value)) { + return true; + } + return false; + } + return true; + }; + $app.data.gameLogTable = { data: [], lastEntryDate: '', @@ -7175,7 +7283,9 @@ speechSynthesis.getVoices(); }, { prop: 'displayName', - value: '' + value: '', + filterFn: (row, filter) => + $app.gameLogSearch(row, filter) }, { prop: 'displayName', From c366a6506ef12d1c2911c3178b9d40b646799dad Mon Sep 17 00:00:00 2001 From: Natsumi Date: Tue, 21 Sep 2021 00:58:16 +1200 Subject: [PATCH 21/25] Discord RPC private toggle --- html/src/app.js | 63 +++++++++++++++++++++++++-------- html/src/index.pug | 5 ++- html/src/repository/database.js | 2 +- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index 070de93f..eb71e7ba 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -6779,7 +6779,7 @@ speechSynthesis.getVoices(); // App: Feed $app.methods.feedSearch = function (row, filter) { - var { value } = filter; + var {value} = filter; if (!value) { return true; } @@ -6813,7 +6813,9 @@ speechSynthesis.getVoices(); if (String(row.displayName).toUpperCase().includes(value)) { return true; } - if (String(row.statusDescription).toUpperCase().includes(value)) { + if ( + String(row.statusDescription).toUpperCase().includes(value) + ) { return true; } return false; @@ -6841,8 +6843,7 @@ speechSynthesis.getVoices(); { prop: 'displayName', value: '', - filterFn: (row, filter) => - $app.feedSearch(row, filter) + filterFn: (row, filter) => $app.feedSearch(row, filter) }, { prop: 'userId', @@ -7204,10 +7205,12 @@ speechSynthesis.getVoices(); $app.data.discordActive = configRepository.getBool('discordActive'); $app.data.discordInstance = configRepository.getBool('discordInstance'); $app.data.discordJoinButton = configRepository.getBool('discordJoinButton'); + $app.data.discordHideInvite = configRepository.getBool('discordHideInvite'); $app.methods.saveDiscordOption = function () { configRepository.setBool('discordActive', this.discordActive); configRepository.setBool('discordInstance', this.discordInstance); configRepository.setBool('discordJoinButton', this.discordJoinButton); + configRepository.setBool('discordHideInvite', this.discordHideInvite); if (!this.discordActive) { Discord.SetText('', ''); Discord.SetActive(false); @@ -7217,7 +7220,7 @@ speechSynthesis.getVoices(); }; $app.methods.gameLogSearch = function (row, filter) { - var { value } = filter; + var {value} = filter; if (!value) { return true; } @@ -7284,8 +7287,7 @@ speechSynthesis.getVoices(); { prop: 'displayName', value: '', - filterFn: (row, filter) => - $app.gameLogSearch(row, filter) + filterFn: (row, filter) => $app.gameLogSearch(row, filter) }, { prop: 'displayName', @@ -7879,6 +7881,13 @@ speechSynthesis.getVoices(); } this.lastLocation$ = L; } + var hidePrivate = false; + if ( + (this.discordHideInvite && L.accessType === 'Invite') || + L.accessType === 'Invite+' + ) { + hidePrivate = true; + } switch (API.currentUser.status) { case 'active': L.statusName = 'Online'; @@ -7891,18 +7900,42 @@ speechSynthesis.getVoices(); case 'ask me': L.statusName = 'Ask Me'; L.statusImage = 'askme'; + hidePrivate = true; break; case 'busy': L.statusName = 'Do Not Disturb'; L.statusImage = 'busy'; + hidePrivate = true; break; } var appId = '883308884863901717'; var bigIcon = 'vrchat'; + var instanceId = L.instanceId; + var partySize = this.lastLocation.playerList.size; + var partyMaxSize = L.worldCapacity; + var buttonText = 'Join'; + var buttonUrl = L.joinUrl; + if (!this.discordJoinButton) { + buttonText = ''; + buttonUrl = ''; + } + if (!this.discordInstance) { + partySize = 0; + partyMaxSize = 0; + } + if (hidePrivate) { + instanceId = ''; + partySize = 0; + partyMaxSize = 0; + buttonText = ''; + buttonUrl = ''; + } if ( - L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1' || + (!hidePrivate && + L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1') || L.worldId === 'wrld_42377cf1-c54f-45ed-8996-5875b0573a83' ) { + // dance world rpc if (L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1') { appId = '784094509008551956'; bigIcon = 'pypy'; @@ -7925,11 +7958,11 @@ speechSynthesis.getVoices(); 'Powered by VRCX', // big icon hover text L.statusImage, // small icon L.statusName, // small icon hover text - L.instanceId, // party id - this.lastLocation.playerList.size, // party size - L.worldCapacity, // party max size - 'Join', // button text - L.joinUrl, // button url + instanceId, // party id + partySize, // party size + partyMaxSize, // party max size + buttonText, // button text + buttonUrl, // button url appId // app id ); // NOTE @@ -7937,8 +7970,8 @@ speechSynthesis.getVoices(); if (L.worldName.length < 2) { L.worldName += '\uFFA0'.repeat(2 - L.worldName.length); } - if (API.currentUser.status === 'busy') { - Discord.SetText('Do Not Disturb', ''); + if (hidePrivate) { + Discord.SetText('Private', ''); Discord.SetTimestamps(0, 0); } else if (this.discordInstance) { Discord.SetText(L.worldName, L.accessType); diff --git a/html/src/index.pug b/html/src/index.pug index d5b57ebc..3249d38b 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -780,11 +780,14 @@ html span.name Enable el-switch(v-model="discordActive" @change="saveDiscordOption") div.options-container-item - span.name Instance type + span.name Instance type/player count el-switch(v-model="discordInstance" @change="saveDiscordOption" :disabled="!discordActive") div.options-container-item span.name Join button (public only) el-switch(v-model="discordJoinButton" @change="saveDiscordOption" :disabled="!discordActive") + div.options-container-item + span.name Hide world details in private + el-switch(v-model="discordHideInvite" @change="saveDiscordOption" :disabled="!discordActive") div.options-container span.header SteamVR Overlay div.options-container-item diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 0ff1042a..6ed5a500 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -371,7 +371,7 @@ class Database { async getGamelogDatabase() { var gamelogDatabase = []; var date = new Date(); - date.setDate(date.getDate() - 3); // 3 day limit + date.setDate(date.getDate() - 7); // 7 day limit var dateOffset = date.toJSON(); await sqliteService.execute((dbRow) => { var row = { From ee38e4b7ffcb86b79bf534741e7fc8e9def243a4 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Tue, 21 Sep 2021 00:57:09 +1200 Subject: [PATCH 22/25] Notification table into SQLite --- html/src/app.js | 211 +++++++++++++++++++++----------- html/src/index.pug | 25 ++-- html/src/repository/database.js | 100 +++++++++++++++ 3 files changed, 256 insertions(+), 80 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index eb71e7ba..1b24ee59 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -476,6 +476,13 @@ speechSynthesis.getVoices(); if (status === 404 && endpoint.substring(0, 6) === 'users/') { throw new Error("404: Can't find user!"); } + if ( + status === 404 && + endpoint.substring(0, 7) === 'invite/' && + init.inviteId + ) { + this.expireNotification(init.inviteId); + } if (data.error === Object(data.error)) { this.$throw( data.error.status_code || status, @@ -2218,11 +2225,9 @@ speechSynthesis.getVoices(); // API: Notification - API.cachedNotifications = new Map(); API.isNotificationsLoading = false; API.$on('LOGIN', function () { - this.cachedNotifications.clear(); this.isNotificationsLoading = false; }); @@ -2254,13 +2259,19 @@ speechSynthesis.getVoices(); }); API.$on('NOTIFICATION:ACCEPT', function (args) { - var ref = this.cachedNotifications.get(args.params.notificationId); - if (typeof ref === 'undefined' || ref.$isDeleted) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === args.params.notificationId) { + var ref = array[i]; + break; + } + } + if (typeof ref === 'undefined') { return; } + ref.$isExpired = true; args.ref = ref; - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { + this.$emit('NOTIFICATION:EXPIRE', { ref, params: { notificationId: ref.id @@ -2274,13 +2285,32 @@ speechSynthesis.getVoices(); }); API.$on('NOTIFICATION:HIDE', function (args) { - var ref = this.cachedNotifications.get(args.params.notificationId); - if (typeof ref === 'undefined' && ref.$isDeleted) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === args.params.notificationId) { + var ref = array[i]; + break; + } + } + if (typeof ref === 'undefined') { return; } args.ref = ref; - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { + if ( + ref.type === 'friendRequest' || + ref.type === 'hiddenFriendRequest' + ) { + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === ref.id) { + array.splice(i, 1); + break; + } + } + } else { + ref.$isExpired = true; + database.updateNotificationExpired(ref); + } + this.$emit('NOTIFICATION:EXPIRE', { ref, params: { notificationId: ref.id @@ -2289,7 +2319,13 @@ speechSynthesis.getVoices(); }); API.applyNotification = function (json) { - var ref = this.cachedNotifications.get(json.id); + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === json.id) { + var ref = array[i]; + break; + } + } if (typeof ref === 'undefined') { ref = { id: '', @@ -2301,12 +2337,10 @@ speechSynthesis.getVoices(); seen: false, created_at: '', // VRCX - $isDeleted: false, $isExpired: false, // ...json }; - this.cachedNotifications.set(ref.id, ref); } else { Object.assign(ref, json); ref.$isExpired = false; @@ -2326,36 +2360,42 @@ speechSynthesis.getVoices(); return ref; }; - API.expireNotifications = function () { - for (var ref of this.cachedNotifications.values()) { - ref.$isExpired = true; + API.expireFriendRequestNotifications = function () { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { if ( - ref.type === 'friendRequest' || - ref.type === 'hiddenFriendRequest' + array[i].type === 'friendRequest' || + array[i].type === 'hiddenFriendRequest' ) { - this.cachedNotifications.delete(ref.id); + array.splice(i, 1); } } }; - API.deleteExpiredNotifcations = function () { - for (var ref of this.cachedNotifications.values()) { - if (ref.$isDeleted || ref.$isExpired === false) { - continue; + API.expireNotification = function (notificationId) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === notificationId) { + var ref = array[i]; + break; } - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { - ref, - params: { - notificationId: ref.id - } - }); } + if (typeof ref === 'undefined') { + return; + } + ref.$isExpired = true; + database.updateNotificationExpired(ref); + this.$emit('NOTIFICATION:EXPIRE', { + ref, + params: { + notificationId: ref.id + } + }); }; API.refreshNotifications = async function () { this.isNotificationsLoading = true; - this.expireNotifications(); + this.expireFriendRequestNotifications(); var params = { n: 100, offset: 0 @@ -2382,7 +2422,6 @@ speechSynthesis.getVoices(); break; } } - this.deleteExpiredNotifcations(); this.isNotificationsLoading = false; }; @@ -2512,31 +2551,33 @@ speechSynthesis.getVoices(); }); }; - API.sendInviteResponse = function (params, inviteID) { - return this.call(`invite/${inviteID}/response`, { + API.sendInviteResponse = function (params, inviteId) { + return this.call(`invite/${inviteId}/response`, { method: 'POST', - params + params, + inviteId }).then((json) => { var args = { json, params, - inviteID + inviteId }; this.$emit('INVITE:RESPONSE:SEND', args); return args; }); }; - API.sendInviteResponsePhoto = function (params, inviteID) { - return this.call(`invite/${inviteID}/response/photo`, { + API.sendInviteResponsePhoto = function (params, inviteId) { + return this.call(`invite/${inviteId}/response/photo`, { uploadImage: true, postData: JSON.stringify(params), - imageData: $app.uploadImage + imageData: $app.uploadImage, + inviteId }).then((json) => { var args = { json, params, - inviteID + inviteId }; this.$emit('INVITE:RESPONSE:PHOTO:SEND', args); return args; @@ -2586,13 +2627,13 @@ speechSynthesis.getVoices(); }; API.getFriendRequest = function (userId) { - for (var ref of this.cachedNotifications.values()) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { if ( - ref.$isDeleted === false && - ref.type === 'friendRequest' && - ref.senderUserId === userId + array[i].type === 'friendRequest' && + array[i].senderUserId === userId ) { - return ref.id; + return array[i].id; } } return ''; @@ -3506,6 +3547,11 @@ speechSynthesis.getVoices(); break; case 'hide-notification': + this.$emit('NOTIFICATION:HIDE', { + params: { + notificationId: content + } + }); this.$emit('NOTIFICATION:SEE', { params: { notificationId: content @@ -4508,9 +4554,6 @@ speechSynthesis.getVoices(); }; $app.methods.queueNotificationNoty = function (noty) { - if (noty.senderUserId === API.currentUser.id) { - return; - } noty.isFriend = this.friends.has(noty.senderUserId); noty.isFavorite = API.cachedFavoritesByObjectId.has(noty.senderUserId); var notyFilter = this.sharedFeedFilters.noty; @@ -6875,6 +6918,8 @@ speechSynthesis.getVoices(); await database.initUserTables(args.json.id); $app.feedTable.data = await database.getFeedDatabase(); $app.sweepFeed(); + // eslint-disable-next-line require-atomic-updates + $app.notificationTable.data = await database.getNotifications(); if (configRepository.getBool(`friendLogInit_${args.json.id}`)) { await $app.getFriendLog(); } else { @@ -8615,6 +8660,20 @@ speechSynthesis.getVoices(); this.friendLog.set(id, friendLogCurrent); database.setFriendLogCurrent(friendLogCurrent); this.notifyMenu('friendLog'); + this.deleteFriendRequest(id); + } + }; + + $app.methods.deleteFriendRequest = function (userId) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if ( + array[i].type === 'friendRequest' && + array[i].senderUserId === userId + ) { + array.splice(i, 1); + return; + } } }; @@ -8859,22 +8918,22 @@ speechSynthesis.getVoices(); var {length} = array; for (var i = 0; i < length; ++i) { if (array[i].id === ref.id) { - if (ref.$isDeleted) { - array.splice(i, 1); - } else { - Vue.set(array, i, ref); - } + Vue.set(array, i, ref); return; } } - if (ref.$isDeleted === false) { - $app.notificationTable.data.push(ref); - if (ref.senderUserId !== this.currentUser.id) { - $app.notifyMenu('notification'); - $app.unseenNotifications.push(ref.id); + if (ref.senderUserId !== this.currentUser.id) { + if ( + ref.type !== 'friendRequest' && + ref.type !== 'hiddenFriendRequest' + ) { + database.addNotificationToDatabase(ref); } + $app.notifyMenu('notification'); + $app.unseenNotifications.push(ref.id); $app.queueNotificationNoty(ref); } + $app.notificationTable.data.push(ref); $app.updateSharedFeed(true); }); @@ -8886,18 +8945,6 @@ speechSynthesis.getVoices(); } }); - API.$on('NOTIFICATION:@DELETE', function (args) { - var {ref} = args; - var array = $app.notificationTable.data; - var {length} = array; - for (var i = 0; i < length; ++i) { - if (array[i].id === ref.id) { - array.splice(i, 1); - return; - } - } - }); - $app.methods.acceptNotification = function (row) { // FIXME: 메시지 수정 this.$confirm('Continue? Accept Friend Request', 'Confirm', { @@ -8915,8 +8962,7 @@ speechSynthesis.getVoices(); }; $app.methods.hideNotification = function (row) { - // FIXME: 메시지 수정 - this.$confirm('Continue? Delete Notification', 'Confirm', { + this.$confirm(`Continue? Decline ${row.type}`, 'Confirm', { confirmButtonText: 'Confirm', cancelButtonText: 'Cancel', type: 'info', @@ -8939,6 +8985,25 @@ speechSynthesis.getVoices(); }); }; + $app.methods.deleteNotificationLog = function (row) { + this.$confirm(`Continue? Delete ${row.type}`, 'Confirm', { + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', + type: 'info', + callback: (action) => { + if (action === 'confirm') { + removeFromArray(this.notificationTable.data, row); + if ( + row.type !== 'friendRequest' && + row.type !== 'hiddenFriendRequest' + ) { + database.deleteNotification(row.id); + } + } + } + }); + }; + $app.methods.acceptRequestInvite = function (row) { this.$confirm('Continue? Send Invite', 'Confirm', { confirmButtonText: 'Confirm', @@ -10146,7 +10211,7 @@ speechSynthesis.getVoices(); D.isFriend = true; }); - API.$on('NOTIFICATION:@DELETE', function (args) { + API.$on('NOTIFICATION:EXPIRE', function (args) { var {ref} = args; var D = $app.userDialog; if ( diff --git a/html/src/index.pug b/html/src/index.pug index 3249d38b..af091f23 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -470,15 +470,26 @@ html span(v-else-if='scope.row.details && scope.row.details.inviteMessage' v-text="scope.row.details.inviteMessage") span(v-else-if='scope.row.details && scope.row.details.requestMessage' v-text="scope.row.details.requestMessage") span(v-else-if='scope.row.details && scope.row.details.responseMessage' v-text="scope.row.details.responseMessage") - el-table-column(label="Action" width="80" align="right") + el-table-column(label="Action" width="100" align="right") template(v-once #default="scope") - template(v-if="scope.row.senderUserId !== API.currentUser.id") - el-button(v-if="scope.row.type === 'friendRequest'" type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)") - el-button(v-else-if="scope.row.type === 'invite'" type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)") + template(v-if="scope.row.senderUserId !== API.currentUser.id && !scope.row.$isExpired") + template(v-if="scope.row.type === 'friendRequest'") + el-tooltip(placement="top" content="Accept" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)") + template(v-else-if="scope.row.type === 'invite'") + el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)") template(v-else-if="scope.row.type === 'requestInvite'") - el-button(v-if="lastLocation.location && isGameRunning && !checkCanInvite(lastLocation.location)" type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)") - el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)") - el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)") + template(v-if="lastLocation.location && isGameRunning && !checkCanInvite(lastLocation.location)") + el-tooltip(placement="top" content="Invite" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)") + el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)") + template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message'") + el-tooltip(placement="top" content="Decline" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)") + el-tooltip(placement="top" content="Delete log" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-delete" size="mini" style="margin-left:5px" @click="deleteNotificationLog(scope.row)") //- profile .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'profile'") diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 6ed5a500..b145567e 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -21,6 +21,9 @@ class Database { await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS ${Database.userId}_friend_log_history (id INTEGER PRIMARY KEY, created_at TEXT, type TEXT, user_id TEXT, display_name TEXT, previous_display_name TEXT, trust_level TEXT, previous_trust_level TEXT)` ); + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS ${Database.userId}_notifications (id TEXT PRIMARY KEY, created_at TEXT, type TEXT, sender_user_id TEXT, sender_username TEXT, receiver_user_id TEXT, message TEXT, world_id TEXT, world_name TEXT, image_url TEXT, invite_message TEXT, request_message TEXT, response_message TEXT, expired INTEGER)` + ); await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS memos (user_id TEXT PRIMARY KEY, edited_at TEXT, memo TEXT)` ); @@ -523,6 +526,103 @@ class Database { } ); } + + async getNotifications() { + var notifications = []; + await sqliteService.execute((dbRow) => { + var row = { + id: dbRow[0], + created_at: dbRow[1], + type: dbRow[2], + senderUserId: dbRow[3], + senderUsername: dbRow[4], + receiverUserId: dbRow[5], + message: dbRow[6], + details: { + worldId: dbRow[7], + worldName: dbRow[8], + imageUrl: dbRow[9], + inviteMessage: dbRow[10], + requestMessage: dbRow[11], + responseMessage: dbRow[12] + } + }; + row.$isExpired = false; + if (dbRow[13] === 1) { + row.$isExpired = true; + } + notifications.unshift(row); + }, `SELECT * FROM ${Database.userId}_notifications LIMIT 5000`); + return notifications; + } + + addNotificationToDatabase(row) { + var entry = { + id: '', + created_at: '', + type: '', + senderUserId: '', + senderUsername: '', + receiverUserId: '', + message: '', + ...row, + details: { + worldId: '', + worldName: '', + imageUrl: '', + inviteMessage: '', + requestMessage: '', + responseMessage: '', + ...row.details + } + }; + var expired = 0; + if (row.$isExpired) { + expired = 1; + } + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO ${Database.userId}_notifications (id, created_at, type, sender_user_id, sender_username, receiver_user_id, message, world_id, world_name, image_url, invite_message, request_message, response_message, expired) VALUES (@id, @created_at, @type, @sender_user_id, @sender_username, @receiver_user_id, @message, @world_id, @world_name, @image_url, @invite_message, @request_message, @response_message, @expired)`, + { + '@id': entry.id, + '@created_at': entry.created_at, + '@type': entry.type, + '@sender_user_id': entry.senderUserId, + '@sender_username': entry.senderUsername, + '@receiver_user_id': entry.receiverUserId, + '@message': entry.message, + '@world_id': entry.details.worldId, + '@world_name': entry.details.worldName, + '@image_url': entry.details.imageUrl, + '@invite_message': entry.details.inviteMessage, + '@request_message': entry.details.requestMessage, + '@response_message': entry.details.responseMessage, + '@expired': expired + } + ); + } + + deleteNotification(rowId) { + sqliteService.executeNonQuery( + `DELETE FROM ${Database.userId}_notifications WHERE id = @row_id`, + { + '@row_id': rowId + } + ); + } + + updateNotificationExpired(entry) { + var expired = 0; + if (entry.$isExpired) { + expired = 1; + } + sqliteService.executeNonQuery( + `UPDATE ${Database.userId}_notifications SET expired = @expired WHERE id = @id`, + { + '@id': entry.id, + '@expired': expired + } + ); + } } var self = new Database(); From 3a9ac4b4aa127b349c13584a6db998bb8ec69ae5 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Wed, 22 Sep 2021 01:25:58 +1200 Subject: [PATCH 23/25] Stop auto cache when joining world --- html/src/app.js | 9 ++++++--- html/src/index.pug | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index 1b24ee59..68c0fc0a 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -7440,7 +7440,8 @@ speechSynthesis.getVoices(); this.addGameLogEntry(gameLog, this.lastLocation.location, pushToTable); }; - $app.lastLocationDestinationTime = 0; + $app.data.lastLocationDestination = ''; + $app.data.lastLocationDestinationTime = 0; $app.methods.addGameLogEntry = function (gameLog, location, pushToTable) { var userId = ''; @@ -7458,6 +7459,7 @@ speechSynthesis.getVoices(); this.cancelVRChatCacheDownload(gameLog.location); this.clearNowPlaying(); } + this.lastLocationDestination = gameLog.location; this.lastLocationDestinationTime = Date.parse(gameLog.dt); var entry = { created_at: gameLog.dt, @@ -14610,7 +14612,7 @@ speechSynthesis.getVoices(); return; } if ( - this.downloadCurrent.type !== 'Auto' || + this.downloadCurrent.type !== 'Auto' && !this.cacheAutoDownloadHistory.has(assetUrl) ) { this.cacheAutoDownloadHistory.add(assetUrl); @@ -14740,7 +14742,8 @@ speechSynthesis.getVoices(); if ( !L.worldId || this.downloadQueue.has(L.worldId) || - this.downloadCurrent.id === L.worldId + this.downloadCurrent.id === L.worldId || + this.lastLocationDestination === location ) { return; } diff --git a/html/src/index.pug b/html/src/index.pug index af091f23..462b4ec0 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -186,7 +186,7 @@ html el-select(v-model="gameLogTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" placeholder="Filter") el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'AvatarChange', 'Event', 'VideoPlay']" :key="type" :label="type" :value="type") el-input(v-model="gameLogTable.filters[1].value" placeholder="Search" style="flex:none;width:150px;margin:0 10px") - el-tooltip(placement="bottom" content="Reset game log" :disabled="hideTooltips") + el-tooltip(placement="bottom" content="Reload game log" :disabled="hideTooltips") el-button(type="default" @click="refreshEntireGameLog" icon="el-icon-refresh" circle style="flex:none") el-table-column(label="Date" prop="created_at" sortable="custom" width="90") template(v-once #default="scope") From 697f30e3439285eb2d7ab08f8d41617f07c10760 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Fri, 24 Sep 2021 23:26:32 +1200 Subject: [PATCH 24/25] Portal Spawn info --- html/src/index.pug | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/html/src/index.pug b/html/src/index.pug index 462b4ec0..13c3aae3 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -1706,7 +1706,7 @@ html //- dialog: YouTube Api Dialog el-dialog.x-dialog(ref="youTubeApiDialog" :visible.sync="youTubeApiDialog.visible" title="YouTube API" width="400px") div(style='font-size:12px;') - | Enter your YouTube API Key #[br] + | Enter your YouTube API Key (optional) #[br] el-input(type="textarea" v-model="youTubeApiKey" placeholder="YouTube API Key" maxlength="39" show-word-limit style="dispaly:block;margin-top:10px") template(#footer) div(style="display:flex") @@ -1817,7 +1817,7 @@ html el-button(type="primary" size="small" style="margin-left:auto" @click="notificationPositionDialog.visible = false") OK //- dialog: Noty feed filters - el-dialog.x-dialog(ref="notyFeedFiltersDialog" :visible.sync="notyFeedFiltersDialog.visible" title="Notification Filters" width="480px") + el-dialog.x-dialog(ref="notyFeedFiltersDialog" :visible.sync="notyFeedFiltersDialog.visible" title="Notification Filters" width="500px") .toggle-list .toggle-item span.toggle-name OnPlayerJoining @@ -1916,18 +1916,20 @@ html el-radio-button(label="Friends") .toggle-item span.toggle-name Portal Spawn + el-tooltip(placement="top" style="margin-left:5px" content="Requires '--enable-sdk-log-levels' steam launch option") + i.el-icon-warning el-radio-group(v-model="sharedFeedFilters.noty.PortalSpawn" size="mini") el-radio-button(label="Off") el-radio-button(label="VIP") el-radio-button(label="Friends") el-radio-button(label="Everyone") - .toggle-item - span.toggle-name Avatar Change - el-radio-group(v-model="sharedFeedFilters.noty.AvatarChange" size="mini") - el-radio-button(label="Off") - el-radio-button(label="VIP") - el-radio-button(label="Friends") - el-radio-button(label="Everyone") + //- .toggle-item + //- span.toggle-name Avatar Change + //- el-radio-group(v-model="sharedFeedFilters.noty.AvatarChange" size="mini") + //- el-radio-button(label="Off") + //- el-radio-button(label="VIP") + //- el-radio-button(label="Friends") + //- el-radio-button(label="Everyone") .toggle-item span.toggle-name Video Play el-radio-group(v-model="sharedFeedFilters.noty.VideoPlay" size="mini") @@ -1971,7 +1973,7 @@ html el-button(type="primary" size="small" style="margin-left:10px" @click="saveSharedFeedFilters") Save //- dialog: wrist feed filters - el-dialog.x-dialog(ref="wristFeedFiltersDialog" :visible.sync="wristFeedFiltersDialog.visible" title="Wrist Feed Filters" width="480px") + el-dialog.x-dialog(ref="wristFeedFiltersDialog" :visible.sync="wristFeedFiltersDialog.visible" title="Wrist Feed Filters" width="500px") .toggle-list .toggle-item span.toggle-name Self Location @@ -2075,18 +2077,20 @@ html el-radio-button(label="Friends") .toggle-item span.toggle-name Portal Spawn + el-tooltip(placement="top" style="margin-left:5px" content="Requires '--enable-sdk-log-levels' steam launch option") + i.el-icon-warning el-radio-group(v-model="sharedFeedFilters.wrist.PortalSpawn" size="mini") el-radio-button(label="Off") el-radio-button(label="VIP") el-radio-button(label="Friends") el-radio-button(label="Everyone") - .toggle-item - span.toggle-name Avatar Change - el-radio-group(v-model="sharedFeedFilters.wrist.AvatarChange" size="mini") - el-radio-button(label="Off") - el-radio-button(label="VIP") - el-radio-button(label="Friends") - el-radio-button(label="Everyone") + //- .toggle-item + //- span.toggle-name Avatar Change + //- el-radio-group(v-model="sharedFeedFilters.wrist.AvatarChange" size="mini") + //- el-radio-button(label="Off") + //- el-radio-button(label="VIP") + //- el-radio-button(label="Friends") + //- el-radio-button(label="Everyone") .toggle-item span.toggle-name Video Play el-radio-group(v-model="sharedFeedFilters.wrist.VideoPlay" size="mini") From 700fd8b89ba8e6e08614fbe3decff905a5606cec Mon Sep 17 00:00:00 2001 From: Natsumi Date: Sun, 26 Sep 2021 09:56:33 +1200 Subject: [PATCH 25/25] Copy user URL --- html/src/app.js | 10 ++++++++++ html/src/index.pug | 1 + 2 files changed, 11 insertions(+) diff --git a/html/src/app.js b/html/src/app.js index 68c0fc0a..d2a6bfdc 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -10834,6 +10834,8 @@ speechSynthesis.getVoices(); this.displayPreviousImages('User', 'Display'); } else if (command === 'Manage Gallery') { this.showGalleryDialog(); + } else if (command === 'Copy User') { + this.copyUser(D.id); } else { this.$confirm(`Continue? ${command}`, 'Confirm', { confirmButtonText: 'Confirm', @@ -12217,6 +12219,14 @@ speechSynthesis.getVoices(); this.copyToClipboard(`https://vrchat.com/home/world/${worldId}`); }; + $app.methods.copyUser = function (userId) { + this.$message({ + message: 'User URL copied to clipboard', + type: 'success' + }); + this.copyToClipboard(`https://vrchat.com/home/user/${userId}`); + }; + // App: VRCPlus Icons API.$on('LOGIN', function () { diff --git a/html/src/index.pug b/html/src/index.pug index 13c3aae3..8db7399b 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -1091,6 +1091,7 @@ html el-button(:type="(userDialog.incomingRequest || userDialog.outgoingRequest) ? 'success' : (userDialog.isBlock || userDialog.isMute || userDialog.isHideAvatar) ? 'danger' : 'default'" icon="el-icon-more" circle style="margin-left:5px") el-dropdown-menu(#default="dropdown") el-dropdown-item(icon="el-icon-refresh" command="Refresh") Refresh + el-dropdown-item(icon="el-icon-s-order" command="Copy User") Copy User URL template(v-if="userDialog.ref.id === API.currentUser.id") el-dropdown-item(icon="el-icon-picture-outline" command="Manage Gallery" divided) Manage Gallery/Icons el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author") Show Avatar Author