2using System.Collections.Concurrent;
3using System.Collections.Generic;
4using System.Globalization;
7using System.Threading.Tasks;
9using Microsoft.Extensions.Logging;
27#pragma warning disable CA1506
58 readonly ILogger<ChatManager>
logger;
149 ILogger<ChatManager>
logger,
150 IEnumerable<Models.ChatBot> initialChatBots)
154 ArgumentNullException.ThrowIfNull(serverControl);
156 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
157 activeChatBots = initialChatBots?.ToList() ??
throw new ArgumentNullException(nameof(initialChatBots));
163 builtinCommands =
new Dictionary<string, ICommand>(StringComparer.OrdinalIgnoreCase);
164 providers =
new Dictionary<long, IProvider>();
178 logger.LogTrace(
"Disposing...");
182 await providerKvp.Value.DisposeAsync();
185 providerKvp.Value.Dispose();
191 public async ValueTask
ChangeChannels(
long connectionId, IEnumerable<Models.ChatChannel> newChannels, CancellationToken cancellationToken)
193 ArgumentNullException.ThrowIfNull(newChannels);
195 logger.LogTrace(
"ChangeChannels {connectionId}...", connectionId);
198 logger.LogTrace(
"Creating ChangeChannels semaphore for connection ID {connectionId}...", connectionId);
199 return new SemaphoreSlim(1);
204 if (provider ==
null)
207 if (!provider.Connected)
209 logger.LogDebug(
"Cannot map channels, provider {providerId} disconnected!", connectionId);
213 var results = await provider.MapChannels(newChannels, cancellationToken);
218 var botToUpdate =
activeChatBots.FirstOrDefault(bot => bot.Id == connectionId);
219 if (botToUpdate !=
null)
220 botToUpdate.Channels = newChannels
221 .Select(apiModel =>
new Models.ChatChannel
223 DiscordChannelId = apiModel.DiscordChannelId,
224 IrcChannel = apiModel.IrcChannel,
225 IsAdminChannel = apiModel.IsAdminChannel,
226 IsUpdatesChannel = apiModel.IsUpdatesChannel,
227 IsSystemChannel = apiModel.IsSystemChannel,
228 IsWatchdogChannel = apiModel.IsWatchdogChannel,
234 var newMappings = results.SelectMany(
235 kvp => kvp.Value.Select(
236 channelRepresentation =>
new ChannelMapping(channelRepresentation)
238 IsWatchdogChannel = kvp.Key.IsWatchdogChannel ==
true,
239 IsUpdatesChannel = kvp.Key.IsUpdatesChannel ==
true,
240 IsAdminChannel = kvp.Key.IsAdminChannel ==
true,
241 IsSystemChannel = kvp.Key.IsSystemChannel ==
true,
242 ProviderChannelId = channelRepresentation.RealId,
243 ProviderId = connectionId,
256 if (!
providers.TryGetValue(connectionId, out var verify) || verify != provider)
258 foreach (var newMapping
in newMappings)
260 var newId = baseId++;
261 logger.LogTrace(
"Mapping channel {connectionName}:{channelFriendlyName} as {newId}", newMapping.Channel.ConnectionName, newMapping.Channel.FriendlyName, newId);
263 newMapping.Channel.RealId = newId;
275 provider.InitialMappingComplete();
281 public async ValueTask
ChangeSettings(Models.ChatBot newSettings, CancellationToken cancellationToken)
283 ArgumentNullException.ThrowIfNull(newSettings);
285 logger.LogTrace(
"ChangeSettings...");
289 var newSettingsId = Models.ModelExtensions.Require(newSettings, x => x.Id);
290 var newSettingsEnabled = Models.ModelExtensions.Require(newSettings, x => x.Enabled);
294 if (
providers.ContainsKey(newSettingsId))
297 disconnectTask = Task.CompletedTask;
298 if (newSettingsEnabled)
306 foreach (var oldMappedChannelId
in mappedChannels.Where(x => x.Value.ProviderId == newSettingsId).Select(x => x.Key).ToList())
309 await disconnectTask;
319 var reconnectionUpdateTask = provider?.SetReconnectInterval(
320 Models.ModelExtensions.Require(newSettings, x => x.ReconnectionInterval),
322 ?? Task.CompletedTask;
325 var originalChatBot =
activeChatBots.FirstOrDefault(bot => bot.Id == newSettings.Id);
326 if (originalChatBot !=
null)
332 ConnectionString = newSettings.ConnectionString,
333 Enabled = newSettings.Enabled,
334 Name = newSettings.Name,
335 ReconnectionInterval = newSettings.ReconnectionInterval,
336 Provider = newSettings.Provider,
340 await reconnectionUpdateTask;
346 ArgumentNullException.ThrowIfNull(message);
347 ArgumentNullException.ThrowIfNull(channelIds);
355 ArgumentNullException.ThrowIfNull(message);
357 message = String.Format(CultureInfo.InvariantCulture,
"WD: {0}", message);
360 logger.LogTrace(
"Waiting for initial provider connections before sending watchdog message...");
372 return mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList();
379 Models.RevisionInformation revisionInformation,
381 DateTimeOffset? estimatedCompletionTime,
384 bool localCommitPushed)
386 List<ulong> wdChannels;
388 wdChannels =
mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList();
390 logger.LogTrace(
"Sending deployment message for RevisionInformation: {revisionInfoId}", revisionInformation.Id);
392 var callbacks =
new List<Func<string?, string, ValueTask<Func<bool, ValueTask>>>>();
394 var task = Task.WhenAll(
398 ChannelMapping? channelMapping;
399 lock (mappedChannels)
400 if (!mappedChannels.TryGetValue(x, out channelMapping))
404 if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
408 var callback = await provider.SendUpdateMessage(
411 estimatedCompletionTime,
414 channelMapping.ProviderChannelId,
419 callbacks.Add(callback);
425 "Error sending deploy message to provider {providerId}!",
426 channelMapping.ProviderId);
433 Func<bool, Task>? finalUpdateAction =
null;
434 async Task CallbackTask(
string? errorMessage,
string dreamMakerOutput)
444 finalUpdateAction = active =>
ValueTaskExtensions.
WhenAll(callbackResults.Select(finalizerCallback => finalizerCallback(active))).AsTask();
447 async Task CompletionTask(
bool active)
462 return (errorMessage, dreamMakerOutput) =>
464 callbackTask = CallbackTask(errorMessage, dreamMakerOutput);
471 public async Task
StartAsync(CancellationToken cancellationToken)
473 foreach (var tgsCommand
in commandFactory.GenerateCommands())
474 builtinCommands.Add(tgsCommand.Name.ToUpperInvariant(), tgsCommand);
475 var initialChatBots = activeChatBots.ToList();
477 initialProviderConnectionsTask = InitialConnection();
478 chatHandler = MonitorMessages(handlerCts.Token);
482 public async Task
StopAsync(CancellationToken cancellationToken)
485 if (chatHandler !=
null)
487 await Task.WhenAll(providers.Select(x => x.Key).Select(x => DeleteConnection(x, cancellationToken)));
488 await messageSendTask;
494 if (customCommandHandler ==
null)
495 throw new InvalidOperationException(
"RegisterCommandHandler() hasn't been called!");
498 lock (mappedChannels)
500 customCommandHandler,
501 mappedChannels.Select(y => y.Value.Channel),
505 lock (trackingContexts)
506 trackingContexts.Remove(context);
509 lock (trackingContexts)
510 trackingContexts.Add(context);
518 var logMessageSent = 0;
519 async Task UpdateTrackingContext(
IChatTrackingContext channelSink, IEnumerable<ChannelRepresentation> channels)
521 if (Interlocked.Exchange(ref logMessageSent, 1) == 0)
526 var waitingForInitialConnection = !initialProviderConnectionsTask!.IsCompleted;
527 if (waitingForInitialConnection)
529 logger.LogTrace(
"Waiting for initial chat bot connections before updating tracking contexts...");
530 await initialProviderConnectionsTask.WaitAsync(cancellationToken);
534 lock (mappedChannels)
535 lock (trackingContexts)
536 tasks = trackingContexts.Select(x => UpdateTrackingContext(x, mappedChannels.Select(y => y.Value.Channel))).ToList();
538 if (waitingForInitialConnection)
540 logger.LogTrace(
"Updating chat tracking contexts...");
542 logger.LogTrace(
"No chat tracking contexts to update");
544 await Task.WhenAll(tasks);
550 if (this.customCommandHandler !=
null)
551 throw new InvalidOperationException(
"RegisterCommandHandler() already called!");
552 this.customCommandHandler = customCommandHandler ??
throw new ArgumentNullException(nameof(customCommandHandler));
558 logger.LogTrace(
"DeleteConnection {connectionId}", connectionId);
559 var hasSemaphore = changeChannelSemaphores.TryRemove(connectionId, out var semaphore);
567 var provider = await RemoveProviderChannels(connectionId,
true, cancellationToken);
568 if (provider !=
null)
570 var startTime = DateTimeOffset.UtcNow;
573 await provider.Disconnect(cancellationToken);
577 logger.LogError(ex,
"Error disconnecting connection {connectionId}!", connectionId);
580 await provider.DisposeAsync();
581 var duration = DateTimeOffset.UtcNow - startTime;
582 if (duration.TotalSeconds > 3)
583 logger.LogWarning(
"Disconnecting a {providerType} took {totalSeconds}s!", provider.GetType().Name, duration.TotalSeconds);
586 logger.LogTrace(
"DeleteConnection: ID {connectionId} doesn't exist!", connectionId);
591 public ValueTask
HandleRestart(Version? updateVersion,
bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
593 var message = updateVersion ==
null
594 ? $
"TGS: {(handlerMayDelayShutdownWithExtremelyLongRunningTasks ? "Graceful shutdown
" : "Going down
")}..."
595 : $
"TGS: Updating to version {updateVersion}...";
596 List<ulong> systemChannels;
597 lock (mappedChannels)
598 systemChannels = mappedChannels
599 .Where(x => x.Value.IsSystemChannel)
620 async ValueTask<IProvider?>
RemoveProviderChannels(
long connectionId,
bool removeProvider, CancellationToken cancellationToken)
622 logger.LogTrace(
"RemoveProviderChannels {connectionId}...", connectionId);
626 if (!providers.TryGetValue(connectionId, out provider))
628 logger.LogTrace(
"Aborted, no such provider!");
633 providers.Remove(connectionId);
636 ValueTask trackingContextsUpdateTask;
637 lock (mappedChannels)
639 foreach (var mappedConnectionChannel
in mappedChannels.Where(x => x.Value.ProviderId == connectionId).Select(x => x.Key).ToList())
640 mappedChannels.Remove(mappedConnectionChannel);
642 var newMappedChannels = mappedChannels.Select(y => y.Value.Channel).ToList();
645 lock (trackingContexts)
646 trackingContextsUpdateTask =
ValueTaskExtensions.
WhenAll(trackingContexts.Select(x => x.UpdateChannels(newMappedChannels, cancellationToken)));
648 trackingContextsUpdateTask = ValueTask.CompletedTask;
651 await trackingContextsUpdateTask;
664 logger.LogTrace(
"Remapping channels for provider reconnection...");
665 IEnumerable<Models.ChatChannel>? channelsToMap;
668 providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
670 lock (activeChatBots)
671 channelsToMap = activeChatBots.FirstOrDefault(x => x.Id == providerId)?.Channels;
673 if (channelsToMap?.Any() ??
false)
674 await ChangeChannels(providerId, channelsToMap, cancellationToken);
685#pragma warning disable CA1502
687#pragma warning restore CA1502
691 logger.LogTrace(
"Abort message processing because provider is disconnected!");
698 await RemapProvider(provider, cancellationToken);
704 KeyValuePair<ulong, ChannelMapping>? mappedChannel;
710 cancellationToken.ThrowIfCancellationRequested();
712 var providerIdNullable = providers
713 .Where(x => x.Value == provider)
714 .Select(x => (
long?)x.Key)
717 if (!providerIdNullable.HasValue)
720 logger.LogDebug(
"Unable to process command \"{command}\" due to provider disconnecting", message.
Content);
724 providerId = providerIdNullable.Value;
725 mappedChannel = mappedChannels
726 .Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == providerChannelId)
727 .Select(x => (KeyValuePair<ulong, ChannelMapping>?)x)
729 hasChannelZero = mappedChannels
730 .Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == 0)
736 logger.LogInformation(
"Receieved message from unmapped channel whose provider contains ID 0. Remapping...");
737 await RemapProvider(provider, cancellationToken);
738 logger.LogTrace(
"Resume processing original message...");
739 await ProcessMessage(provider, message,
true, cancellationToken);
743 ValueTask TextReply(
string reply) => SendMessage(
756 lock (mappedChannels)
757 if (!mappedChannel.HasValue)
760 lock (synchronizationLock)
761 newId = channelIdCounter++;
763 "Mapping private channel {connectionName}:{channelFriendlyName} as {newId}",
769 ProviderChannelId = message.User.Channel.RealId,
770 ProviderId = providerId,
774 "Mapping DM {connectionName}:{userId} ({userFriendlyName}) as {newId}",
779 message.User.Channel.RealId = newId;
782 message.User.Channel.RealId = mappedChannel.Value.Key;
785 if (!mappedChannel.HasValue)
788 "Error mapping message: Provider ID: {providerId}, Channel Real ID: {realId}",
791 logger.LogTrace(
"message: {messageJson}", JsonConvert.SerializeObject(message));
792 lock (mappedChannels)
793 logger.LogTrace(
"mappedChannels: {mappedChannelsJson}", JsonConvert.SerializeObject(mappedChannels));
794 await TextReply(
"TGS: Processing error, check logs!");
798 var mappingChannelRepresentation = mappedChannel.Value.Value.Channel;
800 message.User.Channel.RealId = mappingChannelRepresentation.RealId;
801 message.User.Channel.Tag = mappingChannelRepresentation.Tag;
802 message.User.Channel.IsAdminChannel = mappingChannelRepresentation.IsAdminChannel;
805 var trimmedMessage = message.
Content.Trim();
806 if (trimmedMessage.Length == 0)
809 var splits =
new List<string>(trimmedMessage.Split(
' ', StringSplitOptions.RemoveEmptyEntries));
810 var address = splits[0];
811 if (address.Length > 1 && (address.Last() ==
':' || address.Last() ==
','))
812 address = address[0..^1];
815 address.Equals(CommonMention, StringComparison.OrdinalIgnoreCase)
816 || address.Equals(provider.
BotMention, StringComparison.OrdinalIgnoreCase);
823 "Start processing command: {message}. User (True provider Id): {profiderId}",
825 JsonConvert.SerializeObject(message.
User));
831 if (splits.Count == 0)
834 await TextReply(
"Hi!");
838 var command = splits[0];
840 var arguments = String.Join(
" ", splits);
842 Tuple<ICommand, IChatTrackingContext?>? GetCommand(
string command)
844 if (!builtinCommands.TryGetValue(command, out var handler))
845 return trackingContexts
846 .Where(trackingContext => trackingContext.Active)
847 .SelectMany(trackingContext => trackingContext.CustomCommands.Select(customCommand => Tuple.Create<
ICommand,
IChatTrackingContext?>(customCommand, trackingContext)))
848 .Where(tuple => tuple.Item1.Name.Equals(command, StringComparison.OrdinalIgnoreCase))
854 const string UnknownCommandMessage =
"TGS: Unknown command! Type '?' or 'help' for available commands.";
856 if (command.Equals(
"help", StringComparison.OrdinalIgnoreCase) || command ==
"?")
859 if (splits.Count == 0)
861 var allCommands = builtinCommands.Select(x => x.Value).ToList();
862 allCommands.AddRange(
865 x => x.CustomCommands));
866 helpText = String.Format(CultureInfo.InvariantCulture,
"Available commands (Type '?' or 'help' and then a command name for more details): {0}", String.Join(
", ", allCommands.Select(x => x.Name)));
870 var helpTuple = GetCommand(splits[0]);
871 if (helpTuple !=
default)
873 var (helpHandler, _) = helpTuple;
874 helpText = String.Format(CultureInfo.InvariantCulture,
"{0}: {1}{2}", helpHandler.Name, helpHandler.HelpText, helpHandler.AdminOnly ?
" - May only be used in admin channels" : String.Empty);
877 helpText = UnknownCommandMessage;
880 await TextReply(helpText);
884 var tuple = GetCommand(command);
886 if (tuple ==
default)
888 await TextReply(UnknownCommandMessage);
892 var (commandHandler, trackingContext) = tuple;
894 if (trackingContext?.Active ==
false)
896 await TextReply(
"TGS: The server is rebooting, please try again later");
902 await TextReply(
"TGS: Use this command in an admin channel!");
906 var result = await commandHandler.Invoke(arguments, message.
User, cancellationToken);
908 await SendMessage(
new List<ulong> { message.User.Channel.RealId }, message, result, cancellationToken);
910 catch (OperationCanceledException ex)
912 logger.LogTrace(ex,
"Command processing canceled!");
917 logger.LogError(e,
"Error processing chat command");
918 await TextReply(
"TGS: Internal error processing command! Check server logs!");
922 logger.LogTrace(
"Done processing command.");
933 logger.LogTrace(
"Starting processing loop...");
934 var messageTasks =
new Dictionary<IProvider, Task<Message?>>();
935 ValueTask activeProcessingTask = ValueTask.CompletedTask;
938 Task? updatedTask =
null;
939 while (!cancellationToken.IsCancellationRequested)
941 if (updatedTask?.IsCompleted !=
false)
942 lock (synchronizationLock)
943 updatedTask = connectionsUpdated.Task;
946 foreach (var disposedProviderMessageTaskKvp
in messageTasks.Where(x => x.Key.Disposed).ToList())
947 messageTasks.Remove(disposedProviderMessageTaskKvp.Key);
951 foreach (var providerKvp
in providers)
952 if (!messageTasks.ContainsKey(providerKvp.Value))
955 providerKvp.Value.NextMessage(cancellationToken));
957 if (messageTasks.Count == 0)
959 logger.LogTrace(
"No providers active, pausing messsage monitoring...");
960 await updatedTask.WaitAsync(cancellationToken);
961 logger.LogTrace(
"Resuming message monitoring...");
966 await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value)));
969 foreach (var completedMessageTaskKvp
in messageTasks.Where(x => x.Value.IsCompleted).ToList())
971 var provider = completedMessageTaskKvp.Key;
972 messageTasks.Remove(provider);
974 if (provider.Disposed)
977 var message = await completedMessageTaskKvp.Value;
978 var messageNumber = Interlocked.Increment(ref messagesProcessed);
980 async ValueTask WrapProcessMessage()
982 var localActiveProcessingTask = activeProcessingTask;
986 await ProcessMessage(provider, message,
false, cancellationToken);
990 logger.LogError(ex,
"Error processing message {messageNumber}!", messageNumber);
993 await localActiveProcessingTask;
996 activeProcessingTask = WrapProcessMessage();
1000 catch (OperationCanceledException ex)
1002 logger.LogTrace(ex,
"Message processing loop cancelled!");
1006 logger.LogError(e,
"Message loop crashed!");
1010 await activeProcessingTask;
1013 logger.LogTrace(
"Leaving message processing loop");
1026 var channelIdsList = channelIds.ToList();
1029 "Chat send \"{message}\"{embed} to channels: [{channelIdsCommaSeperated}]",
1031 message.
Embed !=
null ?
" (with embed)" : String.Empty,
1032 String.Join(
", ", channelIdsList));
1034 if (channelIdsList.Count == 0)
1035 return ValueTask.CompletedTask;
1038 channelIdsList.Select(x =>
1040 ChannelMapping? channelMapping;
1041 lock (mappedChannels)
1042 if (!mappedChannels.TryGetValue(x, out channelMapping))
1043 return ValueTask.CompletedTask;
1044 IProvider? provider;
1046 if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
1047 return ValueTask.CompletedTask;
1048 return provider.SendMessage(replyTo, message, channelMapping.ProviderChannelId, cancellationToken);
1058 await Task.WhenAll(providers.Select(x => x.Value.InitialConnectionJob));
1059 logger.LogTrace(
"Initial provider connection task completed");
1068 async Task Wrap(Task originalTask)
1075 catch (OperationCanceledException ex)
1077 logger.LogDebug(ex,
"Async chat message cancelled!");
1081 logger.LogError(ex,
"Error in asynchronous chat message!");
1086 messageSendTask = Wrap(messageSendTask);
1097 async Task SendMessageTask()
1099 var cancellationToken = handlerCts.Token;
1100 if (waitForConnections)
1101 await initialProviderConnectionsTask!.WaitAsync(cancellationToken);
1104 channelIdsFactory(),
1110 AddMessageTask(SendMessageTask());
Information about an engine installation.
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
Represents a mapping of a ChannelRepresentation.RealId.
string ConnectionName
The name of the connection the ChannelRepresentation belongs to.
bool IsAdminChannel
If this is considered a channel for admin commands.
ulong RealId
The Providers.IProvider channel Id.
bool IsPrivateChannel
If this is a 1-to-1 chat channel.
const string CommonMention
The common bot mention.
long messagesProcessed
The number of Messages processed.
readonly IProviderFactory providerFactory
The IProviderFactory for the ChatManager.
ValueTask SendMessage(IEnumerable< ulong > channelIds, Message? replyTo, MessageContent message, CancellationToken cancellationToken)
Asynchronously send a given message to a set of channelIds .
readonly object synchronizationLock
Used for various lock statements throughout this class.
ChatManager(IProviderFactory providerFactory, ICommandFactory commandFactory, IServerControl serverControl, ILoggerFactory loggerFactory, ILogger< ChatManager > logger, IEnumerable< Models.ChatBot > initialChatBots)
Initializes a new instance of the ChatManager class.
Task? initialProviderConnectionsTask
A Task that represents the IProviders initial connection.
ValueTask HandleRestart(Version? updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
Handle a restart of the server.A ValueTask representing the running operation.
void QueueMessageInternal(MessageContent message, Func< IEnumerable< ulong > > channelIdsFactory, bool waitForConnections)
Adds a given message to the send queue.
readonly List< Models.ChatBot > activeChatBots
The active Models.ChatBot for the ChatManager.
async Task InitialConnection()
Aggregate all IProvider.InitialConnectionJobs into one <sse cref="Task">.
async ValueTask RemapProvider(IProvider provider, CancellationToken cancellationToken)
Remap the channels for a given provider .
async ValueTask ProcessMessage(IProvider provider, Message? message, bool recursed, CancellationToken cancellationToken)
Processes a message .
readonly Dictionary< long, IProvider > providers
Map of IProviders in use, keyed by ChatBotSettings EntityId.Id.
async Task MonitorMessages(CancellationToken cancellationToken)
Monitors active providers for new Messages.
ICustomCommandHandler? customCommandHandler
The ICustomCommandHandler for the ChangeChannels(long, IEnumerable<Models.ChatChannel>,...
async ValueTask ChangeChannels(long connectionId, IEnumerable< Models.ChatChannel > newChannels, CancellationToken cancellationToken)
readonly Dictionary< string, ICommand > builtinCommands
Unchanging ICommands in the ChatManager mapped by ICommand.Name.
void QueueMessage(MessageContent message, IEnumerable< ulong > channelIds)
Queue a chat message to a given set of channelIds .
readonly ILoggerFactory loggerFactory
The ILoggerFactory for the ChatManager.
Func< string?, string, Action< bool > > QueueDeploymentMessage(Models.RevisionInformation revisionInformation, EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string? gitHubOwner, string? gitHubRepo, bool localCommitPushed)
Send the message for a deployment to configured deployment channels.A Func<T1, T2,...
Task messageSendTask
A Task that represents all sent messages.
Task? chatHandler
The Task that monitors incoming chat messages.
async ValueTask ChangeSettings(Models.ChatBot newSettings, CancellationToken cancellationToken)
Change chat settings. If the Api.Models.EntityId.Id is not currently in use, a new connection will be...
ulong channelIdCounter
Used for remapping ChannelRepresentation.RealIds.
readonly ICommandFactory commandFactory
The ICommandFactory for the ChatManager.
IChatTrackingContext CreateTrackingContext()
Start tracking Commands.CustomCommands and ChannelRepresentations.A new IChatTrackingContext.
void AddMessageTask(Task task)
Adds a given task to messageSendTask.
readonly ILogger< ChatManager > logger
The ILogger for the ChatManager.
readonly ConcurrentDictionary< long, SemaphoreSlim > changeChannelSemaphores
Map of SemaphoreSlims used to guard concurrent access to ChangeChannels(long, IEnumerable<Models....
async ValueTask DisposeAsync()
readonly IRestartRegistration restartRegistration
The IRestartRegistration for the ChatManager.
void QueueWatchdogMessage(string message)
Queue a chat message to configured watchdog channels.
readonly CancellationTokenSource handlerCts
The CancellationTokenSource for chatHandler.
async Task StartAsync(CancellationToken cancellationToken)
void RegisterCommandHandler(ICustomCommandHandler customCommandHandler)
Registers a customCommandHandler to use.
TaskCompletionSource connectionsUpdated
The TaskCompletionSource that completes when ChatBotSettingss change.
async ValueTask UpdateTrackingContexts(CancellationToken cancellationToken)
Force an update with the active channels on all active IChatTrackingContexts.A ValueTask representing...
async Task DeleteConnection(long connectionId, CancellationToken cancellationToken)
Disconnects and deletes a given connection.A Task representing the running operation.
async ValueTask< IProvider?> RemoveProviderChannels(long connectionId, bool removeProvider, CancellationToken cancellationToken)
Remove a IProvider from mappedChannels optionally removing the provider itself from providers and upd...
async Task StopAsync(CancellationToken cancellationToken)
readonly List< IChatTrackingContext > trackingContexts
The active IChatTrackingContexts for the ChatManager.
readonly Dictionary< ulong, ChannelMapping > mappedChannels
Map of ChannelRepresentation.RealIds to ChannelMappings.
string FriendlyName
The friendly name of the user.
ulong RealId
The internal user id.
ChannelRepresentation Channel
The ChannelRepresentation the user spoke from.
Represents a message received by a IProvider.
string Content
The text of the message.
ChatUser User
The ChatUser who sent the Message.
Represents a message to send to a chat provider.
ChatEmbed? Embed
The ChatEmbed.
string? Text
The message string.
Async lock context helper.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger? logger=null)
Asyncronously locks a semaphore .
Helpers for manipulating the Serilog.Context.LogContext.
const string ChatMessageIterationContextProperty
The Serilog.Context.LogContext property name for the ID of the chat message currently being processed...
Factory for built in ICommands.
Represents a command that can be invoked by talking to chat bots.
ValueTask UpdateChannels(IEnumerable< ChannelRepresentation > newChannels, CancellationToken cancellationToken)
Called when newChannels are set.
For managing connected chat services.
Represents a tracking of dynamic chat json files.
Handles Commands.ICommands that map to those defined in a IChatTrackingContext.
IProvider CreateProvider(ChatBot settings)
Create a IProvider.
For interacting with a chat service.
string BotMention
The string that indicates the IProvider was mentioned.
bool Connected
If the IProvider is currently connected.
Handler for server restarts.
Represents the lifetime of a IRestartHandler registration.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart.