using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; using System.Linq; using DSharpPlus; using DSharpPlus.Interactivity; using DSharpPlus.Interactivity.Enums; using DSharpPlus.Interactivity.Extensions; using DSharpPlus.Commands; using Microsoft.Extensions.Logging; using SupportChild.Commands; using CommandLine; using DSharpPlus.Commands.Processors.SlashCommands; using DSharpPlus.Exceptions; using Microsoft.Extensions.DependencyInjection; namespace SupportChild; internal static class SupportChild { internal static DiscordClient client = null; public class CommandLineArguments { [Option('c', "config", Required = false, HelpText = "Select a config file to use.", MetaValue = "PATH")] public string configPath { get; set; } [Option('t', "transcripts", Required = false, HelpText = "Select directory to store transcripts in.", MetaValue = "PATH")] public string transcriptDir { get; set; } [Option("leave", Required = false, HelpText = "Leaves one or more Discord servers. " + "You can check which servers your bot is in when it starts up.", MetaValue = "ID,ID,ID...", Separator = ',' )] public IEnumerable serversToLeave { get; set; } } internal static CommandLineArguments commandLineArgs; private static void Main(string[] args) { StringWriter sw = new StringWriter(); commandLineArgs = new Parser(settings => { settings.AutoHelp = true; settings.HelpWriter = sw; settings.AutoVersion = false; }).ParseArguments(args).Value; // CommandLineParser has some bugs related to the built-in version option, ignore the output if it isn't found. if (!sw.ToString().Contains("Option 'version' is unknown.")) { Console.Write(sw); } if (args.Contains("--help")) { return; } if (args.Contains("--version")) { Console.WriteLine(Assembly.GetEntryAssembly()?.GetName().Name + ' ' + GetVersion()); Console.WriteLine("Build time: " + BuildInfo.BuildTimeUTC.ToString("yyyy-MM-dd HH:mm:ss") + " UTC"); return; } MainAsync().GetAwaiter().GetResult(); } private static async Task MainAsync() { Logger.Log("Starting " + Assembly.GetEntryAssembly()?.GetName().Name + " version " + GetVersion() + "..."); try { if (!await Reload()) { Logger.Fatal("Aborting startup due to a fatal error..."); return; } // Block this task until the program is closed. await Task.Delay(-1); } catch (Exception e) { Logger.Fatal("Fatal error:\n" + e); Console.ReadLine(); } } public static string GetVersion() { Version version = Assembly.GetEntryAssembly()?.GetName().Version; return version?.Major + "." + version?.Minor + "." + version?.Build + (version?.Revision == 0 ? "" : "-" + (char)(64 + version?.Revision ?? 0)) + " (" + ThisAssembly.Git.Commit + ")"; } public static async Task Reload() { if (client != null) { await client.DisconnectAsync(); client.Dispose(); } Config.LoadConfig(); // Check if token is unset if (Config.token is "" or "") { Logger.Fatal("You need to set your bot token in the config and start the bot again."); throw new ArgumentException("Invalid Discord bot token"); } // Database connection and setup try { Logger.Log("Connecting to database... (" + Config.hostName + ":" + Config.port + ")"); Database.SetConnectionString(Config.hostName, Config.port, Config.database, Config.username, Config.password); Database.SetupTables(); } catch (Exception e) { Logger.Fatal("Could not set up database tables, please confirm connection settings, status of the server and permissions of MySQL user. Error: ", e); return false; } Logger.Log("Setting up Discord client..."); DiscordClientBuilder clientBuilder = DiscordClientBuilder.CreateDefault(Config.token, DiscordIntents.All).SetReconnectOnFatalGatewayErrors(); clientBuilder.ConfigureServices(configure => { configure.AddSingleton(new ErrorHandler()); }); clientBuilder.ConfigureEventHandlers(builder => { builder.HandleGuildDownloadCompleted(EventHandler.OnReady); builder.HandleGuildAvailable(EventHandler.OnGuildAvailable); builder.HandleMessageCreated(EventHandler.OnMessageCreated); builder.HandleGuildMemberAdded(EventHandler.OnMemberAdded); builder.HandleGuildMemberRemoved(EventHandler.OnMemberRemoved); builder.HandleComponentInteractionCreated(EventHandler.OnComponentInteractionCreated); }); clientBuilder.UseInteractivity(new InteractivityConfiguration { PaginationBehaviour = PaginationBehaviour.Ignore, PaginationDeletion = PaginationDeletion.DeleteMessage, Timeout = TimeSpan.FromMinutes(15) }); clientBuilder.UseCommands((_, extension) => { extension.AddCommands( [ typeof(AddCategoryCommand), typeof(AddCommand), typeof(AddMessageCommand), typeof(AddStaffCommand), typeof(AdminCommands), typeof(AssignCommand), typeof(BlacklistCommand), typeof(CloseCommand), typeof(CreateButtonPanelCommand), typeof(CreateSelectionBoxPanelCommand), typeof(InterviewTemplateCommands), typeof(ListAssignedCommand), typeof(ListCommand), typeof(ListInvalidCommand), typeof(ListOpen), typeof(ListUnassignedCommand), typeof(MoveCommand), typeof(NewCommand), typeof(RandomAssignCommand), typeof(RemoveCategoryCommand), typeof(RemoveMessageCommand), typeof(RemoveStaffCommand), typeof(InterviewCommands), typeof(SayCommand), typeof(SetSummaryCommand), typeof(StatusCommand), typeof(SummaryCommand), typeof(ToggleActiveCommand), typeof(TranscriptCommand), typeof(UnassignCommand), typeof(UnblacklistCommand), ]); extension.AddProcessor(new SlashCommandProcessor()); extension.CommandErrored += EventHandler.OnCommandError; }, new CommandsConfiguration() { RegisterDefaultCommandProcessors = false, UseDefaultCommandErrorHandler = false }); clientBuilder.ConfigureExtraFeatures(clientConfig => { clientConfig.LogUnknownEvents = false; clientConfig.LogUnknownAuditlogs = false; }); clientBuilder.ConfigureLogging(config => { config.AddProvider(new LogTestFactory()); }); client = clientBuilder.Build(); Logger.Log("Connecting to Discord..."); EventHandler.hasLoggedGuilds = false; await client.ConnectAsync(); return true; } } internal class ErrorHandler : IClientErrorHandler { public ValueTask HandleEventHandlerError(string name, Exception exception, Delegate invokedDelegate, object sender, object args) { Logger.Error("Client exception occured:\n" + exception); switch (exception) { case BadRequestException ex: Logger.Error("JSON Message: " + ex.JsonMessage); break; default: break; } return ValueTask.FromException(exception); } public ValueTask HandleGatewayError(Exception exception) { Logger.Error("A gateway error occured:\n" + exception); return ValueTask.FromException(exception); } }