Fix bubble party + Download queue + Allow pause user in syncshell + add visual feature + clean log info
This commit is contained in:
@@ -9,6 +9,9 @@ using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
|
||||
using MareSynchronos.WebAPI;
|
||||
using MareSynchronos.API.Dto.User;
|
||||
using MareSynchronos.API.Data;
|
||||
|
||||
namespace MareSynchronos.Services.AutoDetect;
|
||||
|
||||
@@ -26,8 +29,9 @@ public class AutoDetectRequestService
|
||||
private readonly ConcurrentDictionary<string, PendingRequestInfo> _pendingRequests = new(StringComparer.Ordinal);
|
||||
private static readonly TimeSpan RequestCooldown = TimeSpan.FromMinutes(5);
|
||||
private static readonly TimeSpan RefusalLockDuration = TimeSpan.FromMinutes(15);
|
||||
private readonly ApiController _apiController;
|
||||
|
||||
public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService, MareMediator mediator, DalamudUtilService dalamudUtilService)
|
||||
public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService, MareMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController)
|
||||
{
|
||||
_logger = logger;
|
||||
_configProvider = configProvider;
|
||||
@@ -35,9 +39,10 @@ public class AutoDetectRequestService
|
||||
_configService = configService;
|
||||
_mediator = mediator;
|
||||
_dalamud = dalamudUtilService;
|
||||
_apiController = apiController;
|
||||
}
|
||||
|
||||
public async Task<bool> SendRequestAsync(string token, string? uid = null, string? targetDisplayName = null, CancellationToken ct = default)
|
||||
public async Task<bool> SendRequestAsync(string? token, string? uid = null, string? targetDisplayName = null, CancellationToken ct = default)
|
||||
{
|
||||
if (!_configService.Current.AllowAutoDetectPairRequests)
|
||||
{
|
||||
@@ -45,6 +50,13 @@ public class AutoDetectRequestService
|
||||
_mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(token) && string.IsNullOrEmpty(uid))
|
||||
{
|
||||
_logger.LogDebug("Nearby request blocked: no token or UID provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetKey = BuildTargetKey(uid, token, targetDisplayName);
|
||||
if (!string.IsNullOrEmpty(targetKey))
|
||||
{
|
||||
@@ -101,8 +113,11 @@ public class AutoDetectRequestService
|
||||
}
|
||||
catch { }
|
||||
|
||||
var requestToken = string.IsNullOrEmpty(token) ? null : token;
|
||||
var requestUid = requestToken == null ? uid : null;
|
||||
|
||||
_logger.LogInformation("Nearby: sending pair request via {endpoint}", endpoint);
|
||||
var ok = await _client.SendRequestAsync(endpoint!, token, displayName, ct).ConfigureAwait(false);
|
||||
var ok = await _client.SendRequestAsync(endpoint!, requestToken, requestUid, displayName, ct).ConfigureAwait(false);
|
||||
if (ok)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(targetKey))
|
||||
@@ -180,6 +195,126 @@ public class AutoDetectRequestService
|
||||
return await _client.SendAcceptAsync(endpoint!, targetUid, displayName, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SendDirectUidRequestAsync(string uid, string? targetDisplayName = null, CancellationToken ct = default)
|
||||
{
|
||||
if (!_configService.Current.AllowAutoDetectPairRequests)
|
||||
{
|
||||
_logger.LogDebug("Nearby request blocked: AllowAutoDetectPairRequests is disabled");
|
||||
_mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(uid))
|
||||
{
|
||||
_logger.LogDebug("Direct pair request aborted: UID is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetKey = BuildTargetKey(uid, null, targetDisplayName);
|
||||
if (!string.IsNullOrEmpty(targetKey))
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_refusalTrackers.TryGetValue(targetKey, out var tracker))
|
||||
{
|
||||
if (tracker.LockUntil.HasValue && tracker.LockUntil.Value > now)
|
||||
{
|
||||
PublishLockNotification(tracker.LockUntil.Value - now);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tracker.LockUntil.HasValue && tracker.LockUntil.Value <= now)
|
||||
{
|
||||
tracker.LockUntil = null;
|
||||
tracker.Count = 0;
|
||||
if (tracker.Count == 0 && tracker.LockUntil == null)
|
||||
{
|
||||
_refusalTrackers.Remove(targetKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_activeCooldowns.TryGetValue(targetKey, out var lastSent))
|
||||
{
|
||||
var elapsed = now - lastSent;
|
||||
if (elapsed < RequestCooldown)
|
||||
{
|
||||
PublishCooldownNotification(RequestCooldown - elapsed);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elapsed >= RequestCooldown)
|
||||
{
|
||||
_activeCooldowns.Remove(targetKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _apiController.UserAddPair(new UserDto(new UserData(uid))).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(targetKey))
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_activeCooldowns[targetKey] = DateTime.UtcNow;
|
||||
if (_refusalTrackers.TryGetValue(targetKey, out var tracker))
|
||||
{
|
||||
tracker.Count = 0;
|
||||
tracker.LockUntil = null;
|
||||
if (tracker.Count == 0 && tracker.LockUntil == null)
|
||||
{
|
||||
_refusalTrackers.Remove(targetKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_mediator.Publish(new NotificationMessage("Nearby request sent", "The other user will receive a request notification.", NotificationType.Info));
|
||||
var pendingKey = EnsureTargetKey(targetKey);
|
||||
var label = !string.IsNullOrWhiteSpace(targetDisplayName) ? targetDisplayName! : uid;
|
||||
_pendingRequests[pendingKey] = new PendingRequestInfo(pendingKey, uid, null, label, DateTime.UtcNow);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Direct pair request failed for {uid}", uid);
|
||||
if (!string.IsNullOrEmpty(targetKey))
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_activeCooldowns.Remove(targetKey);
|
||||
if (!_refusalTrackers.TryGetValue(targetKey, out var tracker))
|
||||
{
|
||||
tracker = new RefusalTracker();
|
||||
_refusalTrackers[targetKey] = tracker;
|
||||
}
|
||||
|
||||
if (tracker.LockUntil.HasValue && tracker.LockUntil.Value <= now)
|
||||
{
|
||||
tracker.LockUntil = null;
|
||||
tracker.Count = 0;
|
||||
}
|
||||
|
||||
tracker.Count++;
|
||||
if (tracker.Count >= 3)
|
||||
{
|
||||
tracker.LockUntil = now.Add(RefusalLockDuration);
|
||||
}
|
||||
}
|
||||
_pendingRequests.TryRemove(targetKey, out _);
|
||||
}
|
||||
|
||||
_mediator.Publish(new NotificationMessage("Nearby request failed", "The server rejected the request. Try again soon.", NotificationType.Warning));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? BuildTargetKey(string? uid, string? token, string? displayName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(uid)) return "uid:" + uid;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -23,6 +25,7 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
_api = api;
|
||||
_requestService = requestService;
|
||||
_mediator.Subscribe<NotificationMessage>(this, OnNotification);
|
||||
_mediator.Subscribe<ManualPairInviteMessage>(this, OnManualPairInvite);
|
||||
}
|
||||
|
||||
public MareMediator Mediator => _mediator;
|
||||
@@ -65,6 +68,20 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
_logger.LogInformation("NearbyPending: received request from {uid} ({name})", 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)));
|
||||
}
|
||||
|
||||
public void Remove(string uid)
|
||||
{
|
||||
_pending.TryRemove(uid, out _);
|
||||
|
||||
@@ -266,26 +266,47 @@ public sealed class CharaDataNearbyManager : DisposableMediatorSubscriberBase
|
||||
|
||||
private void ManageWispsNearby(List<PoseEntryExtended> previousPoses)
|
||||
{
|
||||
foreach (var data in _nearbyData.Keys)
|
||||
int maxWisps = _charaDataConfigService.Current.NearbyMaxWisps;
|
||||
if (maxWisps <= 0)
|
||||
{
|
||||
if (_poseVfx.TryGetValue(data, out var _)) continue;
|
||||
ClearAllVfx();
|
||||
return;
|
||||
}
|
||||
|
||||
const int hardLimit = 200;
|
||||
if (maxWisps > hardLimit) maxWisps = hardLimit;
|
||||
|
||||
var orderedAllowedPoses = _nearbyData
|
||||
.OrderBy(kvp => kvp.Value.Distance)
|
||||
.Take(maxWisps)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
|
||||
var allowedPoseSet = orderedAllowedPoses.ToHashSet();
|
||||
|
||||
foreach (var data in orderedAllowedPoses)
|
||||
{
|
||||
if (_poseVfx.TryGetValue(data, out _)) continue;
|
||||
|
||||
Guid? vfxGuid = data.MetaInfo.IsOwnData
|
||||
? _vfxSpawnManager.SpawnObject(data.Position, data.Rotation, Vector3.One * 2, 0.8f, 0.5f, 0.0f, 0.7f)
|
||||
: _vfxSpawnManager.SpawnObject(data.Position, data.Rotation, Vector3.One * 2);
|
||||
|
||||
Guid? vfxGuid;
|
||||
if (data.MetaInfo.IsOwnData)
|
||||
{
|
||||
vfxGuid = _vfxSpawnManager.SpawnObject(data.Position, data.Rotation, Vector3.One * 2, 0.8f, 0.5f, 0.0f, 0.7f);
|
||||
}
|
||||
else
|
||||
{
|
||||
vfxGuid = _vfxSpawnManager.SpawnObject(data.Position, data.Rotation, Vector3.One * 2);
|
||||
}
|
||||
if (vfxGuid != null)
|
||||
{
|
||||
_poseVfx[data] = vfxGuid.Value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var data in previousPoses.Except(_nearbyData.Keys))
|
||||
foreach (var data in previousPoses.Except(allowedPoseSet))
|
||||
{
|
||||
if (_poseVfx.Remove(data, out var guid))
|
||||
{
|
||||
_vfxSpawnManager.DespawnObject(guid);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var data in _poseVfx.Keys.Except(allowedPoseSet).ToList())
|
||||
{
|
||||
if (_poseVfx.Remove(data, out var guid))
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
@@ -19,6 +21,7 @@ namespace MareSynchronos.Services;
|
||||
public class ChatService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
public const int DefaultColor = 710;
|
||||
private const string ManualPairInvitePrefix = "[UmbraPairInvite|";
|
||||
public const int CommandMaxNumber = 50;
|
||||
|
||||
private readonly ILogger<ChatService> _logger;
|
||||
@@ -191,6 +194,10 @@ public class ChatService : DisposableMediatorSubscriberBase
|
||||
var extraChatTags = _mareConfig.Current.ExtraChatTags;
|
||||
var logKind = ResolveShellLogKind(shellConfig.LogKind);
|
||||
|
||||
var payload = SeString.Parse(message.ChatMsg.PayloadContent);
|
||||
if (TryHandleManualPairInvite(message, payload))
|
||||
return;
|
||||
|
||||
var msg = new SeStringBuilder();
|
||||
if (extraChatTags)
|
||||
{
|
||||
@@ -209,7 +216,7 @@ public class ChatService : DisposableMediatorSubscriberBase
|
||||
msg.Add(new PlayerPayload(chatMsg.SenderName, chatMsg.SenderHomeWorldId));
|
||||
}
|
||||
msg.AddText("> ");
|
||||
msg.Append(SeString.Parse(message.ChatMsg.PayloadContent));
|
||||
msg.Append(payload);
|
||||
if (color != 0)
|
||||
msg.AddUiForegroundOff();
|
||||
|
||||
@@ -219,6 +226,52 @@ public class ChatService : DisposableMediatorSubscriberBase
|
||||
Type = logKind
|
||||
});
|
||||
}
|
||||
|
||||
private bool TryHandleManualPairInvite(GroupChatMsgMessage message, SeString payload)
|
||||
{
|
||||
var textValue = payload.TextValue;
|
||||
if (string.IsNullOrEmpty(textValue) || !textValue.StartsWith(ManualPairInvitePrefix, StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
var content = textValue[ManualPairInvitePrefix.Length..];
|
||||
if (content.EndsWith("]", StringComparison.Ordinal))
|
||||
{
|
||||
content = content[..^1];
|
||||
}
|
||||
|
||||
var parts = content.Split('|');
|
||||
if (parts.Length < 4)
|
||||
return true;
|
||||
|
||||
var sourceUid = parts[0];
|
||||
var sourceAlias = DecodeInviteField(parts[1]);
|
||||
var targetUid = parts[2];
|
||||
var displayName = DecodeInviteField(parts[3]);
|
||||
var inviteId = parts.Length > 4 ? parts[4] : Guid.NewGuid().ToString("N");
|
||||
|
||||
if (!string.Equals(targetUid, _apiController.UID, StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
Mediator.Publish(new ManualPairInviteMessage(sourceUid, sourceAlias, targetUid, string.IsNullOrEmpty(displayName) ? null : displayName, inviteId));
|
||||
_logger.LogDebug("Received manual pair invite from {source} via syncshell", sourceUid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string DecodeInviteField(string encoded)
|
||||
{
|
||||
if (string.IsNullOrEmpty(encoded)) return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var bytes = Convert.FromBase64String(encoded);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return encoded;
|
||||
}
|
||||
}
|
||||
public void PrintChannelExample(string message, string gid = "")
|
||||
{
|
||||
int chatType = _mareConfig.Current.ChatLogKind;
|
||||
|
||||
@@ -115,6 +115,7 @@ public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMa
|
||||
public record DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase;
|
||||
public record NearbyDetectionToggled(bool Enabled) : MessageBase;
|
||||
public record AllowPairRequestsToggled(bool Enabled) : MessageBase;
|
||||
public record ManualPairInviteMessage(string SourceUid, string SourceAlias, string TargetUid, string? DisplayName, string InviteId) : MessageBase;
|
||||
public record ApplyDefaultPairPermissionsMessage(UserPairDto Pair) : MessageBase;
|
||||
public record ApplyDefaultGroupPermissionsMessage(GroupPairFullInfoDto GroupPair) : MessageBase;
|
||||
public record ApplyDefaultsToAllSyncsMessage(string? Context = null, bool? Disabled = null) : MessageBase;
|
||||
|
||||
@@ -76,12 +76,10 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
|
||||
_typingUsers.AddOrUpdate(uid,
|
||||
_ => new TypingEntry(msg.Typing.User, now, now),
|
||||
(_, existing) => new TypingEntry(msg.Typing.User, existing.FirstSeen, now));
|
||||
_logger.LogInformation("Typing state {uid} -> true", uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
_typingUsers.TryRemove(uid, out _);
|
||||
_logger.LogInformation("Typing state {uid} -> false", uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user