Merge branch 'GameLogSQLite'

This commit is contained in:
Natsumi
2021-09-26 22:32:34 +13:00
12 changed files with 2594 additions and 1752 deletions
+29 -13
View File
@@ -229,20 +229,15 @@ namespace VRCX
return CpuMonitor.Instance.CpuUsage; return CpuMonitor.Instance.CpuUsage;
} }
public void CacheImage(string Base64File) public void DesktopNotification(string BoldText, string Text, string Image)
{
String Icon = Path.Combine(Program.AppDataDirectory, "cache\\toast");
File.WriteAllBytes(Icon, Convert.FromBase64String(Base64File));
}
public void DesktopNotification(string BoldText, string Text, bool Image)
{ {
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02); XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02);
XmlNodeList stringElements = toastXml.GetElementsByTagName("text"); XmlNodeList stringElements = toastXml.GetElementsByTagName("text");
String imagePath = Path.Combine(Program.BaseDirectory, "VRCX.ico"); String imagePath = Path.Combine(Program.BaseDirectory, "VRCX.ico");
if (Image) if (!String.IsNullOrEmpty(Image))
{ {
imagePath = Path.Combine(Program.AppDataDirectory, "cache\\toast"); imagePath = Path.Combine(Program.AppDataDirectory, "cache\\toast");
File.WriteAllBytes(imagePath, Convert.FromBase64String(Image));
} }
stringElements[0].AppendChild(toastXml.CreateTextNode(BoldText)); stringElements[0].AppendChild(toastXml.CreateTextNode(BoldText));
stringElements[1].AppendChild(toastXml.CreateTextNode(Text)); stringElements[1].AppendChild(toastXml.CreateTextNode(Text));
@@ -268,14 +263,20 @@ namespace VRCX
public string sourceApp { get; set; } 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; bool UseBase64Icon;
String Icon = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHaGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1NTE2MWIyMi1hYzgxLTY3NDYtODAyYi1kODIzYWFmN2RjYjciIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3ZjJjNTA2ZS02YTVhLWRhNGEtOTg5Mi02NDZiMzQ0MGQxZTgiPiA8cGhvdG9zaG9wOkRvY3VtZW50QW5jZXN0b3JzPiA8cmRmOkJhZz4gPHJkZjpsaT5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NmJmOGE5MTgtY2QzZS03OTRjLTk3NzktMzM0YjYwZWJiNTYyPC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6N2YyYzUwNmUtNmE1YS1kYTRhLTk4OTItNjQ2YjM0NDBkMWU4IiBzdEV2dDp3aGVuPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhM2ZjODI3LTM0ZjQtYjU0OC05ZGFiLTZhMTZlZmQzZjAxMSIgc3RFdnQ6d2hlbj0iMjAyMS0wNC0wOFQxNTowMTozMSsxMjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHN0RXZ0OndoZW49IjIwMjEtMDQtMDhUMTY6MzM6MTArMTI6MDAiIHN0RXZ0OnNv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"; string Icon;
if (Image) if (String.IsNullOrEmpty(Image))
{
UseBase64Icon = true;
Icon = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHaGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1NTE2MWIyMi1hYzgxLTY3NDYtODAyYi1kODIzYWFmN2RjYjciIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3ZjJjNTA2ZS02YTVhLWRhNGEtOTg5Mi02NDZiMzQ0MGQxZTgiPiA8cGhvdG9zaG9wOkRvY3VtZW50QW5jZXN0b3JzPiA8cmRmOkJhZz4gPHJkZjpsaT5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NmJmOGE5MTgtY2QzZS03OTRjLTk3NzktMzM0YjYwZWJiNTYyPC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6N2YyYzUwNmUtNmE1YS1kYTRhLTk4OTItNjQ2YjM0NDBkMWU4IiBzdEV2dDp3aGVuPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhM2ZjODI3LTM0ZjQtYjU0OC05ZGFiLTZhMTZlZmQzZjAxMSIgc3RFdnQ6d2hlbj0iMjAyMS0wNC0wOFQxNTowMTozMSsxMjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHN0RXZ0OndoZW49IjIwMjEtMDQtMDhUMTY6MzM6MTArMTI6MDAiIHN0RXZ0OnNv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";
}
else
{ {
UseBase64Icon = false; UseBase64Icon = false;
Icon = Path.Combine(Program.AppDataDirectory, "cache\\toast"); Icon = Path.Combine(Program.AppDataDirectory, "cache\\toast");
File.WriteAllBytes(Icon, Convert.FromBase64String(Image));
} }
IPAddress broadcastIP = IPAddress.Parse("127.0.0.1"); IPAddress broadcastIP = IPAddress.Parse("127.0.0.1");
@@ -314,13 +315,28 @@ namespace VRCX
System.Environment.Exit(0); System.Environment.Exit(0);
} }
public bool checkForUpdateZip() public bool CheckForUpdateZip()
{ {
if (File.Exists(Path.Combine(Program.AppDataDirectory, "update.exe"))) if (File.Exists(Path.Combine(Program.AppDataDirectory, "update.exe")))
return true; return true;
return false; 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) public void SetStartup(bool enabled)
{ {
try try
+28 -5
View File
@@ -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. // This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>. // For a copy, see <https://opensource.org/licenses/MIT>.
@@ -17,6 +17,7 @@ namespace VRCX
private DiscordRpcClient m_Client; private DiscordRpcClient m_Client;
private Timer m_Timer; private Timer m_Timer;
private bool m_Active; private bool m_Active;
public static string DiscordAppId;
static Discord() static Discord()
{ {
@@ -78,7 +79,7 @@ namespace VRCX
{ {
if (m_Client == null) if (m_Client == null)
{ {
m_Client = new DiscordRpcClient("525953831020920832"); m_Client = new DiscordRpcClient(DiscordAppId);
if (m_Client.Initialize() == false) if (m_Client.Initialize() == false)
{ {
m_Client.Dispose(); 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(); m_Lock.EnterWriteLock();
try try
@@ -139,13 +140,35 @@ namespace VRCX
else else
{ {
if (m_Presence.Assets == null) if (m_Presence.Assets == null)
{
m_Presence.Assets = new Assets(); m_Presence.Assets = new Assets();
} if (m_Presence.Party == null)
m_Presence.Party = new Party();
m_Presence.Assets.LargeImageKey = largeKey; m_Presence.Assets.LargeImageKey = largeKey;
m_Presence.Assets.LargeImageText = largeText; m_Presence.Assets.LargeImageText = largeText;
m_Presence.Assets.SmallImageKey = smallKey; m_Presence.Assets.SmallImageKey = smallKey;
m_Presence.Assets.SmallImageText = smallText; 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 finally
+92 -21
View File
@@ -3,6 +3,7 @@
// This work is licensed under the terms of the MIT license. // This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>. // For a copy, see <https://opensource.org/licenses/MIT>.
using CefSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@@ -30,6 +31,8 @@ namespace VRCX
private readonly List<string[]> m_LogList; private readonly List<string[]> m_LogList;
private Thread m_Thread; private Thread m_Thread;
private bool m_ResetLog; private bool m_ResetLog;
private bool m_FirstRun = true;
private static DateTime tillDate = DateTime.Now;
// NOTE // NOTE
// FileSystemWatcher() is unreliable // FileSystemWatcher() is unreliable
@@ -65,6 +68,17 @@ namespace VRCX
thread.Join(); thread.Join();
} }
public void Reset()
{
m_ResetLog = true;
m_Thread?.Interrupt();
}
public void SetDateTill(string date)
{
tillDate = DateTime.Parse(date);
}
private void ThreadLoop() private void ThreadLoop()
{ {
while (m_Thread != null) while (m_Thread != null)
@@ -86,6 +100,7 @@ namespace VRCX
{ {
if (m_ResetLog == true) if (m_ResetLog == true)
{ {
m_FirstRun = true;
m_ResetLog = false; m_ResetLog = false;
m_LogContextMap.Clear(); m_LogContextMap.Clear();
m_LogListLock.EnterWriteLock(); m_LogListLock.EnterWriteLock();
@@ -109,26 +124,17 @@ namespace VRCX
// sort by creation time // sort by creation time
Array.Sort(fileInfos, (a, b) => a.CreationTimeUtc.CompareTo(b.CreationTimeUtc)); 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) foreach (var fileInfo in fileInfos)
{
var lastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
if (lastWriteTimeUtc < minLimitDateTime)
{
continue;
}
if (lastWriteTimeUtc >= minRefreshDateTime)
{ {
fileInfo.Refresh(); fileInfo.Refresh();
if (fileInfo.Exists == false) if (fileInfo.Exists == false)
{ {
continue; continue;
} }
if (DateTime.Compare(fileInfo.LastWriteTime, tillDate) < 0)
{
continue;
} }
if (m_LogContextMap.TryGetValue(fileInfo.Name, out LogContext logContext) == true) if (m_LogContextMap.TryGetValue(fileInfo.Name, out LogContext logContext) == true)
@@ -155,6 +161,8 @@ namespace VRCX
{ {
m_LogContextMap.Remove(name); m_LogContextMap.Remove(name);
} }
m_FirstRun = false;
} }
private void ParseLog(FileInfo fileInfo, LogContext logContext) private void ParseLog(FileInfo fileInfo, LogContext logContext)
@@ -184,17 +192,33 @@ namespace VRCX
continue; 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; var offset = 34;
if (line[offset] == '[') if (line[offset] == '[')
{ {
if (ParseLogOnPlayerJoinedOrLeft(fileInfo, logContext, line, offset) == true || if (ParseLogOnPlayerJoinedOrLeft(fileInfo, logContext, line, offset) == true ||
ParseLogLocation(fileInfo, logContext, line, offset) == true || ParseLogLocation(fileInfo, logContext, line, offset) == true ||
ParseLogLocationDestination(fileInfo, logContext, line, offset) == true ||
ParseLogPortalSpawn(fileInfo, logContext, line, offset) == true || ParseLogPortalSpawn(fileInfo, logContext, line, offset) == true ||
ParseLogNotification(fileInfo, logContext, line, offset) == true || ParseLogNotification(fileInfo, logContext, line, offset) == true ||
ParseLogJoinBlocked(fileInfo, logContext, line, offset) == true || ParseLogJoinBlocked(fileInfo, logContext, line, offset) == true ||
ParseLogAvatarPedestalChange(fileInfo, logContext, line, offset) == true || ParseLogAvatarPedestalChange(fileInfo, logContext, line, offset) == true ||
ParseLogVideoError(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; continue;
} }
@@ -221,6 +245,12 @@ namespace VRCX
m_LogListLock.EnterWriteLock(); m_LogListLock.EnterWriteLock();
try 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); m_LogList.Add(item);
} }
finally finally
@@ -244,7 +274,7 @@ namespace VRCX
dt = DateTime.UtcNow; 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) private bool ParseLogLocation(FileInfo fileInfo, LogContext logContext, string line, int offset)
@@ -284,6 +314,29 @@ namespace VRCX
return false; 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) private bool ParseLogOnPlayerJoinedOrLeft(FileInfo fileInfo, LogContext logContext, string line, int offset)
{ {
// 2020.10.31 23:36:58 Log - [NetworkManager] OnPlayerJoined pypy // 2020.10.31 23:36:58 Log - [NetworkManager] OnPlayerJoined pypy
@@ -515,6 +568,28 @@ namespace VRCX
return true; 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) 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 // 2021.04.23 13:12:25 Log - User Natsumi-sama added URL https://www.youtube.com/watch?v=dQw4w9WgXcQ
@@ -579,14 +654,10 @@ namespace VRCX
return true; return true;
} }
public void Reset()
{
m_ResetLog = true;
m_Thread?.Interrupt();
}
public string[][] Get() public string[][] Get()
{ {
Update();
if (m_ResetLog == false && if (m_ResetLog == false &&
m_LogList.Count > 0) m_LogList.Count > 0)
{ {
+2 -2
View File
@@ -34,8 +34,8 @@ namespace VRCX
private Device _device; private Device _device;
private Texture2D _texture1; private Texture2D _texture1;
private Texture2D _texture2; private Texture2D _texture2;
private OffScreenBrowser _browser1; public static OffScreenBrowser _browser1;
private OffScreenBrowser _browser2; public static OffScreenBrowser _browser2;
private bool _active; private bool _active;
static VRCXVR() static VRCXVR()
+4
View File
@@ -147,6 +147,10 @@ namespace VRCX
{ {
request.UserAgent = value; request.UserAgent = value;
} }
else if (string.Compare(key, "Referer", StringComparison.OrdinalIgnoreCase) == 0)
{
request.Referer = value;
}
else else
{ {
request.Headers.Add(key, value); request.Headers.Add(key, value);
+1581 -682
View File
File diff suppressed because it is too large Load Diff
+121 -54
View File
@@ -173,6 +173,7 @@ html
span(v-else-if="scope.row.status === 'busy'") Do Not Disturb span(v-else-if="scope.row.status === 'busy'") Do Not Disturb
span(v-else) Offline span(v-else) Offline
i.x-user-status(:class="statusClass(scope.row.status)") i.x-user-status(:class="statusClass(scope.row.status)")
span
span(v-text="scope.row.statusDescription") span(v-text="scope.row.statusDescription")
template(v-else-if="scope.row.type === 'Avatar'") 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") avatar-info(:imageurl="scope.row.currentAvatarImageUrl" :userid="scope.row.userId" :hintownerid="scope.row.ownerId" :hintavatarname="scope.row.avatarName")
@@ -183,10 +184,10 @@ html
template(#tool) template(#tool)
div(style="margin:0 0 10px;display:flex;align-items:center") 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-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-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="resetGameLog()" 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") el-table-column(label="Date" prop="created_at" sortable="custom" width="90")
template(v-once #default="scope") template(v-once #default="scope")
el-tooltip(placement="right") el-tooltip(placement="right")
@@ -194,17 +195,28 @@ html
span {{ scope.row.created_at | formatDate('YYYY-MM-DD HH24:MI:SS') }} span {{ scope.row.created_at | formatDate('YYYY-MM-DD HH24:MI:SS') }}
span {{ scope.row.created_at | formatDate('MM-DD HH24:MI') }} span {{ scope.row.created_at | formatDate('MM-DD HH24:MI') }}
el-table-column(label="Type" prop="type" width="120") 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") el-table-column(label="Detail" prop="data")
template(v-once #default="scope") 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'") template(v-else-if="scope.row.type === 'Event'")
span(v-text="scope.row.data") span(v-text="scope.row.data")
template(v-else-if="scope.row.type === 'VideoPlay'") template(v-else-if="scope.row.type === 'VideoPlay'")
span.x-link(v-text="scope.row.data" @click="openExternalLink(scope.row.data)") span(v-if="scope.row.videoId") {{ scope.row.videoId }}:
template(v-if="scope.row.displayName") span.x-link(v-if="scope.row.videoName" @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoName")
span.x-link(@click="lookupUser(scope.row.displayName)") ({{ scope.row.displayName }}) span.x-link(v-else @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoUrl")
template(v-else-if="scope.row.type === 'Notification'") 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" @click="lookupUser(scope.row.data)") span.x-link(v-else v-text="scope.row.data")
//- search //- search
.x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'search'") .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'search'")
@@ -458,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.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.requestMessage' v-text="scope.row.details.requestMessage")
span(v-else-if='scope.row.details && scope.row.details.responseMessage' v-text="scope.row.details.responseMessage") 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-once #default="scope")
template(v-if="scope.row.senderUserId !== API.currentUser.id") template(v-if="scope.row.senderUserId !== API.currentUser.id && !scope.row.$isExpired")
el-button(v-if="scope.row.type === 'friendRequest'" type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)") template(v-if="scope.row.type === 'friendRequest'")
el-button(v-else-if="scope.row.type === 'invite'" type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)") 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'") 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)") 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)") 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-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 //- profile
.x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'profile'") .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'profile'")
@@ -696,10 +719,10 @@ html
el-radio-button(label="dark") Dark el-radio-button(label="dark") Dark
div.options-container-item div.options-container-item
span.name VRCPlus Profile Icons span.name VRCPlus Profile Icons
el-switch(v-model="displayVRCPlusIconsAsAvatar") el-switch(v-model="displayVRCPlusIconsAsAvatar" @change="saveOpenVROption")
div.options-container-item div.options-container-item
span.name Disable Tooltips span.name Disable Tooltips
el-switch(v-model="hideTooltips") el-switch(v-model="hideTooltips" @change="saveOpenVROption")
div.options-container div.options-container
span.header Side Panel span.header Side Panel
br br
@@ -766,10 +789,16 @@ html
span * Only works when VRChat is running. span * Only works when VRChat is running.
div.options-container-item div.options-container-item
span.name Enable span.name Enable
el-switch(v-model="discordActive") el-switch(v-model="discordActive" @change="saveDiscordOption")
div.options-container-item div.options-container-item
span.name Instance details span.name Instance type/player count
el-switch(v-model="discordInstance" :disabled="!discordActive") 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 div.options-container
span.header SteamVR Overlay span.header SteamVR Overlay
div.options-container-item div.options-container-item
@@ -782,61 +811,64 @@ html
br br
div.options-container-item div.options-container-item
span.name Enable span.name Enable
el-switch(v-model="openVR") el-switch(v-model="openVR" @change="saveOpenVROption")
div.options-container-item div.options-container-item
span.name Force Run (Opens SteamVR) 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 div.options-container-item
span.name Hide Private Worlds span.name Hide Private Worlds
el-switch(v-model="hidePrivateFromFeed") el-switch(v-model="hidePrivateFromFeed" @change="saveOpenVROption")
br br
span.sub-header Wrist Feed span.sub-header Wrist Feed
div.options-container-item div.options-container-item
span.name Wrist Feed Overlay 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 div.options-container-item
span.name(style="min-width:137px") Overlay Button 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 div.options-container-item
span.name Background Color 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 div.options-container-item
span.name Minimal Feed Icons 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 div.options-container-item
span.name Hide VR Devices 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 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 br
span.sub-header Notifications span.sub-header Notifications
div.options-container-item div.options-container-item
span.name Overlay Notifications span.name Overlay Notifications
el-switch(v-model="overlayNotifications" :disabled="!openVR") el-switch(v-model="overlayNotifications" @change="saveOpenVROption" :disabled="!openVR")
div.options-container-item 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 div.options-container-item
span.name XSOverlay Notifications span.name XSOverlay Notifications
el-switch(v-model="xsNotifications") el-switch(v-model="xsNotifications" @change="saveOpenVROption")
div.options-container-item 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 div.options-container-item
span.name Desktop Notifications, When to display: span.name Desktop Notifications, When to display:
br 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="Never")
el-radio-button(label="Inside VR") el-radio-button(label="Inside VR")
el-radio-button(label="Game Closed") el-radio-button(label="Game Closed")
el-radio-button(label="Game Running") el-radio-button(label="Game Running")
el-radio-button(label="Always") el-radio-button(label="Always")
div.options-container-item 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 br
span.sub-header Text-To-Speech Options span.sub-header Text-To-Speech Options
div.options-container-item div.options-container-item
span.name Notification TTS, When to play: span.name Notification TTS, When to play:
br 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="Never")
el-radio-button(label="Inside VR") el-radio-button(label="Inside VR")
el-radio-button(label="Game Closed") el-radio-button(label="Game Closed")
@@ -857,31 +889,30 @@ html
div.options-container-item div.options-container-item
span.name Download on invite: span.name Download on invite:
br 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="Never")
el-radio-button(label="Game Closed") el-radio-button(label="Game Closed")
el-radio-button(label="Game Running") el-radio-button(label="Game Running")
el-radio-button(label="Always") el-radio-button(label="Always")
div.options-container-item 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 div.options-container-item
span.name Download on GPS: span.name Download on GPS:
br 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="Never")
el-radio-button(label="Game Closed") el-radio-button(label="Game Closed")
el-radio-button(label="Game Running") el-radio-button(label="Game Running")
el-radio-button(label="Always") el-radio-button(label="Always")
div.options-container-item 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 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 br
span.sub-header Automatically Manage Cache When Closing VRChat span.sub-header Automatically Manage Cache When Closing VRChat
div.options-container-item div.options-container-item
span.name(style="min-width:300px") Auto delete old versions from cache 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 div.options-container
span.header Application span.header Application
div.options-container-item div.options-container-item
@@ -900,6 +931,13 @@ html
el-button-group 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="showLaunchOptions()") Launch Options
el-button(size="small" icon="el-icon-s-operation" @click="showVRChatConfig()") VRChat config.json 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") div.options-container(style="margin-top:45px;border-top:1px solid #eee;padding-top:30px")
span.header Legal Notice span.header Legal Notice
div.options-container-item div.options-container-item
@@ -1053,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-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-menu(#default="dropdown")
el-dropdown-item(icon="el-icon-refresh" command="Refresh") Refresh 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") 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-picture-outline" command="Manage Gallery" divided) Manage Gallery/Icons
el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author") Show Avatar Author el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author") Show Avatar Author
@@ -1075,7 +1114,7 @@ html
el-dropdown-item(v-else icon="el-icon-plus" command="Send Friend Request") Send Friend Request 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 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(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-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-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 el-dropdown-item(v-if="userDialog.isMute" icon="el-icon-microphone" command="Unmute" style="color:#F56C6C") Unmute
@@ -1665,6 +1704,16 @@ html
el-button(size="small" @click="VRChatConfigDialog.visible = false") Cancel el-button(size="small" @click="VRChatConfigDialog.visible = false") Cancel
el-button(type="primary" size="small" :disabled="VRChatConfigDialog.loading" @click="saveVRChatConfigFile") Save 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 (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")
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 //- dialog: Cache Download
el-dialog.x-dialog(ref="downloadDialog" :visible.sync="downloadDialog.visible" title="Download History" width="770px") el-dialog.x-dialog(ref="downloadDialog" :visible.sync="downloadDialog.visible" title="Download History" width="770px")
div(v-if="downloadInProgress && downloadCurrent.ref") div(v-if="downloadInProgress && downloadCurrent.ref")
@@ -1769,7 +1818,7 @@ html
el-button(type="primary" size="small" style="margin-left:auto" @click="notificationPositionDialog.visible = false") OK el-button(type="primary" size="small" style="margin-left:auto" @click="notificationPositionDialog.visible = false") OK
//- dialog: Noty feed filters //- 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-list
.toggle-item .toggle-item
span.toggle-name OnPlayerJoining span.toggle-name OnPlayerJoining
@@ -1868,21 +1917,30 @@ html
el-radio-button(label="Friends") el-radio-button(label="Friends")
.toggle-item .toggle-item
span.toggle-name Portal Spawn 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-group(v-model="sharedFeedFilters.noty.PortalSpawn" size="mini")
el-radio-button(label="Off") el-radio-button(label="Off")
el-radio-button(label="VIP") el-radio-button(label="VIP")
el-radio-button(label="Friends") el-radio-button(label="Friends")
el-radio-button(label="Everyone") el-radio-button(label="Everyone")
.toggle-item //- .toggle-item
span.toggle-name Events //- span.toggle-name Avatar Change
el-radio-group(v-model="sharedFeedFilters.noty.Event" size="mini") //- el-radio-group(v-model="sharedFeedFilters.noty.AvatarChange" size="mini")
el-radio-button(label="Off") //- 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 .toggle-item
span.toggle-name Video Play span.toggle-name Video Play
el-radio-group(v-model="sharedFeedFilters.noty.VideoPlay" size="mini") el-radio-group(v-model="sharedFeedFilters.noty.VideoPlay" size="mini")
el-radio-button(label="Off") el-radio-button(label="Off")
el-radio-button(label="On") 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 .toggle-item
span.toggle-name Blocked Player Joins span.toggle-name Blocked Player Joins
el-radio-group(v-model="sharedFeedFilters.noty.BlockedOnPlayerJoined" size="mini") el-radio-group(v-model="sharedFeedFilters.noty.BlockedOnPlayerJoined" size="mini")
@@ -1916,7 +1974,7 @@ html
el-button(type="primary" size="small" style="margin-left:10px" @click="saveSharedFeedFilters") Save el-button(type="primary" size="small" style="margin-left:10px" @click="saveSharedFeedFilters") Save
//- dialog: wrist feed filters //- 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-list
.toggle-item .toggle-item
span.toggle-name Self Location span.toggle-name Self Location
@@ -2020,19 +2078,28 @@ html
el-radio-button(label="Friends") el-radio-button(label="Friends")
.toggle-item .toggle-item
span.toggle-name Portal Spawn 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-group(v-model="sharedFeedFilters.wrist.PortalSpawn" size="mini")
el-radio-button(label="Off") el-radio-button(label="Off")
el-radio-button(label="VIP") el-radio-button(label="VIP")
el-radio-button(label="Friends") el-radio-button(label="Friends")
el-radio-button(label="Everyone") 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 .toggle-item
span.toggle-name Events span.toggle-name Video Play
el-radio-group(v-model="sharedFeedFilters.wrist.Event" size="mini") el-radio-group(v-model="sharedFeedFilters.wrist.VideoPlay" size="mini")
el-radio-button(label="Off") el-radio-button(label="Off")
el-radio-button(label="On") el-radio-button(label="On")
.toggle-item .toggle-item
span.toggle-name Video Play span.toggle-name Events
el-radio-group(v-model="sharedFeedFilters.noty.VideoPlay" size="mini") el-radio-group(v-model="sharedFeedFilters.wrist.Event" size="mini")
el-radio-button(label="Off") el-radio-button(label="Off")
el-radio-button(label="On") el-radio-button(label="On")
.toggle-item .toggle-item
+275 -1
View File
@@ -1,7 +1,7 @@
import sqliteService from '../service/sqlite.js'; import sqliteService from '../service/sqlite.js';
class Database { class Database {
async init(userId) { async initUserTables(userId) {
Database.userId = userId.replaceAll('-', '').replaceAll('_', ''); Database.userId = userId.replaceAll('-', '').replaceAll('_', '');
await sqliteService.executeNonQuery( 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)` `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)`
@@ -21,11 +21,32 @@ class Database {
await sqliteService.executeNonQuery( 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)` `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( await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS memos (user_id TEXT PRIMARY KEY, edited_at TEXT, memo TEXT)` `CREATE TABLE IF NOT EXISTS memos (user_id TEXT PRIMARY KEY, edited_at TEXT, memo TEXT)`
); );
} }
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() { async getFeedDatabase() {
var feedDatabase = []; var feedDatabase = [];
var date = new Date(); var date = new Date();
@@ -349,6 +370,259 @@ class Database {
} }
); );
} }
async getGamelogDatabase() {
var gamelogDatabase = [];
var date = new Date();
date.setDate(date.getDate() - 7); // 7 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
}
);
}
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(); var self = new Database();
+25 -36
View File
@@ -1,9 +1,7 @@
// requires binding of LogWatcher // requires binding of LogWatcher
// <string, object> class GameLogService {
var contextMap = new Map(); parseRawGameLog(dt, type, args) {
function parseRawGameLog(dt, type, args) {
var gameLog = { var gameLog = {
dt, dt,
type type
@@ -15,6 +13,10 @@ function parseRawGameLog(dt, type, args) {
gameLog.worldName = args[1]; gameLog.worldName = args[1];
break; break;
case 'location-destination':
gameLog.location = args[0];
break;
case 'player-joined': case 'player-joined':
gameLog.userDisplayName = args[0]; gameLog.userDisplayName = args[0];
gameLog.userType = args[1]; gameLog.userType = args[1];
@@ -37,10 +39,14 @@ function parseRawGameLog(dt, type, args) {
break; break;
case 'video-play': case 'video-play':
gameLog.videoURL = args[0]; gameLog.videoUrl = args[0];
gameLog.displayName = args[1]; gameLog.displayName = args[1];
break; break;
case 'vrcx':
gameLog.data = args[0];
break;
default: default:
break; break;
} }
@@ -48,46 +54,29 @@ function parseRawGameLog(dt, type, args) {
return gameLog; return gameLog;
} }
class GameLogService { async getAll() {
async poll() {
var rawGameLogs = await LogWatcher.Get();
var gameLogs = []; var gameLogs = [];
var now = Date.now(); 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) { for (var [fileName, dt, type, ...args] of rawGameLogs) {
var context = contextMap.get(fileName); var gameLog = this.parseRawGameLog(dt, type, args);
if (typeof context === 'undefined') {
context = {
updatedAt: null,
// location
location: null
};
contextMap.set(fileName, context);
}
var gameLog = parseRawGameLog(dt, type, args);
switch (gameLog.type) {
case 'location':
context.location = gameLog.location;
break;
default:
break;
}
context.updatedAt = now;
gameLogs.push(gameLog); gameLogs.push(gameLog);
} }
if (rawGameLogs.length === 0) {
done = true;
}
}
return gameLogs; return gameLogs;
} }
async setDateTill(dateTill) {
await LogWatcher.SetDateTill(dateTill);
}
async reset() { async reset() {
await LogWatcher.Reset(); await LogWatcher.Reset();
contextMap.clear();
} }
} }
+131 -699
View File
@@ -8,11 +8,10 @@ import Noty from 'noty';
import Vue from 'vue'; import Vue from 'vue';
import ElementUI from 'element-ui'; import ElementUI from 'element-ui';
import locale from 'element-ui/lib/locale/lang/en'; import locale from 'element-ui/lib/locale/lang/en';
import MarqueeText from 'vue-marquee-text-component';
Vue.component('marquee-text', MarqueeText);
import {appVersion} from './constants.js';
import sharedRepository from './repository/shared.js';
import configRepository from './repository/config.js'; import configRepository from './repository/config.js';
import webApiService from './service/webapi.js';
speechSynthesis.getVoices(); speechSynthesis.getVoices();
@@ -109,273 +108,113 @@ speechSynthesis.getVoices();
}; };
Vue.filter('timeToText', timeToText); Vue.filter('timeToText', timeToText);
// Vue.component('location', {
// API template:
// '<span>{{ text }}<slot></slot><span class="famfamfam-flags" :class="region" style="display:inline-block;margin-left:5px"></span></span>',
props: {
var API = {}; location: String,
hint: {
API.eventHandlers = new Map(); type: String,
default: ''
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);
}
} catch (err) {
console.error(err);
} }
},
data() {
return {
text: this.location,
region: this.region
}; };
},
API.$on = function (name, handler) { methods: {
var handlers = this.eventHandlers.get(name); parse() {
if (typeof handlers === 'undefined') { this.text = this.location;
handlers = []; var L = $app.parseLocation(this.location);
this.eventHandlers.set(name, handlers); if (L.isOffline) {
} this.text = 'Offline';
handlers.push(handler); } else if (L.isPrivate) {
}; this.text = 'Private';
} else if (typeof this.hint === 'string' && this.hint !== '') {
API.$off = function (name, handler) { if (L.instanceId) {
var handlers = this.eventHandlers.get(name); this.text = `${this.hint} #${L.instanceName} ${L.accessType}`;
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 { } else {
this.eventHandlers.delete(name); this.text = this.hint;
}
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 if (L.worldId) {
if (L.instanceId) {
this.text = ` #${L.instanceName} ${L.accessType}`;
} else { } else {
init.headers = { this.text = this.location;
'Content-Type': 'application/json;charset=utf-8',
...init.headers
};
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;
}
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);
} }
} }
this.$throw(status, data); this.region = '';
return data; if (
}); this.location !== '' &&
if (isGetRequest === true) { L.instanceId &&
req.finally(() => { !L.isOffline &&
this.pendingGetRequests.delete(init.url); !L.isPrivate
}); ) {
this.pendingGetRequests.set(init.url, req); if (L.region === 'eu') {
} this.region = 'europeanunion';
return req; } else if (L.region === 'jp') {
}; this.region = 'jp';
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 { } else {
text.push(`${code} ${status}`); this.region = 'us';
} }
} }
if (typeof error !== 'undefined') {
text.push(JSON.stringify(error));
} }
text = text.map((s) => escapeTag(s)).join('<br>'); },
if (text.length) { watch: {
new Noty({ location() {
type: 'error', this.parse();
text }
}).show(); },
created() {
this.parse();
} }
throw new Error(text);
};
// API: Config
API.cachedConfig = {};
API.$on('CONFIG', function (args) {
args.ref = this.applyConfig(args.json);
}); });
API.applyConfig = function (json) { var $app = {
var ref = { data: {
clientApiKey: '', // 1 = 대시보드랑 손목에 보이는거
...json // 2 = 항상 화면에 보이는 거
}; appType: location.href.substr(-1),
this.cachedConfig = ref; currentTime: new Date().toJSON(),
return ref; cpuUsage: 0,
config: {},
nowPlaying: {
url: '',
name: '',
length: 0,
startTime: 0,
elapsed: 0,
percentage: 0,
remainingText: ''
},
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 () { $app.methods.parseLocation = function (tag) {
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) {
var _tag = String(tag || ''); var _tag = String(tag || '');
var ctx = { var ctx = {
tag: _tag, tag: _tag,
@@ -448,380 +287,36 @@ speechSynthesis.getVoices();
return ctx; return ctx;
}; };
Vue.component('location', { $app.methods.configUpdate = function (json) {
template: this.config = JSON.parse(json);
'<span>{{ text }}<slot></slot><span class="famfamfam-flags" :class="region" style="display:inline-block;margin-left:5px"></span></span>',
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.nowPlayingUpdate = function (json) {
params: { this.nowPlaying = JSON.parse(json);
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;
});
}; };
// API: User $app.methods.lastLocationUpdate = function (json) {
this.lastLocation = JSON.parse(json);
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) {
params: { this.wristFeed = JSON.parse(json);
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 () {
params: { try {
userId: string this.currentTime = new Date().toJSON();
} var cpuUsage = await AppApi.CpuUsage();
*/ this.cpuUsage = cpuUsage.toFixed(0);
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 = { this.lastLocationTimer = '';
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;
if (this.lastLocation.date !== 0) { if (this.lastLocation.date !== 0) {
this.lastLocationTimer = timeToText( this.lastLocationTimer = timeToText(
Date.now() - this.lastLocation.date 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 () { if (!this.config.hideDevicesFromFeed) {
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') {
AppApi.GetVRDevices().then((devices) => { AppApi.GetVRDevices().then((devices) => {
devices.forEach((device) => { devices.forEach((device) => {
device[2] = parseInt(device[2], 10); device[2] = parseInt(device[2], 10);
@@ -831,96 +326,25 @@ speechSynthesis.getVoices();
} else { } else {
this.devices = ''; this.devices = '';
} }
await this.updateSharedFeeds();
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
setTimeout(() => this.updateLoop(), 500); setTimeout(() => this.updateStatsLoop(), 500);
}; };
$app.methods.updateCpuUsageLoop = async function () { $app.methods.playNoty = function (json) {
try { var {noty, message, image} = JSON.parse(json);
var cpuUsage = await AppApi.CpuUsage();
this.cpuUsage = cpuUsage.toFixed(0);
} catch (err) {
console.error(err);
}
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]];
}
}
if (message) {
message = `, ${message}`;
}
if (
this.config.overlayNotifications &&
!this.isGameNoVR &&
this.isGameRunning
) {
var text = ''; var text = '';
var img = '';
if (image) {
img = `<img class="noty-img" src="data:image/png;base64, ${image}"></img>`;
}
switch (noty.type) { switch (noty.type) {
case 'OnPlayerJoined': case 'OnPlayerJoined':
text = `<strong>${noty.data}</strong> has joined`; text = `<strong>${noty.displayName}</strong> has joined`;
break; break;
case 'OnPlayerLeft': case 'OnPlayerLeft':
text = `<strong>${noty.data}</strong> has left`; text = `<strong>${noty.displayName}</strong> has left`;
break; break;
case 'OnPlayerJoining': case 'OnPlayerJoining':
text = `<strong>${noty.displayName}</strong> is joining`; text = `<strong>${noty.displayName}</strong> is joining`;
@@ -975,13 +399,23 @@ speechSynthesis.getVoices();
text = `<strong>${noty.previousDisplayName}</strong> changed their name to ${noty.displayName}`; text = `<strong>${noty.previousDisplayName}</strong> changed their name to ${noty.displayName}`;
break; break;
case 'PortalSpawn': case 'PortalSpawn':
text = `<strong>${noty.data}</strong> has spawned a portal`; var locationName = '';
if (noty.worldName) {
locationName = ` to ${this.displayLocation(
noty.instanceId,
noty.worldName
)}`;
}
text = `<strong>${noty.displayName}</strong> has spawned a portal${locationName}`;
break;
case 'AvatarChange':
text = `<strong>${noty.displayName}</strong> changed into avatar ${noty.name}`;
break; break;
case 'Event': case 'Event':
text = noty.data; text = noty.data;
break; break;
case 'VideoPlay': case 'VideoPlay':
text = `<strong>Now playing:</strong> ${noty.data}`; text = `<strong>Now playing:</strong> ${noty.notyName}`;
break; break;
case 'BlockedOnPlayerJoined': case 'BlockedOnPlayerJoined':
text = `Blocked user <strong>${noty.displayName}</strong> has joined`; text = `Blocked user <strong>${noty.displayName}</strong> has joined`;
@@ -1004,11 +438,9 @@ speechSynthesis.getVoices();
theme: this.config.notificationTheme, theme: this.config.notificationTheme,
timeout: this.config.notificationTimeout, timeout: this.config.notificationTimeout,
layout: this.config.notificationPosition, layout: this.config.notificationPosition,
text text: `${img}<div class="noty-text">${text}</div>`
}).show(); }).show();
} }
}
}
}; };
$app.methods.statusClass = function (status) { $app.methods.statusClass = function (status) {
@@ -1033,7 +465,7 @@ speechSynthesis.getVoices();
$app.methods.displayLocation = function (location, worldName) { $app.methods.displayLocation = function (location, worldName) {
var text = ''; var text = '';
var L = API.parseLocation(location); var L = this.parseLocation(location);
if (L.isOffline) { if (L.isOffline) {
text = 'Offline'; text = 'Offline';
} else if (L.isPrivate) { } else if (L.isPrivate) {
+70 -18
View File
@@ -36,17 +36,23 @@ html
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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 }") div(v-else-if="feed.type === 'OnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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 }") div(v-else-if="feed.type === 'OnPlayerLeft'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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 }") div(v-else-if="feed.type === 'OnPlayerJoining'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail .detail
span.extra span.extra
@@ -57,7 +63,16 @@ html
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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 }") div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail .detail
span.extra span.extra
@@ -107,7 +122,16 @@ html
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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") div(v-else-if="feed.type === 'Event'" class="x-friend-item")
.detail .detail
span.extra span.extra
@@ -159,17 +183,23 @@ html
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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 }") div(v-else-if="feed.type === 'OnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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 }") div(v-else-if="feed.type === 'OnPlayerLeft'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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 }") div(v-else-if="feed.type === 'OnPlayerJoining'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail .detail
span.extra span.extra
@@ -179,7 +209,16 @@ html
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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 }") div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail .detail
span.extra span.extra
@@ -229,7 +268,16 @@ html
.detail .detail
span.extra span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }} 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") div(v-else-if="feed.type === 'Event'" class="x-friend-item")
.detail .detail
span.extra span.extra
@@ -301,21 +349,25 @@ html
br br
span {{ device[2] }}% span {{ device[2] }}%
.x-containerbottom .x-containerbottom
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 && 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] span(style="display:inline-block;margin-right:5px") #[i.el-icon-loading]
template(v-else-if="downloadProgress > 0") template(v-else-if="config.downloadProgress > 0")
span(style="display:inline-block;margin-right:5px") {{ downloadProgress }}% 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="float:right") {{ lastLocationTimer }}
span(style="display:inline-block") {{ lastLocation.playerList.length }} span(style="display:inline-block") {{ lastLocation.playerList.length }}
span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? ` (${lastLocation.friendList.length})` : ''}} span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? ` (${lastLocation.friendList.length})` : ''}}
template(v-else) 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] span(style="display:inline-block;margin-right:5px") Downloading: #[i.el-icon-loading]
template(v-else-if="downloadProgress > 0") template(v-else-if="config.downloadProgress > 0")
span(style="display:inline-block;margin-right:5px") Downloading: {{ downloadProgress }}% 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="float:right") Timer: {{ lastLocationTimer }}
span(style="display:inline-block") Players: {{ lastLocation.playerList.length }} span(style="display:inline-block") Players: {{ lastLocation.playerList.length }}
span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? ` (${lastLocation.friendList.length})` : ''}} span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? ` (${lastLocation.friendList.length})` : ''}}
+21 -6
View File
@@ -22,9 +22,6 @@
.noty_body { .noty_body {
display: block; display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.noty_layout { .noty_layout {
@@ -34,6 +31,7 @@
.noty_theme__relax.noty_bar, .noty_theme__relax.noty_bar,
.noty_theme__sunset.noty_bar { .noty_theme__sunset.noty_bar {
height: 42px;
position: relative; position: relative;
margin: 4px 0; margin: 4px 0;
overflow: hidden; overflow: hidden;
@@ -42,9 +40,7 @@
.noty_theme__relax.noty_bar .noty_body, .noty_theme__relax.noty_bar .noty_body,
.noty_theme__sunset.noty_bar .noty_body { .noty_theme__sunset.noty_bar .noty_body {
padding: 5px 10px 10px;
font-size: 15px; font-size: 15px;
text-align: center;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
} }
@@ -143,6 +139,19 @@
opacity: 0.6; 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 { ::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px; height: 8px;
@@ -202,16 +211,22 @@ button {
} }
.x-containerbottom span { .x-containerbottom span {
padding: 0px;
display: block; display: block;
overflow: hidden; overflow: hidden;
} }
.np-progress-bar {
width: 0%;
height: 2px;
background-color: white;
}
.x-friend-item { .x-friend-item {
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 18px; font-size: 18px;
height: 27.1px;
} }
.x-friend-item .time { .x-friend-item .time {