Show account on discord rich presence (#2255)

* add functionality for grabbing userid

* Create UserInfoResponse.cs

* add user pfp small image thingy

* add missing semicolons

* get rid of semicolons

* debugging (remove later)

* make properties nullable

* forgot to put dollar sign before interpolated string

* make properties that cant be nullable not nullable

* more debugging

* remove thing

* remove other thing

* remove thing (again) (again)

* remove thing (again) (again) (again)

* add space between username and displayname to make it more visually pleasing

* matt review changes (better code readability)

* add strings for show account on profile

* add AccountShownOnProfile setting

* add DiscordActivityJoinEnabled to integrations viewmodel

* fix accidentally swapping 2 variables

* refrence correct variables

* refrence correct variables (again)

* add showaccountonprofile strings

* add option to integrations page

* add missing < that somehow got lost

* make that its own option

* dont invert that value

* dont invert that (again)

* Update IntegrationsViewModel.cs

* fix grammatical issue in string

* move else to new line

* fix merge conflicts

* move gameJoinLoadTime check

* matt review changes

* handle if parsing userid fails

---------

Co-authored-by: pizzaboxer <pizzaboxer@pizzaboxer.xyz>
This commit is contained in:
axel 2024-09-28 22:52:12 +02:00 committed by GitHub
parent 0acf1ee24b
commit 55f5ef48e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 126 additions and 7 deletions

View File

@ -17,7 +17,9 @@
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 GameLeavingEntry = "[FLog::SingleSurfaceApp] leaveUGCGameInternal"; private const string GameLeavingEntry = "[FLog::SingleSurfaceApp] leaveUGCGameInternal";
private const string GameJoinLoadTimeEntry = "[FLog::GameJoinLoadTime] Report game_join_loadtime:";
private const string GameJoinLoadTimeEntryPattern = ", userid:([0-9]+)";
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 GameJoiningPrivateServerPattern = @"""accessCode"":""([0-9a-f\-]{36})"""; private const string GameJoiningPrivateServerPattern = @"""accessCode"":""([0-9a-f\-]{36})""";
private const string GameJoiningUniversePattern = @"universeid:([0-9]+)"; private const string GameJoiningUniversePattern = @"universeid:([0-9]+)";
@ -209,6 +211,28 @@
{ {
// We are not confirmed to be in a game, but we are in the process of joining one // We are not confirmed to be in a game, but we are in the process of joining one
if (entry.Contains(GameJoinLoadTimeEntry))
{
Match match = Regex.Match(entry, GameJoinLoadTimeEntryPattern);
if (match.Groups.Count != 2)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join load time entry");
App.Logger.WriteLine(LOG_IDENT, entry);
return;
}
if (!UInt64.TryParse(match.Groups[1].Value, out ulong result))
{
App.Logger.WriteLine(LOG_IDENT, "Failed to parse userid from game join load time entry");
App.Logger.WriteLine(LOG_IDENT, match.Groups[1].Value);
return;
}
Data.UserId = result;
App.Logger.WriteLine(LOG_IDENT, $"Got userid as {Data.UserId}");
}
if (entry.Contains(GameJoiningUniverseEntry)) if (entry.Contains(GameJoiningUniverseEntry))
{ {
var match = Regex.Match(entry, GameJoiningUniversePattern); var match = Regex.Match(entry, GameJoiningUniversePattern);
@ -279,7 +303,6 @@
History.Insert(0, Data); History.Insert(0, Data);
InGame = false; InGame = false;
Data = new(); Data = new();
OnGameLeave?.Invoke(this, new EventArgs()); OnGameLeave?.Invoke(this, new EventArgs());

View File

@ -1,5 +1,5 @@
using System.Windows; using System.Windows;
using Bloxstrap.Models.RobloxApi;
using DiscordRPC; using DiscordRPC;
namespace Bloxstrap.Integrations namespace Bloxstrap.Integrations
@ -192,6 +192,9 @@ namespace Bloxstrap.Integrations
} }
string icon = "roblox"; string icon = "roblox";
string smallImageText = "Roblox";
string smallImage = "roblox";
var activity = _activityWatcher.Data; var activity = _activityWatcher.Data;
long placeId = activity.PlaceId; long placeId = activity.PlaceId;
@ -224,6 +227,31 @@ namespace Bloxstrap.Integrations
icon = universeDetails.Thumbnail.ImageUrl; icon = universeDetails.Thumbnail.ImageUrl;
if (App.Settings.Prop.AccountShownOnProfile)
{
var userPfpResponse = await Http.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds={activity.UserId}&size=180x180&format=Png&isCircular=false"); //we can remove '-headshot' from the url if we want a full avatar picture
if (userPfpResponse is null || !userPfpResponse.Data.Any())
{
App.Logger.WriteLine(LOG_IDENT, "Could not get user thumbnail info!");
}
else
{
smallImage = userPfpResponse.Data.First().ImageUrl;
App.Logger.WriteLine(LOG_IDENT, $"Got user thumbnail as {smallImage}");
}
var userInfoResponse = await Http.GetJson<UserInfoResponse>($"https://users.roblox.com/v1/users/{activity.UserId}");
if (userInfoResponse is null)
{
App.Logger.WriteLine(LOG_IDENT, "Could not get user info!");
}
else
{
smallImageText = userInfoResponse.DisplayName + $" (@{userInfoResponse.Username})"; //example: john doe (@johndoe)
App.Logger.WriteLine(LOG_IDENT, $"Got user info as {smallImageText}");
}
}
if (!_activityWatcher.InGame || placeId != activity.PlaceId) if (!_activityWatcher.InGame || placeId != activity.PlaceId)
{ {
App.Logger.WriteLine(LOG_IDENT, "Aborting presence set because game activity has changed"); App.Logger.WriteLine(LOG_IDENT, "Aborting presence set because game activity has changed");
@ -251,9 +279,9 @@ namespace Bloxstrap.Integrations
Assets = new Assets Assets = new Assets
{ {
LargeImageKey = icon, LargeImageKey = icon,
LargeImageText = universeName, LargeImageText = universeDetails.Data.Name,
SmallImageKey = "roblox", SmallImageKey = smallImage,
SmallImageText = "Roblox" SmallImageText = smallImageText
} }
}; };

View File

@ -0,0 +1,26 @@
namespace Bloxstrap.Models.RobloxApi
{
/// <summary>
/// Roblox.Web.Responses.Users.UserInfoResponse
/// </summary>
public class UserInfoResponse
{
[JsonPropertyName("description")]
public string ProfileDescription { get; set; } = null!;
[JsonPropertyName("created")]
public string JoinDate { get; set; } = null!;
[JsonPropertyName("isBanned")]
public bool IsBanned { get; set; }
[JsonPropertyName("hasVerifiedBadge")]
public bool HasVerifiedBadge { get; set; }
[JsonPropertyName("name")]
public string Username { get; set; } = null!;
[JsonPropertyName("displayName")]
public string DisplayName { get; set; } = null!;
}
}

View File

@ -35,6 +35,8 @@ namespace Bloxstrap.Models.Entities
/// </summary> /// </summary>
public string AccessCode { get; set; } = string.Empty; public string AccessCode { get; set; } = string.Empty;
public ulong UserId { get; set; } = 0;
public string MachineAddress { get; set; } = string.Empty; public string MachineAddress { get; set; } = string.Empty;
public bool MachineAddressValid => !string.IsNullOrEmpty(MachineAddress) && !MachineAddress.StartsWith("10."); public bool MachineAddressValid => !string.IsNullOrEmpty(MachineAddress) && !MachineAddress.StartsWith("10.");

View File

@ -21,6 +21,7 @@ namespace Bloxstrap.Models.Persistable
public bool EnableActivityTracking { get; set; } = true; public bool EnableActivityTracking { get; set; } = true;
public bool UseDiscordRichPresence { get; set; } = true; public bool UseDiscordRichPresence { get; set; } = true;
public bool HideRPCButtons { get; set; } = true; public bool HideRPCButtons { get; set; } = true;
public bool AccountShownOnProfile { get; set; } = true;
public bool ShowServerDetails { get; set; } = false; public bool ShowServerDetails { get; set; } = false;
public ObservableCollection<CustomIntegration> CustomIntegrations { get; set; } = new(); public ObservableCollection<CustomIntegration> CustomIntegrations { get; set; } = new();

View File

@ -2665,6 +2665,24 @@ namespace Bloxstrap.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Show account on profile.
/// </summary>
public static string Menu_Integrations_ShowAccountOnProfile_Title {
get {
return ResourceManager.GetString("Menu.Integrations.ShowAccountOnProfile.Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows what Roblox account your using on your Discord profile.
/// </summary>
public static string Menu_Integrations_ShowAccountOnProfile_Description {
get {
return ResourceManager.GetString("Menu.Integrations.ShowAccountOnProfile.Description", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Allow activity joining. /// Looks up a localized string similar to Allow activity joining.
/// </summary> /// </summary>

View File

@ -701,6 +701,12 @@ Selecting 'No' will ignore this warning and continue installation.</value>
<data name="Menu.Integrations.AllowActivityJoining.Title" xml:space="preserve"> <data name="Menu.Integrations.AllowActivityJoining.Title" xml:space="preserve">
<value>Allow activity joining</value> <value>Allow activity joining</value>
</data> </data>
<data name="Menu.Integrations.ShowAccountOnProfile.Description" xml:space="preserve">
<value>Shows which Roblox account you are using on your Discord profile.</value>
</data>
<data name="Menu.Integrations.ShowAccountOnProfile.Title" xml:space="preserve">
<value>Show account on profile</value>
</data>
<data name="Menu.Integrations.Custom.AppLocation" xml:space="preserve"> <data name="Menu.Integrations.Custom.AppLocation" xml:space="preserve">
<value>Application Location</value> <value>Application Location</value>
</data> </data>

View File

@ -58,6 +58,13 @@
<ui:ToggleSwitch IsChecked="{Binding DiscordActivityJoinEnabled, Mode=TwoWay}" /> <ui:ToggleSwitch IsChecked="{Binding DiscordActivityJoinEnabled, Mode=TwoWay}" />
</controls:OptionControl> </controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_Integrations_ShowAccountOnProfile_Title}"
Description="{x:Static resources:Strings.Menu_Integrations_ShowAccountOnProfile_Description}"
IsEnabled="{Binding InnerContent.IsChecked, ElementName=DiscordActivityOption, Mode=OneWay}">
<ui:ToggleSwitch IsChecked="{Binding DiscordAccountOnProfile, Mode=TwoWay}" />
</controls:OptionControl>
<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Title}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" /> <TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Title}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Description}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorSecondaryBrush}" /> <TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Description}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<Grid Margin="0,8,0,0"> <Grid Margin="0,8,0,0">

View File

@ -100,7 +100,9 @@ namespace Bloxstrap.UI.ViewModels.Settings
if (!value) if (!value)
{ {
DiscordActivityJoinEnabled = value; DiscordActivityJoinEnabled = value;
DiscordAccountOnProfile = value;
OnPropertyChanged(nameof(DiscordActivityJoinEnabled)); OnPropertyChanged(nameof(DiscordActivityJoinEnabled));
OnPropertyChanged(nameof(DiscordAccountOnProfile));
} }
} }
} }
@ -111,6 +113,12 @@ namespace Bloxstrap.UI.ViewModels.Settings
set => App.Settings.Prop.HideRPCButtons = !value; set => App.Settings.Prop.HideRPCButtons = !value;
} }
public bool DiscordAccountOnProfile
{
get => App.Settings.Prop.AccountShownOnProfile;
set => App.Settings.Prop.AccountShownOnProfile = value;
}
public bool DisableAppPatchEnabled public bool DisableAppPatchEnabled
{ {
get => App.Settings.Prop.UseDisableAppPatch; get => App.Settings.Prop.UseDisableAppPatch;