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

This commit is contained in:
axell 2024-09-05 15:24:18 +02:00 committed by GitHub
commit 3855ac68dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 149 additions and 115 deletions

9
Bloxstrap/GlobalCache.cs Normal file
View File

@ -0,0 +1,9 @@
namespace Bloxstrap
{
public static class GlobalCache
{
public static readonly Dictionary<string, Task> PendingTasks = new();
public static readonly Dictionary<string, string> ServerLocation = new();
}
}

View File

@ -494,7 +494,7 @@ namespace Bloxstrap
string oldStartPath = Path.Combine(Paths.WindowsStartMenu, "Bloxstrap");
if (File.Exists(oldDesktopPath))
File.Move(oldDesktopPath, DesktopShortcut);
File.Move(oldDesktopPath, DesktopShortcut, true);
if (Directory.Exists(oldStartPath))
{

View File

@ -127,7 +127,6 @@ namespace Bloxstrap.Integrations
}
}
// TODO: i need to double check how this handles failed game joins (connection error, invalid permissions, etc)
private void ReadLogEntry(string entry)
{
const string LOG_IDENT = "ActivityWatcher::ReadLogEntry";
@ -144,7 +143,17 @@ namespace Bloxstrap.Integrations
App.Logger.WriteLine(LOG_IDENT, $"Read {_logEntriesRead} log entries");
if (entry.Contains(GameLeavingEntry))
OnAppClose?.Invoke(this, new EventArgs());
{
App.Logger.WriteLine(LOG_IDENT, "User is back into the desktop app");
OnAppClose?.Invoke(this, EventArgs.Empty);
if (Data.PlaceId != 0 && !InGame)
{
App.Logger.WriteLine(LOG_IDENT, "User appears to be leaving from a cancelled/errored join");
Data = new();
}
}
if (!InGame && Data.PlaceId == 0)
{
@ -183,6 +192,9 @@ namespace Bloxstrap.Integrations
Data.JobId = match.Groups[1].Value;
Data.MachineAddress = match.Groups[3].Value;
if (App.Settings.Prop.ShowServerDetails && Data.MachineAddressValid)
_ = Data.QueryServerLocation();
if (_teleportMarker)
{
Data.IsTeleport = true;
@ -195,7 +207,7 @@ namespace Bloxstrap.Integrations
_reservedTeleportMarker = false;
}
App.Logger.WriteLine(LOG_IDENT, $"Joining Game ({Data.PlaceId}/{Data.JobId}/{Data.MachineAddress})");
App.Logger.WriteLine(LOG_IDENT, $"Joining Game ({Data})");
}
}
else if (!InGame && Data.PlaceId != 0)
@ -234,7 +246,7 @@ namespace Bloxstrap.Integrations
{
var lastActivity = History.First();
if (lastActivity is not null && Data.UniverseId == lastActivity.UniverseId && Data.IsTeleport)
if (Data.UniverseId == lastActivity.UniverseId && Data.IsTeleport)
Data.RootActivity = lastActivity.RootActivity ?? lastActivity;
}
}
@ -251,7 +263,10 @@ namespace Bloxstrap.Integrations
Data.MachineAddress = match.Groups[1].Value;
App.Logger.WriteLine(LOG_IDENT, $"Server is UDMUX protected ({Data.PlaceId}/{Data.JobId}/{Data.MachineAddress})");
if (App.Settings.Prop.ShowServerDetails)
_ = Data.QueryServerLocation();
App.Logger.WriteLine(LOG_IDENT, $"Server is UDMUX protected ({Data})");
}
else if (entry.Contains(GameJoinedEntry))
{
@ -264,7 +279,7 @@ namespace Bloxstrap.Integrations
return;
}
App.Logger.WriteLine(LOG_IDENT, $"Joined Game ({Data.PlaceId}/{Data.JobId}/{Data.MachineAddress})");
App.Logger.WriteLine(LOG_IDENT, $"Joined Game ({Data})");
InGame = true;
Data.TimeJoined = DateTime.Now;
@ -278,7 +293,7 @@ namespace Bloxstrap.Integrations
if (entry.Contains(GameDisconnectedEntry))
{
App.Logger.WriteLine(LOG_IDENT, $"Disconnected from Game ({Data.PlaceId}/{Data.JobId}/{Data.MachineAddress})");
App.Logger.WriteLine(LOG_IDENT, $"Disconnected from Game ({Data})");
Data.TimeLeft = DateTime.Now;
History.Insert(0, Data);
@ -290,7 +305,7 @@ namespace Bloxstrap.Integrations
}
else if (entry.Contains(GameTeleportingEntry))
{
App.Logger.WriteLine(LOG_IDENT, $"Initiating teleport to server ({Data.PlaceId}/{Data.JobId}/{Data.MachineAddress})");
App.Logger.WriteLine(LOG_IDENT, $"Initiating teleport to server ({Data})");
_teleportMarker = true;
}
else if (_teleportMarker && entry.Contains(GameJoiningReservedServerEntry))
@ -378,44 +393,6 @@ namespace Bloxstrap.Integrations
}
}
public async Task<string> GetServerLocation()
{
const string LOG_IDENT = "ActivityWatcher::GetServerLocation";
if (GeolocationCache.ContainsKey(Data.MachineAddress))
return GeolocationCache[Data.MachineAddress];
try
{
string location = "";
var ipInfo = await Http.GetJson<IPInfoResponse>($"https://ipinfo.io/{Data.MachineAddress}/json");
if (String.IsNullOrEmpty(ipInfo.City))
throw new InvalidHTTPResponseException("Reported city was blank");
if (ipInfo.City == ipInfo.Region)
location = $"{ipInfo.Region}, {ipInfo.Country}";
else
location = $"{ipInfo.City}, {ipInfo.Region}, {ipInfo.Country}";
GeolocationCache[Data.MachineAddress] = location;
if (!InGame)
return "";
return location;
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {Data.MachineAddress}");
App.Logger.WriteException(LOG_IDENT, ex);
Frontend.ShowMessageBox($"{Strings.ActivityWatcher_LocationQueryFailed}\n\n{ex.Message}", MessageBoxImage.Warning);
return "?";
}
}
public void Dispose()
{
IsDisposed = true;

View File

@ -1,4 +1,5 @@
using System.Web;
using System.Windows;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
@ -38,6 +39,8 @@ namespace Bloxstrap.Models
public string UserId { get; set; } = String.Empty;
public bool MachineAddressValid => !String.IsNullOrEmpty(MachineAddress) && !MachineAddress.StartsWith("10.");
public bool IsTeleport { get; set; } = false;
public ServerType ServerType { get; set; } = ServerType.Public;
@ -85,6 +88,55 @@ namespace Bloxstrap.Models
return deeplink;
}
public async Task<string> QueryServerLocation()
{
const string LOG_IDENT = "ActivityData::QueryServerLocation";
if (!MachineAddressValid)
throw new InvalidOperationException($"Machine address is invalid ({MachineAddress})");
if (GlobalCache.PendingTasks.TryGetValue(MachineAddress, out Task? task))
await task;
if (GlobalCache.ServerLocation.TryGetValue(MachineAddress, out string? location))
return location;
try
{
location = "";
var ipInfoTask = Http.GetJson<IPInfoResponse>($"https://ipinfo.io/{MachineAddress}/json");
GlobalCache.PendingTasks.Add(MachineAddress, ipInfoTask);
var ipInfo = await ipInfoTask;
GlobalCache.PendingTasks.Remove(MachineAddress);
if (String.IsNullOrEmpty(ipInfo.City))
throw new InvalidHTTPResponseException("Reported city was blank");
if (ipInfo.City == ipInfo.Region)
location = $"{ipInfo.Region}, {ipInfo.Country}";
else
location = $"{ipInfo.City}, {ipInfo.Region}, {ipInfo.Country}";
GlobalCache.ServerLocation[MachineAddress] = location;
return location;
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {MachineAddress}");
App.Logger.WriteException(LOG_IDENT, ex);
Frontend.ShowMessageBox($"{Strings.ActivityWatcher_LocationQueryFailed}\n\n{ex.Message}", MessageBoxImage.Warning);
return "?";
}
}
public override string ToString() => $"{PlaceId}/{JobId}";
private void RejoinServer()
{
string playerPath = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");

View File

@ -719,15 +719,6 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Loading, please wait....
/// </summary>
public static string ContextMenu_ServerInformation_Loading {
get {
return ResourceManager.GetString("ContextMenu.ServerInformation.Loading", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Location.
/// </summary>
@ -2727,7 +2718,25 @@ namespace Bloxstrap.Resources {
}
/// <summary>
/// Looks up a localized string similar to This feature requires activity tracking to be enabled and the Discord desktop app to be installed and running..
/// Looks up a localized string similar to When in-game, you&apos;ll be able to see where your server is located via [ipinfo.io]({0})..
/// </summary>
public static string Menu_Integrations_QueryServerLocation_Description {
get {
return ResourceManager.GetString("Menu.Integrations.QueryServerLocation.Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Query server location.
/// </summary>
public static string Menu_Integrations_QueryServerLocation_Title {
get {
return ResourceManager.GetString("Menu.Integrations.QueryServerLocation.Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This feature requires activity tracking to be enabled and the Discord desktop app to be installed and running. [Find out more]({0})..
/// </summary>
public static string Menu_Integrations_RequiresActivityTracking {
get {
@ -2753,24 +2762,6 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to When you join a game, you&apos;ll be notified of where your server&apos;s located. Won&apos;t show in fullscreen..
/// </summary>
public static string Menu_Integrations_ShowServerDetails_Description {
get {
return ResourceManager.GetString("Menu.Integrations.ShowServerDetails.Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to See server location when joining a game.
/// </summary>
public static string Menu_Integrations_ShowServerDetails_Title {
get {
return ResourceManager.GetString("Menu.Integrations.ShowServerDetails.Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Integrations.
/// </summary>

View File

@ -268,9 +268,6 @@ Your ReShade configuration files will still be saved, and you can locate them by
<data name="ContextMenu.ServerInformation.InstanceId" xml:space="preserve">
<value>Instance ID</value>
</data>
<data name="ContextMenu.ServerInformation.Loading" xml:space="preserve">
<value>Loading, please wait...</value>
</data>
<data name="ContextMenu.ServerInformation.Location" xml:space="preserve">
<value>Location</value>
</data>
@ -761,7 +758,7 @@ Selecting 'No' will ignore this warning and continue installation.</value>
<value>Enable activity tracking</value>
</data>
<data name="Menu.Integrations.RequiresActivityTracking" xml:space="preserve">
<value>This feature requires activity tracking to be enabled and the Discord desktop app to be installed and running.</value>
<value>This feature requires activity tracking to be enabled and the Discord desktop app to be installed and running. [Find out more]({0}).</value>
</data>
<data name="Menu.Integrations.ShowGameActivity.Description" xml:space="preserve">
<value>The Roblox game you're playing will be shown on your Discord profile. [Not working?]({0})</value>
@ -769,11 +766,11 @@ Selecting 'No' will ignore this warning and continue installation.</value>
<data name="Menu.Integrations.ShowGameActivity.Title" xml:space="preserve">
<value>Show game activity</value>
</data>
<data name="Menu.Integrations.ShowServerDetails.Description" xml:space="preserve">
<value>When you join a game, you'll be notified of where your server's located. Won't show in fullscreen.</value>
<data name="Menu.Integrations.QueryServerLocation.Description" xml:space="preserve">
<value>When in-game, you'll be able to see where your server is located via [ipinfo.io]({0}).</value>
</data>
<data name="Menu.Integrations.ShowServerDetails.Title" xml:space="preserve">
<value>See server location when joining a game</value>
<data name="Menu.Integrations.QueryServerLocation.Title" xml:space="preserve">
<value>Query server location</value>
</data>
<data name="Menu.Integrations.Title" xml:space="preserve">
<value>Integrations</value>

View File

@ -28,6 +28,8 @@ namespace Bloxstrap.UI.Elements.ContextMenu
private ServerInformation? _serverInformationWindow;
private ServerHistory? _gameHistoryWindow;
public MenuContainer(Watcher watcher)
{
InitializeComponent();
@ -51,14 +53,14 @@ namespace Bloxstrap.UI.Elements.ContextMenu
{
if (_serverInformationWindow is null)
{
_serverInformationWindow = new ServerInformation(_watcher);
_serverInformationWindow = new(_watcher);
_serverInformationWindow.Closed += (_, _) => _serverInformationWindow = null;
}
if (!_serverInformationWindow.IsVisible)
_serverInformationWindow.Show();
_serverInformationWindow.Activate();
_serverInformationWindow.ShowDialog();
else
_serverInformationWindow.Activate();
}
public void ActivityWatcher_OnLogOpen(object? sender, EventArgs e) =>
@ -135,7 +137,16 @@ namespace Bloxstrap.UI.Elements.ContextMenu
if (_activityWatcher is null)
throw new ArgumentNullException(nameof(_activityWatcher));
new ServerHistory(_activityWatcher).ShowDialog();
if (_gameHistoryWindow is null)
{
_gameHistoryWindow = new(_activityWatcher);
_gameHistoryWindow.Closed += (_, _) => _gameHistoryWindow = null;
}
if (!_gameHistoryWindow.IsVisible)
_gameHistoryWindow.ShowDialog();
else
_gameHistoryWindow.Activate();
}
}
}

View File

@ -46,14 +46,14 @@
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,16,12" VerticalAlignment="Center" Text="{x:Static resources:Strings.ContextMenu_ServerInformation_InstanceId}" />
<TextBlock Grid.Row="1" Grid.Column="1" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Text="{Binding InstanceId, Mode=OneWay}" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,16,12" VerticalAlignment="Center" Text="{x:Static resources:Strings.ContextMenu_ServerInformation_Location}" />
<TextBlock Grid.Row="2" Grid.Column="1" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Text="{Binding ServerLocation, Mode=OneWay}" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,16,12" VerticalAlignment="Center" Text="{x:Static resources:Strings.ContextMenu_ServerInformation_Location}" Visibility="{Binding ServerLocationVisibility, Mode=OneTime}" />
<TextBlock Grid.Row="2" Grid.Column="1" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Text="{Binding ServerLocation, Mode=OneWay}" Visibility="{Binding ServerLocationVisibility, Mode=OneTime}" />
</Grid>
<Border Grid.Row="2" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Right">
<Button MinWidth="100" Content="{x:Static resources:Strings.ContextMenu_ServerInformation_CopyInstanceId}" Command="{Binding CopyInstanceIdCommand, Mode=OneTime}" />
<Button Margin="12,0,0,0" MinWidth="100" Content="{x:Static resources:Strings.Common_Close}" Command="{Binding CloseWindowCommand, Mode=OneTime}" />
<Button Margin="12,0,0,0" MinWidth="100" Content="{x:Static resources:Strings.Common_Close}" IsCancel="True" />
</StackPanel>
</Border>
</Grid>

View File

@ -24,11 +24,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
{
public ServerInformation(Watcher watcher)
{
var viewModel = new ServerInformationViewModel(watcher);
viewModel.RequestCloseEvent += (_, _) => Close();
DataContext = viewModel;
DataContext = new ServerInformationViewModel(watcher);
InitializeComponent();
}
}

View File

@ -26,8 +26,9 @@
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_Integrations_ShowServerDetails_Title}"
Description="{x:Static resources:Strings.Menu_Integrations_ShowServerDetails_Description}"
Header="{x:Static resources:Strings.Menu_Integrations_QueryServerLocation_Title}"
Description="{Binding Source={x:Static resources:Strings.Menu_Integrations_QueryServerLocation_Description}, Converter={StaticResource StringFormatConverter}, ConverterParameter='https://ipinfo.io'}"
HelpLink="https://github.com/pizzaboxer/bloxstrap/wiki/What-is-activity-tracking%3F#server-location-querying"
IsEnabled="{Binding InnerContent.IsChecked, ElementName=ActivityTrackingOption, Mode=OneWay}">
<ui:ToggleSwitch IsChecked="{Binding ShowServerDetailsEnabled, Mode=TwoWay}" />
</controls:OptionControl>
@ -40,7 +41,7 @@
</controls:OptionControl>
<TextBlock Text="{x:Static resources:Strings.Common_DiscordRichPresence}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_RequiresActivityTracking}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<controls:MarkdownTextBlock MarkdownText="{Binding Source={x:Static resources:Strings.Menu_Integrations_RequiresActivityTracking}, Converter={StaticResource StringFormatConverter}, ConverterParameter='https://github.com/pizzaboxer/bloxstrap/wiki/What-is-activity-tracking%3F#discord-rich-presence'}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_Integrations_ShowGameActivity_Title}"

View File

@ -26,7 +26,7 @@ namespace Bloxstrap.UI
_watcher = watcher;
_notifyIcon = new()
_notifyIcon = new(new System.ComponentModel.Container())
{
Icon = Properties.Resources.IconBloxstrap,
Text = App.ProjectName,
@ -35,7 +35,7 @@ namespace Bloxstrap.UI
_notifyIcon.MouseClick += MouseClickEventHandler;
if (_activityWatcher is not null)
if (_activityWatcher is not null && App.Settings.Prop.ShowServerDetails)
_activityWatcher.OnGameJoin += OnGameJoin;
_menuContainer = new(_watcher);
@ -59,7 +59,7 @@ namespace Bloxstrap.UI
if (_activityWatcher is null)
return;
string serverLocation = await _activityWatcher.GetServerLocation();
string serverLocation = await _activityWatcher.Data.QueryServerLocation();
if (string.IsNullOrEmpty(serverLocation))
return;
@ -81,6 +81,7 @@ namespace Bloxstrap.UI
}
#endregion
// we may need to create our own handler for this, because this sorta sucks
public void ShowAlert(string caption, string message, int duration, EventHandler? clickHandler)
{
string id = Guid.NewGuid().ToString()[..8];

View File

@ -36,8 +36,7 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
if (entries.Any())
{
// TODO: this will duplicate universe ids
string universeIds = String.Join(',', entries.Select(x => x.UniverseId));
string universeIds = String.Join(',', entries.GroupBy(x => x.UniverseId).Select(x => x.First()));
try
{

View File

@ -13,27 +13,28 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
public string ServerType => _activityWatcher.Data.ServerType.ToTranslatedString();
public string ServerLocation { get; private set; } = Strings.ContextMenu_ServerInformation_Loading;
public string ServerLocation { get; private set; } = Strings.Common_Loading;
public Visibility ServerLocationVisibility => App.Settings.Prop.ShowServerDetails ? Visibility.Visible : Visibility.Collapsed;
public ICommand CopyInstanceIdCommand => new RelayCommand(CopyInstanceId);
public ICommand CloseWindowCommand => new RelayCommand(RequestClose);
public EventHandler? RequestCloseEvent;
public ServerInformationViewModel(Watcher watcher)
{
_activityWatcher = watcher.ActivityWatcher!;
Task.Run(async () =>
{
ServerLocation = await _activityWatcher.GetServerLocation();
OnPropertyChanged(nameof(ServerLocation));
});
if (ServerLocationVisibility == Visibility.Visible)
QueryServerLocation();
}
public async void QueryServerLocation()
{
ServerLocation = await _activityWatcher.Data.QueryServerLocation();
OnPropertyChanged(nameof(ServerLocation));
}
private void CopyInstanceId() => Clipboard.SetDataObject(InstanceId);
private void RequestClose() => RequestCloseEvent?.Invoke(this, EventArgs.Empty);
}
}

View File

@ -1,6 +1,5 @@
namespace Bloxstrap.UI.ViewModels.Installer
{
// TODO: administrator check?
public class WelcomeViewModel : NotifyPropertyChangedViewModel
{
// formatting is done here instead of in xaml, it's just a bit easier