Merge branch 'main' into user-pfp-discord-rpc

This commit is contained in:
axell 2024-09-04 17:16:11 +02:00 committed by GitHub
commit 3510730a6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 123 additions and 79 deletions

View File

@ -110,22 +110,24 @@ namespace Bloxstrap
public static async Task<GithubRelease?> GetLatestRelease()
{
const string LOG_IDENT = "App::GetLatestRelease";
GithubRelease? releaseInfo = null;
try
{
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
var releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
if (releaseInfo is null || releaseInfo.Assets is null)
{
Logger.WriteLine(LOG_IDENT, "Encountered invalid data");
return null;
}
return releaseInfo;
}
catch (Exception ex)
{
Logger.WriteException(LOG_IDENT, ex);
}
return releaseInfo;
return null;
}
protected override void OnStartup(StartupEventArgs e)

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.Exceptions
{
internal class InvalidHTTPResponseException : Exception
{
public InvalidHTTPResponseException(string message) : base(message) { }
}
}

View File

@ -1,4 +1,6 @@
namespace Bloxstrap.Integrations
using System.Windows;
namespace Bloxstrap.Integrations
{
public class ActivityWatcher : IDisposable
{
@ -388,21 +390,19 @@
string location = "";
var ipInfo = await Http.GetJson<IPInfoResponse>($"https://ipinfo.io/{Data.MachineAddress}/json");
if (ipInfo is null)
return $"? ({Strings.ActivityTracker_LookupFailed})";
if (String.IsNullOrEmpty(ipInfo.City))
throw new InvalidHTTPResponseException("Reported city was blank");
if (string.IsNullOrEmpty(ipInfo.Country))
location = "?";
else if (ipInfo.City == ipInfo.Region)
if (ipInfo.City == ipInfo.Region)
location = $"{ipInfo.Region}, {ipInfo.Country}";
else
location = $"{ipInfo.City}, {ipInfo.Region}, {ipInfo.Country}";
if (!InGame)
return $"? ({Strings.ActivityTracker_LeftGame})";
GeolocationCache[Data.MachineAddress] = location;
if (!InGame)
return "";
return location;
}
catch (Exception ex)
@ -410,7 +410,9 @@
App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {Data.MachineAddress}");
App.Logger.WriteException(LOG_IDENT, ex);
return $"? ({Strings.ActivityTracker_LookupFailed})";
Frontend.ShowMessageBox($"{Strings.ActivityWatcher_LocationQueryFailed}\n\n{ex.Message}", MessageBoxImage.Warning);
return "?";
}
}

View File

@ -1,3 +1,4 @@
using System.Windows;
using DiscordRPC;
namespace Bloxstrap.Integrations
@ -207,17 +208,21 @@ namespace Bloxstrap.Integrations
if (activity.UniverseDetails is null)
{
await UniverseDetails.FetchSingle(activity.UniverseId);
try
{
await UniverseDetails.FetchSingle(activity.UniverseId);
}
catch (Exception ex)
{
App.Logger.WriteException(LOG_IDENT, ex);
Frontend.ShowMessageBox($"{Strings.ActivityWatcher_RichPresenceLoadFailed}\n\n{ex.Message}", MessageBoxImage.Warning);
return false;
}
activity.UniverseDetails = UniverseDetails.LoadFromCache(activity.UniverseId);
}
var universeDetails = activity.UniverseDetails;
if (universeDetails is null)
{
Frontend.ShowMessageBox(Strings.ActivityTracker_RichPresenceLoadFailed, System.Windows.MessageBoxImage.Warning);
return false;
}
var universeDetails = activity.UniverseDetails!;
icon = universeDetails.Thumbnail.ImageUrl;

View File

@ -46,7 +46,7 @@ namespace Bloxstrap
message = Strings.JsonManager_FastFlagsLoadFailed;
if (!String.IsNullOrEmpty(message))
Frontend.ShowMessageBox($"{message}\n\n{ex.GetType()}: {ex.Message}", System.Windows.MessageBoxImage.Warning);
Frontend.ShowMessageBox($"{message}\n\n{ex.Message}", System.Windows.MessageBoxImage.Warning);
}
Save();

