2.0.0 (#92)
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m27s
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m27s
2.0.0 Changes: - Reworked shell finder UI with compact or list view with profile tags showing with the listing, allowing moderators to broadcast the syncshell as well to have it be used more. - Reworked user list in syncshell admin screen to have filter visible and moved away from table to its own thing, allowing to copy uid/note/alias when clicking on the name. - Reworked download bars and download box to make it look more modern, removed the jitter around, so it shouldn't vibrate around much. - Chat has been added to the top menu, working in Zone or in Syncshells to be used there. - Paired system has been revamped to make pausing and unpausing faster, and loading people should be faster as well. - Moved to the internal object table to have faster load times for users; people should load in faster - Compactor is running on a multi-threaded level instead of single-threaded; this should increase the speed of compacting files - Nameplate Service has been reworked so it wouldn't use the nameplate handler anymore. - Files can be resized when downloading to reduce load on users if they aren't compressed. (can be toggled to resize all). - Penumbra Collections are now only made when people are visible, reducing the load on boot-up when having many syncshells in your list. - Lightfinder plates have been moved away from using Nameplates, but will use an overlay. - Main UI has been changed a bit with a gradient, and on hover will glow up now. - Reworked Profile UI for Syncshell and Users to be more user-facing with more customizable items. - Reworked Settings UI to look more modern. - Performance should be better due to new systems that would dispose of the collections and better caching of items. Co-authored-by: defnotken <itsdefnotken@gmail.com> Co-authored-by: azyges <aaaaaa@aaa.aaa> Co-authored-by: choco <choco@patat.nl> Co-authored-by: cake <admin@cakeandbanana.nl> Co-authored-by: Minmoose <KennethBohr@outlook.com> Reviewed-on: #92
This commit was merged in pull request #92.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
using System.Reflection;
|
||||
using Dalamud.Utility;
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Data.Extensions;
|
||||
using LightlessSync.API.Dto;
|
||||
using LightlessSync.API.Dto.Chat;
|
||||
using LightlessSync.API.Dto.Group;
|
||||
using LightlessSync.API.Dto.User;
|
||||
using LightlessSync.API.SignalR;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
@@ -16,7 +16,6 @@ using LightlessSync.WebAPI.SignalR;
|
||||
using LightlessSync.WebAPI.SignalR.Utils;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LightlessSync.WebAPI;
|
||||
|
||||
@@ -28,7 +27,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly HubFactory _hubFactory;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly PairCoordinator _pairCoordinator;
|
||||
private readonly PairRequestService _pairRequestService;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
private readonly TokenProvider _tokenProvider;
|
||||
@@ -42,14 +41,17 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
private HubConnection? _lightlessHub;
|
||||
private ServerState _serverState;
|
||||
private CensusUpdateMessage? _lastCensus;
|
||||
private IReadOnlyList<ZoneChatChannelInfoDto> _zoneChatChannels = Array.Empty<ZoneChatChannelInfoDto>();
|
||||
private IReadOnlyList<GroupChatChannelInfoDto> _groupChatChannels = Array.Empty<GroupChatChannelInfoDto>();
|
||||
private event Action<ChatMessageDto>? ChatMessageReceived;
|
||||
|
||||
public ApiController(ILogger<ApiController> logger, HubFactory hubFactory, DalamudUtilService dalamudUtil,
|
||||
PairManager pairManager, PairRequestService pairRequestService, ServerConfigurationManager serverManager, LightlessMediator mediator,
|
||||
PairCoordinator pairCoordinator, PairRequestService pairRequestService, ServerConfigurationManager serverManager, LightlessMediator mediator,
|
||||
TokenProvider tokenProvider, LightlessConfigService lightlessConfigService, NotificationService lightlessNotificationService) : base(logger, mediator)
|
||||
{
|
||||
_hubFactory = hubFactory;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_pairManager = pairManager;
|
||||
_pairCoordinator = pairCoordinator;
|
||||
_pairRequestService = pairRequestService;
|
||||
_serverManager = serverManager;
|
||||
_tokenProvider = tokenProvider;
|
||||
@@ -61,7 +63,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
Mediator.Subscribe<HubClosedMessage>(this, (msg) => LightlessHubOnClosed(msg.Exception));
|
||||
Mediator.Subscribe<HubReconnectedMessage>(this, (msg) => _ = LightlessHubOnReconnectedAsync());
|
||||
Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => LightlessHubOnReconnecting(msg.Exception));
|
||||
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePauseAsync(msg.UserData));
|
||||
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePauseAsync(msg.Pair));
|
||||
Mediator.Subscribe<CensusUpdateMessage>(this, (msg) => _lastCensus = msg);
|
||||
Mediator.Subscribe<PauseMessage>(this, (msg) => _ = PauseAsync(msg.UserData));
|
||||
|
||||
@@ -106,15 +108,65 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
|
||||
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
||||
|
||||
public IReadOnlyList<ZoneChatChannelInfoDto> ZoneChatChannels => _zoneChatChannels;
|
||||
public IReadOnlyList<GroupChatChannelInfoDto> GroupChatChannels => _groupChatChannels;
|
||||
public string UID => _connectionDto?.User.UID ?? string.Empty;
|
||||
|
||||
public event Action? OnConnected;
|
||||
|
||||
public async Task<bool> CheckClientHealth()
|
||||
{
|
||||
return await _lightlessHub!.InvokeAsync<bool>(nameof(CheckClientHealth)).ConfigureAwait(false);
|
||||
var hub = _lightlessHub;
|
||||
if (hub is null || !IsConnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return await hub.InvokeAsync<bool>(nameof(CheckClientHealth)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogDebug(ex, "Client health check failed.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshChatChannelsAsync()
|
||||
{
|
||||
if (_lightlessHub is null || !IsConnected)
|
||||
return;
|
||||
|
||||
await Task.WhenAll(GetZoneChatChannelsAsync(), GetGroupChatChannelsAsync()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ZoneChatChannelInfoDto>> GetZoneChatChannelsAsync()
|
||||
{
|
||||
if (_lightlessHub is null || !IsConnected)
|
||||
return _zoneChatChannels;
|
||||
|
||||
var channels = await _lightlessHub.InvokeAsync<IReadOnlyList<ZoneChatChannelInfoDto>>("GetZoneChatChannels").ConfigureAwait(false);
|
||||
_zoneChatChannels = channels;
|
||||
return channels;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<GroupChatChannelInfoDto>> GetGroupChatChannelsAsync()
|
||||
{
|
||||
if (_lightlessHub is null || !IsConnected)
|
||||
return _groupChatChannels;
|
||||
|
||||
var channels = await _lightlessHub.InvokeAsync<IReadOnlyList<GroupChatChannelInfoDto>>("GetGroupChatChannels").ConfigureAwait(false);
|
||||
_groupChatChannels = channels;
|
||||
return channels;
|
||||
}
|
||||
|
||||
Task<IReadOnlyList<ZoneChatChannelInfoDto>> ILightlessHub.GetZoneChatChannels()
|
||||
=> _lightlessHub!.InvokeAsync<IReadOnlyList<ZoneChatChannelInfoDto>>("GetZoneChatChannels");
|
||||
|
||||
Task<IReadOnlyList<GroupChatChannelInfoDto>> ILightlessHub.GetGroupChatChannels()
|
||||
=> _lightlessHub!.InvokeAsync<IReadOnlyList<GroupChatChannelInfoDto>>("GetGroupChatChannels");
|
||||
|
||||
public async Task CreateConnectionsAsync()
|
||||
{
|
||||
if (!_serverManager.ShownCensusPopup)
|
||||
@@ -133,7 +185,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
Logger.LogInformation("Not recreating Connection, paused");
|
||||
_connectionDto = null;
|
||||
await StopConnectionAsync(ServerState.Disconnected).ConfigureAwait(false);
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
if (_connectionCancellationTokenSource != null)
|
||||
{
|
||||
await _connectionCancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -147,7 +202,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
Mediator.Publish(new NotificationMessage("Multiple Identical Characters detected", "Your Service configuration has multiple characters with the same name and world set up. Delete the duplicates in the character management to be able to connect to Lightless.",
|
||||
NotificationType.Error));
|
||||
await StopConnectionAsync(ServerState.MultiChara).ConfigureAwait(false);
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
if (_connectionCancellationTokenSource != null)
|
||||
{
|
||||
await _connectionCancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -156,7 +214,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
Logger.LogWarning("No secret key set for current character");
|
||||
_connectionDto = null;
|
||||
await StopConnectionAsync(ServerState.NoSecretKey).ConfigureAwait(false);
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
if (_connectionCancellationTokenSource != null)
|
||||
{
|
||||
await _connectionCancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -170,7 +231,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
Mediator.Publish(new NotificationMessage("Multiple Identical Characters detected", "Your Service configuration has multiple characters with the same name and world set up. Delete the duplicates in the character management to be able to connect to Lightless.",
|
||||
NotificationType.Error));
|
||||
await StopConnectionAsync(ServerState.MultiChara).ConfigureAwait(false);
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
if (_connectionCancellationTokenSource != null)
|
||||
{
|
||||
await _connectionCancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -179,7 +243,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
Logger.LogWarning("No UID/OAuth set for current character");
|
||||
_connectionDto = null;
|
||||
await StopConnectionAsync(ServerState.OAuthMisconfigured).ConfigureAwait(false);
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
if (_connectionCancellationTokenSource != null)
|
||||
{
|
||||
await _connectionCancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,7 +255,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
Logger.LogWarning("OAuth2 login token could not be updated");
|
||||
_connectionDto = null;
|
||||
await StopConnectionAsync(ServerState.OAuthLoginTokenStale).ConfigureAwait(false);
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
if (_connectionCancellationTokenSource != null)
|
||||
{
|
||||
await _connectionCancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -199,7 +269,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Informational,
|
||||
$"Starting Connection to {_serverManager.CurrentServer.ServerName}")));
|
||||
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
if (_connectionCancellationTokenSource != null)
|
||||
{
|
||||
await _connectionCancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
_connectionCancellationTokenSource?.Dispose();
|
||||
_connectionCancellationTokenSource = new CancellationTokenSource();
|
||||
var token = _connectionCancellationTokenSource.Token;
|
||||
@@ -337,35 +410,86 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
|
||||
private bool _naggedAboutLod = false;
|
||||
|
||||
public Task CyclePauseAsync(UserData userData)
|
||||
public Task CyclePauseAsync(Pair pair)
|
||||
{
|
||||
CancellationTokenSource cts = new();
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
ArgumentNullException.ThrowIfNull(pair);
|
||||
return CyclePauseAsync(pair.UniqueIdent);
|
||||
}
|
||||
|
||||
public Task CyclePauseAsync(PairUniqueIdentifier ident)
|
||||
{
|
||||
var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(8));
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
|
||||
var perm = pair.UserPair!.OwnPermissions;
|
||||
perm.SetPaused(paused: true);
|
||||
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
|
||||
// wait until it's changed
|
||||
while (pair.UserPair!.OwnPermissions != perm)
|
||||
var token = timeoutCts.Token;
|
||||
try
|
||||
{
|
||||
await Task.Delay(250, cts.Token).ConfigureAwait(false);
|
||||
Logger.LogTrace("Waiting for permissions change for {data}", userData);
|
||||
if (!_pairCoordinator.Ledger.TryGetEntry(ident, out var entry) || entry is null)
|
||||
{
|
||||
Logger.LogWarning("CyclePauseAsync: pair {uid} not found in ledger", ident.UserId);
|
||||
return;
|
||||
}
|
||||
|
||||
var originalPermissions = entry.SelfPermissions;
|
||||
var targetPermissions = originalPermissions;
|
||||
targetPermissions.SetPaused(!originalPermissions.IsPaused());
|
||||
|
||||
await UserSetPairPermissions(new UserPermissionsDto(entry.User, targetPermissions)).ConfigureAwait(false);
|
||||
|
||||
var applied = false;
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
if (_pairCoordinator.Ledger.TryGetEntry(ident, out var updated) && updated is not null)
|
||||
{
|
||||
if (updated.SelfPermissions == targetPermissions)
|
||||
{
|
||||
applied = true;
|
||||
entry = updated;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(250, token).ConfigureAwait(false);
|
||||
Logger.LogTrace("Waiting for permissions change for {uid}", ident.UserId);
|
||||
}
|
||||
|
||||
if (!applied)
|
||||
{
|
||||
Logger.LogWarning("CyclePauseAsync timed out waiting for pause acknowledgement for {uid}", ident.UserId);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogDebug("CyclePauseAsync toggled paused for {uid} to {state}", ident.UserId, targetPermissions.IsPaused());
|
||||
}
|
||||
perm.SetPaused(paused: false);
|
||||
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
|
||||
}, cts.Token).ContinueWith((t) => cts.Dispose());
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogDebug("CyclePauseAsync cancelled for {uid}", ident.UserId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "CyclePauseAsync failed for {uid}", ident.UserId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
timeoutCts.Dispose();
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PauseAsync(UserData userData)
|
||||
{
|
||||
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
|
||||
var perm = pair.UserPair!.OwnPermissions;
|
||||
perm.SetPaused(paused: true);
|
||||
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
|
||||
var pairIdent = new PairUniqueIdentifier(userData.UID);
|
||||
if (!_pairCoordinator.Ledger.TryGetEntry(pairIdent, out var entry) || entry is null)
|
||||
{
|
||||
Logger.LogWarning("PauseAsync: pair {uid} not found in ledger", userData.UID);
|
||||
return;
|
||||
}
|
||||
|
||||
var permissions = entry.SelfPermissions;
|
||||
permissions.SetPaused(paused: true);
|
||||
await UserSetPairPermissions(new UserPermissionsDto(userData, permissions)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<ConnectionDto> GetConnectionDto() => GetConnectionDtoAsync(true);
|
||||
@@ -388,8 +512,13 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
|
||||
private async Task ClientHealthCheckAsync(CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested && _lightlessHub != null)
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
if (_lightlessHub is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false);
|
||||
Logger.LogDebug("Checking Client Health State");
|
||||
|
||||
@@ -455,6 +584,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
|
||||
OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto));
|
||||
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
|
||||
if (!_initialized)
|
||||
{
|
||||
_lightlessHub.On(nameof(Client_ChatReceive), (Func<ChatMessageDto, Task>)Client_ChatReceive);
|
||||
}
|
||||
|
||||
OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
|
||||
OnGposeLobbyLeave((dto) => _ = Client_GposeLobbyLeave(dto));
|
||||
@@ -470,18 +603,36 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private readonly HashSet<Action<ChatMessageDto>> _chatHandlers = new();
|
||||
|
||||
public void RegisterChatMessageHandler(Action<ChatMessageDto> handler)
|
||||
{
|
||||
if (_chatHandlers.Add(handler))
|
||||
{
|
||||
ChatMessageReceived += handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterChatMessageHandler(Action<ChatMessageDto> handler)
|
||||
{
|
||||
if (_chatHandlers.Remove(handler))
|
||||
{
|
||||
ChatMessageReceived -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadIninitialPairsAsync()
|
||||
{
|
||||
foreach (var entry in await GroupsGetAll().ConfigureAwait(false))
|
||||
{
|
||||
Logger.LogDebug("Group: {entry}", entry);
|
||||
_pairManager.AddGroup(entry);
|
||||
_pairCoordinator.HandleGroupFullInfo(entry);
|
||||
}
|
||||
|
||||
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
|
||||
{
|
||||
Logger.LogDebug("Individual Pair: {userPair}", userPair);
|
||||
_pairManager.AddUserPair(userPair);
|
||||
_pairCoordinator.HandleUserAddPair(userPair);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +649,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
foreach (var entry in await UserGetOnlinePairs(dto).ConfigureAwait(false))
|
||||
{
|
||||
Logger.LogDebug("Pair online: {pair}", entry);
|
||||
_pairManager.MarkPairOnline(entry, sendNotif: false);
|
||||
_pairCoordinator.HandleUserOnline(entry, sendNotification: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,53 +749,18 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
$"Stopping existing connection to {_serverManager.CurrentServer.ServerName}")));
|
||||
|
||||
_initialized = false;
|
||||
_healthCheckTokenSource?.Cancel();
|
||||
if (_healthCheckTokenSource != null)
|
||||
{
|
||||
await _healthCheckTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
Mediator.Publish(new DisconnectedMessage());
|
||||
_lightlessHub = null;
|
||||
_connectionDto = null;
|
||||
_zoneChatChannels = Array.Empty<ZoneChatChannelInfoDto>();
|
||||
_groupChatChannels = Array.Empty<GroupChatChannelInfoDto>();
|
||||
}
|
||||
|
||||
ServerState = state;
|
||||
}
|
||||
|
||||
public Task<UserProfileDto?> UserGetLightfinderProfile(string hashedCid)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task UpdateChatPresence(ChatPresenceUpdateDto presence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Client_ChatReceive(ChatMessageDto message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<ZoneChatChannelInfoDto>> GetZoneChatChannels()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<GroupChatChannelInfoDto>> GetGroupChatChannels()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SendChatMessage(ChatSendRequestDto request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task ReportChatMessage(ChatReportSubmitDto request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<ChatParticipantResolveResultDto?> ResolveChatParticipant(ChatParticipantResolveRequestDto request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
#pragma warning restore MA0040
|
||||
#pragma warning restore MA0040
|
||||
|
||||
Reference in New Issue
Block a user