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,
380 Models.RevisionInformation? previousRevisionInformation,
382 DateTimeOffset? estimatedCompletionTime,
385 bool localCommitPushed)
387 List<ulong> wdChannels;
389 wdChannels =
mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList();
391 logger.LogTrace(
"Sending deployment message for RevisionInformation: {revisionInfoId}", revisionInformation.Id);
393 var callbacks =
new List<Func<string?, string, ValueTask<Func<bool, ValueTask>>>>();
395 var task = Task.WhenAll(
399 ChannelMapping? channelMapping;
400 lock (mappedChannels)
401 if (!mappedChannels.TryGetValue(x, out channelMapping))
405 if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
409 var callback = await provider.SendUpdateMessage(
411 previousRevisionInformation,
413 estimatedCompletionTime,
416 channelMapping.ProviderChannelId,
421 callbacks.Add(callback);
427 "Error sending deploy message to provider {providerId}!",
428 channelMapping.ProviderId);
435 Func<bool, Task>? finalUpdateAction =
null;
436 async Task CallbackTask(
string? errorMessage,
string dreamMakerOutput)
446 finalUpdateAction = active =>
ValueTaskExtensions.
WhenAll(callbackResults.Select(finalizerCallback => finalizerCallback(active))).AsTask();
449 async Task CompletionTask(
bool active)
464 return (errorMessage, dreamMakerOutput) =>
466 callbackTask = CallbackTask(errorMessage, dreamMakerOutput);
473 public async Task
StartAsync(CancellationToken cancellationToken)
475 foreach (var tgsCommand
in commandFactory.GenerateCommands())
476 builtinCommands.Add(tgsCommand.Name.ToUpperInvariant(), tgsCommand);
477 var initialChatBots = activeChatBots.ToList();
479 initialProviderConnectionsTask = InitialConnection();
480 chatHandler = MonitorMessages(handlerCts.Token);
484 public async Task
StopAsync(CancellationToken cancellationToken)
487 if (chatHandler !=
null)
489 await Task.WhenAll(providers.Select(x => x.Key).Select(x => DeleteConnection(x, cancellationToken)));
490 await messageSendTask;
496 if (customCommandHandler ==
null)
497 throw new InvalidOperationException(
"RegisterCommandHandler() hasn't been called!");
500 lock (mappedChannels)
502 customCommandHandler,
503 mappedChannels.Select(y => y.Value.Channel),
507 lock (trackingContexts)
508 trackingContexts.Remove(context);
511 lock (trackingContexts)
512 trackingContexts.Add(context);
520 var logMessageSent = 0;
521 async Task UpdateTrackingContext(
IChatTrackingContext channelSink, IEnumerable<ChannelRepresentation> channels)
523 if (Interlocked.Exchange(ref logMessageSent, 1) == 0)
528 var waitingForInitialConnection = !initialProviderConnectionsTask!.IsCompleted;
529 if (waitingForInitialConnection)
531 logger.LogTrace(
"Waiting for initial chat bot connections before updating tracking contexts...");
532 await initialProviderConnectionsTask.WaitAsync(cancellationToken);
536 lock (mappedChannels)
537 lock (trackingContexts)
538 tasks = trackingContexts.Select(x => UpdateTrackingContext(x, mappedChannels.Select(y => y.Value.Channel))).ToList();
540 if (waitingForInitialConnection)
542 logger.LogTrace(
"Updating chat tracking contexts...");
544 logger.LogTrace(
"No chat tracking contexts to update");
546 await Task.WhenAll(tasks);
552 if (this.customCommandHandler !=
null)
553 throw new InvalidOperationException(
"RegisterCommandHandler() already called!");
554 this.customCommandHandler = customCommandHandler ??
throw new ArgumentNullException(nameof(customCommandHandler));
560 logger.LogTrace(
"DeleteConnection {connectionId}", connectionId);
561 var hasSemaphore = changeChannelSemaphores.TryRemove(connectionId, out var semaphore);
569 var provider = await RemoveProviderChannels(connectionId,
true, cancellationToken);
570 if (provider !=
null)
572 var startTime = DateTimeOffset.UtcNow;
575 await provider.Disconnect(cancellationToken);
579 logger.LogError(ex,
"Error disconnecting connection {connectionId}!", connectionId);
582 await provider.DisposeAsync();
583 var duration = DateTimeOffset.UtcNow - startTime;
584 if (duration.TotalSeconds > 3)
585 logger.LogWarning(
"Disconnecting a {providerType} took {totalSeconds}s!", provider.GetType().Name, duration.TotalSeconds);
588 logger.LogTrace(
"DeleteConnection: ID {connectionId} doesn't exist!", connectionId);
593 public ValueTask
HandleRestart(Version? updateVersion,
bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
595 var message = updateVersion ==
null
596 ? $
"TGS: {(handlerMayDelayShutdownWithExtremelyLongRunningTasks ? "Graceful shutdown
" : "Going down
")}..."
597 : $
"TGS: Updating to version {updateVersion}...";
598 List<ulong> systemChannels;
599 lock (mappedChannels)
600 systemChannels = mappedChannels
601 .Where(x => x.Value.IsSystemChannel)
622 async ValueTask<IProvider?>
RemoveProviderChannels(
long connectionId,
bool removeProvider, CancellationToken cancellationToken)
624 logger.LogTrace(
"RemoveProviderChannels {connectionId}...", connectionId);
628 if (!providers.TryGetValue(connectionId, out provider))
630 logger.LogTrace(
"Aborted, no such provider!");
635 providers.Remove(connectionId);
638 ValueTask trackingContextsUpdateTask;
639 lock (mappedChannels)
641 foreach (var mappedConnectionChannel
in mappedChannels.Where(x => x.Value.ProviderId == connectionId).Select(x => x.Key).ToList())
642 mappedChannels.Remove(mappedConnectionChannel);
644 var newMappedChannels = mappedChannels.Select(y => y.Value.Channel).ToList();
647 lock (trackingContexts)
648 trackingContextsUpdateTask =
ValueTaskExtensions.
WhenAll(trackingContexts.Select(x => x.UpdateChannels(newMappedChannels, cancellationToken)));
650 trackingContextsUpdateTask = ValueTask.CompletedTask;
653 await trackingContextsUpdateTask;
666 logger.LogTrace(
"Remapping channels for provider reconnection...");
667 IEnumerable<Models.ChatChannel>? channelsToMap;
670 providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
672 lock (activeChatBots)
673 channelsToMap = activeChatBots.FirstOrDefault(x => x.Id == providerId)?.Channels;
675 if (channelsToMap?.Any() ??
false)
676 await ChangeChannels(providerId, channelsToMap, cancellationToken);
687#pragma warning disable CA1502
689#pragma warning restore CA1502
693 logger.LogTrace(
"Abort message processing because provider is disconnected!");
700 await RemapProvider(provider, cancellationToken);
706 KeyValuePair<ulong, ChannelMapping>? mappedChannel;
712 cancellationToken.ThrowIfCancellationRequested();
714 var providerIdNullable = providers
715 .Where(x => x.Value == provider)
716 .Select(x => (
long?)x.Key)
719 if (!providerIdNullable.HasValue)
722 logger.LogDebug(
"Unable to process command \"{command}\" due to provider disconnecting", message.
Content);
726 providerId = providerIdNullable.Value;
727 mappedChannel = mappedChannels
728 .Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == providerChannelId)
729 .Select(x => (KeyValuePair<ulong, ChannelMapping>?)x)
731 hasChannelZero = mappedChannels
732 .Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == 0)
738 logger.LogInformation(
"Receieved message from unmapped channel whose provider contains ID 0. Remapping...");
739 await RemapProvider(provider, cancellationToken);
740 logger.LogTrace(
"Resume processing original message...");
741 await ProcessMessage(provider, message,
true, cancellationToken);
745 ValueTask TextReply(
string reply) => SendMessage(
758 lock (mappedChannels)
759 if (!mappedChannel.HasValue)
762 lock (synchronizationLock)
763 newId = channelIdCounter++;
765 "Mapping private channel {connectionName}:{channelFriendlyName} as {newId}",
771 ProviderChannelId = message.User.Channel.RealId,
772 ProviderId = providerId,
776 "Mapping DM {connectionName}:{userId} ({userFriendlyName}) as {newId}",
781 message.User.Channel.RealId = newId;
784 message.User.Channel.RealId = mappedChannel.Value.Key;
787 if (!mappedChannel.HasValue)
790 "Error mapping message: Provider ID: {providerId}, Channel Real ID: {realId}",
793 logger.LogTrace(
"message: {messageJson}", JsonConvert.SerializeObject(message));
794 lock (mappedChannels)
795 logger.LogTrace(
"mappedChannels: {mappedChannelsJson}", JsonConvert.SerializeObject(mappedChannels));
796 await TextReply(
"TGS: Processing error, check logs!");
800 var mappingChannelRepresentation = mappedChannel.Value.Value.Channel;
802 message.User.Channel.RealId = mappingChannelRepresentation.RealId;
803 message.User.Channel.Tag = mappingChannelRepresentation.Tag;
804 message.User.Channel.IsAdminChannel = mappingChannelRepresentation.IsAdminChannel;
807 var trimmedMessage = message.
Content.Trim();
808 if (trimmedMessage.Length == 0)
811 var splits =
new List<string>(trimmedMessage.Split(
' ', StringSplitOptions.RemoveEmptyEntries));
812 var address = splits[0];
813 if (address.Length > 1 && (address.Last() ==
':' || address.Last() ==
','))
814 address = address[0..^1];
817 address.Equals(CommonMention, StringComparison.OrdinalIgnoreCase)
818 || address.Equals(provider.
BotMention, StringComparison.OrdinalIgnoreCase);
825 "Start processing command: {message}. User (True provider Id): {profiderId}",
827 JsonConvert.SerializeObject(message.
User));
833 if (splits.Count == 0)
836 await TextReply(
"Hi!");
840 var command = splits[0];
842 var arguments = String.Join(
" ", splits);
844 Tuple<ICommand, IChatTrackingContext?>? GetCommand(
string command)
846 if (!builtinCommands.TryGetValue(command, out var handler))
847 return trackingContexts
848 .Where(trackingContext => trackingContext.Active)
849 .SelectMany(trackingContext => trackingContext.CustomCommands.Select(customCommand => Tuple.Create<
ICommand,
IChatTrackingContext?>(customCommand, trackingContext)))
850 .Where(tuple => tuple.Item1.Name.Equals(command, StringComparison.OrdinalIgnoreCase))
856 const string UnknownCommandMessage =
"TGS: Unknown command! Type '?' or 'help' for available commands.";
858 if (command.Equals(
"help", StringComparison.OrdinalIgnoreCase) || command ==
"?")
861 if (splits.Count == 0)
863 var allCommands = builtinCommands.Select(x => x.Value).ToList();
864 allCommands.AddRange(
867 x => x.CustomCommands));
868 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)));
872 var helpTuple = GetCommand(splits[0]);
873 if (helpTuple !=
default)
875 var (helpHandler, _) = helpTuple;
876 helpText = String.Format(CultureInfo.InvariantCulture,
"{0}: {1}{2}", helpHandler.Name, helpHandler.HelpText, helpHandler.AdminOnly ?
" - May only be used in admin channels" : String.Empty);
879 helpText = UnknownCommandMessage;
882 await TextReply(helpText);
886 var tuple = GetCommand(command);
888 if (tuple ==
default)
890 await TextReply(UnknownCommandMessage);
894 var (commandHandler, trackingContext) = tuple;
896 if (trackingContext?.Active ==
false)
898 await TextReply(
"TGS: The server is rebooting, please try again later");
904 await TextReply(
"TGS: Use this command in an admin channel!");
908 var result = await commandHandler.Invoke(arguments, message.
User, cancellationToken);
910 await SendMessage(
new List<ulong> { message.User.Channel.RealId }, message, result, cancellationToken);
912 catch (OperationCanceledException ex)
914 logger.LogTrace(ex,
"Command processing canceled!");
919 logger.LogError(e,
"Error processing chat command");
920 await TextReply(
"TGS: Internal error processing command! Check server logs!");
924 logger.LogTrace(
"Done processing command.");
935 logger.LogTrace(
"Starting processing loop...");
936 var messageTasks =
new Dictionary<IProvider, Task<Message?>>();
937 ValueTask activeProcessingTask = ValueTask.CompletedTask;
940 Task? updatedTask =
null;
941 while (!cancellationToken.IsCancellationRequested)
943 if (updatedTask?.IsCompleted !=
false)
944 lock (synchronizationLock)
945 updatedTask = connectionsUpdated.Task;
948 foreach (var disposedProviderMessageTaskKvp
in messageTasks.Where(x => x.Key.Disposed).ToList())
949 messageTasks.Remove(disposedProviderMessageTaskKvp.Key);
953 foreach (var providerKvp
in providers)
954 if (!messageTasks.ContainsKey(providerKvp.Value))
957 providerKvp.Value.NextMessage(cancellationToken));
959 if (messageTasks.Count == 0)
961 logger.LogTrace(
"No providers active, pausing messsage monitoring...");
962 await updatedTask.WaitAsync(cancellationToken);
963 logger.LogTrace(
"Resuming message monitoring...");
968 await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value)));
971 foreach (var completedMessageTaskKvp
in messageTasks.Where(x => x.Value.IsCompleted).ToList())
973 var provider = completedMessageTaskKvp.Key;
974 messageTasks.Remove(provider);
976 if (provider.Disposed)
979 var message = await completedMessageTaskKvp.Value;
980 var messageNumber = Interlocked.Increment(ref messagesProcessed);
982 async ValueTask WrapProcessMessage()
984 var localActiveProcessingTask = activeProcessingTask;
988 await ProcessMessage(provider, message,
false, cancellationToken);
992 logger.LogError(ex,
"Error processing message {messageNumber}!", messageNumber);
995 await localActiveProcessingTask;
998 activeProcessingTask = WrapProcessMessage();
1002 catch (OperationCanceledException ex)
1004 logger.LogTrace(ex,
"Message processing loop cancelled!");
1008 logger.LogError(e,
"Message loop crashed!");
1012 await activeProcessingTask;
1015 logger.LogTrace(
"Leaving message processing loop");
1028 var channelIdsList = channelIds.ToList();
1031 "Chat send \"{message}\"{embed} to channels: [{channelIdsCommaSeperated}]",
1033 message.
Embed !=
null ?
" (with embed)" : String.Empty,
1034 String.Join(
", ", channelIdsList));
1036 if (channelIdsList.Count == 0)
1037 return ValueTask.CompletedTask;
1040 channelIdsList.Select(x =>
1042 ChannelMapping? channelMapping;
1043 lock (mappedChannels)
1044 if (!mappedChannels.TryGetValue(x, out channelMapping))
1045 return ValueTask.CompletedTask;
1046 IProvider? provider;
1048 if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
1049 return ValueTask.CompletedTask;
1050 return provider.SendMessage(replyTo, message, channelMapping.ProviderChannelId, cancellationToken);
1060 await Task.WhenAll(providers.Select(x => x.Value.InitialConnectionJob));
1061 logger.LogTrace(
"Initial provider connection task completed");
1070 async Task Wrap(Task originalTask)
1077 catch (OperationCanceledException ex)
1079 logger.LogDebug(ex,
"Async chat message cancelled!");
1083 logger.LogError(ex,
"Error in asynchronous chat message!");
1088 messageSendTask = Wrap(messageSendTask);
1099 async Task SendMessageTask()
1101 var cancellationToken = handlerCts.Token;
1102 if (waitForConnections)
1103 await initialProviderConnectionsTask!.WaitAsync(cancellationToken);
1106 channelIdsFactory(),
1112 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.
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.
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.