using System.Web; using System.Windows; using System.Windows.Input; using Bloxstrap.Models.APIs; using CommunityToolkit.Mvvm.Input; namespace Bloxstrap.Models.Entities { public class ActivityData { private long _universeId = 0; /// /// 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 /// 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; /// /// This will be empty unless the server joined is a private server /// public string AccessCode { get; set; } = string.Empty; public string MachineAddress { 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; public DateTime TimeJoined { get; set; } public DateTime? TimeLeft { get; set; } // everything below here is optional strictly for bloxstraprpc, discord rich presence, or game history /// /// This is intended only for other people to use, i.e. context menu invite link, rich presence joining /// 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); private SemaphoreSlim serverQuerySemaphore = new(1, 1); 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; } public async Task QueryServerLocation() { const string LOG_IDENT = "ActivityData::QueryServerLocation"; if (!MachineAddressValid) throw new InvalidOperationException($"Machine address is invalid ({MachineAddress})"); await serverQuerySemaphore.WaitAsync(); if (GlobalCache.ServerLocation.TryGetValue(MachineAddress, out string? location)) { serverQuerySemaphore.Release(); return location; } try { var ipInfo = await Http.GetJson($"https://ipinfo.io/{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}"; GlobalCache.ServerLocation[MachineAddress] = location; serverQuerySemaphore.Release(); } catch (Exception ex) { App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {MachineAddress}"); App.Logger.WriteException(LOG_IDENT, ex); GlobalCache.ServerLocation[MachineAddress] = location; serverQuerySemaphore.Release(); Frontend.ShowConnectivityDialog( string.Format(Strings.Dialog_Connectivity_UnableToConnect, "ipinfo.io"), Strings.ActivityWatcher_LocationQueryFailed, MessageBoxImage.Warning, ex ); } return location; } public override string ToString() => $"{PlaceId}/{JobId}"; private void RejoinServer() { string playerPath = Path.Combine(Paths.Roblox, "Player", "RobloxPlayerBeta.exe"); Process.Start(playerPath, GetInviteDeeplink(false)); } } }