2022-03-11 03:41:45 +00:00
|
|
|
|
using BirthdayBot.ApplicationCommands;
|
|
|
|
|
using BirthdayBot.BackgroundServices;
|
2022-02-23 22:31:54 +00:00
|
|
|
|
using Discord.Interactions;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using System.Reflection;
|
2020-10-05 04:40:38 +00:00
|
|
|
|
|
2021-11-21 21:21:42 +00:00
|
|
|
|
namespace BirthdayBot;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Single shard instance for Birthday Bot. This shard independently handles all input and output to Discord.
|
|
|
|
|
/// </summary>
|
2022-03-11 03:41:45 +00:00
|
|
|
|
public sealed class ShardInstance : IDisposable {
|
2021-11-21 21:21:42 +00:00
|
|
|
|
private readonly ShardManager _manager;
|
|
|
|
|
private readonly ShardBackgroundWorker _background;
|
2022-02-23 22:31:54 +00:00
|
|
|
|
private readonly InteractionService _interactionService;
|
|
|
|
|
private readonly IServiceProvider _services;
|
2021-11-21 21:21:42 +00:00
|
|
|
|
|
2022-02-23 22:31:54 +00:00
|
|
|
|
internal DiscordSocketClient DiscordClient { get; }
|
2021-11-21 21:21:42 +00:00
|
|
|
|
public int ShardId => DiscordClient.ShardId;
|
2020-10-05 04:40:38 +00:00
|
|
|
|
/// <summary>
|
2021-11-21 21:21:42 +00:00
|
|
|
|
/// Returns a value showing the time in which the last background run successfully completed.
|
2020-10-05 04:40:38 +00:00
|
|
|
|
/// </summary>
|
2022-02-23 22:31:54 +00:00
|
|
|
|
internal DateTimeOffset LastBackgroundRun => _background.LastBackgroundRun;
|
2021-11-21 21:21:42 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the name of the background service currently in execution.
|
|
|
|
|
/// </summary>
|
2022-02-23 22:31:54 +00:00
|
|
|
|
internal string? CurrentExecutingService => _background.CurrentExecutingService;
|
|
|
|
|
internal Configuration Config => _manager.Config;
|
2020-10-05 04:40:38 +00:00
|
|
|
|
|
2022-01-31 06:26:33 +00:00
|
|
|
|
public const string InternalError = ":x: An unknown error occurred. If it persists, please notify the bot owner.";
|
|
|
|
|
|
2021-11-21 21:21:42 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prepares and configures the shard instances, but does not yet start its connection.
|
|
|
|
|
/// </summary>
|
2022-08-29 19:22:42 +00:00
|
|
|
|
internal ShardInstance(ShardManager manager, IServiceProvider services) {
|
2021-11-21 21:21:42 +00:00
|
|
|
|
_manager = manager;
|
2022-02-23 22:31:54 +00:00
|
|
|
|
_services = services;
|
2021-11-21 21:21:42 +00:00
|
|
|
|
|
2022-02-23 22:31:54 +00:00
|
|
|
|
DiscordClient = _services.GetRequiredService<DiscordSocketClient>();
|
2021-11-21 21:21:42 +00:00
|
|
|
|
DiscordClient.Log += Client_Log;
|
|
|
|
|
DiscordClient.Ready += Client_Ready;
|
2022-02-23 22:31:54 +00:00
|
|
|
|
|
|
|
|
|
_interactionService = _services.GetRequiredService<InteractionService>();
|
|
|
|
|
DiscordClient.InteractionCreated += DiscordClient_InteractionCreated;
|
|
|
|
|
_interactionService.SlashCommandExecuted += InteractionService_SlashCommandExecuted;
|
2022-03-14 04:24:17 +00:00
|
|
|
|
DiscordClient.ModalSubmitted += modal => { return ModalResponder.DiscordClient_ModalSubmitted(this, modal); };
|
2020-10-05 23:10:02 +00:00
|
|
|
|
|
2021-11-21 21:21:42 +00:00
|
|
|
|
// Background task constructor begins background processing immediately.
|
|
|
|
|
_background = new ShardBackgroundWorker(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Starts up this shard's connection to Discord and background task handling associated with it.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public async Task StartAsync() {
|
2022-02-25 02:02:20 +00:00
|
|
|
|
await _interactionService.AddModulesAsync(Assembly.GetExecutingAssembly(), _services).ConfigureAwait(false);
|
2021-11-21 21:21:42 +00:00
|
|
|
|
await DiscordClient.LoginAsync(TokenType.Bot, Config.BotToken).ConfigureAwait(false);
|
|
|
|
|
await DiscordClient.StartAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2021-12-06 02:58:10 +00:00
|
|
|
|
/// Does all necessary steps to stop this shard, including canceling background tasks and disconnecting.
|
2021-11-21 21:21:42 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
public void Dispose() {
|
|
|
|
|
_background.Dispose();
|
2021-12-06 02:58:10 +00:00
|
|
|
|
DiscordClient.LogoutAsync().Wait(5000);
|
|
|
|
|
DiscordClient.Dispose();
|
2022-03-11 03:41:45 +00:00
|
|
|
|
_interactionService.Dispose();
|
2021-11-21 21:21:42 +00:00
|
|
|
|
}
|
2020-10-05 04:40:38 +00:00
|
|
|
|
|
2022-03-14 04:24:17 +00:00
|
|
|
|
internal void Log(string source, string message) => Program.Log($"Shard {ShardId:00}] [{source}", message);
|
2021-02-02 06:31:24 +00:00
|
|
|
|
|
2021-11-21 21:21:42 +00:00
|
|
|
|
private Task Client_Log(LogMessage arg) {
|
|
|
|
|
// Suppress certain messages
|
|
|
|
|
if (arg.Message != null) {
|
2023-05-28 01:46:28 +00:00
|
|
|
|
if (!_manager.Config.LogConnectionStatus) {
|
|
|
|
|
switch (arg.Message) {
|
|
|
|
|
case "Connecting":
|
|
|
|
|
case "Connected":
|
|
|
|
|
case "Ready":
|
|
|
|
|
case "Disconnecting":
|
|
|
|
|
case "Disconnected":
|
|
|
|
|
case "Resumed previous session":
|
|
|
|
|
case "Failed to resume previous session":
|
|
|
|
|
case "Serializer Error": // The exception associated with this log appears a lot as of v3.2-ish
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
2020-10-05 04:40:38 +00:00
|
|
|
|
}
|
2021-11-21 21:21:42 +00:00
|
|
|
|
Log("Discord.Net", $"{arg.Severity}: {arg.Message}");
|
2020-10-05 04:40:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-31 04:50:58 +00:00
|
|
|
|
if (arg.Exception != null) {
|
2023-05-28 01:46:28 +00:00
|
|
|
|
if (!_manager.Config.LogConnectionStatus) {
|
|
|
|
|
if (arg.Exception is GatewayReconnectException || arg.Exception.Message == "WebSocket connection was closed")
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
2022-03-31 04:50:58 +00:00
|
|
|
|
|
2023-09-04 23:15:48 +00:00
|
|
|
|
if (arg.Exception is TaskCanceledException) return Task.CompletedTask; // We don't ever need to know these...
|
2023-07-04 21:13:44 +00:00
|
|
|
|
Log("Discord.Net exception", $"{arg.Exception.GetType().FullName}: {arg.Exception.Message}");
|
2022-03-31 04:50:58 +00:00
|
|
|
|
}
|
2021-11-21 21:21:42 +00:00
|
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-31 06:26:33 +00:00
|
|
|
|
private async Task Client_Ready() {
|
|
|
|
|
#if !DEBUG
|
2022-02-23 22:31:54 +00:00
|
|
|
|
// Update slash/interaction commands
|
2022-03-11 03:41:45 +00:00
|
|
|
|
if (ShardId == 0) {
|
2022-08-10 00:18:25 +00:00
|
|
|
|
await _interactionService.RegisterCommandsGloballyAsync(true);
|
2022-03-11 03:41:45 +00:00
|
|
|
|
Log(nameof(ShardInstance), "Updated global command registration.");
|
|
|
|
|
}
|
2022-01-31 06:26:33 +00:00
|
|
|
|
#else
|
|
|
|
|
// Debug: Register our commands locally instead, in each guild we're in
|
2022-08-10 00:18:25 +00:00
|
|
|
|
if (DiscordClient.Guilds.Count > 5) {
|
|
|
|
|
Program.Log(nameof(ShardInstance), "Are you debugging in production?! Skipping DEBUG command registration.");
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
foreach (var g in DiscordClient.Guilds) {
|
|
|
|
|
await _interactionService.RegisterCommandsToGuildAsync(g.Id, true).ConfigureAwait(false);
|
|
|
|
|
Log(nameof(ShardInstance), $"Updated DEBUG command registration in guild {g.Id}.");
|
|
|
|
|
}
|
2022-01-31 06:26:33 +00:00
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2021-11-21 21:21:42 +00:00
|
|
|
|
|
2022-03-11 03:41:45 +00:00
|
|
|
|
// Slash command preparation and invocation
|
2022-02-23 22:31:54 +00:00
|
|
|
|
private async Task DiscordClient_InteractionCreated(SocketInteraction arg) {
|
2022-02-25 02:02:20 +00:00
|
|
|
|
var context = new SocketInteractionContext(DiscordClient, arg);
|
|
|
|
|
|
2022-01-31 06:26:33 +00:00
|
|
|
|
try {
|
2022-03-11 03:41:45 +00:00
|
|
|
|
await _interactionService.ExecuteCommandAsync(context, _services).ConfigureAwait(false);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log(nameof(DiscordClient_InteractionCreated), $"Unhandled exception. {e}");
|
2022-03-12 05:52:46 +00:00
|
|
|
|
if (arg.Type == InteractionType.ApplicationCommand) {
|
|
|
|
|
if (arg.HasResponded) await arg.ModifyOriginalResponseAsync(prop => prop.Content = InternalError);
|
|
|
|
|
else await arg.RespondAsync(InternalError);
|
|
|
|
|
}
|
2022-03-11 03:41:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Slash command logging and failed execution handling
|
2023-02-04 06:55:00 +00:00
|
|
|
|
private Task InteractionService_SlashCommandExecuted(SlashCommandInfo info, IInteractionContext context, IResult result) {
|
2022-03-11 03:41:45 +00:00
|
|
|
|
string sender;
|
2023-02-04 06:55:00 +00:00
|
|
|
|
if (context.Guild != null) sender = $"{context.Guild}!{context.User}";
|
|
|
|
|
else sender = $"{context.User} in non-guild context";
|
2022-03-11 03:41:45 +00:00
|
|
|
|
var logresult = $"{(result.IsSuccess ? "Success" : "Fail")}: `/{info}` by {sender}.";
|
|
|
|
|
|
|
|
|
|
if (result.Error != null) {
|
|
|
|
|
// Additional log information with error detail
|
2022-03-12 05:52:46 +00:00
|
|
|
|
logresult += " " + Enum.GetName(typeof(InteractionCommandError), result.Error) + ": " + result.ErrorReason;
|
2022-01-31 06:26:33 +00:00
|
|
|
|
}
|
2022-03-11 03:41:45 +00:00
|
|
|
|
|
|
|
|
|
Log("Command", logresult);
|
2023-02-04 06:55:00 +00:00
|
|
|
|
return Task.CompletedTask;
|
2022-01-31 06:26:33 +00:00
|
|
|
|
}
|
2020-10-05 04:40:38 +00:00
|
|
|
|
}
|