From c8c49a292128e945ba96d27a20498fb9ab0b99a8 Mon Sep 17 00:00:00 2001 From: pizzaboxer Date: Fri, 28 Jul 2023 21:02:54 +0100 Subject: [PATCH] Revamp Bloxstrap game messaging, now BloxstrapRPC support for nested and dynamically-typed json fields, and games now have greater liberty over how the rich presence can be configured --- Bloxstrap/GlobalUsings.cs | 1 + Bloxstrap/Integrations/ActivityWatcher.cs | 10 +- Bloxstrap/Integrations/DiscordRichPresence.cs | 159 ++++++------------ Bloxstrap/Models/BloxstrapRPC/Message.cs | 10 ++ Bloxstrap/Models/BloxstrapRPC/RichPresence.cs | 23 +++ .../Models/BloxstrapRPC/RichPresenceImage.cs | 11 ++ Bloxstrap/Models/GameMessage.cs | 11 -- 7 files changed, 101 insertions(+), 124 deletions(-) create mode 100644 Bloxstrap/Models/BloxstrapRPC/Message.cs create mode 100644 Bloxstrap/Models/BloxstrapRPC/RichPresence.cs create mode 100644 Bloxstrap/Models/BloxstrapRPC/RichPresenceImage.cs delete mode 100644 Bloxstrap/Models/GameMessage.cs diff --git a/Bloxstrap/GlobalUsings.cs b/Bloxstrap/GlobalUsings.cs index bcb1931..e66b091 100644 --- a/Bloxstrap/GlobalUsings.cs +++ b/Bloxstrap/GlobalUsings.cs @@ -17,6 +17,7 @@ global using System.Threading.Tasks; global using Bloxstrap.Enums; global using Bloxstrap.Extensions; global using Bloxstrap.Models; +global using Bloxstrap.Models.BloxstrapRPC; global using Bloxstrap.Models.Attributes; global using Bloxstrap.Models.RobloxApi; global using Bloxstrap.UI; diff --git a/Bloxstrap/Integrations/ActivityWatcher.cs b/Bloxstrap/Integrations/ActivityWatcher.cs index dec25cd..44e0475 100644 --- a/Bloxstrap/Integrations/ActivityWatcher.cs +++ b/Bloxstrap/Integrations/ActivityWatcher.cs @@ -11,7 +11,7 @@ private const string GameJoinedEntry = "[FLog::Network] serverId:"; private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:"; private const string GameTeleportingEntry = "[FLog::SingleSurfaceApp] initiateTeleport"; - private const string GameMessageEntry = "[FLog::Output] [SendBloxstrapMessage]"; + private const string GameMessageEntry = "[FLog::Output] [BloxstrapRPC]"; private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)"; private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+"; @@ -24,7 +24,7 @@ public event EventHandler? OnLogEntry; public event EventHandler? OnGameJoin; public event EventHandler? OnGameLeave; - public event EventHandler? OnGameMessage; + public event EventHandler? OnRPCMessage; private Dictionary GeolocationCache = new(); @@ -233,13 +233,13 @@ else if (entry.Contains(GameMessageEntry)) { string messagePlain = entry.Substring(entry.IndexOf(GameMessageEntry) + GameMessageEntry.Length + 1); - GameMessage? message; + Message? message; App.Logger.WriteLine(LOG_IDENT, $"Received message: '{messagePlain}'"); try { - message = JsonSerializer.Deserialize(messagePlain); + message = JsonSerializer.Deserialize(messagePlain); } catch (Exception) { @@ -259,7 +259,7 @@ return; } - OnGameMessage?.Invoke(this, message); + OnRPCMessage?.Invoke(this, message); } } } diff --git a/Bloxstrap/Integrations/DiscordRichPresence.cs b/Bloxstrap/Integrations/DiscordRichPresence.cs index d0e8bb5..8afe597 100644 --- a/Bloxstrap/Integrations/DiscordRichPresence.cs +++ b/Bloxstrap/Integrations/DiscordRichPresence.cs @@ -7,21 +7,20 @@ namespace Bloxstrap.Integrations private readonly DiscordRpcClient _rpcClient = new("1005469189907173486"); private readonly ActivityWatcher _activityWatcher; - private RichPresence? _currentPresence; + private DiscordRPC.RichPresence? _currentPresence; private bool _visible = true; - private string? _initialStatus; private long _currentUniverseId; private DateTime? _timeStartedUniverse; public DiscordRichPresence(ActivityWatcher activityWatcher) { - const string LOG_IDENT = "DiscordRichPresence::"; + const string LOG_IDENT = "DiscordRichPresence::DiscordRichPresence"; _activityWatcher = activityWatcher; _activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetCurrentGame()); _activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetCurrentGame()); - _activityWatcher.OnGameMessage += (_, message) => OnGameMessage(message); + _activityWatcher.OnRPCMessage += (_, message) => ProcessRPCMessage(message); _rpcClient.OnReady += (_, e) => App.Logger.WriteLine(LOG_IDENT, $"Received ready from user {e.User} ({e.User.ID})"); @@ -45,71 +44,12 @@ namespace Bloxstrap.Integrations _rpcClient.Initialize(); } - public void OnGameMessage(GameMessage message) + public void ProcessRPCMessage(Message message) { - switch (message.Command) - { - case "SetPresenceStatus": - SetStatus(message.Data); - break; + const string LOG_IDENT = "DiscordRichPresence::ProcessRPCMessage"; - case "SetPresenceTimestamp": - SetTimestamp(message.Data); - break; - - case "SetPresenceIcon": - SetIcon(message.Data); - break; - } - } - - #region Game message commands - public void SetStatus(string status) - { - const string LOG_IDENT = "DiscordRichPresence::SetStatus"; - - App.Logger.WriteLine(LOG_IDENT, $"Setting status to '{status}'"); - - if (_currentPresence is null) - { - App.Logger.WriteLine(LOG_IDENT, $"Presence is not set, aborting"); + if (message.Command != "SetRichPresence") return; - } - - if (status.Length > 128) - { - App.Logger.WriteLine(LOG_IDENT, $"Status cannot be longer than 128 characters, aborting"); - return; - } - - if (_initialStatus is null) - _initialStatus = _currentPresence.State; - - string finalStatus; - - if (String.IsNullOrEmpty(status)) - { - App.Logger.WriteLine(LOG_IDENT, $"Status is empty, reverting to initial status"); - finalStatus = _initialStatus; - } - else - { - finalStatus = status; - } - - if (_currentPresence.State == finalStatus) - { - App.Logger.WriteLine(LOG_IDENT, $"Status is unchanged, aborting"); - return; - } - - _currentPresence.State = finalStatus; - UpdatePresence(); - } - - public void SetTimestamp(string data) - { - const string LOG_IDENT = "DiscordRichPresence::SetTimestamp"; if (_currentPresence is null) { @@ -117,62 +57,66 @@ namespace Bloxstrap.Integrations return; } - if (String.IsNullOrEmpty(data)) - { - App.Logger.WriteLine(LOG_IDENT, "Clearing timestamp"); + Models.BloxstrapRPC.RichPresence? presence; - _currentPresence.Timestamps.Start = null; - _currentPresence.Timestamps.End = null; + try + { + presence = message.Data.Deserialize(); } - else + catch (Exception) { - var parameters = data.Split(';'); - - if (parameters.Length >= 1 && ulong.TryParse(parameters[0], out ulong startTimestamp)) - _currentPresence.Timestamps.StartUnixMilliseconds = startTimestamp * 1000; - - if (parameters.Length >= 2 && ulong.TryParse(parameters[1], out ulong endTimestamp)) - _currentPresence.Timestamps.EndUnixMilliseconds = endTimestamp * 1000; - } - - UpdatePresence(); - } - - public void SetIcon(string data) - { - const string LOG_IDENT = "DiscordRichPresence::SetIcon"; - - if (_currentPresence is null) - { - App.Logger.WriteLine(LOG_IDENT, "Presence is not set, aborting"); + App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization threw an exception)"); return; } - if (String.IsNullOrEmpty(data)) + if (presence is null) { - _currentPresence.Assets.SmallImageKey = "roblox"; - _currentPresence.Assets.SmallImageText = "Roblox"; + App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization returned null)"); + return; } - else + + if (presence.Details is not null) { - var parameters = data.Split(';'); + if (presence.Details.Length > 128) + App.Logger.WriteLine(LOG_IDENT, $"Details cannot be longer than 128 characters"); + else + _currentPresence.Details = presence.Details; + } - if (parameters.Length < 2 || !ulong.TryParse(parameters[0], out ulong assetId)) - return; + if (presence.State is not null) + { + if (presence.State.Length > 128) + App.Logger.WriteLine(LOG_IDENT, $"State cannot be longer than 128 characters"); + else + _currentPresence.State = presence.State; + } - if (parameters[1].Length > 128) - { - App.Logger.WriteLine(LOG_IDENT, $"Icon text cannot be longer than 128 characters, aborting"); - return; - } + if (presence.TimestampStart is not null) + _currentPresence.Timestamps.StartUnixMilliseconds = presence.TimestampStart * 1000; - _currentPresence.Assets.SmallImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={assetId}"; - _currentPresence.Assets.SmallImageText = parameters[1]; + if (presence.TimestampEnd is not null) + _currentPresence.Timestamps.EndUnixMilliseconds = presence.TimestampEnd * 1000; + + if (presence.SmallImage is not null) + { + if (presence.SmallImage.AssetId is not null) + _currentPresence.Assets.SmallImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={presence.SmallImage.AssetId}"; + + if (presence.SmallImage.HoverText is not null) + _currentPresence.Assets.SmallImageText = presence.SmallImage.HoverText; + } + + if (presence.LargeImage is not null) + { + if (presence.LargeImage.AssetId is not null) + _currentPresence.Assets.LargeImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={presence.LargeImage.AssetId}"; + + if (presence.LargeImage.HoverText is not null) + _currentPresence.Assets.LargeImageText = presence.LargeImage.HoverText; } UpdatePresence(); } - #endregion public void SetVisibility(bool visible) { @@ -194,7 +138,6 @@ namespace Bloxstrap.Integrations { App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence"); _currentPresence = null; - _initialStatus = null; UpdatePresence(); return true; } @@ -271,7 +214,7 @@ namespace Bloxstrap.Integrations _ => $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""), }; - _currentPresence = new RichPresence + _currentPresence = new DiscordRPC.RichPresence { Details = $"Playing {universeDetails.Name}", State = status, diff --git a/Bloxstrap/Models/BloxstrapRPC/Message.cs b/Bloxstrap/Models/BloxstrapRPC/Message.cs new file mode 100644 index 0000000..ff7caf8 --- /dev/null +++ b/Bloxstrap/Models/BloxstrapRPC/Message.cs @@ -0,0 +1,10 @@ +namespace Bloxstrap.Models.BloxstrapRPC; + +public class Message +{ + [JsonPropertyName("command")] + public string Command { get; set; } = null!; + + [JsonPropertyName("data")] + public JsonElement Data { get; set; } +} diff --git a/Bloxstrap/Models/BloxstrapRPC/RichPresence.cs b/Bloxstrap/Models/BloxstrapRPC/RichPresence.cs new file mode 100644 index 0000000..3cbc5a4 --- /dev/null +++ b/Bloxstrap/Models/BloxstrapRPC/RichPresence.cs @@ -0,0 +1,23 @@ +namespace Bloxstrap.Models.BloxstrapRPC +{ + class RichPresence + { + [JsonPropertyName("details")] + public string? Details { get; set; } + + [JsonPropertyName("state")] + public string? State { get; set; } + + [JsonPropertyName("timestampStart")] + public ulong? TimestampStart { get; set; } + + [JsonPropertyName("timestampEnd")] + public ulong? TimestampEnd { get; set; } + + [JsonPropertyName("smallImage")] + public RichPresenceImage? SmallImage { get; set; } + + [JsonPropertyName("largeImage")] + public RichPresenceImage? LargeImage { get; set; } + } +} diff --git a/Bloxstrap/Models/BloxstrapRPC/RichPresenceImage.cs b/Bloxstrap/Models/BloxstrapRPC/RichPresenceImage.cs new file mode 100644 index 0000000..1b3d12c --- /dev/null +++ b/Bloxstrap/Models/BloxstrapRPC/RichPresenceImage.cs @@ -0,0 +1,11 @@ +namespace Bloxstrap.Models.BloxstrapRPC +{ + class RichPresenceImage + { + [JsonPropertyName("assetId")] + public ulong? AssetId { get; set; } + + [JsonPropertyName("hoverText")] + public string? HoverText { get; set; } + } +} diff --git a/Bloxstrap/Models/GameMessage.cs b/Bloxstrap/Models/GameMessage.cs deleted file mode 100644 index 7639652..0000000 --- a/Bloxstrap/Models/GameMessage.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Bloxstrap.Models -{ - public class GameMessage - { - [JsonPropertyName("command")] - public string Command { get; set; } = null!; - - [JsonPropertyName("data")] - public string Data { get; set; } = null!; - } -}