View File

@ -21,17 +21,19 @@
return null;
}
public static Task<bool> FetchSingle(long id) => FetchBulk(id.ToString());
public static Task FetchSingle(long id) => FetchBulk(id.ToString());
public static async Task<bool> FetchBulk(string ids)
public static async Task 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;
if (!gameDetailResponse.Data.Any())
throw new InvalidHTTPResponseException("Roblox API for Game Details returned invalid data");
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;
if (!universeThumbnailResponse.Data.Any())
throw new InvalidHTTPResponseException("Roblox API for Game Thumbnails returned invalid data");
foreach (string strId in ids.Split(','))
{
@ -43,8 +45,6 @@
Thumbnail = universeThumbnailResponse.Data.Where(x => x.TargetId == id).First(),
});
}
return true;
}
}
}

View File

@ -106,29 +106,20 @@ namespace Bloxstrap.Resources {
}
/// <summary>
/// Looks up a localized string similar to left game.
/// Looks up a localized string similar to Failed to query server location..
/// </summary>
public static string ActivityTracker_LeftGame {
public static string ActivityWatcher_LocationQueryFailed {
get {
return ResourceManager.GetString("ActivityTracker.LeftGame", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to lookup failed.
/// </summary>
public static string ActivityTracker_LookupFailed {
get {
return ResourceManager.GetString("ActivityTracker.LookupFailed", resourceCulture);
return ResourceManager.GetString("ActivityWatcher.LocationQueryFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your current game will not show on your Discord presence because an error occurred when loading the game information..
/// </summary>
public static string ActivityTracker_RichPresenceLoadFailed {
public static string ActivityWatcher_RichPresenceLoadFailed {
get {
return ResourceManager.GetString("ActivityTracker.RichPresenceLoadFailed", resourceCulture);
return ResourceManager.GetString("ActivityWatcher.RichPresenceLoadFailed", resourceCulture);
}
}

View File

@ -117,12 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActivityTracker.LeftGame" xml:space="preserve">
<value>left game</value>
</data>
<data name="ActivityTracker.LookupFailed" xml:space="preserve">
<value>lookup failed</value>
</data>
<data name="Bootstrapper.AutoUpdateFailed" xml:space="preserve">
<value>Bloxstrap was unable to automatically update to version {0}. Please update it manually by downloading and running it from the website.</value>
</data>
@ -1177,10 +1171,13 @@ Are you sure you want to continue?</value>
<data name="ContextMenu.GameHistory.Rejoin" xml:space="preserve">
<value>Rejoin</value>
</data>
<data name="ActivityTracker.RichPresenceLoadFailed" xml:space="preserve">
<data name="ActivityWatcher.RichPresenceLoadFailed" xml:space="preserve">
<value>Your current game will not show on your Discord presence because an error occurred when loading the game information.</value>
</data>
<data name="ContextMenu.GameHistory.Description" xml:space="preserve">
<value>Game history is only recorded for your current Roblox session. Games will appear here as you leave them or teleport within them.</value>
</data>
<data name="ActivityWatcher.LocationQueryFailed" xml:space="preserve">
<value>Failed to query server location.</value>
</data>
</root>

View File

@ -8,6 +8,7 @@
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.ContextMenu"
xmlns:resources="clr-namespace:Bloxstrap.Resources"
xmlns:enums="clr-namespace:Bloxstrap.Enums"
d:DataContext="{d:DesignInstance Type=models:ServerHistoryViewModel}"
mc:Ignorable="d"
Title="{x:Static resources:Strings.ContextMenu_GameHistory_Title}"
@ -30,11 +31,24 @@
<TextBlock Grid.Row="1" Margin="16,8,16,0" Text="{x:Static resources:Strings.ContextMenu_GameHistory_Description}" TextWrapping="Wrap" />
<TextBlock Grid.Row="2" Margin="16,8,16,0" Text="{Binding Error, Mode=OneWay}" TextWrapping="Wrap">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding LoadState, Mode=OneWay}" Value="{x:Static enums:GenericTriState.Failed}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</TextBlock.Style>
</TextBlock>
<Border Grid.Row="2">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding GameHistory, Mode=OneWay}" Value="{x:Null}">
<DataTrigger Binding="{Binding LoadState, Mode=OneWay}" Value="{x:Static enums:GenericTriState.Unknown}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
@ -49,11 +63,11 @@
<ListView.Style>
<Style TargetType="ListView" BasedOn="{StaticResource {x:Type ListView}}">
<Style.Triggers>
<DataTrigger Binding="{Binding GameHistory, Mode=OneWay}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
<DataTrigger Binding="{Binding LoadState, Mode=OneWay}" Value="{x:Static enums:GenericTriState.Successful}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</ListView.Style>
<ListView.ItemTemplate>

View File

@ -60,6 +60,10 @@ namespace Bloxstrap.UI
return;
string serverLocation = await _activityWatcher.GetServerLocation();
if (string.IsNullOrEmpty(serverLocation))
return;
string title = _activityWatcher.Data.ServerType switch
{
ServerType.Public => Strings.ContextMenu_ServerInformation_Notification_Title_Public,

View File

@ -10,6 +10,10 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
public List<ActivityData>? GameHistory { get; private set; }
public GenericTriState LoadState { get; private set; } = GenericTriState.Unknown;
public string Error { get; private set; } = String.Empty;
public ICommand CloseWindowCommand => new RelayCommand(RequestClose);
public EventHandler? RequestCloseEvent;
@ -25,6 +29,9 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
private async void LoadData()
{
LoadState = GenericTriState.Unknown;
OnPropertyChanged(nameof(LoadState));
var entries = _activityWatcher.History.Where(x => x.UniverseDetails is null);
if (entries.Any())
@ -32,8 +39,22 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
// TODO: this will duplicate universe ids
string universeIds = String.Join(',', entries.Select(x => x.UniverseId));
if (!await UniverseDetails.FetchBulk(universeIds))
try
{
await UniverseDetails.FetchBulk(universeIds);
}
catch (Exception ex)
{
App.Logger.WriteException("ServerHistoryViewModel::LoadData", ex);
Error = ex.Message;
OnPropertyChanged(nameof(Error));
LoadState = GenericTriState.Failed;
OnPropertyChanged(nameof(LoadState));
return;
}
foreach (var entry in entries)
entry.UniverseDetails = UniverseDetails.LoadFromCache(entry.UniverseId);
@ -64,6 +85,9 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
}
OnPropertyChanged(nameof(GameHistory));
LoadState = GenericTriState.Successful;
OnPropertyChanged(nameof(LoadState));
}
private void RequestClose() => RequestCloseEvent?.Invoke(this, EventArgs.Empty);

View File

@ -5,14 +5,6 @@ namespace Bloxstrap
{
static class Utilities
{
/// <summary>
/// Is process running as administrator
/// https://stackoverflow.com/a/11660205
/// </summary>
public static bool IsAdministrator =>
new WindowsPrincipal(WindowsIdentity.GetCurrent())
.IsInRole(WindowsBuiltInRole.Administrator);
public static void ShellExecute(string website)
{
try

View File

@ -2,22 +2,22 @@
{
internal static class Http
{
public static async Task<T?> GetJson<T>(string url)
/// <summary>
/// Gets and deserializes a JSON API response to the specified object
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <exception cref="HttpRequestException"></exception>
/// <exception cref="JsonException"></exception>
public static async Task<T> GetJson<T>(string url)
{
string LOG_IDENT = $"Http::GetJson<{typeof(T).Name}>";
var request = await App.HttpClient.GetAsync(url);
string json = await App.HttpClient.GetStringAsync(url);
request.EnsureSuccessStatusCode();
try
{
return JsonSerializer.Deserialize<T>(json);
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to deserialize JSON for {url}!");
App.Logger.WriteException(LOG_IDENT, ex);
return default;
}
string json = await request.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(json)!;
}
}
}