diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
index a56464cb72..af798e4ae6 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
@@ -761,7 +761,7 @@ namespace Tgstation.Server.Host.Components.Chat
{
messageTasks.Remove(undisposedMessageTaskKvp.Key);
if (undisposedMessageTaskKvp.Value.IsCompleted)
- (await undisposedMessageTaskKvp.Value.ConfigureAwait(false))?.Context?.Dispose();
+ await undisposedMessageTaskKvp.Value.ConfigureAwait(false);
}
// add new ones
@@ -786,7 +786,6 @@ namespace Tgstation.Server.Host.Components.Chat
foreach (var completedMessageTaskKvp in messageTasks.Where(x => x.Value.IsCompleted).ToList())
{
var message = await completedMessageTaskKvp.Value.ConfigureAwait(false);
- using var messageContext = message?.Context;
var messageNumber = Interlocked.Increment(ref messagesProcessed);
using (LogContext.PushProperty("ChatMessage", messageNumber))
await ProcessMessage(completedMessageTaskKvp.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 8f04d8b2ea..ba4befa0a9 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Message.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs
@@ -1,6 +1,4 @@
-using System;
-
-namespace Tgstation.Server.Host.Components.Chat.Providers
+namespace Tgstation.Server.Host.Components.Chat.Providers
{
///
/// Represents a message recieved by a .
@@ -16,10 +14,5 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// The who sent the .
///
public ChatUser User { get; set; }
-
- ///
- /// The that should be d once the is processed.
- ///
- public IDisposable Context { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordForwardingResponder.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordForwardingResponder.cs
new file mode 100644
index 0000000000..899380df1b
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordForwardingResponder.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Remora.Discord.API.Abstractions.Gateway.Events;
+using Remora.Discord.Gateway.Responders;
+using Remora.Results;
+
+namespace Tgstation.Server.Host.Components.Chat.Providers
+{
+ ///
+ /// An that forwards to another .
+ ///
+ sealed class DiscordForwardingResponder : IDiscordResponders
+ {
+ ///
+ /// The to forward the event to.
+ ///
+ readonly IDiscordResponders targetResponder;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ public DiscordForwardingResponder(IDiscordResponders targetResponder)
+ {
+ this.targetResponder = targetResponder ?? throw new ArgumentNullException(nameof(targetResponder));
+ }
+
+ ///
+ public Task RespondAsync(IMessageCreate gatewayEvent, CancellationToken ct) => targetResponder.RespondAsync(gatewayEvent, ct);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
index f04f9ba1bb..d660ca3eba 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
@@ -1,12 +1,20 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Discord;
-using Discord.WebSocket;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Remora.Discord.API.Abstractions.Gateway.Events;
+using Remora.Discord.API.Abstractions.Objects;
+using Remora.Discord.API.Abstractions.Rest;
+using Remora.Discord.API.Objects;
+using Remora.Discord.Core;
+using Remora.Discord.Gateway;
+using Remora.Discord.Gateway.Extensions;
+using Remora.Results;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Jobs;
@@ -18,10 +26,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// for the Discord app.
///
- sealed class DiscordProvider : Provider
+ sealed class DiscordProvider : Provider, IDiscordResponders
{
///
- public override bool Connected => client.ConnectionState != ConnectionState.Disconnected;
+ public override bool Connected => gatewayTask?.IsCompleted == false;
///
public override string BotMention
@@ -30,7 +38,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
{
if (!Connected)
throw new InvalidOperationException("Provider not connected");
- return NormalizeMentions(client.CurrentUser.Mention);
+ return NormalizeMentions($"<@{currentUserId}>");
}
}
@@ -40,20 +48,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
readonly IAssemblyInformationProvider assemblyInformationProvider;
///
- /// The for the .
+ /// The containing Discord services.
///
- readonly DiscordSocketClient client;
+ readonly ServiceProvider serviceProvider;
///
- /// of mapped s.
+ /// of mapped channel s.
///
readonly List mappedChannels;
- ///
- /// The Discord bot token.
- ///
- readonly string botToken;
-
///
/// to enable based mode. Will auto reply with a youtube link to a video that says "based on the hardware that's installed in it" to anyone saying 'based on what?' case-insensitive.
///
@@ -64,6 +67,21 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
readonly DiscordDMOutputDisplayType outputDisplayType;
+ ///
+ /// The for the .
+ ///
+ CancellationTokenSource gatewayCts;
+
+ ///
+ /// The representing the lifetime of the client.
+ ///
+ Task gatewayTask;
+
+ ///
+ /// The bot's .
+ ///
+ Snowflake currentUserId;
+
///
/// Normalize a discord mention string.
///
@@ -72,15 +90,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
static string NormalizeMentions(string fromDiscord) => fromDiscord.Replace("<@!", "<@", StringComparison.Ordinal);
///
- /// Create a of s for a discord update embed.
+ /// Create a of s for a discord update embed.
///
/// The of the deployment.
/// The BYOND of the deployment.
/// The repository GitHub owner, if any.
/// The repository GitHub name, if any.
/// if the local deployment commit was pushed to the remote repository.
- /// A new of s to use.
- static List BuildUpdateEmbedFields(
+ /// A new of s to use.
+ static List BuildUpdateEmbedFields(
Models.RevisionInformation revisionInformation,
Version byondVersion,
string gitHubOwner,
@@ -88,39 +106,32 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
bool localCommitPushed)
{
bool gitHub = gitHubOwner != null && gitHubRepo != null;
- var fields = new List
+ var fields = new List
{
- new EmbedFieldBuilder
- {
- Name = "BYOND Version",
- Value = $"{byondVersion.Major}.{byondVersion.Minor}{(byondVersion.Build > 0 ? $".{byondVersion.Build}" : String.Empty)}",
- IsInline = true,
- },
- new EmbedFieldBuilder
- {
- Name = "Local Commit",
- Value = localCommitPushed && gitHub
+ new EmbedField(
+ "BYOND Version",
+ $"{byondVersion.Major}.{byondVersion.Minor}{(byondVersion.Build > 0 ? $".{byondVersion.Build}" : String.Empty)}",
+ true),
+ new EmbedField(
+ "Local Commit",
+ localCommitPushed && gitHub
? $"[{revisionInformation.CommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.CommitSha})"
: revisionInformation.CommitSha.Substring(0, 7),
- IsInline = true,
- },
- new EmbedFieldBuilder
- {
- Name = "Branch Commit",
- Value = gitHub
+ true),
+ new EmbedField(
+ "Branch Commit",
+ gitHub
? $"[{revisionInformation.OriginCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.OriginCommitSha})"
: revisionInformation.OriginCommitSha.Substring(0, 7),
- IsInline = true,
- },
+ true),
};
fields.AddRange((revisionInformation.ActiveTestMerges ?? Enumerable.Empty())
.Select(x => x.TestMerge)
- .Select(x => new EmbedFieldBuilder
- {
- Name = $"#{x.Number}",
- Value = $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}",
- }));
+ .Select(x => new EmbedField(
+ $"#{x.Number}",
+ $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}",
+ false)));
return fields;
}
@@ -141,25 +152,32 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
{
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
+ mappedChannels = new List();
+
var csb = new DiscordConnectionStringBuilder(chatBot.ConnectionString);
- botToken = csb.BotToken;
+ var botToken = csb.BotToken;
basedMeme = csb.BasedMeme;
outputDisplayType = csb.DMOutputDisplay;
- client = new DiscordSocketClient();
- client.MessageReceived += Client_MessageReceived;
- mappedChannels = new List();
+ serviceProvider = new ServiceCollection()
+ .AddDiscordGateway(serviceProvider => botToken)
+ .AddSingleton(serviceProvider => this)
+ .AddResponder()
+ .BuildServiceProvider();
}
///
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync().ConfigureAwait(false);
- client.Dispose();
+ await serviceProvider.DisposeAsync().ConfigureAwait(false);
+
+ // this line is purely here to shutup CA2213
+ gatewayCts?.Dispose();
}
///
- public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken)
+ public override async Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken)
{
if (channels == null)
throw new ArgumentNullException(nameof(channels));
@@ -167,10 +185,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (!Connected)
{
Logger.LogWarning("Cannot map channels, provider disconnected!");
- return Task.FromResult>(Array.Empty());
+ return Array.Empty();
}
- ChannelRepresentation GetModelChannelFromDBChannel(Api.Models.ChatChannel channelFromDB)
+ var usersClient = serviceProvider.GetRequiredService();
+ var currentUserResponse = await usersClient.GetCurrentUserAsync(cancellationToken).ConfigureAwait(false);
+
+ if (!currentUserResponse.IsSuccess)
+ {
+ Logger.LogWarning("Error retrieving current Discord user: {0}", currentUserResponse.Error.Message);
+ return Array.Empty();
+ }
+
+ async Task GetModelChannelFromDBChannel(Api.Models.ChatChannel channelFromDB)
{
if (!channelFromDB.DiscordChannelId.HasValue)
throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
@@ -181,22 +208,44 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
string friendlyName;
if (channelId == 0)
{
- connectionName = client.CurrentUser.Username;
+ connectionName = currentUserResponse.Entity.Username;
friendlyName = "(Unmapped accessible channels)";
discordChannelId = 0;
}
else
{
- var discordChannel = client.GetChannel(channelId);
- if (!(discordChannel is ITextChannel textChannel))
+ var channelsClient = serviceProvider.GetRequiredService();
+ var discordChannelResponse = await channelsClient.GetChannelAsync(new Snowflake(channelId), cancellationToken);
+ if (!discordChannelResponse.IsSuccess)
{
- Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType());
+ Logger.LogWarning("Error retrieving discord channel {0}: {1}", channelId, discordChannelResponse.Error.Message);
return null;
}
- discordChannelId = textChannel.Id;
- connectionName = textChannel.Guild.Name;
- friendlyName = textChannel.Name;
+ if (discordChannelResponse.Entity.Type != ChannelType.GuildText)
+ {
+ Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannelResponse.Entity.Type);
+ return null;
+ }
+
+ discordChannelId = discordChannelResponse.Entity.ID.Value;
+ friendlyName = discordChannelResponse.Entity.Name.Value;
+
+ var guildsClient = serviceProvider.GetRequiredService();
+ var guildsResponse = await guildsClient.GetGuildAsync(
+ discordChannelResponse.Entity.GuildID.Value,
+ false,
+ cancellationToken);
+ if (!guildsResponse.IsSuccess)
+ {
+ Logger.LogWarning(
+ "Error retrieving discord guild {0}: {1}",
+ discordChannelResponse.Entity.GuildID.Value,
+ discordChannelResponse.Error.Message);
+ return null;
+ }
+
+ connectionName = guildsResponse.Entity.Name;
}
var channelModel = new ChannelRepresentation
@@ -208,13 +257,21 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
IsPrivateChannel = false,
Tag = channelFromDB.Tag,
};
+
Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName);
return channelModel;
}
- var enumerator = channels
+ var tasks = channels
.Select(x => GetModelChannelFromDBChannel(x))
- .Where(x => x != null).ToList();
+ .Where(x => x != null)
+ .ToList();
+
+ await Task.WhenAll(tasks);
+
+ var enumerator = tasks
+ .Select(x => x.Result)
+ .ToList();
lock (mappedChannels)
{
@@ -222,57 +279,64 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
mappedChannels.AddRange(enumerator.Select(x => x.RealId));
}
- return Task.FromResult>(enumerator);
+ return enumerator;
}
///
public override async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken)
{
- var requestOptions = new RequestOptions
+ var channelsClient = serviceProvider.GetRequiredService();
+ async Task SendToChannel(Snowflake channelId)
{
- CancelToken = cancellationToken,
- Timeout = 10000, // prevent stupid long hold ups from this
- };
+ var result = await channelsClient.CreateMessageAsync(
+ channelId,
+ message,
+ ct: cancellationToken);
- Task SendToChannel(IMessageChannel channel) => channel.SendMessageAsync(
- message,
- false,
- null,
- requestOptions);
+ if (!result.IsSuccess)
+ Logger.LogWarning(
+ "Failed to send to channel {0}: {1}",
+ channelId,
+ result.Error.Message);
+ }
try
{
if (channelId == 0)
{
- var unmappedTextChannels = client
- .Guilds
- .SelectMany(x => x.TextChannels);
+ var usersClient = serviceProvider.GetRequiredService();
+ var currentGuildsResponse = await usersClient.GetCurrentUserGuildsAsync(ct: cancellationToken).ConfigureAwait(false);
+ if (!currentGuildsResponse.IsSuccess)
+ {
+ Logger.LogWarning(
+ "Error retrieving current discord guilds: {0}",
+ currentGuildsResponse.Error.Message);
+ return;
+ }
+
+ var unmappedTextChannels = currentGuildsResponse
+ .Entity
+ .SelectMany(x => x.Channels.Value);
lock (mappedChannels)
- unmappedTextChannels = unmappedTextChannels.Where(x => !mappedChannels.Contains(x.Id));
+ unmappedTextChannels = unmappedTextChannels
+ .Where(x => !mappedChannels.Contains(x.ID.Value))
+ .ToList();
// 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)
+ if (unmappedTextChannels.Any())
{
- Logger.LogTrace("Dispatched to {0} unmapped channels...", channelCount);
- await Task.WhenAll(tasks).ConfigureAwait(false);
+ Logger.LogTrace("Dispatching to {0} unmapped channels...", unmappedTextChannels.Count());
+ await Task.WhenAll(
+ unmappedTextChannels.Select(
+ x => SendToChannel(x.ID)))
+ .ConfigureAwait(false);
}
return;
}
- if (!(client.GetChannel(channelId) is IMessageChannel channel))
- return;
-
- await SendToChannel(channel).ConfigureAwait(false);
+ await SendToChannel(new Snowflake(channelId)).ConfigureAwait(false);
}
catch (Exception e)
{
@@ -283,6 +347,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
///
+#pragma warning disable CA1506
public override async Task> SendUpdateMessage(
Models.RevisionInformation revisionInformation,
Version byondVersion,
@@ -296,51 +361,53 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
var fields = BuildUpdateEmbedFields(revisionInformation, byondVersion, gitHubOwner, gitHubRepo, localCommitPushed);
- var builder = new EmbedBuilder
+ var embed = new Embed
{
- Author = new EmbedAuthorBuilder
+ Author = new EmbedAuthor
{
Name = assemblyInformationProvider.VersionPrefix,
Url = "https://github.com/tgstation/tgstation-server",
IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4",
},
- Color = Color.Gold,
+ Colour = Color.FromArgb(0xF1, 0xC4, 0x0F),
Description = "TGS has begun deploying active repository code to production.",
Fields = fields,
Title = "Code Deployment",
- Footer = new EmbedFooterBuilder
- {
- Text = $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}",
- },
- Timestamp = estimatedCompletionTime,
+ Footer = new EmbedFooter(
+ $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}"),
+ Timestamp = estimatedCompletionTime ?? default,
};
Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId);
- if (!(client.GetChannel(channelId) is IMessageChannel channel))
- {
- Logger.LogTrace("Channel ID {0} does not exist or is not an IMessageChannel!", channelId);
- return (errorMessage, dreamMakerOutput) => Task.CompletedTask;
- }
+ var channelsClient = serviceProvider.GetRequiredService();
- var message = await channel.SendMessageAsync(
+ var messageResponse = await channelsClient.CreateMessageAsync(
+ new Snowflake(channelId),
"DM: Deployment in Progress...",
- false,
- builder.Build(),
- new RequestOptions
- {
- CancelToken = cancellationToken,
- })
+ embeds: new List { embed },
+ ct: cancellationToken)
.ConfigureAwait(false);
+ if (!messageResponse.IsSuccess)
+ Logger.LogWarning("Failed to post deploy embed to channel {0}: {1}", channelId, messageResponse.Error.Message);
+
return async (errorMessage, dreamMakerOutput) =>
{
var completionString = errorMessage == null ? "Succeeded" : "Failed";
- builder.Footer.Text = completionString;
- builder.Color = errorMessage == null ? Color.Green : Color.Red;
- builder.Timestamp = DateTimeOffset.UtcNow;
- builder.Description = errorMessage == null
+
+ embed = new Embed
+ {
+ Author = embed.Author,
+ Colour = errorMessage == null ? Color.Green : Color.Red,
+ Description = errorMessage == null
? "The deployment completed successfully and will be available at the next server reboot."
- : "The deployment failed.";
+ : "The deployment failed.",
+ Fields = fields,
+ Title = embed.Title,
+ Footer = new EmbedFooter(
+ completionString),
+ Timestamp = DateTimeOffset.UtcNow,
+ };
var showDMOutput = outputDisplayType switch
{
@@ -352,83 +419,188 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (dreamMakerOutput != null)
{
- showDMOutput = showDMOutput && dreamMakerOutput.Length < EmbedFieldBuilder.MaxFieldValueLength - (6 + Environment.NewLine.Length);
+ // https://github.com/discord-net/Discord.Net/blob/8349cd7e1eb92e9a3baff68082c30a7b43e8e9b7/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs#L431
+ const int MaxFieldValueLength = 1024;
+ showDMOutput = showDMOutput && dreamMakerOutput.Length < MaxFieldValueLength - (6 + Environment.NewLine.Length);
if (showDMOutput)
- builder.AddField(new EmbedFieldBuilder
- {
- Name = "DreamMaker Output",
- Value = $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```",
- });
+ fields.Add(new EmbedField(
+ "DreamMaker Output",
+ $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```",
+ false));
}
if (errorMessage != null)
- builder.AddField(new EmbedFieldBuilder
- {
- Name = "Error Message",
- Value = errorMessage,
- });
+ fields.Add(new EmbedField(
+ "Error Message",
+ errorMessage,
+ false));
var updatedMessage = $"DM: Deployment {completionString}!";
- try
+
+ async Task CreateUpdatedMessage()
{
- await message.ModifyAsync(
- props =>
- {
- props.Content = updatedMessage;
- props.Embed = builder.Build();
- })
+ var createUpdatedMessageResponse = await channelsClient.CreateMessageAsync(
+ new Snowflake(channelId),
+ updatedMessage,
+ embeds: new List { embed },
+ ct: cancellationToken)
.ConfigureAwait(false);
+
+ if (!createUpdatedMessageResponse.IsSuccess)
+ Logger.LogWarning(
+ "Creating updated deploy embed failed! Error: {0}",
+ createUpdatedMessageResponse.Error.Message);
}
- catch (Exception ex)
+
+ if (!messageResponse.IsSuccess)
+ await CreateUpdatedMessage();
+ else
{
- Logger.LogWarning(ex, "Updating deploy embed {0} failed, attempting new post!", message.Id);
- try
+ var editResponse = await channelsClient.EditMessageAsync(
+ new Snowflake(channelId),
+ messageResponse.Entity.ID,
+ updatedMessage,
+ embeds: new List { embed },
+ ct: cancellationToken)
+ .ConfigureAwait(false);
+
+ if (!editResponse.IsSuccess)
{
- await channel.SendMessageAsync(
- updatedMessage,
- false,
- builder.Build())
- .ConfigureAwait(false);
- }
- catch (Exception ex2)
- {
- Logger.LogWarning(ex2, "Posting completion deploy embed failed!");
+ Logger.LogWarning(
+ "Updating deploy embed {0} failed, attempting new post! Error: {1}",
+ messageResponse.Entity.ID,
+ editResponse.Error.Message);
+ await CreateUpdatedMessage();
}
}
};
}
+ #pragma warning restore CA1506
+
+ ///
+ public async Task RespondAsync(IMessageCreate messageCreateEvent, CancellationToken cancellationToken)
+ {
+ if ((messageCreateEvent.Type != MessageType.Default
+ && messageCreateEvent.Type != MessageType.InlineReply)
+ || messageCreateEvent.Author.ID == currentUserId)
+ return Result.FromSuccess();
+
+ if (basedMeme && messageCreateEvent.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase))
+ {
+ // DCT: None available
+ await SendMessage(
+ messageCreateEvent.ChannelID.Value,
+ "https://youtu.be/LrNu-SuFF_o",
+ default)
+ .ConfigureAwait(false);
+ return Result.FromSuccess();
+ }
+
+ var channelsClient = serviceProvider.GetRequiredService();
+ var channelResponse = await channelsClient.GetChannelAsync(messageCreateEvent.ChannelID, cancellationToken).ConfigureAwait(false);
+ if (!channelResponse.IsSuccess)
+ {
+ Logger.LogWarning(
+ "Failed to get channel {0} in response to message {1}!",
+ messageCreateEvent.ChannelID,
+ messageCreateEvent.ID);
+
+ // we'll handle the errors ourselves
+ return Result.FromSuccess();
+ }
+
+ var pm = channelResponse.Entity.Type == ChannelType.DM || channelResponse.Entity.Type == ChannelType.GroupDM;
+ var shouldNotAnswer = !pm;
+ if (shouldNotAnswer)
+ lock (mappedChannels)
+ shouldNotAnswer = !mappedChannels.Contains(messageCreateEvent.ChannelID.Value);
+
+ var content = NormalizeMentions(messageCreateEvent.Content);
+ var mentionedUs = messageCreateEvent.Mentions.Any(x => x.ID == currentUserId)
+ || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase));
+
+ if (shouldNotAnswer)
+ {
+ if (mentionedUs)
+ Logger.LogTrace(
+ "Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!",
+ messageCreateEvent.ChannelID,
+ channelResponse.Entity.Name,
+ messageCreateEvent.Author.ID,
+ messageCreateEvent.Author.Username);
+
+ return Result.FromSuccess();
+ }
+
+ string guildName = "UNKNOWN";
+ if (!pm)
+ {
+ var guildsClient = serviceProvider.GetRequiredService();
+ var messageGuildResponse = await guildsClient.GetGuildAsync(messageCreateEvent.GuildID.Value, false, cancellationToken).ConfigureAwait(false);
+ if (messageGuildResponse.IsSuccess)
+ guildName = messageGuildResponse.Entity.Name;
+ else
+ Logger.LogWarning(
+ "Failed to get channel {0} in response to message {1}!",
+ messageCreateEvent.ChannelID,
+ messageCreateEvent.ID);
+ }
+
+ var result = new Message
+ {
+ Content = content,
+ User = new ChatUser
+ {
+ RealId = messageCreateEvent.Author.ID.Value,
+ Channel = new ChannelRepresentation
+ {
+ RealId = messageCreateEvent.ChannelID.Value,
+ IsPrivateChannel = pm,
+ ConnectionName = pm ? messageCreateEvent.Author.Username : guildName,
+ FriendlyName = channelResponse.Entity.Name.Value,
+
+ // isAdmin and Tag populated by manager
+ },
+ FriendlyName = messageCreateEvent.Author.Username,
+ Mention = NormalizeMentions($"<@{messageCreateEvent.Author.ID}>"),
+ },
+ };
+
+ EnqueueMessage(result);
+ return Result.FromSuccess();
+ }
///
protected override async Task Connect(CancellationToken cancellationToken)
{
try
{
- await client.LoginAsync(TokenType.Bot, botToken, true).ConfigureAwait(false);
+ if (gatewayCts != null)
+ throw new InvalidOperationException("Discord gateway still active!");
- Logger.LogTrace("Logged in.");
- cancellationToken.ThrowIfCancellationRequested();
+ gatewayCts = new CancellationTokenSource();
- var channelsAvailable = new TaskCompletionSource