From 04a8ee318624eae695d41568a8ead2a67a20f4bc Mon Sep 17 00:00:00 2001 From: SirConstance Date: Sat, 13 Sep 2025 13:41:00 +0200 Subject: [PATCH] Update 0.1.6 - Deploy AutoDetect, last debug and optimization --- MareSynchronos/MareSynchronos.csproj | 2 +- .../AutoDetect/AutoDetectRequestService.cs | 19 +++ .../AutoDetect/DiscoveryConfigProvider.cs | 5 +- .../AutoDetect/NearbyDiscoveryService.cs | 133 ++++++++++++++++-- .../AutoDetect/NearbyPendingService.cs | 19 ++- MareSynchronos/Services/Mediator/Messages.cs | 4 +- MareSynchronos/UI/AutoDetectUi.cs | 78 ++++++++-- MareSynchronos/UI/CompactUI.cs | 7 +- MareSynchronos/UI/SettingsUi.cs | 7 - .../WebAPI/AutoDetect/DiscoveryApiClient.cs | 68 ++++++++- 10 files changed, 297 insertions(+), 45 deletions(-) diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index b0ff00a..b9b60d8 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -3,7 +3,7 @@ UmbraSync UmbraSync - 0.1.4.1 + 0.1.6.2 diff --git a/MareSynchronos/Services/AutoDetect/AutoDetectRequestService.cs b/MareSynchronos/Services/AutoDetect/AutoDetectRequestService.cs index 0ee9e31..527c08c 100644 --- a/MareSynchronos/Services/AutoDetect/AutoDetectRequestService.cs +++ b/MareSynchronos/Services/AutoDetect/AutoDetectRequestService.cs @@ -61,4 +61,23 @@ public class AutoDetectRequestService } return ok; } + + public async Task SendAcceptNotifyAsync(string targetUid, CancellationToken ct = default) + { + var endpoint = _configProvider.AcceptEndpoint; + if (string.IsNullOrEmpty(endpoint)) + { + _logger.LogDebug("No accept endpoint configured"); + return false; + } + string? displayName = null; + try + { + var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false); + displayName = me?.Name.TextValue; + } + catch { } + _logger.LogInformation("Nearby: sending accept notify via {endpoint}", endpoint); + return await _client.SendAcceptAsync(endpoint!, targetUid, displayName, ct).ConfigureAwait(false); + } } diff --git a/MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs b/MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs index 248aefc..ffd921d 100644 --- a/MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs +++ b/MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs @@ -29,6 +29,7 @@ public class DiscoveryConfigProvider public bool HasConfig => _config != null; public bool NearbyEnabled => _config?.NearbyDiscovery?.Enabled ?? false; public byte[]? Salt => _config?.NearbyDiscovery?.SaltBytes; + public string? SaltB64 => _config?.NearbyDiscovery?.SaltB64; public DateTimeOffset? SaltExpiresAt => _config?.NearbyDiscovery?.SaltExpiresAt; public int RefreshSec => _config?.NearbyDiscovery?.RefreshSec ?? 300; public int MinQueryIntervalMs => _config?.NearbyDiscovery?.Policies?.MinQueryIntervalMs ?? 2000; @@ -36,6 +37,7 @@ public class DiscoveryConfigProvider public string? PublishEndpoint => _config?.NearbyDiscovery?.Endpoints?.Publish; public string? QueryEndpoint => _config?.NearbyDiscovery?.Endpoints?.Query; public string? RequestEndpoint => _config?.NearbyDiscovery?.Endpoints?.Request; + public string? AcceptEndpoint => _config?.NearbyDiscovery?.Endpoints?.Accept; public bool TryLoadFromStapled() { @@ -50,7 +52,7 @@ public class DiscoveryConfigProvider root.NearbyDiscovery?.Hydrate(); _config = root; _lastLoad = DateTimeOffset.UtcNow; - _logger.LogInformation("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt); + _logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt); return true; } catch (Exception ex) @@ -158,6 +160,7 @@ public class DiscoveryConfigProvider [JsonPropertyName("publish")] public string? Publish { get; set; } [JsonPropertyName("query")] public string? Query { get; set; } [JsonPropertyName("request")] public string? Request { get; set; } + [JsonPropertyName("accept")] public string? Accept { get; set; } } private sealed class Policies diff --git a/MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs b/MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs index 06a644b..9fc3a22 100644 --- a/MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs +++ b/MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs @@ -28,6 +28,10 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber private bool _loggedConfigReady; private string? _lastSnapshotSig; private volatile bool _isConnected; + private bool _notifiedDisabled; + private bool _notifiedEnabled; + private bool _disableSent; + private bool _lastAutoDetectState; public NearbyDiscoveryService(ILogger logger, MareMediator mediator, MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService, @@ -50,6 +54,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber _mediator.Subscribe(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); }); _mediator.Subscribe(this, _ => { _isConnected = false; _lastPublishedSignature = null; }); _ = Task.Run(() => Loop(_loopCts.Token)); + _lastAutoDetectState = _config.Current.EnableAutoDetectDiscovery; return Task.CompletedTask; } @@ -62,20 +67,112 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber private async Task Loop(CancellationToken ct) { - // best effort config load _configProvider.TryLoadFromStapled(); while (!ct.IsCancellationRequested) { try { + bool currentState = _config.Current.EnableAutoDetectDiscovery; + if (currentState != _lastAutoDetectState) + { + _lastAutoDetectState = currentState; + if (currentState) + { + // Force immediate publish on toggle ON + try + { + // Ensure well-known is present + if (!_configProvider.HasConfig || _configProvider.IsExpired()) + { + if (!_configProvider.TryLoadFromStapled()) + { + await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false); + } + } + + var ep = _configProvider.PublishEndpoint; + var saltBytes = _configProvider.Salt; + if (!string.IsNullOrEmpty(ep) && saltBytes is { Length: > 0 }) + { + var saltHex = Convert.ToHexString(saltBytes); + string? displayName = null; + ushort meWorld = 0; + try + { + var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false); + if (me != null) + { + displayName = me.Name.TextValue; + if (me is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter mePc) + meWorld = (ushort)mePc.HomeWorld.RowId; + } + } + catch { } + + if (!string.IsNullOrEmpty(displayName)) + { + var selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256(); + _lastPublishedSignature = null; // ensure future loop doesn't skip + var okNow = await _api.PublishAsync(ep!, new[] { selfHash }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false); + _logger.LogInformation("Nearby immediate publish on toggle ON: {result}", okNow ? "success" : "failed"); + } + } + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Nearby immediate publish on toggle ON failed"); + } + + if (!_notifiedEnabled) + { + _mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect enabled : you are now visible.", default)); + _notifiedEnabled = true; + _notifiedDisabled = false; + _disableSent = false; + } + } + else + { + var ep = _configProvider.PublishEndpoint; + if (!string.IsNullOrEmpty(ep) && !_disableSent) + { + var disableUrl = ep.Replace("/publish", "/disable"); + try { await _api.DisableAsync(disableUrl, ct).ConfigureAwait(false); _disableSent = true; } catch { } + } + if (!_notifiedDisabled) + { + _mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect disabled : you are not visible.", default)); + _notifiedDisabled = true; + _notifiedEnabled = false; + } + } + } if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn || !_isConnected) { + if (!_config.Current.EnableAutoDetectDiscovery && !string.IsNullOrEmpty(_configProvider.PublishEndpoint)) + { + var disableUrl = _configProvider.PublishEndpoint.Replace("/publish", "/disable"); + try + { + if (!_disableSent) + { + await _api.DisableAsync(disableUrl, ct).ConfigureAwait(false); + _disableSent = true; + } + } + catch { } + + if (!_notifiedDisabled) + { + _mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect disabled : you are not visible.", default)); + _notifiedDisabled = true; + _notifiedEnabled = false; + } + } await Task.Delay(1000, ct).ConfigureAwait(false); continue; } - - // Ensure we have a valid Nearby config: try stapled, then HTTP fallback if (!_configProvider.HasConfig || _configProvider.IsExpired()) { if (!_configProvider.TryLoadFromStapled()) @@ -114,7 +211,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber hashes.Add(h); } - // Debug snapshot once per change try { var snapSig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256(); @@ -134,7 +230,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber } catch { } - // Publish OUR presence (own hash) if endpoint is available (deduplicated) if (!string.IsNullOrEmpty(_configProvider.PublishEndpoint)) { string? displayName = null; @@ -161,9 +256,19 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber { _lastPublishedSignature = sig; var shortSelf = selfHash!.Length > 8 ? selfHash[..8] : selfHash; - _logger.LogInformation("Nearby publish: self presence updated (hash={hash})", shortSelf); - var ok = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct).ConfigureAwait(false); + _logger.LogDebug("Nearby publish: self presence updated (hash={hash})", shortSelf); + var ok = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false); _logger.LogInformation("Nearby publish result: {result}", ok ? "success" : "failed"); + if (ok) + { + if (!_notifiedEnabled) + { + _mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect enabled : you are now visible.", default)); + _notifiedEnabled = true; + _notifiedDisabled = false; + _disableSent = false; // allow future /disable when turning off again + } + } } else { @@ -193,7 +298,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber if (hashToIndex.TryGetValue(m.Hash, out var idx)) { var e = entries[idx]; - entries[idx] = new NearbyEntry(e.Name, e.WorldId, e.Distance, true, m.Token); + entries[idx] = new NearbyEntry(e.Name, e.WorldId, e.Distance, true, m.Token, m.DisplayName, m.Uid); } } } @@ -204,13 +309,18 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber if (matchCount != _lastMatchCount) { _lastMatchCount = matchCount; - _logger.LogInformation("Nearby: {count} Umbra users nearby", matchCount); + _logger.LogDebug("Nearby: {count} Umbra users nearby", matchCount); } } } catch (Exception ex) { _logger.LogDebug(ex, "Nearby query failed; falling back to local list"); + if (ex.Message.Contains("DISCOVERY_SALT_EXPIRED", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogInformation("Nearby: salt expired, refetching well-known"); + try { await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false); } catch { } + } } } else @@ -218,12 +328,13 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber if (!_loggedLocalOnly) { _loggedLocalOnly = true; - _logger.LogInformation("Nearby: well-known not available or disabled; running in local-only mode"); + _logger.LogDebug("Nearby: well-known not available or disabled; running in local-only mode"); } } _mediator.Publish(new DiscoveryListUpdated(entries)); var delayMs = Math.Max(1000, _configProvider.MinQueryIntervalMs); + if (entries.Count == 0) delayMs = Math.Max(delayMs, 5000); await Task.Delay(delayMs, ct).ConfigureAwait(false); } catch (OperationCanceledException) { } @@ -259,7 +370,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber if (obj is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter pc) worldId = (ushort)pc.HomeWorld.RowId; - list.Add(new NearbyEntry(name, worldId, dist, false, null)); + list.Add(new NearbyEntry(name, worldId, dist, false, null, null, null)); } } catch diff --git a/MareSynchronos/Services/AutoDetect/NearbyPendingService.cs b/MareSynchronos/Services/AutoDetect/NearbyPendingService.cs index ddc3855..478ecbe 100644 --- a/MareSynchronos/Services/AutoDetect/NearbyPendingService.cs +++ b/MareSynchronos/Services/AutoDetect/NearbyPendingService.cs @@ -11,14 +11,17 @@ public sealed class NearbyPendingService : IMediatorSubscriber private readonly ILogger _logger; private readonly MareMediator _mediator; private readonly ApiController _api; + private readonly AutoDetectRequestService _requestService; private readonly ConcurrentDictionary _pending = new(StringComparer.Ordinal); private static readonly Regex ReqRegex = new(@"^Nearby Request: (.+) \[(?[A-Z0-9]+)\]$", RegexOptions.Compiled); + private static readonly Regex AcceptRegex = new(@"^Nearby Accept: (.+) \[(?[A-Z0-9]+)\]$", RegexOptions.Compiled); - public NearbyPendingService(ILogger logger, MareMediator mediator, ApiController api) + public NearbyPendingService(ILogger logger, MareMediator mediator, ApiController api, AutoDetectRequestService requestService) { _logger = logger; _mediator = mediator; _api = api; + _requestService = requestService; _mediator.Subscribe(this, OnNotification); } @@ -30,6 +33,19 @@ public sealed class NearbyPendingService : IMediatorSubscriber { // Watch info messages for Nearby request pattern if (msg.Type != MareSynchronos.MareConfiguration.Models.NotificationType.Info) return; + var ma = AcceptRegex.Match(msg.Message); + if (ma.Success) + { + var uidA = ma.Groups["uid"].Value; + if (!string.IsNullOrEmpty(uidA)) + { + _ = _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uidA))); + _pending.TryRemove(uidA, out _); + _logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA); + } + return; + } + var m = ReqRegex.Match(msg.Message); if (!m.Success) return; var uid = m.Groups["uid"].Value; @@ -59,6 +75,7 @@ public sealed class NearbyPendingService : IMediatorSubscriber { await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false); _pending.TryRemove(uid, out _); + _ = _requestService.SendAcceptNotifyAsync(uid); return true; } catch (Exception ex) diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 078e845..7dbbaaa 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -108,9 +108,9 @@ public record GPoseLobbyReceiveCharaData(CharaDataDownloadDto CharaDataDownloadD public record GPoseLobbyReceivePoseData(UserData UserData, PoseData PoseData) : MessageBase; public record GPoseLobbyReceiveWorldData(UserData UserData, WorldData WorldData) : MessageBase; -public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMatch, string? Token); +public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMatch, string? Token, string? DisplayName, string? Uid); public record DiscoveryListUpdated(List Entries) : MessageBase; public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName); #pragma warning restore S2094 -#pragma warning restore MA0048 \ No newline at end of file +#pragma warning restore MA0048 diff --git a/MareSynchronos/UI/AutoDetectUi.cs b/MareSynchronos/UI/AutoDetectUi.cs index 1d4d95b..57246c7 100644 --- a/MareSynchronos/UI/AutoDetectUi.cs +++ b/MareSynchronos/UI/AutoDetectUi.cs @@ -6,9 +6,12 @@ using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Services; using MareSynchronos.MareConfiguration; using MareSynchronos.Services; +using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services.Mediator; using Microsoft.Extensions.Logging; using System.Numerics; +using System.Globalization; +using System.Text; namespace MareSynchronos.UI; @@ -18,18 +21,20 @@ public class AutoDetectUi : WindowMediatorSubscriberBase private readonly DalamudUtilService _dalamud; private readonly IObjectTable _objectTable; private readonly Services.AutoDetect.AutoDetectRequestService _requestService; + private readonly PairManager _pairManager; private List _entries = new(); public AutoDetectUi(ILogger logger, MareMediator mediator, MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable, - Services.AutoDetect.AutoDetectRequestService requestService, + Services.AutoDetect.AutoDetectRequestService requestService, PairManager pairManager, PerformanceCollectorService performanceCollectorService) - : base(logger, mediator, "Umbra Nearby", performanceCollectorService) + : base(logger, mediator, "AutoDetect", performanceCollectorService) { _configService = configService; _dalamud = dalamudUtilService; _objectTable = objectTable; _requestService = requestService; + _pairManager = pairManager; Flags |= ImGuiWindowFlags.NoScrollbar; SizeConstraints = new WindowSizeConstraints() @@ -78,7 +83,7 @@ public class AutoDetectUi : WindowMediatorSubscriberBase ImGui.TableSetupColumn("Action"); ImGui.TableHeadersRow(); - var data = _entries.Count > 0 ? _entries : BuildLocalSnapshot(maxDist); + var data = _entries.Count > 0 ? _entries.Where(e => e.IsMatch).ToList() : new List(); foreach (var e in data) { ImGui.TableNextColumn(); @@ -88,20 +93,25 @@ public class AutoDetectUi : WindowMediatorSubscriberBase ImGui.TableNextColumn(); ImGui.TextUnformatted(float.IsNaN(e.Distance) ? "-" : $"{e.Distance:0.0} m"); ImGui.TableNextColumn(); - ImGui.TextUnformatted(e.IsMatch ? "On Umbra" : "Unknown"); + bool alreadyPaired = IsAlreadyPairedByUidOrAlias(e); + string status = alreadyPaired ? "Paired" : (string.IsNullOrEmpty(e.Token) ? "Requests disabled" : "On Umbra"); + ImGui.TextUnformatted(status); ImGui.TableNextColumn(); - bool allowRequests = _configService.Current.AllowAutoDetectPairRequests; - using (ImRaii.Disabled(!allowRequests || !e.IsMatch || string.IsNullOrEmpty(e.Token))) + using (ImRaii.Disabled(alreadyPaired || string.IsNullOrEmpty(e.Token))) { - if (ImGui.Button($"Send request##{e.Name}")) + if (alreadyPaired) + { + ImGui.Button($"Already sync##{e.Name}"); + } + else if (string.IsNullOrEmpty(e.Token)) + { + ImGui.Button($"Requests disabled##{e.Name}"); + } + else if (ImGui.Button($"Send request##{e.Name}")) { _ = _requestService.SendRequestAsync(e.Token!); } } - if (!allowRequests) - { - UiSharedService.AttachToolTip("Enable 'Allow pair requests' in Settings to send a request."); - } } ImGui.EndTable(); @@ -140,8 +150,52 @@ public class AutoDetectUi : WindowMediatorSubscriberBase string name = obj.Name.ToString(); ushort worldId = 0; if (obj is IPlayerCharacter pc) worldId = (ushort)pc.HomeWorld.RowId; - list.Add(new Services.Mediator.NearbyEntry(name, worldId, dist, false, null)); + list.Add(new Services.Mediator.NearbyEntry(name, worldId, dist, false, null, null, null)); } return list; } + + private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e) + { + try + { + // 1) Match by UID when available (authoritative) + if (!string.IsNullOrEmpty(e.Uid)) + { + foreach (var p in _pairManager.DirectPairs) + { + if (string.Equals(p.UserData.UID, e.Uid, StringComparison.Ordinal)) + return true; + } + } + + // 2) Fallback on alias/name (legacy compatibility) + var key = NormalizeKey(e.DisplayName ?? e.Name); + if (string.IsNullOrEmpty(key)) return false; + foreach (var p in _pairManager.DirectPairs) + { + if (NormalizeKey(p.UserData.AliasOrUID) == key) return true; + if (!string.IsNullOrEmpty(p.UserData.Alias) && NormalizeKey(p.UserData.Alias!) == key) return true; + } + } + catch + { + // ignore + } + return false; + } + + private static string NormalizeKey(string? input) + { + if (string.IsNullOrWhiteSpace(input)) return string.Empty; + var formD = input.Normalize(NormalizationForm.FormD); + var sb = new StringBuilder(formD.Length); + foreach (var ch in formD) + { + var cat = CharUnicodeInfo.GetUnicodeCategory(ch); + if (cat != UnicodeCategory.NonSpacingMark) + sb.Append(char.ToLowerInvariant(ch)); + } + return sb.ToString(); + } } diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 69f724c..84b7005 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -384,11 +384,8 @@ public class CompactUi : WindowMediatorSubscriberBase _uiSharedService.IconText(icon); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen; ImGui.SameLine(); - var nearbyCount = _nearbyEntries?.Count ?? 0; var onUmbra = _nearbyEntries?.Count(e => e.IsMatch) ?? 0; - ImGui.TextUnformatted(onUmbra > 0 - ? $"Nearby ({nearbyCount} — {onUmbra} on Umbra)" - : $"Nearby ({nearbyCount} Players)"); + ImGui.TextUnformatted($"Nearby ({onUmbra})"); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen; // Header action button to open Nearby window @@ -404,7 +401,7 @@ public class CompactUi : WindowMediatorSubscriberBase if (_nearbyOpen) { ImGui.Indent(); - if (nearbyCount == 0) + if (onUmbra == 0) { UiSharedService.ColorTextWrapped("No nearby players detected.", ImGuiColors.DalamudGrey3); } diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 11d3787..e363b80 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -235,13 +235,6 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.AutoDetectMaxDistanceMeters = maxMeters; _configService.Save(); } - int muteMin = _configService.Current.AutoDetectMuteMinutes; - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - if (ImGui.SliderInt("Default mute duration (minutes)", ref muteMin, 1, 120)) - { - _configService.Current.AutoDetectMuteMinutes = muteMin; - _configService.Save(); - } ImGui.Unindent(); } diff --git a/MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs b/MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs index dd24560..0731a94 100644 --- a/MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs +++ b/MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs @@ -3,6 +3,7 @@ using System.Text; using System.Text.Json; using Microsoft.Extensions.Logging; using MareSynchronos.WebAPI.SignalR; +using MareSynchronos.Services.AutoDetect; namespace MareSynchronos.WebAPI.AutoDetect; @@ -10,13 +11,16 @@ public class DiscoveryApiClient { private readonly ILogger _logger; private readonly TokenProvider _tokenProvider; + private readonly DiscoveryConfigProvider _configProvider; private readonly HttpClient _httpClient = new(); private static readonly JsonSerializerOptions JsonOpt = new() { PropertyNameCaseInsensitive = true }; + // private readonly ISaltProvider _saltProvider; // For future use if needed - public DiscoveryApiClient(ILogger logger, TokenProvider tokenProvider) + public DiscoveryApiClient(ILogger logger, TokenProvider tokenProvider, DiscoveryConfigProvider configProvider) { _logger = logger; _tokenProvider = tokenProvider; + _configProvider = configProvider; _httpClient.Timeout = TimeSpan.FromSeconds(30); } @@ -28,7 +32,11 @@ public class DiscoveryApiClient if (string.IsNullOrEmpty(token)) return []; using var req = new HttpRequestMessage(HttpMethod.Post, endpoint); req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - var body = JsonSerializer.Serialize(new { hashes = hashes.Distinct(StringComparer.Ordinal).ToArray() }); + var body = JsonSerializer.Serialize(new + { + hashes = hashes.Distinct(StringComparer.Ordinal).ToArray(), + salt = _configProvider.SaltB64 + }); req.Content = new StringContent(body, Encoding.UTF8, "application/json"); var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false); resp.EnsureSuccessStatusCode(); @@ -70,7 +78,7 @@ public class DiscoveryApiClient } } - public async Task PublishAsync(string endpoint, IEnumerable hashes, string? displayName, CancellationToken ct) + public async Task PublishAsync(string endpoint, IEnumerable hashes, string? displayName, CancellationToken ct, bool allowRequests = true) { try { @@ -78,7 +86,13 @@ public class DiscoveryApiClient if (string.IsNullOrEmpty(jwt)) return false; using var req = new HttpRequestMessage(HttpMethod.Post, endpoint); req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt); - var bodyObj = new { hashes = hashes.Distinct(StringComparer.Ordinal).ToArray(), displayName }; + var bodyObj = new + { + hashes = hashes.Distinct(StringComparer.Ordinal).ToArray(), + displayName, + salt = _configProvider.SaltB64, + allowRequests + }; var body = JsonSerializer.Serialize(bodyObj); req.Content = new StringContent(body, Encoding.UTF8, "application/json"); var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false); @@ -90,11 +104,55 @@ public class DiscoveryApiClient return false; } } + + public async Task SendAcceptAsync(string endpoint, string targetUid, string? displayName, CancellationToken ct) + { + try + { + var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false); + if (string.IsNullOrEmpty(jwt)) return false; + using var req = new HttpRequestMessage(HttpMethod.Post, endpoint); + req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt); + var bodyObj = new { targetUid, displayName }; + var body = JsonSerializer.Serialize(bodyObj); + req.Content = new StringContent(body, Encoding.UTF8, "application/json"); + var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false); + return resp.IsSuccessStatusCode; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Discovery accept notify failed"); + return false; + } + } + public async Task DisableAsync(string endpoint, CancellationToken ct) + { + try + { + var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false); + if (string.IsNullOrEmpty(jwt)) return; + using var req = new HttpRequestMessage(HttpMethod.Post, endpoint); + req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt); + // no body required + var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) + { + string txt = string.Empty; + try { txt = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); } catch { } + _logger.LogWarning("Discovery disable failed: {code} {reason} {body}", (int)resp.StatusCode, resp.ReasonPhrase, txt); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Discovery disable failed"); + } + } } public sealed class ServerMatch { public string Hash { get; set; } = string.Empty; - public string Token { get; set; } = string.Empty; + public string? Token { get; set; } + public string? Uid { get; set; } public string? DisplayName { get; set; } }