Update 0.1.6 - Deploy AutoDetect, last debug and optimization
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<AssemblyName>UmbraSync</AssemblyName>
|
||||
<RootNamespace>UmbraSync</RootNamespace>
|
||||
<Version>0.1.4.1</Version>
|
||||
<Version>0.1.6.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -61,4 +61,23 @@ public class AutoDetectRequestService
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
public async Task<bool> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<NearbyDiscoveryService> logger, MareMediator mediator,
|
||||
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
|
||||
@@ -50,6 +54,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
||||
_mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
|
||||
_mediator.Subscribe<DisconnectedMessage>(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
|
||||
|
||||
@@ -11,14 +11,17 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
private readonly ILogger<NearbyPendingService> _logger;
|
||||
private readonly MareMediator _mediator;
|
||||
private readonly ApiController _api;
|
||||
private readonly AutoDetectRequestService _requestService;
|
||||
private readonly ConcurrentDictionary<string, string> _pending = new(StringComparer.Ordinal);
|
||||
private static readonly Regex ReqRegex = new(@"^Nearby Request: (.+) \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled);
|
||||
private static readonly Regex AcceptRegex = new(@"^Nearby Accept: (.+) \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled);
|
||||
|
||||
public NearbyPendingService(ILogger<NearbyPendingService> logger, MareMediator mediator, ApiController api)
|
||||
public NearbyPendingService(ILogger<NearbyPendingService> logger, MareMediator mediator, ApiController api, AutoDetectRequestService requestService)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediator = mediator;
|
||||
_api = api;
|
||||
_requestService = requestService;
|
||||
_mediator.Subscribe<NotificationMessage>(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)
|
||||
|
||||
@@ -108,7 +108,7 @@ 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<NearbyEntry> Entries) : MessageBase;
|
||||
|
||||
public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName);
|
||||
|
||||
@@ -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<Services.Mediator.NearbyEntry> _entries = new();
|
||||
|
||||
public AutoDetectUi(ILogger<AutoDetectUi> 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<Services.Mediator.NearbyEntry>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<DiscoveryApiClient> _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<DiscoveryApiClient> logger, TokenProvider tokenProvider)
|
||||
public DiscoveryApiClient(ILogger<DiscoveryApiClient> 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<bool> PublishAsync(string endpoint, IEnumerable<string> hashes, string? displayName, CancellationToken ct)
|
||||
public async Task<bool> PublishAsync(string endpoint, IEnumerable<string> 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<bool> 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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user