Basic syncshell chat impl with game hooks
This commit is contained in:
205
MareSynchronos/Interop/GameChatHooks.cs
Normal file
205
MareSynchronos/Interop/GameChatHooks.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
|
||||
using FFXIVClientStructs.FFXIV.Component.Shell;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop;
|
||||
|
||||
public class ChatChannelOverride
|
||||
{
|
||||
public string ChannelName = string.Empty;
|
||||
public Action<byte[]>? ChatMessageHandler;
|
||||
}
|
||||
|
||||
public unsafe sealed class GameChatHooks : IDisposable
|
||||
{
|
||||
// Based on https://git.anna.lgbt/anna/ExtraChat/src/branch/main/client/ExtraChat/GameFunctions.cs
|
||||
|
||||
private readonly ILogger<GameChatHooks> _logger;
|
||||
|
||||
#region signatures
|
||||
// I do not know what kind of black magic this function performs
|
||||
[Signature("E8 ?? ?? ?? ?? 0F B7 7F 08 48 8B CE")]
|
||||
private readonly delegate* unmanaged<PronounModule*, Utf8String*, byte, Utf8String*> ProcessStringStep2;
|
||||
|
||||
// Component::Shell::ShellCommandModule::ExecuteCommandInner
|
||||
private delegate void SendMessageDelegate(ShellCommandModule* module, Utf8String* message, UIModule* uiModule);
|
||||
[Signature(
|
||||
"E8 ?? ?? ?? ?? FE 86 ?? ?? ?? ?? C7 86",
|
||||
DetourName = nameof(SendMessageDetour)
|
||||
)]
|
||||
private Hook<SendMessageDelegate>? SendMessageHook { get; init; }
|
||||
|
||||
// Client::UI::Shell::RaptureShellModule::SetChatChannel
|
||||
private delegate void SetChatChannelDelegate(RaptureShellModule* module, uint channel);
|
||||
[Signature(
|
||||
"E8 ?? ?? ?? ?? 33 C0 EB 1D",
|
||||
DetourName = nameof(SetChatChannelDetour)
|
||||
)]
|
||||
private Hook<SetChatChannelDelegate>? SetChatChannelHook { get; init; }
|
||||
|
||||
// Component::Shell::ShellCommandModule::ExecuteCommandInner
|
||||
private delegate byte* ChangeChannelNameDelegate(AgentChatLog* agent);
|
||||
[Signature(
|
||||
"E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6",
|
||||
DetourName = nameof(ChangeChannelNameDetour)
|
||||
)]
|
||||
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; init; }
|
||||
|
||||
// Client::UI::Agent::AgentChatLog_???
|
||||
private delegate byte ShouldDoNameLookupDelegate(AgentChatLog* agent);
|
||||
[Signature(
|
||||
"48 89 5C 24 ?? 57 48 83 EC 20 48 8B D9 40 32 FF 48 8B 49 10",
|
||||
DetourName = nameof(ShouldDoNameLookupDetour)
|
||||
)]
|
||||
private Hook<ShouldDoNameLookupDelegate>? ShouldDoNameLookupHook { get; init; }
|
||||
|
||||
#endregion
|
||||
|
||||
private ChatChannelOverride? _chatChannelOverride;
|
||||
private bool _shouldForceNameLookup = false;
|
||||
|
||||
public ChatChannelOverride? ChatChannelOverride
|
||||
{
|
||||
get => _chatChannelOverride;
|
||||
set {
|
||||
_chatChannelOverride = value;
|
||||
this._shouldForceNameLookup = true;
|
||||
}
|
||||
}
|
||||
|
||||
public GameChatHooks(ILogger<GameChatHooks> logger, IGameInteropProvider gameInteropProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
logger.LogInformation("Initializing GameChatHooks");
|
||||
gameInteropProvider.InitializeFromAttributes(this);
|
||||
|
||||
this.SendMessageHook?.Enable();
|
||||
this.SetChatChannelHook?.Enable();
|
||||
this.ChangeChannelNameHook?.Enable();
|
||||
this.ShouldDoNameLookupHook?.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.SendMessageHook?.Dispose();
|
||||
this.SetChatChannelHook?.Dispose();
|
||||
this.ChangeChannelNameHook?.Dispose();
|
||||
this.ShouldDoNameLookupHook?.Dispose();
|
||||
}
|
||||
|
||||
private void SendMessageDetour(ShellCommandModule* thisPtr, Utf8String* message, UIModule* uiModule)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageLength = message->Length;
|
||||
var messageSpan = message->AsSpan();
|
||||
|
||||
bool isCommand = false;
|
||||
|
||||
// Check if chat input begins with a command (or auto-translated command)
|
||||
if (messageLength == 0 || messageSpan[0] == (byte)'/' || !messageSpan.ContainsAnyExcept((byte)' '))
|
||||
{
|
||||
isCommand = true;
|
||||
}
|
||||
else if (messageSpan[0] == (byte)0x02) /* Payload.START_BYTE */
|
||||
{
|
||||
var payload = Payload.Decode(new BinaryReader(new UnmanagedMemoryStream(message->StringPtr, message->BufSize))) as AutoTranslatePayload;
|
||||
|
||||
// Auto-translate text begins with /
|
||||
if (payload != null && payload.Text.Length > 2 && payload.Text[2] == '/')
|
||||
isCommand = true;
|
||||
}
|
||||
|
||||
// If not a command, or no override is set, then call the original chat handler
|
||||
if (isCommand || this._chatChannelOverride == null)
|
||||
{
|
||||
SendMessageHook!.OriginalDisposeSafe(thisPtr, message, uiModule);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, the text is to be sent to the emulated chat channel handler
|
||||
// The chat input string is rendered in to a payload for display first
|
||||
var pronounModule = UIModule.Instance()->GetPronounModule();
|
||||
var chatString1 = pronounModule->ProcessString(message, true);
|
||||
var chatString2 = this.ProcessStringStep2(pronounModule, chatString1, 1);
|
||||
var chatBytes = MemoryHelper.ReadRaw((nint)chatString2->StringPtr, chatString2->Length);
|
||||
|
||||
if (chatBytes.Length > 0)
|
||||
this._chatChannelOverride.ChatMessageHandler?.Invoke(chatBytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception thrown during SendMessageDetour");
|
||||
}
|
||||
}
|
||||
|
||||
private void SetChatChannelDetour(RaptureShellModule* module, uint channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this._chatChannelOverride != null)
|
||||
{
|
||||
this._chatChannelOverride = null;
|
||||
this._shouldForceNameLookup = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception thrown during SetChatChannelDetour");
|
||||
}
|
||||
|
||||
SetChatChannelHook!.OriginalDisposeSafe(module, channel);
|
||||
}
|
||||
|
||||
private byte* ChangeChannelNameDetour(AgentChatLog* agent)
|
||||
{
|
||||
var originalResult = ChangeChannelNameHook!.OriginalDisposeSafe(agent);
|
||||
|
||||
try
|
||||
{
|
||||
// Replace the chat channel name on the UI if active
|
||||
if (this._chatChannelOverride != null)
|
||||
{
|
||||
agent->ChannelLabel.SetString(this._chatChannelOverride.ChannelName);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception thrown during ChangeChannelNameDetour");
|
||||
}
|
||||
|
||||
return originalResult;
|
||||
}
|
||||
|
||||
private byte ShouldDoNameLookupDetour(AgentChatLog* agent)
|
||||
{
|
||||
var originalResult = ShouldDoNameLookupHook!.OriginalDisposeSafe(agent);
|
||||
|
||||
try
|
||||
{
|
||||
// Force the chat channel name to update when required
|
||||
if (this._shouldForceNameLookup)
|
||||
{
|
||||
_shouldForceNameLookup = false;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception thrown during ShouldDoNameLookupDetour");
|
||||
}
|
||||
|
||||
return originalResult;
|
||||
}
|
||||
}
|
||||
@@ -54,4 +54,6 @@ public class MareConfig : IMareConfiguration
|
||||
public bool UseCompactor { get; set; } = false;
|
||||
public int Version { get; set; } = 1;
|
||||
public NotificationLocation WarningNotification { get; set; } = NotificationLocation.Both;
|
||||
|
||||
public bool DisableSyncshellChat { get; set; } = false;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace MareSynchronos.MareConfiguration.Configurations;
|
||||
|
||||
[Serializable]
|
||||
public class ServerTagConfig : IMareConfiguration
|
||||
{
|
||||
public Dictionary<string, ServerTagStorage> ServerTagStorage { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
|
||||
namespace MareSynchronos.MareConfiguration.Configurations;
|
||||
|
||||
[Serializable]
|
||||
public class SyncshellConfig : IMareConfiguration
|
||||
{
|
||||
public Dictionary<string, ServerShellStorage> ServerShellStorage { get; set; } = new(StringComparer.Ordinal);
|
||||
public int Version { get; set; } = 0;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace MareSynchronos.MareConfiguration.Configurations;
|
||||
|
||||
[Serializable]
|
||||
public class UidNotesConfig : IMareConfiguration
|
||||
{
|
||||
public Dictionary<string, ServerNotesStorage> ServerNotes { get; set; } = new(StringComparer.Ordinal);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace MareSynchronos.MareConfiguration.Models;
|
||||
|
||||
[Serializable]
|
||||
public class ServerNotesStorage
|
||||
{
|
||||
public Dictionary<string, string> GidServerComments { get; set; } = new(StringComparer.Ordinal);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MareSynchronos.MareConfiguration.Models;
|
||||
|
||||
[Serializable]
|
||||
public class ServerShellStorage
|
||||
{
|
||||
public Dictionary<string, ShellConfig> GidShellConfig { get; set; } = new(StringComparer.Ordinal);
|
||||
}
|
||||
7
MareSynchronos/MareConfiguration/Models/ShellConfig.cs
Normal file
7
MareSynchronos/MareConfiguration/Models/ShellConfig.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace MareSynchronos.MareConfiguration.Models;
|
||||
|
||||
[Serializable]
|
||||
public class ShellConfig
|
||||
{
|
||||
public int ShellNumber { get; set; }
|
||||
}
|
||||
14
MareSynchronos/MareConfiguration/SyncshellConfigService.cs
Normal file
14
MareSynchronos/MareConfiguration/SyncshellConfigService.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using MareSynchronos.MareConfiguration.Configurations;
|
||||
|
||||
namespace MareSynchronos.MareConfiguration;
|
||||
|
||||
public class SyncshellConfigService : ConfigurationServiceBase<SyncshellConfig>
|
||||
{
|
||||
public const string ConfigName = "syncshells.json";
|
||||
|
||||
public SyncshellConfigService(string configDir) : base(configDir)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string ConfigurationName => ConfigName;
|
||||
}
|
||||
@@ -147,6 +147,7 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService
|
||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<TransientResourceManager>();
|
||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<OnlinePlayerManager>();
|
||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<NotificationService>();
|
||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<ChatService>();
|
||||
|
||||
#if !DEBUG
|
||||
if (_mareConfigService.Current.LogLevel != LogLevel.Information)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
public Plugin(IDalamudPluginInterface pluginInterface, ICommandManager commandManager, IDataManager gameData,
|
||||
IFramework framework, IObjectTable objectTable, IClientState clientState, ICondition condition, IChatGui chatGui,
|
||||
IGameGui gameGui, IDtrBar dtrBar, IToastGui toastGui, IPluginLog pluginLog, ITargetManager targetManager, IGameLifecycle addonLifecycle,
|
||||
INotificationManager notificationManager, ITextureProvider textureProvider, IContextMenu contextMenu)
|
||||
INotificationManager notificationManager, ITextureProvider textureProvider, IContextMenu contextMenu, IGameInteropProvider gameInteropProvider)
|
||||
{
|
||||
Plugin.Self = this;
|
||||
_hostBuilderRunTask = new HostBuilder()
|
||||
@@ -103,6 +103,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new NotesConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new ServerTagConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new SyncshellConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new TransientConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new ConfigurationMigrator(s.GetRequiredService<ILogger<ConfigurationMigrator>>(), pluginInterface, s.GetRequiredService<NotesConfigService>()));
|
||||
collection.AddSingleton<HubFactory>();
|
||||
@@ -133,13 +134,17 @@ public sealed class Plugin : IDalamudPlugin
|
||||
s.GetRequiredService<UiFactory>(),
|
||||
s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<MareMediator>()));
|
||||
collection.AddScoped((s) => new CommandManagerService(commandManager, s.GetRequiredService<PerformanceCollectorService>(),
|
||||
s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<PeriodicFileScanner>(), s.GetRequiredService<ApiController>(),
|
||||
s.GetRequiredService<MareMediator>(), s.GetRequiredService<MareConfigService>()));
|
||||
s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<PeriodicFileScanner>(), s.GetRequiredService<ChatService>(),
|
||||
s.GetRequiredService<ApiController>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<MareConfigService>()));
|
||||
collection.AddScoped((s) => new NotificationService(s.GetRequiredService<ILogger<NotificationService>>(),
|
||||
s.GetRequiredService<MareMediator>(), notificationManager, chatGui, s.GetRequiredService<MareConfigService>()));
|
||||
collection.AddScoped((s) => new UiSharedService(s.GetRequiredService<ILogger<UiSharedService>>(), s.GetRequiredService<IpcManager>(), s.GetRequiredService<ApiController>(),
|
||||
s.GetRequiredService<PeriodicFileScanner>(), s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<MareConfigService>(), s.GetRequiredService<DalamudUtilService>(),
|
||||
pluginInterface, textureProvider, s.GetRequiredService<Dalamud.Localization>(), s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<MareMediator>()));
|
||||
collection.AddScoped((s) => new ChatService(s.GetRequiredService<ILogger<ChatService>>(), s.GetRequiredService<DalamudUtilService>(),
|
||||
s.GetRequiredService<MareMediator>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<PairManager>(),
|
||||
s.GetRequiredService<ILogger<GameChatHooks>>(), gameInteropProvider, chatGui,
|
||||
s.GetRequiredService<MareConfigService>(), s.GetRequiredService<ServerConfigurationManager>()));
|
||||
|
||||
collection.AddHostedService(p => p.GetRequiredService<MareMediator>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<ConfigurationMigrator>());
|
||||
|
||||
23
MareSynchronos/Services/ChatIntegrationService.cs
Normal file
23
MareSynchronos/Services/ChatIntegrationService.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
public sealed class ChatIntegrationService : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
public bool Activated { get; private set; } = false;
|
||||
|
||||
public ChatIntegrationService(ILogger<ChatIntegrationService> logger, MareMediator mediator) : base(logger, mediator)
|
||||
{
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
if (Activated)
|
||||
return;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
157
MareSynchronos/Services/ChatService.cs
Normal file
157
MareSynchronos/Services/ChatService.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Plugin.Services;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.Interop;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
public class ChatService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private readonly ILogger<ChatService> _logger;
|
||||
private readonly IChatGui _chatGui;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly MareConfigService _mareConfig;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
private readonly Lazy<GameChatHooks> _gameChatHooks;
|
||||
|
||||
public ChatService(ILogger<ChatService> logger, DalamudUtilService dalamudUtil, MareMediator mediator, ApiController apiController,
|
||||
PairManager pairManager, ILogger<GameChatHooks> logger2, IGameInteropProvider gameInteropProvider, IChatGui chatGui,
|
||||
MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager) : base(logger, mediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_chatGui = chatGui;
|
||||
_mareConfig = mareConfig;
|
||||
_apiController = apiController;
|
||||
_pairManager = pairManager;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
|
||||
Mediator.Subscribe<UserChatMsgMessage>(this, HandleUserChat);
|
||||
Mediator.Subscribe<GroupChatMsgMessage>(this, HandleGroupChat);
|
||||
|
||||
_gameChatHooks = new(() => new GameChatHooks(logger2, gameInteropProvider));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_gameChatHooks.IsValueCreated)
|
||||
_gameChatHooks.Value!.Dispose();
|
||||
}
|
||||
|
||||
private void HandleUserChat(UserChatMsgMessage message)
|
||||
{
|
||||
var chatMsg = message.ChatMsg;
|
||||
var prefix = new SeStringBuilder();
|
||||
prefix.AddText("[BnnuyChat] ");
|
||||
_chatGui.Print(new XivChatEntry{
|
||||
MessageBytes = [..prefix.Build().Encode(), ..message.ChatMsg.PayloadContent],
|
||||
Name = chatMsg.SenderName,
|
||||
Type = XivChatType.TellIncoming
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleGroupChat(GroupChatMsgMessage message)
|
||||
{
|
||||
if (_mareConfig.Current.DisableSyncshellChat)
|
||||
return;
|
||||
|
||||
var chatMsg = message.ChatMsg;
|
||||
var shellNumber = _serverConfigurationManager.GetShellNumberForGid(message.GroupInfo.GID);
|
||||
var prefix = new SeStringBuilder();
|
||||
|
||||
// TODO: Configure colors and appearance
|
||||
prefix.AddUiForeground(710);
|
||||
prefix.AddText($"[SS{shellNumber}]<");
|
||||
// TODO: Don't link to the local player because it lets you do invalid things
|
||||
prefix.Add(new PlayerPayload(chatMsg.SenderName, (uint)chatMsg.SenderHomeWorldId));
|
||||
prefix.AddText("> ");
|
||||
|
||||
_chatGui.Print(new XivChatEntry{
|
||||
MessageBytes = [..prefix.Build().Encode(), ..message.ChatMsg.PayloadContent],
|
||||
Name = chatMsg.SenderName,
|
||||
Type = XivChatType.Debug
|
||||
});
|
||||
}
|
||||
|
||||
// Called to update the active chat shell name if its renamed
|
||||
public void MaybeUpdateShellName(int shellNumber)
|
||||
{
|
||||
if (_mareConfig.Current.DisableSyncshellChat)
|
||||
return;
|
||||
|
||||
foreach (var group in _pairManager.Groups)
|
||||
{
|
||||
if (_serverConfigurationManager.GetShellNumberForGid(group.Key.GID) == shellNumber)
|
||||
{
|
||||
if (_gameChatHooks.IsValueCreated && _gameChatHooks.Value.ChatChannelOverride != null)
|
||||
{
|
||||
// Very dumb and won't handle re-numbering -- need to identify the active chat channel more reliably later
|
||||
if (_gameChatHooks.Value.ChatChannelOverride.ChannelName.StartsWith($"SS [{shellNumber}]"))
|
||||
SwitchChatShell(shellNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchChatShell(int shellNumber)
|
||||
{
|
||||
if (_mareConfig.Current.DisableSyncshellChat)
|
||||
return;
|
||||
|
||||
foreach (var group in _pairManager.Groups)
|
||||
{
|
||||
if (_serverConfigurationManager.GetShellNumberForGid(group.Key.GID) == shellNumber)
|
||||
{
|
||||
var name = _serverConfigurationManager.GetNoteForGid(group.Key.GID) ?? group.Key.AliasOrGID;
|
||||
// BUG: This doesn't always update the chat window e.g. when renaming a group
|
||||
_gameChatHooks.Value.ChatChannelOverride = new()
|
||||
{
|
||||
ChannelName = $"SS [{shellNumber}]: {name}",
|
||||
ChatMessageHandler = chatBytes => SendChatShell(shellNumber, chatBytes)
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_chatGui.PrintError($"[LoporritSync] Syncshell number #{shellNumber} not found");
|
||||
}
|
||||
|
||||
public void SendChatShell(int shellNumber, byte[] chatBytes)
|
||||
{
|
||||
if (_mareConfig.Current.DisableSyncshellChat)
|
||||
return;
|
||||
|
||||
foreach (var group in _pairManager.Groups)
|
||||
{
|
||||
if (_serverConfigurationManager.GetShellNumberForGid(group.Key.GID) == shellNumber)
|
||||
{
|
||||
Task.Run(async () => {
|
||||
// TODO: Cache the name and home world instead of fetching it every time
|
||||
var chatMsg = await _dalamudUtil.RunOnFrameworkThread(() => {
|
||||
return new ChatMessage()
|
||||
{
|
||||
SenderName = _dalamudUtil.GetPlayerName(),
|
||||
SenderHomeWorldId = _dalamudUtil.GetHomeWorldId(),
|
||||
PayloadContent = chatBytes
|
||||
};
|
||||
});
|
||||
await _apiController.GroupChatSendMsg(new(group.Key), chatMsg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_chatGui.PrintError($"[LoporritSync] Syncshell number #{shellNumber} not found");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Plugin.Services;
|
||||
using MareSynchronos.FileCache;
|
||||
@@ -8,6 +9,7 @@ using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.UI;
|
||||
using MareSynchronos.WebAPI;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
@@ -16,22 +18,27 @@ public sealed class CommandManagerService : IDisposable
|
||||
private const string _commandName = "/sync";
|
||||
private const string _commandName2 = "/loporrit";
|
||||
|
||||
private const string _ssCommandPrefix = "/ss";
|
||||
private const int _ssCommandMaxNumber = 50;
|
||||
|
||||
private readonly ApiController _apiController;
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly MareMediator _mediator;
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private readonly PerformanceCollectorService _performanceCollectorService;
|
||||
private readonly PeriodicFileScanner _periodicFileScanner;
|
||||
private readonly ChatService _chatService;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
public CommandManagerService(ICommandManager commandManager, PerformanceCollectorService performanceCollectorService,
|
||||
ServerConfigurationManager serverConfigurationManager, PeriodicFileScanner periodicFileScanner,
|
||||
ServerConfigurationManager serverConfigurationManager, PeriodicFileScanner periodicFileScanner, ChatService chatService,
|
||||
ApiController apiController, MareMediator mediator, MareConfigService mareConfigService)
|
||||
{
|
||||
_commandManager = commandManager;
|
||||
_performanceCollectorService = performanceCollectorService;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
_periodicFileScanner = periodicFileScanner;
|
||||
_chatService = chatService;
|
||||
_apiController = apiController;
|
||||
_mediator = mediator;
|
||||
_mareConfigService = mareConfigService;
|
||||
@@ -43,12 +50,24 @@ public sealed class CommandManagerService : IDisposable
|
||||
{
|
||||
HelpMessage = "Opens the Loporrit UI"
|
||||
});
|
||||
|
||||
// Lazy registration of all possible /ss# commands which tbf is what the game does for linkshells anyway
|
||||
for (int i = 1; i <= _ssCommandMaxNumber; ++i)
|
||||
{
|
||||
_commandManager.AddHandler($"{_ssCommandPrefix}{i}", new CommandInfo(OnChatCommand)
|
||||
{
|
||||
ShowInHelp = false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_commandManager.RemoveHandler(_commandName);
|
||||
_commandManager.RemoveHandler(_commandName2);
|
||||
|
||||
for (int i = 1; i <= _ssCommandMaxNumber; ++i)
|
||||
_commandManager.RemoveHandler($"{_ssCommandPrefix}{i}");
|
||||
}
|
||||
|
||||
private void OnCommand(string command, string args)
|
||||
@@ -116,4 +135,23 @@ public sealed class CommandManagerService : IDisposable
|
||||
_mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChatCommand(string command, string args)
|
||||
{
|
||||
if (_mareConfigService.Current.DisableSyncshellChat)
|
||||
return;
|
||||
|
||||
int shellNumber = int.Parse(command[_ssCommandPrefix.Length..]);
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
_chatService.SwitchChatShell(shellNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: Chat content seems to already be stripped of any special characters here?
|
||||
byte[] chatBytes = Encoding.UTF8.GetBytes(args);
|
||||
_chatService.SendChatShell(shellNumber, chatBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,5 +79,7 @@ public record TargetPairMessage(Pair Pair) : MessageBase;
|
||||
public record CombatStartMessage : MessageBase;
|
||||
public record CombatEndMessage : MessageBase;
|
||||
|
||||
public record UserChatMsgMessage(SignedChatMessage ChatMsg) : MessageBase;
|
||||
public record GroupChatMsgMessage(GroupDto GroupInfo, SignedChatMessage ChatMsg) : MessageBase;
|
||||
#pragma warning restore S2094
|
||||
#pragma warning restore MA0048 // File name must match type name
|
||||
@@ -15,14 +15,16 @@ public class ServerConfigurationManager
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly NotesConfigService _notesConfig;
|
||||
private readonly ServerTagConfigService _serverTagConfig;
|
||||
private readonly SyncshellConfigService _syncshellConfig;
|
||||
|
||||
public ServerConfigurationManager(ILogger<ServerConfigurationManager> logger, ServerConfigService configService,
|
||||
ServerTagConfigService serverTagConfig, NotesConfigService notesConfig, DalamudUtilService dalamudUtil,
|
||||
MareMediator mareMediator)
|
||||
ServerTagConfigService serverTagConfig, SyncshellConfigService syncshellConfig, NotesConfigService notesConfig,
|
||||
DalamudUtilService dalamudUtil, MareMediator mareMediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_configService = configService;
|
||||
_serverTagConfig = serverTagConfig;
|
||||
_syncshellConfig = syncshellConfig;
|
||||
_notesConfig = notesConfig;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_mareMediator = mareMediator;
|
||||
@@ -244,6 +246,18 @@ public class ServerConfigurationManager
|
||||
return CurrentServerTagStorage().ServerAvailablePairTags;
|
||||
}
|
||||
|
||||
internal int GetShellNumberForGid(string gid)
|
||||
{
|
||||
if (CurrentSyncshellStorage().GidShellConfig.TryGetValue(gid, out var config))
|
||||
{
|
||||
return config.ShellNumber;
|
||||
}
|
||||
|
||||
int newNumber = CurrentSyncshellStorage().GidShellConfig.Count > 0 ? CurrentSyncshellStorage().GidShellConfig.Select(x => x.Value.ShellNumber).Max() + 1 : 1;
|
||||
SetShellNumberForGid(gid, newNumber, false);
|
||||
return newNumber;
|
||||
}
|
||||
|
||||
internal Dictionary<string, List<string>> GetUidServerPairedUserTags()
|
||||
{
|
||||
return CurrentServerTagStorage().UidServerPairedUserTags;
|
||||
@@ -345,6 +359,25 @@ public class ServerConfigurationManager
|
||||
_notesConfig.Save();
|
||||
}
|
||||
|
||||
internal void SetShellNumberForGid(string gid, int number, bool save = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(gid)) return;
|
||||
|
||||
if (CurrentSyncshellStorage().GidShellConfig.TryGetValue(gid, out var config))
|
||||
{
|
||||
config.ShellNumber = number;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentSyncshellStorage().GidShellConfig.Add(gid, new(){
|
||||
ShellNumber = number
|
||||
});
|
||||
}
|
||||
|
||||
if (save)
|
||||
_syncshellConfig.Save();
|
||||
}
|
||||
|
||||
private ServerNotesStorage CurrentNotesStorage()
|
||||
{
|
||||
TryCreateCurrentNotesStorage();
|
||||
@@ -357,6 +390,12 @@ public class ServerConfigurationManager
|
||||
return _serverTagConfig.Current.ServerTagStorage[CurrentApiUrl];
|
||||
}
|
||||
|
||||
private ServerShellStorage CurrentSyncshellStorage()
|
||||
{
|
||||
TryCreateCurrentSyncshellStorage();
|
||||
return _syncshellConfig.Current.ServerShellStorage[CurrentApiUrl];
|
||||
}
|
||||
|
||||
private void EnsureMainExists()
|
||||
{
|
||||
bool lopExists = false;
|
||||
@@ -406,4 +445,12 @@ public class ServerConfigurationManager
|
||||
_serverTagConfig.Current.ServerTagStorage[CurrentApiUrl] = new();
|
||||
}
|
||||
}
|
||||
|
||||
private void TryCreateCurrentSyncshellStorage()
|
||||
{
|
||||
if (!_syncshellConfig.Current.ServerShellStorage.ContainsKey(CurrentApiUrl))
|
||||
{
|
||||
_syncshellConfig.Current.ServerShellStorage[CurrentApiUrl] = new();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using MareSynchronos.API.Dto.User;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.UI.Components;
|
||||
@@ -38,6 +39,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
private readonly GroupPanel _groupPanel;
|
||||
private readonly PairGroupsUi _pairGroupsUi;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly ChatService _chatService;
|
||||
private readonly SelectGroupForPairUi _selectGroupForPairUi;
|
||||
private readonly SelectPairForGroupUi _selectPairsForGroupUi;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
@@ -56,19 +58,20 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
private bool _showSyncShells;
|
||||
private bool _wasOpen;
|
||||
|
||||
public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager,
|
||||
public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService,
|
||||
ServerConfigurationManager serverManager, MareMediator mediator, FileUploadManager fileTransferManager, UidDisplayHandler uidDisplayHandler) : base(logger, mediator, "###LoporritSyncMainUI")
|
||||
{
|
||||
_uiShared = uiShared;
|
||||
_configService = configService;
|
||||
_apiController = apiController;
|
||||
_pairManager = pairManager;
|
||||
_chatService = chatService;
|
||||
_serverManager = serverManager;
|
||||
_fileTransferManager = fileTransferManager;
|
||||
_uidDisplayHandler = uidDisplayHandler;
|
||||
var tagHandler = new TagHandler(_serverManager);
|
||||
|
||||
_groupPanel = new(this, uiShared, _pairManager, uidDisplayHandler, _serverManager);
|
||||
_groupPanel = new(this, uiShared, _pairManager, _chatService, uidDisplayHandler, _configService, _serverManager);
|
||||
_selectGroupForPairUi = new(tagHandler, uidDisplayHandler);
|
||||
_selectPairsForGroupUi = new(tagHandler, uidDisplayHandler);
|
||||
_pairGroupsUi = new(configService, tagHandler, uidDisplayHandler, apiController, _selectPairsForGroupUi);
|
||||
|
||||
@@ -17,6 +17,9 @@ using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.UI.Components;
|
||||
using MareSynchronos.UI.Handlers;
|
||||
using Dalamud.Interface.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
@@ -25,6 +28,8 @@ internal sealed class GroupPanel
|
||||
private readonly Dictionary<string, bool> _expandedGroupState = new(StringComparer.Ordinal);
|
||||
private readonly CompactUi _mainUi;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly ChatService _chatService;
|
||||
private readonly MareConfigService _mareConfig;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly Dictionary<string, bool> _showGidForEntry = new(StringComparer.Ordinal);
|
||||
private readonly UidDisplayHandler _uidDisplayHandler;
|
||||
@@ -50,12 +55,15 @@ internal sealed class GroupPanel
|
||||
private string _syncShellPassword = string.Empty;
|
||||
private string _syncShellToJoin = string.Empty;
|
||||
|
||||
public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, UidDisplayHandler uidDisplayHandler, ServerConfigurationManager serverConfigurationManager)
|
||||
public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, ChatService chatServivce,
|
||||
UidDisplayHandler uidDisplayHandler, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
_mainUi = mainUi;
|
||||
_uiShared = uiShared;
|
||||
_pairManager = pairManager;
|
||||
_chatService = chatServivce;
|
||||
_uidDisplayHandler = uidDisplayHandler;
|
||||
_mareConfig = mareConfig;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
@@ -185,6 +193,8 @@ internal sealed class GroupPanel
|
||||
|
||||
private void DrawSyncshell(GroupFullInfoDto groupDto, List<Pair> pairsInGroup)
|
||||
{
|
||||
int shellNumber = _serverConfigurationManager.GetShellNumberForGid(groupDto.GID);
|
||||
|
||||
var name = groupDto.Group.Alias ?? groupDto.GID;
|
||||
if (!_expandedGroupState.TryGetValue(groupDto.GID, out bool isExpanded))
|
||||
{
|
||||
@@ -230,7 +240,13 @@ internal sealed class GroupPanel
|
||||
|
||||
if (!string.Equals(_editGroupEntry, groupDto.GID, StringComparison.Ordinal))
|
||||
{
|
||||
if (!_mareConfig.Current.DisableSyncshellChat)
|
||||
{
|
||||
ImGui.TextUnformatted($"[{shellNumber}]");
|
||||
UiSharedService.AttachToolTip("Chat command prefix: /ss" + shellNumber);
|
||||
}
|
||||
if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(groupName);
|
||||
if (textIsGid) ImGui.PopFont();
|
||||
UiSharedService.AttachToolTip("Left click to switch between GID display and comment" + Environment.NewLine +
|
||||
@@ -252,6 +268,7 @@ internal sealed class GroupPanel
|
||||
_serverConfigurationManager.SetNoteForGid(_editGroupEntry, _editGroupComment);
|
||||
_editGroupComment = _serverConfigurationManager.GetNoteForGid(groupDto.GID) ?? string.Empty;
|
||||
_editGroupEntry = groupDto.GID;
|
||||
_chatService.MaybeUpdateShellName(shellNumber);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -262,6 +279,7 @@ internal sealed class GroupPanel
|
||||
{
|
||||
_serverConfigurationManager.SetNoteForGid(groupDto.GID, _editGroupComment);
|
||||
_editGroupEntry = string.Empty;
|
||||
_chatService.MaybeUpdateShellName(shellNumber);
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
|
||||
@@ -651,6 +651,18 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
UiSharedService.DrawHelpText("This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs.");
|
||||
|
||||
ImGui.Separator();
|
||||
UiSharedService.FontText("Chat", _uiShared.UidFont);
|
||||
|
||||
var disableSyncshellChat = _configService.Current.DisableSyncshellChat;
|
||||
|
||||
if (ImGui.Checkbox("Disable Syncshell Chat", ref disableSyncshellChat))
|
||||
{
|
||||
_configService.Current.DisableSyncshellChat = disableSyncshellChat;
|
||||
_configService.Save();
|
||||
}
|
||||
UiSharedService.DrawHelpText("Disable sending/receiving all syncshell chat messages.");
|
||||
|
||||
ImGui.Separator();
|
||||
UiSharedService.FontText("UI", _uiShared.UidFont);
|
||||
var showCharacterNames = _configService.Current.ShowCharacterNames;
|
||||
@@ -664,7 +676,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
var enableDtrEntry = _configService.Current.EnableDtrEntry;
|
||||
var showUidInDtrTooltip = _configService.Current.ShowUidInDtrTooltip;
|
||||
var preferNoteInDtrTooltip = _configService.Current.PreferNoteInDtrTooltip;
|
||||
var preferNotesInsteadOfName = _configService.Current.PreferNotesOverNamesForVisible;
|
||||
|
||||
if (ImGui.Checkbox("Enable Game Right Click Menu Entries", ref enableRightClickMenu))
|
||||
{
|
||||
|
||||
@@ -33,6 +33,12 @@ public partial class ApiController
|
||||
await _mareHub!.SendAsync(nameof(UserAddPair), user).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task UserChatSendMsg(UserDto user, ChatMessage message)
|
||||
{
|
||||
CheckConnection();
|
||||
await _mareHub!.SendAsync(nameof(UserChatSendMsg), user, message).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task UserDelete()
|
||||
{
|
||||
CheckConnection();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.API.Dto;
|
||||
using MareSynchronos.API.Dto.Chat;
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using MareSynchronos.API.Dto.User;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
@@ -25,6 +26,13 @@ public partial class ApiController
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Client_GroupChatMsg(GroupChatMsgDto groupChatMsgDto)
|
||||
{
|
||||
Logger.LogDebug("Client_GroupChatMsg: {msg}", groupChatMsgDto.Message);
|
||||
Mediator.Publish(new GroupChatMsgMessage(groupChatMsgDto.Group, groupChatMsgDto.Message));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Client_GroupPairChangePermissions(GroupPairUserPermissionDto dto)
|
||||
{
|
||||
Logger.LogTrace("Client_GroupPairChangePermissions: {dto}", dto);
|
||||
@@ -120,6 +128,13 @@ public partial class ApiController
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Client_UserChatMsg(UserChatMsgDto chatMsgDto)
|
||||
{
|
||||
Logger.LogDebug("Client_UserChatMsg: {msg}", chatMsgDto.Message);
|
||||
Mediator.Publish(new UserChatMsgMessage(chatMsgDto.Message));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Client_UserReceiveCharacterData(OnlineUserCharaDataDto dataDto)
|
||||
{
|
||||
Logger.LogTrace("Client_UserReceiveCharacterData: {user}", dataDto.User);
|
||||
@@ -188,6 +203,12 @@ public partial class ApiController
|
||||
_mareHub!.On(nameof(Client_GroupChangePermissions), act);
|
||||
}
|
||||
|
||||
public void OnGroupChatMsg(Action<GroupChatMsgDto> act)
|
||||
{
|
||||
if (_initialized) return;
|
||||
_mareHub!.On(nameof(Client_GroupChatMsg), act);
|
||||
}
|
||||
|
||||
public void OnGroupPairChangePermissions(Action<GroupPairUserPermissionDto> act)
|
||||
{
|
||||
if (_initialized) return;
|
||||
@@ -248,6 +269,12 @@ public partial class ApiController
|
||||
_mareHub!.On(nameof(Client_UserAddClientPair), act);
|
||||
}
|
||||
|
||||
public void OnUserChatMsg(Action<UserChatMsgDto> act)
|
||||
{
|
||||
if (_initialized) return;
|
||||
_mareHub!.On(nameof(Client_UserChatMsg), act);
|
||||
}
|
||||
|
||||
public void OnUserReceiveCharacterData(Action<OnlineUserCharaDataDto> act)
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using MareSynchronos.WebAPI.SignalR.Utils;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
@@ -36,6 +37,12 @@ public partial class ApiController
|
||||
return await _mareHub!.InvokeAsync<bool>(nameof(GroupChangePassword), groupPassword).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task GroupChatSendMsg(GroupDto group, ChatMessage message)
|
||||
{
|
||||
CheckConnection();
|
||||
await _mareHub!.SendAsync(nameof(GroupChatSendMsg), group, message).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task GroupClear(GroupDto group)
|
||||
{
|
||||
CheckConnection();
|
||||
|
||||
@@ -323,6 +323,9 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
||||
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
|
||||
OnGroupPairChangePermissions((dto) => _ = Client_GroupPairChangePermissions(dto));
|
||||
|
||||
OnUserChatMsg((dto) => _ = Client_UserChatMsg(dto));
|
||||
OnGroupChatMsg((dto) => _ = Client_GroupChatMsg(dto));
|
||||
|
||||
_healthCheckTokenSource?.Cancel();
|
||||
_healthCheckTokenSource?.Dispose();
|
||||
_healthCheckTokenSource = new CancellationTokenSource();
|
||||
|
||||
Reference in New Issue
Block a user