diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index 7bfb8df..858b85e 100644 --- a/Bloxstrap/App.xaml.cs +++ b/Bloxstrap/App.xaml.cs @@ -110,22 +110,24 @@ namespace Bloxstrap public static async Task GetLatestRelease() { const string LOG_IDENT = "App::GetLatestRelease"; - - GithubRelease? releaseInfo = null; - try { - releaseInfo = await Http.GetJson($"https://api.github.com/repos/{ProjectRepository}/releases/latest"); + var releaseInfo = await Http.GetJson($"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) diff --git a/Bloxstrap/Exceptions/InvalidHTTPResponseException.cs b/Bloxstrap/Exceptions/InvalidHTTPResponseException.cs new file mode 100644 index 0000000..f6b45a0 --- /dev/null +++ b/Bloxstrap/Exceptions/InvalidHTTPResponseException.cs @@ -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) { } + } +} diff --git a/Bloxstrap/Integrations/ActivityWatcher.cs b/Bloxstrap/Integrations/ActivityWatcher.cs index cb329af..164c4d9 100644 --- a/Bloxstrap/Integrations/ActivityWatcher.cs +++ b/Bloxstrap/Integrations/ActivityWatcher.cs @@ -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($"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 "?"; } } diff --git a/Bloxstrap/Integrations/DiscordRichPresence.cs b/Bloxstrap/Integrations/DiscordRichPresence.cs index 729d2c6..3f2513e 100644 --- a/Bloxstrap/Integrations/DiscordRichPresence.cs +++ b/Bloxstrap/Integrations/DiscordRichPresence.cs @@ -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; diff --git a/Bloxstrap/JsonManager.cs b/Bloxstrap/JsonManager.cs index cf4d66a..8e786be 100644 --- a/Bloxstrap/JsonManager.cs +++ b/Bloxstrap/JsonManager.cs @@ -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(); diff --git a/Bloxstrap/Models/UniverseDetails.cs b/Bloxstrap/Models/UniverseDetails.cs index cdf4ec5..aa87501 100644 --- a/Bloxstrap/Models/UniverseDetails.cs +++ b/Bloxstrap/Models/UniverseDetails.cs @@ -21,17 +21,19 @@ return null; } - public static Task FetchSingle(long id) => FetchBulk(id.ToString()); + public static Task FetchSingle(long id) => FetchBulk(id.ToString()); - public static async Task FetchBulk(string ids) + public static async Task FetchBulk(string ids) { var gameDetailResponse = await Http.GetJson>($"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>($"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; } } } diff --git a/Bloxstrap/Resources/Strings.Designer.cs b/Bloxstrap/Resources/Strings.Designer.cs index 3ea5c3a..f38705d 100644 --- a/Bloxstrap/Resources/Strings.Designer.cs +++ b/Bloxstrap/Resources/Strings.Designer.cs @@ -106,29 +106,20 @@ namespace Bloxstrap.Resources { } /// - /// Looks up a localized string similar to left game. + /// Looks up a localized string similar to Failed to query server location.. /// - public static string ActivityTracker_LeftGame { + public static string ActivityWatcher_LocationQueryFailed { get { - return ResourceManager.GetString("ActivityTracker.LeftGame", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to lookup failed. - /// - public static string ActivityTracker_LookupFailed { - get { - return ResourceManager.GetString("ActivityTracker.LookupFailed", resourceCulture); + return ResourceManager.GetString("ActivityWatcher.LocationQueryFailed", resourceCulture); } } /// /// 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.. /// - public static string ActivityTracker_RichPresenceLoadFailed { + public static string ActivityWatcher_RichPresenceLoadFailed { get { - return ResourceManager.GetString("ActivityTracker.RichPresenceLoadFailed", resourceCulture); + return ResourceManager.GetString("ActivityWatcher.RichPresenceLoadFailed", resourceCulture); } } diff --git a/Bloxstrap/Resources/Strings.resx b/Bloxstrap/Resources/Strings.resx index c245328..2380203 100644 --- a/Bloxstrap/Resources/Strings.resx +++ b/Bloxstrap/Resources/Strings.resx @@ -117,12 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - left game - - - lookup failed - Bloxstrap was unable to automatically update to version {0}. Please update it manually by downloading and running it from the website. @@ -1177,10 +1171,13 @@ Are you sure you want to continue? Rejoin - + Your current game will not show on your Discord presence because an error occurred when loading the game information. Game history is only recorded for your current Roblox session. Games will appear here as you leave them or teleport within them. + + Failed to query server location. + diff --git a/Bloxstrap/UI/Elements/ContextMenu/ServerHistory.xaml b/Bloxstrap/UI/Elements/ContextMenu/ServerHistory.xaml index 2b49385..a373896 100644 --- a/Bloxstrap/UI/Elements/ContextMenu/ServerHistory.xaml +++ b/Bloxstrap/UI/Elements/ContextMenu/ServerHistory.xaml @@ -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 @@ + + + + + + diff --git a/Bloxstrap/UI/NotifyIconWrapper.cs b/Bloxstrap/UI/NotifyIconWrapper.cs index cf1dd78..59ac16e 100644 --- a/Bloxstrap/UI/NotifyIconWrapper.cs +++ b/Bloxstrap/UI/NotifyIconWrapper.cs @@ -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, diff --git a/Bloxstrap/UI/ViewModels/ContextMenu/ServerHistoryViewModel.cs b/Bloxstrap/UI/ViewModels/ContextMenu/ServerHistoryViewModel.cs index ead0033..bea9d61 100644 --- a/Bloxstrap/UI/ViewModels/ContextMenu/ServerHistoryViewModel.cs +++ b/Bloxstrap/UI/ViewModels/ContextMenu/ServerHistoryViewModel.cs @@ -10,6 +10,10 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu public List? 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); diff --git a/Bloxstrap/Utilities.cs b/Bloxstrap/Utilities.cs index 9382c9a..4613d01 100644 --- a/Bloxstrap/Utilities.cs +++ b/Bloxstrap/Utilities.cs @@ -5,14 +5,6 @@ namespace Bloxstrap { static class Utilities { - /// - /// Is process running as administrator - /// https://stackoverflow.com/a/11660205 - /// - public static bool IsAdministrator => - new WindowsPrincipal(WindowsIdentity.GetCurrent()) - .IsInRole(WindowsBuiltInRole.Administrator); - public static void ShellExecute(string website) { try diff --git a/Bloxstrap/Utility/Http.cs b/Bloxstrap/Utility/Http.cs index f053208..f95b307 100644 --- a/Bloxstrap/Utility/Http.cs +++ b/Bloxstrap/Utility/Http.cs @@ -2,22 +2,22 @@ { internal static class Http { - public static async Task GetJson(string url) + /// + /// Gets and deserializes a JSON API response to the specified object + /// + /// + /// + /// + /// + public static async Task GetJson(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(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(json)!; } } }