Skip to content

Commit

Permalink
Refactoring of activity tracker handling
Browse files Browse the repository at this point in the history
this will either go really well or horribly wrong
  • Loading branch information
pizzaboxer committed Sep 3, 2024
1 parent dfcd5b6 commit 26b7cbd
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 211 deletions.
183 changes: 86 additions & 97 deletions Bloxstrap/Integrations/ActivityWatcher.cs

Large diffs are not rendered by default.

66 changes: 29 additions & 37 deletions Bloxstrap/Integrations/DiscordRichPresence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ public class DiscordRichPresence : IDisposable
private Queue<Message> _messageQueue = new();

private bool _visible = true;
private long _currentUniverseId;
private DateTime? _timeStartedUniverse;

public DiscordRichPresence(ActivityWatcher activityWatcher)
{
Expand Down Expand Up @@ -70,7 +68,7 @@ public void ProcessRPCMessage(Message message, bool implicitUpdate = true)
if (!buttonQuery.Any())
return;

buttonQuery.First().Url = _activityWatcher.GetActivityDeeplink();
buttonQuery.First().Url = _activityWatcher.Data.GetInviteDeeplink();
}
else if (message.Command == "SetRichPresence")
{
Expand Down Expand Up @@ -185,7 +183,7 @@ public async Task<bool> SetCurrentGame()
{
const string LOG_IDENT = "DiscordRichPresence::SetCurrentGame";

if (!_activityWatcher.ActivityInGame)
if (!_activityWatcher.InGame)
{
App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence");

Expand All @@ -197,50 +195,42 @@ public async Task<bool> SetCurrentGame()
}

string icon = "roblox";
long placeId = _activityWatcher.ActivityPlaceId;

App.Logger.WriteLine(LOG_IDENT, $"Setting presence for Place ID {placeId}");

// TODO: move this to its own function under the activity watcher?
// TODO: show error if information cannot be queried instead of silently failing
var activity = _activityWatcher.Data;
long placeId = activity.PlaceId;

long universeId = _activityWatcher.ActivityUniverseId;
App.Logger.WriteLine(LOG_IDENT, $"Setting presence for Place ID {placeId}");

// preserve time spent playing if we're teleporting between places in the same universe
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
_timeStartedUniverse = DateTime.UtcNow;
var timeStarted = activity.TimeJoined;

_currentUniverseId = universeId;
if (activity.RootActivity is not null)
timeStarted = activity.RootActivity.TimeJoined;

var gameDetailResponse = await Http.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeId}");
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
if (activity.UniverseDetails is null)
{
App.Logger.WriteLine(LOG_IDENT, "Could not get Universe info!");
return false;
await UniverseDetails.FetchSingle(activity.UniverseId);
activity.UniverseDetails = UniverseDetails.LoadFromCache(activity.UniverseId);
}

GameDetailResponse universeDetails = gameDetailResponse.Data.ToArray()[0];
App.Logger.WriteLine(LOG_IDENT, "Got Universe details");
var universeDetails = activity.UniverseDetails;

var universeThumbnailResponse = await Http.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())
{
App.Logger.WriteLine(LOG_IDENT, "Could not get Universe thumbnail info!");
}
else
if (universeDetails is null)
{
icon = universeThumbnailResponse.Data.ToArray()[0].ImageUrl;
App.Logger.WriteLine(LOG_IDENT, $"Got Universe thumbnail as {icon}");
Frontend.ShowMessageBox(Strings.ActivityTracker_RichPresenceLoadFailed, System.Windows.MessageBoxImage.Warning);
return false;
}

icon = universeDetails.Thumbnail.ImageUrl;

List<Button> buttons = new();

if (!App.Settings.Prop.HideRPCButtons && _activityWatcher.ActivityServerType == ServerType.Public)
if (!App.Settings.Prop.HideRPCButtons && activity.ServerType == ServerType.Public)
{
buttons.Add(new Button
{
Label = "Join server",
Url = _activityWatcher.GetActivityDeeplink()
Url = activity.GetInviteDeeplink()
});
}

Expand All @@ -250,32 +240,34 @@ public async Task<bool> SetCurrentGame()
Url = $"https://www.roblox.com/games/{placeId}"
});

if (!_activityWatcher.ActivityInGame || placeId != _activityWatcher.ActivityPlaceId)
if (!_activityWatcher.InGame || placeId != activity.PlaceId)
{
App.Logger.WriteLine(LOG_IDENT, "Aborting presence set because game activity has changed");
return false;
}

string status = _activityWatcher.ActivityServerType switch
string status = _activityWatcher.Data.ServerType switch
{
ServerType.Private => "In a private server",
ServerType.Reserved => "In a reserved server",
_ => $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""),
_ => $"by {universeDetails.Data.Creator.Name}" + (universeDetails.Data.Creator.HasVerifiedBadge ? " ☑️" : ""),
};

if (universeDetails.Name.Length < 2)
universeDetails.Name = $"{universeDetails.Name}\x2800\x2800\x2800";
string universeName = universeDetails.Data.Name;

