using System; using System.Collections.Concurrent; using System.Text.RegularExpressions; using MareSynchronos.MareConfiguration.Models; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Notifications; using MareSynchronos.WebAPI; using Microsoft.Extensions.Logging; namespace MareSynchronos.Services.AutoDetect; public sealed class NearbyPendingService : IMediatorSubscriber { private readonly ILogger _logger; private readonly MareMediator _mediator; private readonly ApiController _api; private readonly AutoDetectRequestService _requestService; private readonly NotificationTracker _notificationTracker; private readonly ConcurrentDictionary _pending = new(StringComparer.Ordinal); private static readonly TimeSpan RegexTimeout = TimeSpan.FromSeconds(1); private static readonly Regex ReqRegex = new(@"^Nearby Request: .+ \[(?[A-Z0-9]+)\]$", RegexOptions.Compiled | RegexOptions.ExplicitCapture, RegexTimeout); private static readonly Regex AcceptRegex = new(@"^Nearby Accept: .+ \[(?[A-Z0-9]+)\]$", RegexOptions.Compiled | RegexOptions.ExplicitCapture, RegexTimeout); public NearbyPendingService(ILogger logger, MareMediator mediator, ApiController api, AutoDetectRequestService requestService, NotificationTracker notificationTracker) { _logger = logger; _mediator = mediator; _api = api; _requestService = requestService; _notificationTracker = notificationTracker; _mediator.Subscribe(this, OnNotification); _mediator.Subscribe(this, OnManualPairInvite); } public MareMediator Mediator => _mediator; public IReadOnlyDictionary Pending => _pending; private void OnNotification(NotificationMessage msg) { // 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 _); _requestService.RemovePendingRequestByUid(uidA); _notificationTracker.Remove(NotificationCategory.AutoDetect, uidA); _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; if (string.IsNullOrEmpty(uid)) return; // Try to extract name as everything before space and '[' var name = msg.Message; try { var idx = msg.Message.IndexOf(':'); if (idx >= 0) name = msg.Message[(idx + 1)..].Trim(); var br = name.LastIndexOf('['); if (br > 0) name = name[..br].Trim(); } catch { name = uid; } _pending[uid] = name; _logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name); _notificationTracker.Upsert(NotificationEntry.AutoDetect(uid, name)); } private void OnManualPairInvite(ManualPairInviteMessage msg) { if (!string.Equals(msg.TargetUid, _api.UID, StringComparison.Ordinal)) return; var display = !string.IsNullOrWhiteSpace(msg.DisplayName) ? msg.DisplayName! : (!string.IsNullOrWhiteSpace(msg.SourceAlias) ? msg.SourceAlias : msg.SourceUid); _pending[msg.SourceUid] = display; _logger.LogInformation("NearbyPending: received manual invite from {uid} ({name})", msg.SourceUid, display); _mediator.Publish(new NotificationMessage("Nearby request", $"{display} vous a envoyé une invitation de pair.", NotificationType.Info, TimeSpan.FromSeconds(5))); _notificationTracker.Upsert(NotificationEntry.AutoDetect(msg.SourceUid, display)); } public void Remove(string uid) { _pending.TryRemove(uid, out _); _requestService.RemovePendingRequestByUid(uid); _notificationTracker.Remove(NotificationCategory.AutoDetect, uid); } public async Task AcceptAsync(string uid) { try { await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false); _pending.TryRemove(uid, out _); _requestService.RemovePendingRequestByUid(uid); _ = _requestService.SendAcceptNotifyAsync(uid); _notificationTracker.Remove(NotificationCategory.AutoDetect, uid); return true; } catch (Exception ex) { _logger.LogWarning(ex, "NearbyPending: accept failed for {uid}", uid); return false; } } }