|
6 | 6 | using DSharpPlus.Commands; |
7 | 7 | using DSharpPlus.Commands.Processors.SlashCommands; |
8 | 8 | using DSharpPlus.Commands.Processors.TextCommands; |
| 9 | +using DSharpPlus.Commands.Processors.TextCommands.Parsing; |
9 | 10 | using Microsoft.Extensions.Configuration; |
10 | 11 | using Microsoft.Extensions.DependencyInjection; |
11 | 12 | using Microsoft.Extensions.Logging; |
| 13 | +using NotifierRedirecter.Configuration; |
12 | 14 | using NotifierRedirecter.Events; |
13 | 15 | using Serilog; |
14 | 16 | using Serilog.Events; |
15 | 17 | using Serilog.Sinks.SystemConsole.Themes; |
| 18 | +using DSharpPlusDiscordConfiguration = DSharpPlus.DiscordConfiguration; |
| 19 | +using SerilogLoggerConfiguration = Serilog.LoggerConfiguration; |
16 | 20 |
|
17 | 21 | namespace NotifierRedirecter; |
18 | 22 |
|
19 | 23 | public sealed class Program |
20 | 24 | { |
21 | | - internal static IServiceProvider ServiceProvider = null!; |
22 | | - |
23 | 25 | public static async Task Main(string[] args) |
24 | 26 | { |
25 | | - ConfigurationBuilder configurationBuilder = new(); |
26 | | - configurationBuilder.Sources.Clear(); |
27 | | - configurationBuilder.AddJsonFile("config.json", true, true); |
| 27 | + IServiceCollection services = new ServiceCollection(); |
| 28 | + services.AddSingleton(serviceProvider => |
| 29 | + { |
| 30 | + ConfigurationBuilder configurationBuilder = new(); |
| 31 | + configurationBuilder.Sources.Clear(); |
| 32 | + configurationBuilder.AddJsonFile("config.json", true, true); |
28 | 33 | #if DEBUG |
29 | | - configurationBuilder.AddJsonFile("config.debug.json", true, true); |
| 34 | + configurationBuilder.AddJsonFile("config.debug.json", true, true); |
30 | 35 | #endif |
31 | | - configurationBuilder.AddEnvironmentVariables("NOTIFIER_REDIRECTER__"); |
32 | | - configurationBuilder.AddCommandLine(args); |
| 36 | + configurationBuilder.AddEnvironmentVariables("NotifierRedirecter__"); |
| 37 | + configurationBuilder.AddCommandLine(args); |
33 | 38 |
|
34 | | - IConfiguration configuration = configurationBuilder.Build(); |
35 | | - ServiceCollection serviceCollection = new(); |
36 | | - serviceCollection.AddSingleton(configuration); |
37 | | - serviceCollection.AddLogging(logger => |
38 | | - { |
39 | | - string? loggingFormat = configuration.GetValue<string?>("Logging:Format"); |
40 | | - if (string.IsNullOrWhiteSpace(loggingFormat)) |
| 39 | + IConfiguration configuration = configurationBuilder.Build(); |
| 40 | + NotifierConfiguration? notifierConfiguration = configuration.Get<NotifierConfiguration>(); |
| 41 | + if (notifierConfiguration is null) |
41 | 42 | { |
42 | | - loggingFormat = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u4}] {SourceContext}: {Message:lj}{NewLine}{Exception}"; |
| 43 | + Console.WriteLine("No configuration found! Please modify the config file, set environment variables or pass command line arguments. Exiting..."); |
| 44 | + Environment.Exit(1); |
43 | 45 | } |
44 | 46 |
|
45 | | - string? filename = configuration.GetValue<string?>("Logging:Filename"); |
46 | | - if (!string.IsNullOrWhiteSpace(filename)) |
47 | | - { |
48 | | - filename = "yyyy'-'MM'-'dd' 'HH'.'mm'.'ss"; |
49 | | - } |
| 47 | + return notifierConfiguration; |
| 48 | + }); |
50 | 49 |
|
51 | | - // Log both to console and the file |
52 | | - LoggerConfiguration loggerConfiguration = new LoggerConfiguration() |
53 | | - .MinimumLevel.Is(configuration.GetValue("Logging:Level", LogEventLevel.Debug)) |
54 | | - .WriteTo.Console( |
| 50 | + services.AddLogging(logging => |
| 51 | + { |
| 52 | + IServiceProvider serviceProvider = logging.Services.BuildServiceProvider(); |
| 53 | + NotifierConfiguration notifierConfiguration = serviceProvider.GetRequiredService<NotifierConfiguration>(); |
| 54 | + SerilogLoggerConfiguration serilogLoggerConfiguration = new(); |
| 55 | + serilogLoggerConfiguration.MinimumLevel.Is(notifierConfiguration.Logger.LogLevel); |
| 56 | + serilogLoggerConfiguration.WriteTo.Console( |
55 | 57 | formatProvider: CultureInfo.InvariantCulture, |
56 | | - outputTemplate: loggingFormat, |
57 | | - theme: new AnsiConsoleTheme(new Dictionary<ConsoleThemeStyle, string> |
58 | | - { |
59 | | - [ConsoleThemeStyle.Text] = "\x1b[0m", |
60 | | - [ConsoleThemeStyle.SecondaryText] = "\x1b[90m", |
61 | | - [ConsoleThemeStyle.TertiaryText] = "\x1b[90m", |
62 | | - [ConsoleThemeStyle.Invalid] = "\x1b[31m", |
63 | | - [ConsoleThemeStyle.Null] = "\x1b[95m", |
64 | | - [ConsoleThemeStyle.Name] = "\x1b[93m", |
65 | | - [ConsoleThemeStyle.String] = "\x1b[96m", |
66 | | - [ConsoleThemeStyle.Number] = "\x1b[95m", |
67 | | - [ConsoleThemeStyle.Boolean] = "\x1b[95m", |
68 | | - [ConsoleThemeStyle.Scalar] = "\x1b[95m", |
69 | | - [ConsoleThemeStyle.LevelVerbose] = "\x1b[34m", |
70 | | - [ConsoleThemeStyle.LevelDebug] = "\x1b[90m", |
71 | | - [ConsoleThemeStyle.LevelInformation] = "\x1b[36m", |
72 | | - [ConsoleThemeStyle.LevelWarning] = "\x1b[33m", |
73 | | - [ConsoleThemeStyle.LevelError] = "\x1b[31m", |
74 | | - [ConsoleThemeStyle.LevelFatal] = "\x1b[97;91m" |
75 | | - })) |
76 | | - .WriteTo.File( |
77 | | - $"logs/{DateTime.Now.ToUniversalTime().ToString(filename, CultureInfo.InvariantCulture)}-.log", |
| 58 | + outputTemplate: notifierConfiguration.Logger.Format, |
| 59 | + theme: AnsiConsoleTheme.Code |
| 60 | + ); |
| 61 | + |
| 62 | + serilogLoggerConfiguration.WriteTo.File( |
78 | 63 | formatProvider: CultureInfo.InvariantCulture, |
79 | | - outputTemplate: loggingFormat, |
80 | | - rollingInterval: RollingInterval.Day |
| 64 | + path: $"{notifierConfiguration.Logger.Path}/{notifierConfiguration.Logger.FileName}.log", |
| 65 | + rollingInterval: notifierConfiguration.Logger.RollingInterval, |
| 66 | + outputTemplate: notifierConfiguration.Logger.Format |
81 | 67 | ); |
82 | 68 |
|
83 | | - // Allow specific namespace log level overrides, which allows us to hush output from things like the database basic SELECT queries on the Information level. |
84 | | - foreach (IConfigurationSection logOverride in configuration.GetSection("logging:overrides").GetChildren()) |
| 69 | + // Sometimes the user/dev needs more or less information about a speific part of the bot |
| 70 | + // so we allow them to override the log level for a specific namespace. |
| 71 | + if (notifierConfiguration.Logger.Overrides.Count > 0) |
85 | 72 | { |
86 | | - if (!string.IsNullOrWhiteSpace(logOverride.Value) && Enum.TryParse(logOverride.Value, out LogEventLevel logEventLevel)) |
| 73 | + foreach ((string key, LogEventLevel value) in notifierConfiguration.Logger.Overrides) |
87 | 74 | { |
88 | | - loggerConfiguration.MinimumLevel.Override(logOverride.Key, logEventLevel); |
| 75 | + serilogLoggerConfiguration.MinimumLevel.Override(key, value); |
89 | 76 | } |
90 | 77 | } |
91 | 78 |
|
92 | | - logger.AddSerilog(loggerConfiguration.CreateLogger()); |
| 79 | + logging.AddSerilog(serilogLoggerConfiguration.CreateLogger()); |
93 | 80 | }); |
94 | 81 |
|
95 | | - serviceCollection.AddSingleton<Database>(); |
96 | | - serviceCollection.AddSingleton<UserActivityTracker>(); |
97 | | - serviceCollection.AddSingleton((serviceProvider) => |
| 82 | + services.AddSingleton<Database>(); |
| 83 | + services.AddSingleton<UserActivityTracker>(); |
| 84 | + services.AddSingleton((serviceProvider) => |
98 | 85 | { |
99 | 86 | DiscordEventManager eventManager = new(serviceProvider); |
100 | 87 | eventManager.GatherEventHandlers(typeof(Program).Assembly); |
101 | 88 | return eventManager; |
102 | 89 | }); |
103 | 90 |
|
104 | | - // Register the Discord sharded client to the service collection |
105 | | - serviceCollection.AddSingleton((serviceProvider) => |
| 91 | + services.AddSingleton(serviceProvider => |
106 | 92 | { |
107 | | - DiscordEventManager eventManager = serviceProvider.GetRequiredService<DiscordEventManager>(); |
108 | | - ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>(); |
109 | | - IConfiguration configuration = serviceProvider.GetRequiredService<IConfiguration>(); |
110 | | - string? discordToken = configuration.GetValue<string>("discord:token"); |
111 | | - if (string.IsNullOrWhiteSpace(discordToken)) |
| 93 | + NotifierConfiguration notifierConfiguration = serviceProvider.GetRequiredService<NotifierConfiguration>(); |
| 94 | + if (notifierConfiguration.Discord is null || string.IsNullOrWhiteSpace(notifierConfiguration.Discord.Token)) |
112 | 95 | { |
113 | | - logger.LogCritical("Discord:Token was not provided to the application. Please provide a token in the configuration file, through an environment variable, or as a command line argument."); |
| 96 | + serviceProvider.GetRequiredService<ILogger<Program>>().LogCritical("Discord token is not set! Exiting..."); |
114 | 97 | Environment.Exit(1); |
115 | 98 | } |
116 | 99 |
|
117 | | - DiscordClient client = new(new DiscordConfiguration() |
| 100 | + DiscordShardedClient discordClient = new(new DSharpPlusDiscordConfiguration |
118 | 101 | { |
119 | | - Token = discordToken, |
120 | | - Intents = TextCommandProcessor.RequiredIntents | SlashCommandProcessor.RequiredIntents | DiscordIntents.MessageContents, |
| 102 | + Token = notifierConfiguration.Discord.Token, |
| 103 | + Intents = TextCommandProcessor.RequiredIntents | SlashCommandProcessor.RequiredIntents | DiscordIntents.GuildVoiceStates | DiscordIntents.MessageContents, |
121 | 104 | LoggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>(), |
122 | | - LogUnknownEvents = false |
123 | 105 | }); |
124 | 106 |
|
125 | | - CommandsExtension commands = client.UseCommands(new CommandsConfiguration() |
| 107 | + return discordClient; |
| 108 | + }); |
| 109 | + |
| 110 | + // Almost start the program |
| 111 | + IServiceProvider serviceProvider = services.BuildServiceProvider(); |
| 112 | + NotifierConfiguration notifierConfiguration = serviceProvider.GetRequiredService<NotifierConfiguration>(); |
| 113 | + DiscordShardedClient discordClient = serviceProvider.GetRequiredService<DiscordShardedClient>(); |
| 114 | + DiscordEventManager eventManager = serviceProvider.GetRequiredService<DiscordEventManager>(); |
| 115 | + |
| 116 | + // Register extensions here since these involve asynchronous operations |
| 117 | + IReadOnlyDictionary<int, CommandsExtension> commandsExtensions = await discordClient.UseCommandsAsync(new CommandsConfiguration() |
| 118 | + { |
| 119 | + ServiceProvider = serviceProvider, |
| 120 | + DebugGuildId = notifierConfiguration.Discord.GuildId |
| 121 | + }); |
| 122 | + |
| 123 | + // Iterate through each Discord shard |
| 124 | + foreach (CommandsExtension commandsExtension in commandsExtensions.Values) |
| 125 | + { |
| 126 | + // Add all commands by scanning the current assembly |
| 127 | + commandsExtension.AddCommands(typeof(Program).Assembly); |
| 128 | + TextCommandProcessor textCommandProcessor = new(new() |
126 | 129 | { |
127 | | - ServiceProvider = serviceProvider, |
128 | | -#if DEBUG |
129 | | - DebugGuildId = configuration.GetValue<ulong>("discord:debug_guild_id") |
130 | | -#endif |
| 130 | + PrefixResolver = new DefaultPrefixResolver(notifierConfiguration.Discord.Prefix).ResolvePrefixAsync |
131 | 131 | }); |
132 | 132 |
|
133 | | - commands.AddCommands(typeof(Program).Assembly); |
134 | | - eventManager.RegisterEventHandlers(client); |
135 | | - eventManager.RegisterEventHandlers(commands); |
136 | | - return client; |
137 | | - }); |
| 133 | + // Add text commands (h!ping) and slash commands (/ping) |
| 134 | + await commandsExtension.AddProcessorsAsync(textCommandProcessor, new SlashCommandProcessor()); |
| 135 | + eventManager.RegisterEventHandlers(commandsExtension); |
| 136 | + } |
138 | 137 |
|
139 | | - ServiceProvider = serviceCollection.BuildServiceProvider(); |
140 | | - DiscordClient client = ServiceProvider.GetRequiredService<DiscordClient>(); |
141 | | - await client.ConnectAsync(); |
| 138 | + eventManager.RegisterEventHandlers(discordClient); |
| 139 | + await discordClient.StartAsync(); |
142 | 140 | await Task.Delay(-1); |
143 | 141 | } |
144 | 142 | } |
0 commit comments