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);
362 Models.RevisionInformation revisionInformation,
363 Models.RevisionInformation? previousRevisionInformation,
365 DateTimeOffset? estimatedCompletionTime,
368 bool localCommitPushed)
370 List<ulong> wdChannels;
372 wdChannels =
mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList();
374 logger.LogTrace(
"Sending deployment message for RevisionInformation: {revisionInfoId}", revisionInformation.Id);
376 var callbacks =
new List<Func<string?, string, ValueTask<Func<bool, ValueTask>>>>();
378 var task = Task.WhenAll(
382 ChannelMapping? channelMapping;
383 lock (mappedChannels)
384 if (!mappedChannels.TryGetValue(x, out channelMapping))
388 if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
392 var callback = await provider.SendUpdateMessage(
394 previousRevisionInformation,
396 estimatedCompletionTime,
399 channelMapping.ProviderChannelId,
404 callbacks.Add(callback);
410 "Error sending deploy message to provider {providerId}!",
411 channelMapping.ProviderId);
418 Func<bool, Task>? finalUpdateAction =
null;
419 async Task CallbackTask(
string? errorMessage,
string dreamMakerOutput)
429 finalUpdateAction = active =>
ValueTaskExtensions.
WhenAll(callbackResults.Select(finalizerCallback => finalizerCallback(active))).AsTask();
432 async Task CompletionTask(
bool active)
447 return (errorMessage, dreamMakerOutput) =>
449 callbackTask = CallbackTask(errorMessage, dreamMakerOutput);
456 public async Task
StartAsync(CancellationToken cancellationToken)
458 foreach (var tgsCommand
in commandFactory.GenerateCommands())
459 builtinCommands.Add(tgsCommand.Name.ToUpperInvariant(), tgsCommand);
460 var initialChatBots = activeChatBots.ToList();
462 initialProviderConnectionsTask = InitialConnection();
463 chatHandler = MonitorMessages(handlerCts.Token);
467 public async Task
StopAsync(CancellationToken cancellationToken)
470 if (chatHandler !=
null)
472 await Task.WhenAll(providers.Select(x => x.Key).Select(x => DeleteConnection(x, cancellationToken)));
473 await messageSendTask;
479 if (customCommandHandler ==
null)
480 throw new InvalidOperationException(
"RegisterCommandHandler() hasn't been called!");
483 lock (mappedChannels)
485 customCommandHandler,
486 mappedChannels.Select(y => y.Value.Channel),
490 lock (trackingContexts)
491 trackingContexts.Remove(context);
494 lock (trackingContexts)
495 trackingContexts.Add(context);
503 var logMessageSent = 0;
504 async Task UpdateTrackingContext(
IChatTrackingContext channelSink, IEnumerable<ChannelRepresentation> channels)
506 if (Interlocked.Exchange(ref logMessageSent, 1) == 0)
511 var waitingForInitialConnection = !initialProviderConnectionsTask!.IsCompleted;
512 if (waitingForInitialConnection)
514 logger.LogTrace(
"Waiting for initial chat bot connections before updating tracking contexts...");
515 await initialProviderConnectionsTask.WaitAsync(cancellationToken);
519 lock (mappedChannels)
520 lock (trackingContexts)
521 tasks = trackingContexts.Select(x => UpdateTrackingContext(x, mappedChannels.Select(y => y.Value.Channel))).ToList();
523 if (waitingForInitialConnection)
525 logger.LogTrace(
"Updating chat tracking contexts...");
527 logger.LogTrace(
"No chat tracking contexts to update");
529 await Task.WhenAll(tasks);
535 if (this.customCommandHandler !=
null)
536 throw new InvalidOperationException(
"RegisterCommandHandler() already called!");
537 this.customCommandHandler = customCommandHandler ??
throw new ArgumentNullException(nameof(customCommandHandler));
543 logger.LogTrace(
"DeleteConnection {connectionId}", connectionId);
544 var hasSemaphore = changeChannelSemaphores.TryRemove(connectionId, out var semaphore);
552 var provider = await RemoveProviderChannels(connectionId,
true, cancellationToken);
553 if (provider !=
null)
555 var startTime = DateTimeOffset.UtcNow;
558 await provider.Disconnect(cancellationToken);
562 logger.LogError(ex,
"Error disconnecting connection {connectionId}!", connectionId);
565 await provider.DisposeAsync();
566 var duration = DateTimeOffset.UtcNow - startTime;
567 if (duration.TotalSeconds > 3)
568 logger.LogWarning(
"Disconnecting a {providerType} took {totalSeconds}s!", provider.GetType().Name, duration.TotalSeconds);
571 logger.LogTrace(
"DeleteConnection: ID {connectionId} doesn't exist!", connectionId);
576 public ValueTask
HandleRestart(Version? updateVersion,
bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
578 var message = updateVersion ==
null
579 ? $
"TGS: {(handlerMayDelayShutdownWithExtremelyLongRunningTasks ? "Graceful shutdown
" : "Going down
")}..."
580 : $
"TGS: Updating to version {updateVersion}...";
581 List<ulong> systemChannels;
582 lock (mappedChannels)
583 systemChannels = mappedChannels
584 .Where(x => x.Value.IsSystemChannel)
605 async ValueTask<IProvider?>
RemoveProviderChannels(
long connectionId,
bool removeProvider, CancellationToken cancellationToken)
607 logger.LogTrace(
"RemoveProviderChannels {connectionId}...", connectionId);
611 if (!providers.TryGetValue(connectionId, out provider))
613 logger.LogTrace(
"Aborted, no such provider!");
618 providers.Remove(connectionId);
621 ValueTask trackingContextsUpdateTask;
622 lock (mappedChannels)
624 foreach (var mappedConnectionChannel
in mappedChannels.Where(x => x.Value.ProviderId == connectionId).Select(x => x.Key).ToList())
625 mappedChannels.Remove(mappedConnectionChannel);
627 var newMappedChannels = mappedChannels.Select(y => y.Value.Channel).ToList();
630 lock (trackingContexts)
631 trackingContextsUpdateTask =
ValueTaskExtensions.
WhenAll(trackingContexts.Select(x => x.UpdateChannels(newMappedChannels, cancellationToken)));
633 trackingContextsUpdateTask = ValueTask.CompletedTask;
636 await trackingContextsUpdateTask;
649 logger.LogTrace(
"Remapping channels for provider reconnection...");
650 IEnumerable<Models.ChatChannel>? channelsToMap;
653 providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
655 lock (activeChatBots)
656 channelsToMap = activeChatBots.FirstOrDefault(x => x.Id == providerId)?.Channels;
658 if (channelsToMap?.Any() ??
false)
659 await ChangeChannels(providerId, channelsToMap, cancellationToken);
670#pragma warning disable CA1502
672#pragma warning restore CA1502
676 logger.LogTrace(
"Abort message processing because provider is disconnected!");
683 await RemapProvider(provider, cancellationToken);
689 KeyValuePair<ulong, ChannelMapping>? mappedChannel;
695 cancellationToken.ThrowIfCancellationRequested();
697 var providerIdNullable = providers
698 .Where(x => x.Value == provider)
699 .Select(x => (
long?)x.Key)
702 if (!providerIdNullable.HasValue)
705 logger.LogDebug(
"Unable to process command \"{command}\" due to provider disconnecting", message.
Content);
709 providerId = providerIdNullable.Value;
710 mappedChannel = mappedChannels
711 .Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == providerChannelId)
712 .Select(x => (KeyValuePair<ulong, ChannelMapping>?)x)
714 hasChannelZero = mappedChannels
715 .Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == 0)
721 logger.LogInformation(
"Receieved message from unmapped channel whose provider contains ID 0. Remapping...");
722 await RemapProvider(provider, cancellationToken);
723 logger.LogTrace(
"Resume processing original message...");
724 await ProcessMessage(provider, message,
true, cancellationToken);
728 ValueTask TextReply(
string reply) => SendMessage(
741 lock (mappedChannels)
742 if (!mappedChannel.HasValue)
745 lock (synchronizationLock)
746 newId = channelIdCounter++;
748 "Mapping private channel {connectionName}:{channelFriendlyName} as {newId}",
754 ProviderChannelId = message.User.Channel.RealId,
755 ProviderId = providerId,
759 "Mapping DM {connectionName}:{userId} ({userFriendlyName}) as {newId}",
764 message.User.Channel.RealId = newId;
767 message.User.Channel.RealId = mappedChannel.Value.Key;
770 if (!mappedChannel.HasValue)
773 "Error mapping message: Provider ID: {providerId}, Channel Real ID: {realId}",
776 logger.LogTrace(
"message: {messageJson}", JsonConvert.SerializeObject(message));
777 lock (mappedChannels)
778 logger.LogTrace(
"mappedChannels: {mappedChannelsJson}", JsonConvert.SerializeObject(mappedChannels));
779 await TextReply(
"TGS: Processing error, check logs!");
783 var mappingChannelRepresentation = mappedChannel.Value.Value.Channel;
785 message.User.Channel.RealId = mappingChannelRepresentation.RealId;
786 message.User.Channel.Tag = mappingChannelRepresentation.Tag;
787 message.User.Channel.IsAdminChannel = mappingChannelRepresentation.IsAdminChannel;
790 var trimmedMessage = message.
Content.Trim();
791 if (trimmedMessage.Length == 0)
794 var splits =
new List<string>(trimmedMessage.Split(
' ', StringSplitOptions.RemoveEmptyEntries));
795 var address = splits[0];
796 if (address.Length > 1 && (address.Last() ==
':' || address.Last() ==
','))
797 address = address[0..^1];
800 address.Equals(CommonMention, StringComparison.OrdinalIgnoreCase)
801 || address.Equals(provider.
BotMention, StringComparison.OrdinalIgnoreCase);
808 "Start processing command: {message}. User (True provider Id): {profiderId}",
810 JsonConvert.SerializeObject(message.
User));
816 if (splits.Count == 0)
819 await TextReply(
"Hi!");
823 var command = splits[0];
825 var arguments = String.Join(
" ", splits);
827 Tuple<ICommand, IChatTrackingContext?>? GetCommand(
string command)
829 if (!builtinCommands.TryGetValue(command, out var handler))
830 return trackingContexts
831 .Where(trackingContext => trackingContext.Active)
832 .SelectMany(trackingContext => trackingContext.CustomCommands.Select(customCommand => Tuple.Create<
ICommand,
IChatTrackingContext?>(customCommand, trackingContext)))
833 .Where(tuple => tuple.Item1.Name.Equals(command, StringComparison.OrdinalIgnoreCase))
839 const string UnknownCommandMessage =
"TGS: Unknown command! Type '?' or 'help' for available commands.";
841 if (command.Equals(
"help", StringComparison.OrdinalIgnoreCase) || command ==
"?")
844 if (splits.Count == 0)
846 var allCommands = builtinCommands.Select(x => x.Value).ToList();
847 allCommands.AddRange(
850 x => x.CustomCommands));
851 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)));
855 var helpTuple = GetCommand(splits[0]);
856 if (helpTuple !=
default)
858 var (helpHandler, _) = helpTuple;
859 helpText = String.Format(CultureInfo.InvariantCulture,
"{0}: {1}{2}", helpHandler.Name, helpHandler.HelpText, helpHandler.AdminOnly ?
" - May only be used in admin channels" : String.Empty);
862 helpText = UnknownCommandMessage;
865 await TextReply(helpText);
869 var tuple = GetCommand(command);
871 if (tuple ==
default)
873 await TextReply(UnknownCommandMessage);
877 var (commandHandler, trackingContext) = tuple;
879 if (trackingContext?.Active ==
false)
881 await TextReply(
"TGS: The server is rebooting, please try again later");
887 await TextReply(
"TGS: Use this command in an admin channel!");
891 var result = await commandHandler.Invoke(arguments, message.
User, cancellationToken);
893 await SendMessage(
new List<ulong> { message.User.Channel.RealId }, message, result, cancellationToken);
895 catch (OperationCanceledException ex)
897 logger.LogTrace(ex,
"Command processing canceled!");
902 logger.LogError(e,
"Error processing chat command");
903 await TextReply(
"TGS: Internal error processing command! Check server logs!");
907 logger.LogTrace(
"Done processing command.");
918 logger.LogTrace(
"Starting processing loop...");
919 var messageTasks =
new Dictionary<IProvider, Task<Message?>>();
920 ValueTask activeProcessingTask = ValueTask.CompletedTask;
923 Task? updatedTask =
null;
924 while (!cancellationToken.IsCancellationRequested)
926 if (updatedTask?.IsCompleted !=
false)
927 lock (synchronizationLock)
928 updatedTask = connectionsUpdated.Task;
931 foreach (var disposedProviderMessageTaskKvp
in messageTasks.Where(x => x.Key.Disposed).ToList())
932 messageTasks.Remove(disposedProviderMessageTaskKvp.Key);
936 foreach (var providerKvp
in providers)
937 if (!messageTasks.ContainsKey(providerKvp.Value))
940 providerKvp.Value.NextMessage(cancellationToken));
942 if (messageTasks.Count == 0)
944 logger.LogTrace(
"No providers active, pausing messsage monitoring...");
945 await updatedTask.WaitAsync(cancellationToken);
946 logger.LogTrace(
"Resuming message monitoring...");
951 await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value)));
954 foreach (var completedMessageTaskKvp
in messageTasks.Where(x => x.Value.IsCompleted).ToList())
956 var provider = completedMessageTaskKvp.Key;
957 messageTasks.Remove(provider);
959 if (provider.Disposed)
962 var message = await completedMessageTaskKvp.Value;
963 var messageNumber = Interlocked.Increment(ref messagesProcessed);
965 async ValueTask WrapProcessMessage()
967 var localActiveProcessingTask = activeProcessingTask;
971 await ProcessMessage(provider, message,
false, cancellationToken);
975 logger.LogError(ex,
"Error processing message {messageNumber}!", messageNumber);
978 await localActiveProcessingTask;
981 activeProcessingTask = WrapProcessMessage();
985 catch (OperationCanceledException ex)
987 logger.LogTrace(ex,
"Message processing loop cancelled!");
991 logger.LogError(e,
"Message loop crashed!");
995 await activeProcessingTask;
998 logger.LogTrace(
"Leaving message processing loop");
1011 var channelIdsList = channelIds.ToList();
1014 "Chat send \"{message}\"{embed} to channels: [{channelIdsCommaSeperated}]",
1016 message.
Embed !=
null ?
" (with embed)" : String.Empty,
1017 String.Join(
", ", channelIdsList));
1019 if (channelIdsList.Count == 0)
1020 return ValueTask.CompletedTask;
1023 channelIdsList.Select(x =>
1025 ChannelMapping? channelMapping;
1026 lock (mappedChannels)
1027 if (!mappedChannels.TryGetValue(x, out channelMapping))
1028 return ValueTask.CompletedTask;
1029 IProvider? provider;
1031 if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
1032 return ValueTask.CompletedTask;
1033 return provider.SendMessage(replyTo, message, channelMapping.ProviderChannelId, cancellationToken);
1043 await Task.WhenAll(providers.Select(x => x.Value.InitialConnectionJob));
1044 logger.LogTrace(
"Initial provider connection task completed");
1053 async Task Wrap(Task originalTask)
1060 catch (OperationCanceledException ex)
1062 logger.LogDebug(ex,
"Async chat message cancelled!");
1066 logger.LogError(ex,
"Error in asynchronous chat message!");
1071 messageSendTask = Wrap(messageSendTask);
1082 async Task SendMessageTask()
1084 var cancellationToken = handlerCts.Token;
1085 if (waitForConnections)
1086 await initialProviderConnectionsTask!.WaitAsync(cancellationToken);
1089 channelIdsFactory(),
1095 AddMessageTask(SendMessageTask());
1106 ArgumentNullException.ThrowIfNull(message);
1110 message = $
"{prefix}: {message}";
1113 if (!initialProviderConnectionsTask!.IsCompleted)
1114 logger.LogTrace(
"Waiting for initial provider connections before sending chat message...");
1117 QueueMessageInternal(
1125 lock (mappedChannels)
1126 return mappedChannels.Where(x => channelSelector(x.Value)).Select(x => x.Key).ToList();
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.
void QueueMessageGeneric(Predicate< ChannelMapping > channelSelector, string message, string? prefix)
Queues a message to a selected set of ChannelMappings.
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....
void QueueRawDeploymentMessage(string message)
Queue a chat message to configured deployment channels.
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.
Func< string?, string, Action< bool > > QueueDeploymentMessage(Models.RevisionInformation revisionInformation, Models.RevisionInformation? previousRevisionInformation, EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string? gitHubOwner, string? gitHubRepo, bool localCommitPushed)
Send the message for a deployment to configured deployment channels.A Func<T1, T2,...
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.