Discord improvements

- Channel 0 can be mapped to indicate all unmapped channels the bot has access to.
- Discord bot now "types" when responding to a command
This commit is contained in:
Jordan Brown
2020-08-17 23:08:13 -04:00
parent bf313e9e28
commit 882e05ac6e
3 changed files with 150 additions and 64 deletions
@@ -21,7 +21,10 @@ namespace Tgstation.Server.Host.Components.Chat
#pragma warning disable CA1506
sealed class ChatManager : IChatManager, IRestartHandler
{
const string CommonMention = "!tgs";
/// <summary>
/// The common bot mention.
/// </summary>
public const string CommonMention = "!tgs";
/// <summary>
/// The <see cref="IProviderFactory"/> for the <see cref="ChatManager"/>
@@ -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);
@@ -1,4 +1,6 @@
namespace Tgstation.Server.Host.Components.Chat.Providers
using System;
namespace Tgstation.Server.Host.Components.Chat.Providers
{
/// <summary>
/// Represents a message recieved by a <see cref="IProvider"/>
@@ -14,5 +16,10 @@
/// The <see cref="ChatUser"/> who sent the <see cref="Message"/>
/// </summary>
public ChatUser User { get; set; }
/// <summary>
/// The <see cref="IDisposable"/> that should be <see cref="IDisposable.Dispose"/>d once the <see cref="Message"/> is processed.
/// </summary>
public IDisposable Context { get; set; }
}
}
}
@@ -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();
}
}
/// <inheritdoc />
@@ -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
/// <inheritdoc />
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)
{