tgstation-server  4.3.2
The /tg/station 13 server suite
ChatManager.cs
Go to the documentation of this file.
1 using Microsoft.Extensions.Logging;
2 using Newtonsoft.Json;
3 using Serilog.Context;
4 using System;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.Linq;
8 using System.Threading;
9 using System.Threading.Tasks;
14 using Tgstation.Server.Host.IO;
15 
16 namespace Tgstation.Server.Host.Components.Chat
17 {
19  // TODO: Decomplexify
20  #pragma warning disable CA1506
22  {
23  const string CommonMention = "!tgs";
24 
29 
34 
39 
44 
49 
53  readonly ILoggerFactory loggerFactory;
54 
58  readonly ILogger<ChatManager> logger;
59 
63  readonly IDictionary<string, ICommand> builtinCommands;
64 
68  readonly IDictionary<long, IProvider> providers;
69 
73  readonly IDictionary<ulong, ChannelMapping> mappedChannels;
74 
78  readonly IList<IChatTrackingContext> trackingContexts;
79 
83  readonly CancellationTokenSource handlerCts;
84 
88  readonly List<Models.ChatBot> activeChatBots;
89 
93  readonly object synchronizationLock;
94 
99 
104 
108  TaskCompletionSource<object> connectionsUpdated;
109 
114 
119 
123  bool started;
124 
136  public ChatManager(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory, ILogger<ChatManager> logger, IEnumerable<Models.ChatBot> initialChatBots)
137  {
138  this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
139  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
140  this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
141  if (serverControl == null)
142  throw new ArgumentNullException(nameof(serverControl));
143  this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
144  this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
145  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
146  activeChatBots = initialChatBots?.ToList() ?? throw new ArgumentNullException(nameof(initialChatBots));
147 
148  restartRegistration = serverControl.RegisterForRestart(this);
149 
150  synchronizationLock = new object();
151 
152  builtinCommands = new Dictionary<string, ICommand>();
153  providers = new Dictionary<long, IProvider>();
154  mappedChannels = new Dictionary<ulong, ChannelMapping>();
155  trackingContexts = new List<IChatTrackingContext>();
156  handlerCts = new CancellationTokenSource();
157  connectionsUpdated = new TaskCompletionSource<object>();
158  channelIdCounter = 1;
159  }
160 
162  public void Dispose()
163  {
164  restartRegistration.Dispose();
165  handlerCts.Dispose();
166  foreach (var I in providers)
167  I.Value.Dispose();
168  }
169 
177  async Task<IProvider> RemoveProvider(long connectionId, bool updateTrackings, CancellationToken cancellationToken)
178  {
179  IProvider provider;
180  lock (providers)
181  if (!providers.TryGetValue(connectionId, out provider))
182  return null;
183 
184  Task trackingContextsUpdateTask;
185  lock (mappedChannels)
186  {
187  foreach (var I in mappedChannels.Where(x => x.Value.ProviderId == connectionId).Select(x => x.Key).ToList())
188  mappedChannels.Remove(I);
189 
190  var newMappedChannels = mappedChannels.Select(y => y.Value.Channel).ToList();
191 
192  if (updateTrackings)
193  lock (trackingContexts)
194  trackingContextsUpdateTask = Task.WhenAll(trackingContexts.Select(x => x.UpdateChannels(newMappedChannels, cancellationToken)));
195  else
196  trackingContextsUpdateTask = Task.CompletedTask;
197  }
198 
199  await trackingContextsUpdateTask.ConfigureAwait(false);
200 
201  return provider;
202  }
203 
211  #pragma warning disable CA1502
212  async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken)
213  #pragma warning restore CA1502
214  {
215  // provider reconnected, remap channels.
216  if (message == null)
217  {
218  IEnumerable<Api.Models.ChatChannel> channelsToMap;
219  lock (activeChatBots)
220  channelsToMap = activeChatBots.FirstOrDefault()?.Channels;
221 
222  if (channelsToMap?.Any() ?? false)
223  {
224  long providerId;
225  lock (providers)
226  providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
227  await ChangeChannels(providerId, channelsToMap, cancellationToken).ConfigureAwait(false);
228  }
229 
230  return;
231  }
232 
233  // map the channel if it's private and we haven't seen it
234  lock (providers)
235  {
236  var providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
237  var enumerable = mappedChannels.Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == message.User.Channel.RealId);
238  if (message.User.Channel.IsPrivateChannel)
239  lock (mappedChannels)
240  {
241  if (!provider.Connected)
242  return;
243  if (!enumerable.Any())
244  {
245  ulong newId;
246  lock (synchronizationLock)
247  newId = channelIdCounter++;
248  logger.LogTrace(
249  "Mapping private channel {0}:{1} as {2}",
250  message.User.Channel.ConnectionName,
251  message.User.FriendlyName,
252  newId);
253  mappedChannels.Add(newId, new ChannelMapping
254  {
255  IsWatchdogChannel = false,
256  ProviderChannelId = message.User.Channel.RealId,
257  ProviderId = providerId,
258  Channel = message.User.Channel
259  });
260  message.User.Channel.RealId = newId;
261  }
262  else
263  message.User.Channel.RealId = enumerable.First().Key;
264  }
265  else
266  {
267  // need to add tag and isAdminChannel
268  var mapping = enumerable.First().Value;
269  message.User.Channel.Id = mapping.Channel.Id;
270  message.User.Channel.Tag = mapping.Channel.Tag;
271  message.User.Channel.IsAdminChannel = mapping.Channel.IsAdminChannel;
272  }
273  }
274 
275  var splits = new List<string>(message.Content.Trim().Split(' '));
276  var address = splits[0];
277  if (address.Length > 1 && (address.Last() == ':' || address.Last() == ','))
278  address = address[0..^1];
279 
280  address = address.ToUpperInvariant();
281 
282  var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant();
283 
284  // no mention
285  if (!addressed && !message.User.Channel.IsPrivateChannel)
286  return;
287 
288  logger.LogTrace("Chat command: {0}. User (True provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User));
289 
290  if (addressed)
291  splits.RemoveAt(0);
292 
293  if (splits.Count == 0)
294  {
295  // just a mention
296  await SendMessage("Hi!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
297  return;
298  }
299 
300  var command = splits[0].ToUpperInvariant();
301  splits.RemoveAt(0);
302  var arguments = String.Join(" ", splits);
303 
304  try
305  {
306  ICommand GetCommand(string commandName)
307  {
308  if (!builtinCommands.TryGetValue(commandName, out var handler))
309  {
310  handler = trackingContexts
311  .Where(x => x.CustomCommands != null)
312  .SelectMany(x => x.CustomCommands)
313  .Where(x => x.Name.ToUpperInvariant() == commandName)
314  .FirstOrDefault();
315  }
316 
317  return handler;
318  }
319 
320  const string UnknownCommandMessage = "Unknown command! Type '?' or 'help' for available commands.";
321 
322  if (command == "HELP" || command == "?")
323  {
324  string helpText;
325  if (splits.Count == 0)
326  {
327  var allCommands = builtinCommands.Select(x => x.Value).ToList();
328  allCommands.AddRange(
329  trackingContexts
330  .Where(x => x.CustomCommands != null)
331  .SelectMany(
332  x => x.CustomCommands));
333  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)));
334  }
335  else
336  {
337  var helpHandler = GetCommand(splits[0].ToUpperInvariant());
338  if (helpHandler != default)
339  helpText = String.Format(CultureInfo.InvariantCulture, "{0}: {1}{2}", helpHandler.Name, helpHandler.HelpText, helpHandler.AdminOnly ? " - May only be used in admin channels" : String.Empty);
340  else
341  helpText = UnknownCommandMessage;
342  }
343 
344  await SendMessage(helpText, new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
345  return;
346  }
347 
348  var commandHandler = GetCommand(command);
349 
350  if (commandHandler == default)
351  {
352  await SendMessage(UnknownCommandMessage, new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
353  return;
354  }
355 
356  if (commandHandler.AdminOnly && !message.User.Channel.IsAdminChannel)
357  {
358  await SendMessage("Use this command in an admin channel!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
359  return;
360  }
361 
362  var result = await commandHandler.Invoke(arguments, message.User, cancellationToken).ConfigureAwait(false);
363  if (result != null)
364  await SendMessage(result, new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
365  }
366  catch (OperationCanceledException)
367  {
368  throw;
369  }
370  catch (Exception e)
371  {
372  // error bc custom commands should reply about why it failed
373  logger.LogError("Error processing chat command: {0}", e);
374  await SendMessage("Internal error processing command!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
375  }
376  }
377 
383  async Task MonitorMessages(CancellationToken cancellationToken)
384  {
385  var messageTasks = new Dictionary<IProvider, Task<Message>>();
386  try
387  {
388  while (!cancellationToken.IsCancellationRequested)
389  {
390  // prune disconnected providers
391  foreach (var I in messageTasks.Where(x => !x.Key.Connected).ToList())
392  messageTasks.Remove(I.Key);
393 
394  // add new ones
395  Task updatedTask;
396  lock (synchronizationLock)
397  updatedTask = connectionsUpdated.Task;
398  lock (providers)
399  foreach (var I in providers)
400  if (I.Value.Connected && !messageTasks.ContainsKey(I.Value))
401  messageTasks.Add(I.Value, I.Value.NextMessage(cancellationToken));
402 
403  if (messageTasks.Count == 0)
404  {
405  await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
406  continue;
407  }
408 
409  // wait for a message
410  await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value))).ConfigureAwait(false);
411 
412  // process completed ones
413  foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList())
414  {
415  var message = await I.Value.ConfigureAwait(false);
416  var messageNumber = Interlocked.Increment(ref messagesProcessed);
417  using (LogContext.PushProperty("ChatMessage", messageNumber))
418  await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false);
419  messageTasks.Remove(I.Key);
420  }
421  }
422  }
423  catch (OperationCanceledException)
424  {
425  logger.LogTrace("Message processing loop cancelled!");
426  }
427  catch (Exception e)
428  {
429  logger.LogError("Message monitor crashed! Exception: {0}", e);
430  }
431  }
432 
434  public async Task ChangeChannels(long connectionId, IEnumerable<Api.Models.ChatChannel> newChannels, CancellationToken cancellationToken)
435  {
436  if (newChannels == null)
437  throw new ArgumentNullException(nameof(newChannels));
438  var provider = await RemoveProvider(connectionId, false, cancellationToken).ConfigureAwait(false);
439  if (provider == null)
440  return;
441  var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false);
442  lock (activeChatBots)
443  {
444  var botToUpdate = activeChatBots.FirstOrDefault(bot => bot.Id == connectionId);
445  if (botToUpdate != null)
446  botToUpdate.Channels = newChannels
447  .Select(apiModel => new Models.ChatChannel
448  {
449  DiscordChannelId = apiModel.DiscordChannelId,
450  IrcChannel = apiModel.IrcChannel,
451  IsAdminChannel = apiModel.IsAdminChannel,
452  IsUpdatesChannel = apiModel.IsUpdatesChannel,
453  IsWatchdogChannel = apiModel.IsWatchdogChannel,
454  Tag = apiModel.Tag
455  })
456  .ToList();
457  }
458 
459  var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping
460  {
461  IsWatchdogChannel = x.IsWatchdogChannel == true,
462  IsUpdatesChannel = x.IsUpdatesChannel == true,
463  IsAdminChannel = x.IsAdminChannel == true,
464  ProviderChannelId = y.RealId,
465  ProviderId = connectionId,
466  Channel = y
467  });
468 
469  ulong baseId;
470  lock (synchronizationLock)
471  {
472  baseId = channelIdCounter;
473  channelIdCounter += (ulong)results.Count;
474  }
475 
476  Task trackingContextUpdateTask;
477  lock (mappedChannels)
478  {
479  lock (providers)
480  if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) // aborted again
481  return;
482  foreach (var I in mappings)
483  {
484  var newId = baseId++;
485  logger.LogTrace("Mapping channel {0}:{1} as {2}", I.Channel.ConnectionName, I.Channel.FriendlyName, newId);
486  mappedChannels.Add(newId, I);
487  I.Channel.RealId = newId;
488  }
489 
490  lock (trackingContexts)
491  trackingContextUpdateTask = Task.WhenAll(
492  trackingContexts.Select(
493  x => x.UpdateChannels(
494  mappedChannels.Select(y => y.Value.Channel).ToList(),
495  cancellationToken)));
496  }
497 
498  await trackingContextUpdateTask.ConfigureAwait(false);
499  }
500 
502  public async Task ChangeSettings(ChatBot newSettings, CancellationToken cancellationToken)
503  {
504  if (newSettings == null)
505  throw new ArgumentNullException(nameof(newSettings));
506  IProvider provider;
507 
508  async Task DisconnectProvider(IProvider p)
509  {
510  try
511  {
512  await p.Disconnect(cancellationToken).ConfigureAwait(false);
513  }
514  finally
515  {
516  p.Dispose();
517  }
518  }
519 
520  Task disconnectTask;
521  lock (providers)
522  {
523  // raw settings changes forces a rebuild of the provider
524  if (providers.TryGetValue(newSettings.Id, out provider))
525  {
526  providers.Remove(newSettings.Id);
527  disconnectTask = DisconnectProvider(provider);
528  }
529  else
530  disconnectTask = Task.CompletedTask;
531  if (newSettings.Enabled.Value)
532  {
533  provider = providerFactory.CreateProvider(newSettings);
534  providers.Add(newSettings.Id, provider);
535  }
536  }
537 
538  lock (mappedChannels)
539  foreach (var I in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key).ToList())
540  mappedChannels.Remove(I);
541 
542  await disconnectTask.ConfigureAwait(false);
543 
544  if (started)
545  {
546  if (newSettings.Enabled.Value)
547  await provider.Connect(cancellationToken).ConfigureAwait(false);
548  lock (synchronizationLock)
549  {
550  // same thread shennanigans
551  var oldOne = connectionsUpdated;
552  connectionsUpdated = new TaskCompletionSource<object>();
553  oldOne.SetResult(null);
554  }
555  }
556 
557  Task reconnectionUpdateTask = Task.CompletedTask;
558  lock (activeChatBots)
559  {
560  var originalChatBot = activeChatBots.FirstOrDefault(bot => bot.Id == newSettings.Id);
561  if (originalChatBot != null)
562  {
563  if (originalChatBot.ReconnectionInterval != newSettings.ReconnectionInterval)
564  reconnectionUpdateTask = provider.SetReconnectInterval(newSettings.ReconnectionInterval.Value);
565 
566  activeChatBots.Remove(originalChatBot);
567  }
568 
569  activeChatBots.Add(new Models.ChatBot
570  {
571  Id = newSettings.Id,
572  ConnectionString = newSettings.ConnectionString,
573  Enabled = newSettings.Enabled,
574  Name = newSettings.Name,
575  ReconnectionInterval = newSettings.ReconnectionInterval,
576  Provider = newSettings.Provider
577  });
578  }
579 
580  await reconnectionUpdateTask.ConfigureAwait(false);
581  }
582 
584  public Task SendMessage(string message, IEnumerable<ulong> channelIds, CancellationToken cancellationToken)
585  {
586  if (message == null)
587  throw new ArgumentNullException(nameof(message));
588  if (channelIds == null)
589  throw new ArgumentNullException(nameof(channelIds));
590 
591  logger.LogTrace("Chat send \"{0}\" to channels: {1}", message, String.Join(", ", channelIds));
592 
593  return Task.WhenAll(channelIds.Select(x =>
594  {
595  ChannelMapping channelMapping;
596  lock (mappedChannels)
597  if (!mappedChannels.TryGetValue(x, out channelMapping))
598  return Task.CompletedTask;
599  IProvider provider;
600  lock (providers)
601  if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
602  return Task.CompletedTask;
603  return provider.SendMessage(channelMapping.ProviderChannelId, message, cancellationToken);
604  }));
605  }
606 
608  public Task SendWatchdogMessage(string message, bool adminOnly, CancellationToken cancellationToken)
609  {
610  List<ulong> wdChannels = null;
611  message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message);
612 
613  // so it doesn't change while we're using it
614  lock (mappedChannels)
615  {
616  if (adminOnly)
617  {
618  wdChannels = mappedChannels.Where(x => x.Value.IsAdminChannel).Select(x => x.Key).ToList();
619  if (wdChannels.Count == 0)
620  adminOnly = false;
621  }
622 
623  if (!adminOnly)
624  wdChannels = mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList();
625  }
626 
627  return SendMessage(message, wdChannels, cancellationToken);
628  }
629 
631  public async Task<Func<string, string, Task>> SendDeploymentMessage(
632  Models.RevisionInformation revisionInformation,
633  Version byondVersion,
634  DateTimeOffset? estimatedCompletionTime,
635  string gitHubOwner,
636  string gitHubRepo,
637  bool localCommitPushed,
638  CancellationToken cancellationToken)
639  {
640  List<ulong> wdChannels;
641  lock (mappedChannels) // so it doesn't change while we're using it
642  wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList();
643 
644  logger.LogTrace("Sending deployment message for RevisionInformation: {0}", revisionInformation.Id);
645 
646  var callbacks = new List<Func<string, string, Task>>();
647 
648  await Task.WhenAll(
649  wdChannels.Select(
650  async x =>
651  {
652  ChannelMapping channelMapping;
653  lock (mappedChannels)
654  if (!mappedChannels.TryGetValue(x, out channelMapping))
655  return;
656  IProvider provider;
657  lock (providers)
658  if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
659  return;
660  try
661  {
662  var callback = await provider.SendUpdateMessage(
663  revisionInformation,
664  byondVersion,
665  estimatedCompletionTime,
666  gitHubOwner,
667  gitHubRepo,
668  channelMapping.ProviderChannelId,
669  localCommitPushed,
670  cancellationToken)
671  .ConfigureAwait(false);
672 
673  callbacks.Add(callback);
674  }
675  catch (Exception ex)
676  {
677  logger.LogWarning(
678  "Error sending deploy message to provider {0}! Exception: {1}",
679  channelMapping.ProviderId,
680  ex);
681  }
682  }))
683  .ConfigureAwait(false);
684 
685  return (errorMessage, dreamMakerOutput) => Task.WhenAll(callbacks.Select(x => x(errorMessage, dreamMakerOutput)));
686  }
687 
689  public async Task StartAsync(CancellationToken cancellationToken)
690  {
691  foreach (var I in commandFactory.GenerateCommands())
692  builtinCommands.Add(I.Name.ToUpperInvariant(), I);
693  var initialChatBots = activeChatBots.ToList();
694  await Task.WhenAll(initialChatBots.Select(x => ChangeSettings(x, cancellationToken))).ConfigureAwait(false);
695  await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))).ConfigureAwait(false);
696  await Task.WhenAll(initialChatBots.Select(x => ChangeChannels(x.Id, x.Channels, cancellationToken))).ConfigureAwait(false);
697  chatHandler = MonitorMessages(handlerCts.Token);
698  started = true;
699  }
700 
702  public async Task StopAsync(CancellationToken cancellationToken)
703  {
704  handlerCts.Cancel();
705  if (chatHandler != null)
706  await chatHandler.ConfigureAwait(false);
707  await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Disconnect(cancellationToken))).ConfigureAwait(false);
708  }
709 
712  {
713  if (customCommandHandler == null)
714  throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!");
715 
716  IChatTrackingContext context = null;
717  lock (mappedChannels)
718  context = new ChatTrackingContext(
719  customCommandHandler,
720  mappedChannels.Select(y => y.Value.Channel),
721  loggerFactory.CreateLogger<ChatTrackingContext>(),
722  () =>
723  {
724  lock (trackingContexts)
725  trackingContexts.Remove(context);
726  });
727 
728  lock (trackingContexts)
729  trackingContexts.Add(context);
730 
731  return context;
732  }
733 
735  public void RegisterCommandHandler(ICustomCommandHandler customCommandHandler)
736  {
737  if (this.customCommandHandler != null)
738  throw new InvalidOperationException("RegisterCommandHandler() already called!");
739  this.customCommandHandler = customCommandHandler ?? throw new ArgumentNullException(nameof(customCommandHandler));
740  }
741 
743  public async Task DeleteConnection(long connectionId, CancellationToken cancellationToken)
744  {
745  var provider = await RemoveProvider(connectionId, true, cancellationToken).ConfigureAwait(false);
746  if (provider != null)
747  try
748  {
749  await provider.Disconnect(cancellationToken).ConfigureAwait(false);
750  }
751  finally
752  {
753  provider.Dispose();
754  }
755  }
756 
758  public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
759  {
760  var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion);
761  List<ulong> wdChannels;
762  lock (mappedChannels) // so it doesn't change while we're using it
763  wdChannels = mappedChannels.Select(x => x.Key).ToList();
764  return SendMessage(message, wdChannels, cancellationToken);
765  }
766  }
767 }
bool IsWatchdogChannel
If Channel is a watchdog channel
Represents a mapping of a ChannelRepresentation.RealId
ulong channelIdCounter
Used for remapping ChannelRepresentation.RealIds
Definition: ChatManager.cs:113
Task SendWatchdogMessage(string message, bool adminOnly, CancellationToken cancellationToken)
Send a chat message to configured watchdog channels
Definition: ChatManager.cs:608
async Task ChangeSettings(ChatBot newSettings, CancellationToken cancellationToken)
Change chat settings. If the ChatBot.Id is not currently in use, a new connection will be made instea...
Definition: ChatManager.cs:502
readonly ICommandFactory commandFactory
The ICommandFactory for the ChatManager
Definition: ChatManager.cs:38
ChatUser User
The ChatUser who sent the Message
Definition: Message.cs:16
readonly IDictionary< string, ICommand > builtinCommands
Unchanging ICommands in the ChatManager mapped by ICommand.Name
Definition: ChatManager.cs:63
ChatManager(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory, ILogger< ChatManager > logger, IEnumerable< Models.ChatBot > initialChatBots)
Construct a ChatManager
Definition: ChatManager.cs:136
Task Disconnect(CancellationToken cancellationToken)
Gracefully disconnects the provider. Implies a call to IDisposable.Dispose
readonly IDictionary< long, IProvider > providers
Map of IProviders in use, keyed by ChatBot.Id
Definition: ChatManager.cs:68
bool IsAdminChannel
If this is considered a channel for admin commands
async Task< IProvider > RemoveProvider(long connectionId, bool updateTrackings, CancellationToken cancellationToken)
Remove a IProvider from providers and mappedChannels optionally updating the trackingContexts as well...
Definition: ChatManager.cs:177
Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
Handle a restart of the server
Definition: ChatManager.cs:758
Represents a message recieved by a IProvider
Definition: Message.cs:6
async Task DeleteConnection(long connectionId, CancellationToken cancellationToken)
Disconnects and deletes a given connection
Definition: ChatManager.cs:743
For interacting with a chat service
Definition: IProvider.cs:12
Task chatHandler
The Task that monitors incoming chat messages
Definition: ChatManager.cs:103
readonly IRestartRegistration restartRegistration
The IRestartRegistration for the ChatManager
Definition: ChatManager.cs:43
Manage the server chat bots
Definition: ChatBot.cs:9
readonly IList< IChatTrackingContext > trackingContexts
The active IChatTrackingContexts for the ChatManager
Definition: ChatManager.cs:78
For managing connected chat services
Definition: IChatManager.cs:13
string Id
Backing field for RealId. Represented as a string to avoid BYOND percision loss
async Task< Func< string, string, Task > > SendDeploymentMessage(Models.RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset?estimatedCompletionTime, string gitHubOwner, string gitHubRepo, bool localCommitPushed, CancellationToken cancellationToken)
Send the message for a deployment to configured deployment channels.
Definition: ChatManager.cs:631
string FriendlyName
The friendly name of the user
Definition: ChatUser.cs:30
IChatTrackingContext CreateTrackingContext()
Start tracking Commands.CustomCommands and ChannelRepresentations.
Definition: ChatManager.cs:711
string ConnectionName
The name of the connection the ChannelRepresentation belongs to
readonly CancellationTokenSource handlerCts
The CancellationTokenSource for chatHandler
Definition: ChatManager.cs:83
ICustomCommandHandler customCommandHandler
The ICustomCommandHandler for the ChangeChannels(long, IEnumerable<Api.Models.ChatChannel>, CancellationToken)
Definition: ChatManager.cs:98
Task< bool > Connect(CancellationToken cancellationToken)
Attempt to connect the IProvider
readonly object synchronizationLock
Used for various lock statements throughout this .
Definition: ChatManager.cs:93
bool started
If StartAsync(CancellationToken) has been called
Definition: ChatManager.cs:123
readonly IIOManager ioManager
The IIOManager for the ChatManager
Definition: ChatManager.cs:33
async Task ChangeChannels(long connectionId, IEnumerable< Api.Models.ChatChannel > newChannels, CancellationToken cancellationToken)
Change chat channels
Definition: ChatManager.cs:434
string BotMention
The string that indicates the IProvider was mentioned
Definition: IProvider.cs:22
async Task StopAsync(CancellationToken cancellationToken)
Definition: ChatManager.cs:702
void RegisterCommandHandler(ICustomCommandHandler customCommandHandler)
Registers a customCommandHandler to use
Definition: ChatManager.cs:735
TaskCompletionSource< object > connectionsUpdated
The TaskCompletionSource<TResult> that completes when ChatBots change
Definition: ChatManager.cs:108
async Task StartAsync(CancellationToken cancellationToken)
Definition: ChatManager.cs:689
readonly List< Models.ChatBot > activeChatBots
The active Models.ChatBot for the ChatManager
Definition: ChatManager.cs:88
Handles Commands.ICommands that map to those defined in a IChatTrackingContext
Task SendMessage(string message, IEnumerable< ulong > channelIds, CancellationToken cancellationToken)
Send a chat message to a given set of channelIds
Definition: ChatManager.cs:584
Represents a tracking of dynamic chat json files
readonly ILoggerFactory loggerFactory
The ILoggerFactory for the ChatManager
Definition: ChatManager.cs:53
async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken)
Processes a message
Definition: ChatManager.cs:212
Interface for using filesystems
Definition: IIOManager.cs:11
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the ChatManager
Definition: ChatManager.cs:48
ChannelRepresentation Channel
The ChannelRepresentation the user spoke from
Definition: ChatUser.cs:40
readonly IDictionary< ulong, ChannelMapping > mappedChannels
Map of ChannelRepresentation.RealIds to ChannelMappings
Definition: ChatManager.cs:73
Represents the lifetime of a IRestartHandler registration
readonly IProviderFactory providerFactory
The IProviderFactory for the ChatManager
Definition: ChatManager.cs:28
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart
readonly ILogger< ChatManager > logger
The ILogger for the ChatManager
Definition: ChatManager.cs:58
bool Enabled
If the connection is enabled
Definition: ChatBot.cs:26
async Task MonitorMessages(CancellationToken cancellationToken)
Monitors active providers for new Messages
Definition: ChatManager.cs:383
Represents a command that can be invoked by talking to chat bots
Definition: ICommand.cs:9
Task< IReadOnlyCollection< ChannelRepresentation > > MapChannels(IEnumerable< Api.Models.ChatChannel > channels, CancellationToken cancellationToken)
Get the ChannelRepresentations for given channels
long messagesProcessed
The number of Messages processed.
Definition: ChatManager.cs:118
Task SetReconnectInterval(uint reconnectInterval)
Set the interval at which the provider tries to reconnect.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...