diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
index 49ffccb072..25807b4d2f 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
@@ -21,7 +21,10 @@ namespace Tgstation.Server.Host.Components.Chat
#pragma warning disable CA1506
sealed class ChatManager : IChatManager, IRestartHandler
{
- const string CommonMention = "!tgs";
+ ///
+ /// The common bot mention.
+ ///
+ public const string CommonMention = "!tgs";
///
/// The for the
@@ -415,7 +418,11 @@ namespace Tgstation.Server.Host.Components.Chat
{
// prune disconnected providers
foreach (var I in messageTasks.Where(x => !x.Key.Disposed).ToList())
+ {
messageTasks.Remove(I.Key);
+ if (I.Value.IsCompleted)
+ (await I.Value.ConfigureAwait(false))?.Context?.Dispose();
+ }
// add new ones
Task updatedTask;
@@ -439,6 +446,7 @@ namespace Tgstation.Server.Host.Components.Chat
foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList())
{
var message = await I.Value.ConfigureAwait(false);
+ using var messageContext = message?.Context;
var messageNumber = Interlocked.Increment(ref messagesProcessed);
using (LogContext.PushProperty("ChatMessage", messageNumber))
await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs
index 5f661fd4f7..ef87a84d34 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Message.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs
@@ -1,4 +1,6 @@
-namespace Tgstation.Server.Host.Components.Chat.Providers
+using System;
+
+namespace Tgstation.Server.Host.Components.Chat.Providers
{
///
/// Represents a message recieved by a
@@ -14,5 +16,10 @@
/// The who sent the
///
public ChatUser User { get; set; }
+
+ ///
+ /// The that should be d once the is processed.
+ ///
+ public IDisposable Context { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
index 4488b7319a..d624c960d4 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
@@ -106,53 +106,76 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (e.Author.Id == client.CurrentUser.Id)
return;
- if (e.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase))
+ IDisposable typingState = null;
+ void StartTyping() => typingState = e.Channel.EnterTypingState();
+ try
{
- // DCT: None available
- await SendMessage(
- e.Channel.Id,
- "https://youtu.be/LrNu-SuFF_o",
- default)
- .ConfigureAwait(false);
- }
-
- var pm = e.Channel is IPrivateChannel;
-
- if (!pm && !mappedChannels.Contains(e.Channel.Id))
- {
- var mentionedUs = e.MentionedUsers.Any(x => x.Id == client.CurrentUser.Id);
- if (mentionedUs)
+ if (e.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase))
{
- Logger.LogTrace("Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!", e.Channel.Id, e.Channel.Name, e.Author.Id, e.Author.Username);
+ StartTyping();
// DCT: None available
- await SendMessage(e.Channel.Id, "I do not respond to this channel!", default).ConfigureAwait(false);
+ await SendMessage(
+ e.Channel.Id,
+ "https://youtu.be/LrNu-SuFF_o",
+ default)
+ .ConfigureAwait(false);
+ return;
}
- return;
- }
+ var pm = e.Channel is IPrivateChannel;
+ var shouldNotAnswer = !pm;
+ if (shouldNotAnswer)
+ lock (mappedChannels)
+ shouldNotAnswer = !mappedChannels.Contains(e.Channel.Id);
- var result = new Message
- {
- Content = NormalizeMentions(e.Content),
- User = new ChatUser
+ var content = NormalizeMentions(e.Content);
+ var mentionedUs = e.MentionedUsers.Any(x => x.Id == client.CurrentUser.Id)
+ || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase));
+ if (mentionedUs)
+ StartTyping();
+
+ if (shouldNotAnswer)
{
- RealId = e.Author.Id,
- Channel = new ChannelRepresentation
+ if (mentionedUs)
{
- RealId = e.Channel.Id,
- IsPrivateChannel = pm,
- ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN",
- FriendlyName = e.Channel.Name
+ Logger.LogTrace("Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!", e.Channel.Id, e.Channel.Name, e.Author.Id, e.Author.Username);
- // isAdmin and Tag populated by manager
- },
- FriendlyName = e.Author.Username,
- Mention = NormalizeMentions(e.Author.Mention)
+ // DCT: None available
+ await SendMessage(e.Channel.Id, "I do not respond to this channel!", default).ConfigureAwait(false);
+ }
+
+ return;
}
- };
- EnqueueMessage(result);
+ var result = new Message
+ {
+ Content = content,
+ User = new ChatUser
+ {
+ RealId = e.Author.Id,
+ Channel = new ChannelRepresentation
+ {
+ RealId = e.Channel.Id,
+ IsPrivateChannel = pm,
+ ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN",
+ FriendlyName = e.Channel.Name
+
+ // isAdmin and Tag populated by manager
+ },
+ FriendlyName = e.Author.Username,
+ Mention = NormalizeMentions(e.Author.Mention)
+ },
+ Context = typingState
+ };
+
+ EnqueueMessage(result);
+ typingState = null;
+ }
+ finally
+ {
+ typingState?.Dispose();
+ }
}
///
@@ -236,29 +259,47 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
var channelId = channelFromDB.DiscordChannelId.Value;
- var discordChannel = client.GetChannel(channelId);
- if (discordChannel is ITextChannel textChannel)
+ ulong discordChannelId;
+ string connectionName;
+ string friendlyName;
+ if (channelId == 0)
{
- var channelModel = new ChannelRepresentation
+ connectionName = client.CurrentUser.Username;
+ friendlyName = "(Unmapped accessible channels)";
+ discordChannelId = 0;
+ }
+ else
+ {
+ var discordChannel = client.GetChannel(channelId);
+ if (!(discordChannel is ITextChannel textChannel))
{
- RealId = discordChannel.Id,
- IsAdminChannel = channelFromDB.IsAdminChannel == true,
- ConnectionName = textChannel.Guild.Name,
- FriendlyName = textChannel.Name,
- IsPrivateChannel = false,
- Tag = channelFromDB.Tag
- };
- Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName);
- return channelModel;
+ Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType());
+ return null;
+ }
+
+ discordChannelId = textChannel.Id;
+ connectionName = textChannel.Guild.Name;
+ friendlyName = textChannel.Name;
}
- Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType());
- return null;
+ var channelModel = new ChannelRepresentation
+ {
+ RealId = discordChannelId,
+ IsAdminChannel = channelFromDB.IsAdminChannel == true,
+ ConnectionName = connectionName,
+ FriendlyName = friendlyName,
+ IsPrivateChannel = false,
+ Tag = channelFromDB.Tag
+ };
+ Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName);
+ return channelModel;
}
- var enumerator = channels.Select(x => GetModelChannelFromDBChannel(x)).Where(x => x != null).ToList();
+ var enumerator = channels
+ .Select(x => GetModelChannelFromDBChannel(x))
+ .Where(x => x != null).ToList();
- lock (client)
+ lock (mappedChannels)
{
mappedChannels.Clear();
mappedChannels.AddRange(enumerator.Select(x => x.RealId));
@@ -270,21 +311,51 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
public override async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken)
{
+ var requestOptions = new RequestOptions
+ {
+ CancelToken = cancellationToken,
+ Timeout = 10000 // prevent stupid long hold ups from this
+ };
+
+ Task SendToChannel(IMessageChannel channel) => channel.SendMessageAsync(
+ message,
+ false,
+ null,
+ requestOptions);
+
try
{
+ if (channelId == 0)
+ {
+ var unmappedTextChannels = client
+ .Guilds
+ .SelectMany(x => x.TextChannels);
+
+ lock (mappedChannels)
+ unmappedTextChannels = unmappedTextChannels.Where(x => !mappedChannels.Contains(x.Id));
+
+ // discord API confirmed weak boned: https://stackoverflow.com/a/52462336
+ var channelCount = 0UL;
+ var tasks = unmappedTextChannels
+ .Select(x =>
+ {
+ ++channelCount;
+ return SendToChannel(x);
+ });
+
+ if (channelCount > 0)
+ {
+ Logger.LogTrace("Dispatched to {0} unmapped channels...", channelCount);
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ }
+
+ return;
+ }
+
if (!(client.GetChannel(channelId) is IMessageChannel channel))
return;
- await channel.SendMessageAsync(
- message,
- false,
- null,
- new RequestOptions
- {
- CancelToken = cancellationToken,
- Timeout = 10000 // prevent stupid long hold ups from this
- })
- .ConfigureAwait(false);
+ await SendToChannel(channel).ConfigureAwait(false);
}
catch (Exception e)
{