mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-21 10:01:27 -07:00
Add support for game message communication (#183)
game scripts can print a client message to send messages to bloxstrap, right now only for setting the presence status message is a json string formatted with the properties "command" and "data" that's prefixed with the string "[SendBloxstrapMessage]" example: print('[SendBloxstrapMessage] {"command":"SetPresenceStatus", "data":"hi"}')
This commit is contained in:
parent
51ff6680e3
commit
39d919811f
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
|||||||
using DiscordRPC;
|
using DiscordRPC;
|
||||||
|
|
||||||
using Bloxstrap.Models.RobloxApi;
|
using Bloxstrap.Models.RobloxApi;
|
||||||
|
using Bloxstrap.Models;
|
||||||
|
|
||||||
namespace Bloxstrap.Integrations
|
namespace Bloxstrap.Integrations
|
||||||
{
|
{
|
||||||
@ -14,6 +15,8 @@ namespace Bloxstrap.Integrations
|
|||||||
private readonly DiscordRpcClient _rpcClient = new("1005469189907173486");
|
private readonly DiscordRpcClient _rpcClient = new("1005469189907173486");
|
||||||
private readonly RobloxActivity _activityWatcher;
|
private readonly RobloxActivity _activityWatcher;
|
||||||
|
|
||||||
|
private RichPresence? _currentPresence;
|
||||||
|
private string? _initialStatus;
|
||||||
private long _currentUniverseId;
|
private long _currentUniverseId;
|
||||||
private DateTime? _timeStartedUniverse;
|
private DateTime? _timeStartedUniverse;
|
||||||
|
|
||||||
@ -21,8 +24,9 @@ namespace Bloxstrap.Integrations
|
|||||||
{
|
{
|
||||||
_activityWatcher = activityWatcher;
|
_activityWatcher = activityWatcher;
|
||||||
|
|
||||||
_activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetPresence());
|
_activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetCurrentGame());
|
||||||
_activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetPresence());
|
_activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetCurrentGame());
|
||||||
|
_activityWatcher.OnGameMessage += (_, message) => OnGameMessage(message);
|
||||||
|
|
||||||
_rpcClient.OnReady += (_, e) =>
|
_rpcClient.OnReady += (_, e) =>
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::DiscordRichPresence] Received ready from user {e.User.Username} ({e.User.ID})");
|
App.Logger.WriteLine($"[DiscordRichPresence::DiscordRichPresence] Received ready from user {e.User.Username} ({e.User.ID})");
|
||||||
@ -46,28 +50,77 @@ namespace Bloxstrap.Integrations
|
|||||||
_rpcClient.Initialize();
|
_rpcClient.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SetPresence()
|
public void OnGameMessage(GameMessage message)
|
||||||
|
{
|
||||||
|
if (message.Command == "SetPresenceStatus")
|
||||||
|
SetStatus(message.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStatus(string status)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Setting status to '{status}'");
|
||||||
|
|
||||||
|
if (_currentPresence is null)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Presence is not set, aborting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.Length > 128)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] 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($"[DiscordRichPresence::SetStatus] Status is empty, reverting to initial status");
|
||||||
|
finalStatus = _initialStatus;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
finalStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentPresence.State == finalStatus)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Status is unchanged, aborting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentPresence.State = finalStatus;
|
||||||
|
UpdatePresence();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SetCurrentGame()
|
||||||
{
|
{
|
||||||
if (!_activityWatcher.ActivityInGame)
|
if (!_activityWatcher.ActivityInGame)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Clearing presence");
|
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Not in game, clearing presence");
|
||||||
_rpcClient.ClearPresence();
|
_currentPresence = null;
|
||||||
|
_initialStatus = null;
|
||||||
|
UpdatePresence();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
string icon = "roblox";
|
string icon = "roblox";
|
||||||
|
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Setting presence for Place ID {_activityWatcher.ActivityPlaceId}");
|
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Setting presence for Place ID {_activityWatcher.ActivityPlaceId}");
|
||||||
|
|
||||||
var universeIdResponse = await Utilities.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{_activityWatcher.ActivityPlaceId}/universe");
|
var universeIdResponse = await Utilities.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{_activityWatcher.ActivityPlaceId}/universe");
|
||||||
if (universeIdResponse is null)
|
if (universeIdResponse is null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Could not get Universe ID!");
|
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe ID!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
long universeId = universeIdResponse.UniverseId;
|
long universeId = universeIdResponse.UniverseId;
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Got Universe ID as {universeId}");
|
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe ID as {universeId}");
|
||||||
|
|
||||||
// preserve time spent playing if we're teleporting between places in the same universe
|
// preserve time spent playing if we're teleporting between places in the same universe
|
||||||
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
|
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
|
||||||
@ -79,22 +132,22 @@ namespace Bloxstrap.Integrations
|
|||||||
var gameDetailResponse = await Utilities.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeId}");
|
var gameDetailResponse = await Utilities.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeId}");
|
||||||
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
|
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Could not get Universe info!");
|
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe info!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameDetailResponse universeDetails = gameDetailResponse.Data.ToArray()[0];
|
GameDetailResponse universeDetails = gameDetailResponse.Data.ToArray()[0];
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Got Universe details");
|
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe details");
|
||||||
|
|
||||||
var universeThumbnailResponse = await Utilities.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
|
var universeThumbnailResponse = await Utilities.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
|
||||||
if (universeThumbnailResponse is null || !universeThumbnailResponse.Data.Any())
|
if (universeThumbnailResponse is null || !universeThumbnailResponse.Data.Any())
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Could not get Universe thumbnail info!");
|
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe thumbnail info!");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
icon = universeThumbnailResponse.Data.ToArray()[0].ImageUrl;
|
icon = universeThumbnailResponse.Data.ToArray()[0].ImageUrl;
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Got Universe thumbnail as {icon}");
|
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe thumbnail as {icon}");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Button> buttons = new()
|
List<Button> buttons = new()
|
||||||
@ -119,7 +172,7 @@ namespace Bloxstrap.Integrations
|
|||||||
if (universeDetails.Name.Length < 2)
|
if (universeDetails.Name.Length < 2)
|
||||||
universeDetails.Name = $"{universeDetails.Name}\x2800\x2800\x2800";
|
universeDetails.Name = $"{universeDetails.Name}\x2800\x2800\x2800";
|
||||||
|
|
||||||
_rpcClient.SetPresence(new RichPresence
|
_currentPresence = new RichPresence
|
||||||
{
|
{
|
||||||
Details = universeDetails.Name,
|
Details = universeDetails.Name,
|
||||||
State = $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""),
|
State = $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""),
|
||||||
@ -132,11 +185,27 @@ namespace Bloxstrap.Integrations
|
|||||||
SmallImageKey = "roblox",
|
SmallImageKey = "roblox",
|
||||||
SmallImageText = "Roblox"
|
SmallImageText = "Roblox"
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
UpdatePresence();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdatePresence()
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[DiscordRichPresence::UpdatePresence] Updating presence");
|
||||||
|
|
||||||
|
if (_currentPresence is null)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[DiscordRichPresence::UpdatePresence] Presence is empty, clearing");
|
||||||
|
_rpcClient.ClearPresence();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rpcClient.SetPresence(_currentPresence);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine("[DiscordRichPresence::Dispose] Cleaning up Discord RPC and Presence");
|
App.Logger.WriteLine("[DiscordRichPresence::Dispose] Cleaning up Discord RPC and Presence");
|
||||||
|
13
Bloxstrap/Models/GameMessage.cs
Normal file
13
Bloxstrap/Models/GameMessage.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Bloxstrap.Models
|
||||||
|
{
|
||||||
|
public class GameMessage
|
||||||
|
{
|
||||||
|
[JsonPropertyName("command")]
|
||||||
|
public string Command { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("data")]
|
||||||
|
public string Data { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Bloxstrap.Models;
|
||||||
|
|
||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
public class RobloxActivity : IDisposable
|
public class RobloxActivity : IDisposable
|
||||||
@ -16,6 +19,7 @@ namespace Bloxstrap
|
|||||||
private const string GameJoinedEntry = "[FLog::Network] serverId:";
|
private const string GameJoinedEntry = "[FLog::Network] serverId:";
|
||||||
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
|
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
|
||||||
private const string GameTeleportingEntry = "[FLog::SingleSurfaceApp] initiateTeleport";
|
private const string GameTeleportingEntry = "[FLog::SingleSurfaceApp] initiateTeleport";
|
||||||
|
private const string GameMessageEntry = "[FLog::Output] [SendBloxstrapMessage]";
|
||||||
|
|
||||||
private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
|
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]+";
|
private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+";
|
||||||
@ -25,6 +29,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
public event EventHandler? OnGameJoin;
|
public event EventHandler? OnGameJoin;
|
||||||
public event EventHandler? OnGameLeave;
|
public event EventHandler? OnGameLeave;
|
||||||
|
public event EventHandler<GameMessage>? OnGameMessage;
|
||||||
|
|
||||||
// these are values to use assuming the player isn't currently in a game
|
// these are values to use assuming the player isn't currently in a game
|
||||||
// keep in mind ActivityIsTeleport is only reset by DiscordRichPresence when it's done accessing it
|
// keep in mind ActivityIsTeleport is only reset by DiscordRichPresence when it's done accessing it
|
||||||
@ -184,6 +189,37 @@ namespace Bloxstrap
|
|||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Initiating teleport to server ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Initiating teleport to server ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||||
ActivityIsTeleport = true;
|
ActivityIsTeleport = true;
|
||||||
}
|
}
|
||||||
|
else if (entry.Contains(GameMessageEntry))
|
||||||
|
{
|
||||||
|
string messagePlain = entry.Substring(entry.IndexOf(GameMessageEntry) + GameMessageEntry.Length + 1);
|
||||||
|
GameMessage? message;
|
||||||
|
|
||||||
|
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Received message: '{messagePlain}'");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
message = JsonSerializer.Deserialize<GameMessage>(messagePlain);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (JSON deserialization threw an exception)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message is null)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (JSON deserialization returned null)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.IsNullOrEmpty(message.Command))
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (Command is empty)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnGameMessage?.Invoke(this, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user