if (universeName.Length < 2)
universeName = $"{universeName}\x2800\x2800\x2800";

_currentPresence = new DiscordRPC.RichPresence
{
Details = $"Playing {universeDetails.Name}",
Details = $"Playing {universeName}",
State = status,
Timestamps = new Timestamps { Start = _timeStartedUniverse },
Timestamps = new Timestamps { Start = timeStarted.ToUniversalTime() },
Buttons = buttons.ToArray(),
Assets = new Assets
{
LargeImageKey = icon,
LargeImageText = universeDetails.Name,
LargeImageText = universeName,
SmallImageKey = "roblox",
SmallImageText = "Roblox"
}
Expand Down
98 changes: 98 additions & 0 deletions Bloxstrap/Models/ActivityData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System.Web;
using System.Windows.Input;

using CommunityToolkit.Mvvm.Input;

namespace Bloxstrap.Models
{
public class ActivityData
{
private long _universeId = 0;

/// <summary>
/// If the current activity stems from an in-universe teleport, then this will be
/// set to the activity that corresponds to the initial game join
/// </summary>
public ActivityData? RootActivity;

public long UniverseId
{
get => _universeId;
set
{
_universeId = value;
UniverseDetails.LoadFromCache(value);
}
}

public long PlaceId { get; set; } = 0;

public string JobId { get; set; } = String.Empty;

/// <summary>
/// This will be empty unless the server joined is a private server
/// </summary>
public string AccessCode { get; set; } = String.Empty;

public string MachineAddress { get; set; } = String.Empty;

public bool IsTeleport { get; set; } = false;

public ServerType ServerType { get; set; } = ServerType.Public;

public DateTime TimeJoined { get; set; }

public DateTime? TimeLeft { get; set; }

/// <summary>
/// This gets set to true if this activity is from a same-universe teleport, whether public or reserved
/// </summary>
public bool IsRetainedUniverse { get; set; } = false;

// everything below here is optional strictly for bloxstraprpc, discord rich presence, or game history

/// <summary>
/// This is intended only for other people to use, i.e. context menu invite link, rich presence joining
/// </summary>
public string RPCLaunchData { get; set; } = String.Empty;

public UniverseDetails? UniverseDetails { get; set; }

public string GameHistoryDescription
{
get
{
string desc = String.Format("{0} • {1} - {2}", UniverseDetails?.Data.Creator.Name, TimeJoined.ToString("h:mm tt"), TimeLeft?.ToString("h:mm tt"));

if (ServerType != ServerType.Public)
desc += "" + ServerType.ToTranslatedString();

return desc;
}
}

public ICommand RejoinServerCommand => new RelayCommand(RejoinServer);

public string GetInviteDeeplink(bool launchData = true)
{
string deeplink = $"roblox://experiences/start?placeId={PlaceId}";

if (ServerType == ServerType.Private)
deeplink += "&accessCode=" + AccessCode;
else
deeplink += "&gameInstanceId=" + JobId;

if (launchData && !String.IsNullOrEmpty(RPCLaunchData))
deeplink += "&launchData=" + HttpUtility.UrlEncode(RPCLaunchData);

return deeplink;
}

private void RejoinServer()
{
string playerPath = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");

Process.Start(playerPath, GetInviteDeeplink(false));
}
}
}
38 changes: 0 additions & 38 deletions Bloxstrap/Models/ActivityHistoryEntry.cs

This file was deleted.

50 changes: 50 additions & 0 deletions Bloxstrap/Models/UniverseDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace Bloxstrap.Models
{
public class UniverseDetails
{
private static List<UniverseDetails> _cache { get; set; } = new();

public GameDetailResponse Data { get; set; } = null!;

/// <summary>
/// Returns data for a 128x128 icon
/// </summary>
public ThumbnailResponse Thumbnail { get; set; } = null!;

public static UniverseDetails? LoadFromCache(long id)
{
var cacheQuery = _cache.Where(x => x.Data?.Id == id);

if (cacheQuery.Any())
return cacheQuery.First();

return null;
}

public static Task<bool> FetchSingle(long id) => FetchBulk(id.ToString());

public static async Task<bool> FetchBulk(string ids)
{
var gameDetailResponse = await Http.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={ids}");
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
return false;

var universeThumbnailResponse = await Http.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={ids}&returnPolicy=PlaceHolder&size=128x128&format=Png&isCircular=false");
if (universeThumbnailResponse is null || !universeThumbnailResponse.Data.Any())
return false;

foreach (string strId in ids.Split(','))
{
long id = long.Parse(strId);

_cache.Add(new UniverseDetails
{
Data = gameDetailResponse.Data.Where(x => x.Id == id).First(),
Thumbnail = universeThumbnailResponse.Data.Where(x => x.TargetId == id).First(),
});
}

return true;
}
}
}
24 changes: 21 additions & 3 deletions Bloxstrap/Resources/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 26b7cbd

Please sign in to comment.