diff --git a/Bloxstrap/Helpers/Integrations/DiscordRichPresence.cs b/Bloxstrap/Helpers/Integrations/DiscordRichPresence.cs index 77b4a36..5b07b64 100644 --- a/Bloxstrap/Helpers/Integrations/DiscordRichPresence.cs +++ b/Bloxstrap/Helpers/Integrations/DiscordRichPresence.cs @@ -15,80 +15,92 @@ namespace Bloxstrap.Helpers.Integrations { class DiscordRichPresence : IDisposable { - readonly DiscordRpcClient RichPresence = new("1005469189907173486"); + private readonly DiscordRpcClient _rpcClient = new("1005469189907173486"); - const string GameJoiningEntry = "[FLog::Output] ! Joining game"; - const string GameJoinedEntry = "[FLog::Network] serverId:"; - const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:"; + // i'm thinking the functionality for parsing roblox logs could be broadened for more features than just rich presence, + // like checking the ping and region of the current connected server. maybe that's something to add? + private const string GameJoiningEntry = "[FLog::Output] ! Joining game"; + private const string GameJoinedEntry = "[FLog::Network] serverId:"; + private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:"; - const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)"; - const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|([0-9]+)"; + private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)"; + private const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|([0-9]+)"; + + private int _logEntriesRead = 0; // these are values to use assuming the player isn't currently in a game - bool ActivityInGame = false; - long ActivityPlaceId = 0; - string ActivityJobId = ""; - string ActivityMachineAddress = ""; // we're only really using this to confirm a place join. todo: maybe this could be used to see server location/ping? + private bool _activityInGame = false; + private long _activityPlaceId = 0; + private string _activityJobId = ""; + private string _activityMachineAddress = ""; public DiscordRichPresence() { - RichPresence.OnReady += (_, e) => + _rpcClient.OnReady += (_, e) => App.Logger.WriteLine($"[DiscordRichPresence::DiscordRichPresence] Received ready from user {e.User.Username} ({e.User.ID})"); - RichPresence.OnPresenceUpdate += (_, e) => + _rpcClient.OnPresenceUpdate += (_, e) => App.Logger.WriteLine("[DiscordRichPresence::DiscordRichPresence] Updated presence"); - RichPresence.OnConnectionEstablished += (_, e) => + _rpcClient.OnConnectionEstablished += (_, e) => App.Logger.WriteLine("[DiscordRichPresence::DiscordRichPresence] Established connection with Discord RPC"); //spams log as it tries to connect every ~15 sec when discord is closed so not now - //RichPresence.OnConnectionFailed += (_, e) => + //_rpcClient.OnConnectionFailed += (_, e) => // App.Logger.WriteLine("[DiscordRichPresence::DiscordRichPresence] Failed to establish connection with Discord RPC"); - RichPresence.OnClose += (_, e) => + _rpcClient.OnClose += (_, e) => App.Logger.WriteLine($"[DiscordRichPresence::DiscordRichPresence] Lost connection to Discord RPC - {e.Reason} ({e.Code})"); - RichPresence.Initialize(); + _rpcClient.Initialize(); } private async Task ExamineLogEntry(string entry) { // App.Logger.WriteLine(entry); + _logEntriesRead += 1; - if (entry.Contains(GameJoiningEntry) && !ActivityInGame && ActivityPlaceId == 0) + // debug stats to ensure that the log reader is working correctly + // if more than 5000 log entries have been read, only log per 100 to save on spam + if (_logEntriesRead <= 5000 && _logEntriesRead % 50 == 0) + App.Logger.WriteLine($"[DiscordRichPresence::ExamineLogEntry] Read {_logEntriesRead} log entries"); + else if (_logEntriesRead % 100 == 0) + App.Logger.WriteLine($"[DiscordRichPresence::ExamineLogEntry] Read {_logEntriesRead} log entries"); + + if (entry.Contains(GameJoiningEntry) && !_activityInGame && _activityPlaceId == 0) { Match match = Regex.Match(entry, GameJoiningEntryPattern); if (match.Groups.Count != 4) return; - ActivityInGame = false; - ActivityPlaceId = Int64.Parse(match.Groups[2].Value); - ActivityJobId = match.Groups[1].Value; - ActivityMachineAddress = match.Groups[3].Value; + _activityInGame = false; + _activityPlaceId = Int64.Parse(match.Groups[2].Value); + _activityJobId = match.Groups[1].Value; + _activityMachineAddress = match.Groups[3].Value; - App.Logger.WriteLine($"[DiscordRichPresence::ExamineLogEntry] Joining Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})"); + App.Logger.WriteLine($"[DiscordRichPresence::ExamineLogEntry] Joining Game ({_activityPlaceId}/{_activityJobId}/{_activityMachineAddress})"); } - else if (entry.Contains(GameJoinedEntry) && !ActivityInGame && ActivityPlaceId != 0) + else if (entry.Contains(GameJoinedEntry) && !_activityInGame && _activityPlaceId != 0) { Match match = Regex.Match(entry, GameJoinedEntryPattern); - if (match.Groups.Count != 3 || match.Groups[1].Value != ActivityMachineAddress) + if (match.Groups.Count != 3 || match.Groups[1].Value != _activityMachineAddress) return; - App.Logger.WriteLine($"[DiscordRichPresence::ExamineLogEntry] Joined Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})"); + App.Logger.WriteLine($"[DiscordRichPresence::ExamineLogEntry] Joined Game ({_activityPlaceId}/{_activityJobId}/{_activityMachineAddress})"); - ActivityInGame = true; + _activityInGame = true; await SetPresence(); } - else if (entry.Contains(GameDisconnectedEntry) && ActivityInGame && ActivityPlaceId != 0) + else if (entry.Contains(GameDisconnectedEntry) && _activityInGame && _activityPlaceId != 0) { - App.Logger.WriteLine($"[DiscordRichPresence::ExamineLogEntry] Disconnected from Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})"); + App.Logger.WriteLine($"[DiscordRichPresence::ExamineLogEntry] Disconnected from Game ({_activityPlaceId}/{_activityJobId}/{_activityMachineAddress})"); - ActivityInGame = false; - ActivityPlaceId = 0; - ActivityJobId = ""; - ActivityMachineAddress = ""; + _activityInGame = false; + _activityPlaceId = 0; + _activityJobId = ""; + _activityMachineAddress = ""; await SetPresence(); } } @@ -117,6 +129,8 @@ namespace Bloxstrap.Helpers.Integrations // if roblox doesn't start quickly enough, we can wind up fetching the previous log file // good rule of thumb is to find a log file that was created in the last 15 seconds or so + App.Logger.WriteLine("[DiscordRichPresence::MonitorGameActivity] Opening Roblox log file..."); + while (true) { logFileInfo = new DirectoryInfo(logDirectory).GetFiles().OrderByDescending(x => x.CreationTime).First(); @@ -124,10 +138,12 @@ namespace Bloxstrap.Helpers.Integrations if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now) break; + App.Logger.WriteLine($"[DiscordRichPresence::MonitorGameActivity] Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})"); await Task.Delay(1000); } FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + App.Logger.WriteLine($"[DiscordRichPresence::MonitorGameActivity] Opened {logFileInfo.Name}"); AutoResetEvent logUpdatedEvent = new(false); FileSystemWatcher logWatcher = new() @@ -138,24 +154,16 @@ namespace Bloxstrap.Helpers.Integrations }; logWatcher.Changed += (s, e) => logUpdatedEvent.Set(); - using (StreamReader sr = new(logFileStream)) + using StreamReader sr = new(logFileStream); + + while (true) { - string? log = null; + string? log = await sr.ReadLineAsync(); - while (true) - { - log = await sr.ReadLineAsync(); - - if (String.IsNullOrEmpty(log)) - { - logUpdatedEvent.WaitOne(1000); - } - else - { - //App.Logger.WriteLine(log); - await ExamineLogEntry(log); - } - } + if (String.IsNullOrEmpty(log)) + logUpdatedEvent.WaitOne(1000); + else + await ExamineLogEntry(log); } // no need to close the event, its going to be finished with when the program closes anyway @@ -163,49 +171,49 @@ namespace Bloxstrap.Helpers.Integrations public async Task SetPresence() { - if (!ActivityInGame) + if (!_activityInGame) { - RichPresence.ClearPresence(); + _rpcClient.ClearPresence(); return true; } string placeThumbnail = "roblox"; - var placeInfo = await Utilities.GetJson($"https://economy.roblox.com/v2/assets/{ActivityPlaceId}/details"); + var placeInfo = await Utilities.GetJson($"https://economy.roblox.com/v2/assets/{_activityPlaceId}/details"); if (placeInfo is null || placeInfo.Creator is null) return false; - var thumbnailInfo = await Utilities.GetJson($"https://thumbnails.roblox.com/v1/places/gameicons?placeIds={ActivityPlaceId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false"); + var thumbnailInfo = await Utilities.GetJson($"https://thumbnails.roblox.com/v1/places/gameicons?placeIds={_activityPlaceId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false"); if (thumbnailInfo is not null) placeThumbnail = thumbnailInfo.Data![0].ImageUrl!; - List buttons = new() + List