Skip to content
This repository was archived by the owner on Apr 23, 2025. It is now read-only.

Commit c29eef0

Browse files
committed
Cleanup startup
1 parent 948575e commit c29eef0

8 files changed

Lines changed: 134 additions & 97 deletions

File tree

.env

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Uncomment the environment variables as needed.
2-
#Notifier_Redirecter_Database__DatabaseName=database.db
3-
#Notifier_Redirecter_Database__Password=
4-
#Notifier_Redirecter_Discord__Debug_Guild_Id=
5-
#Notifier_Redirecter_Discord__Token=
6-
#Notifier_Redirecter_Discord__Prefixes__0=n!
7-
#Notifier_Redirecter_Discord__Prefixes__1=n?
8-
#Notifier_Redirecter_Logging__Level=Debug
9-
#Notifier_Redirecter_Logging__Overrides__DSharpPlus=Information
2+
#NotifierRedirecter_Database__DatabaseName=database.db
3+
#NotifierRedirecter_Database__Password=
4+
#NotifierRedirecter_Discord__Debug_Guild_Id=
5+
#NotifierRedirecter_Discord__Token=
6+
#NotifierRedirecter_Discord__Prefixes__0=n!
7+
#NotifierRedirecter_Discord__Prefixes__1=n?
8+
#NotifierRedirecter_Logger__Level=Debug
9+
#NotifierRedirecter_Logger__Overrides__DSharpPlus=Information
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace NotifierRedirecter.Configuration;
2+
3+
public sealed record DatabaseConfiguration
4+
{
5+
public string Path { get; init; } = "database.db";
6+
public string? Password { get; init; }
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace NotifierRedirecter.Configuration;
2+
3+
public sealed record DiscordConfiguration
4+
{
5+
public required string? Token { get; init; }
6+
public string Prefix { get; init; } = "h!";
7+
public ulong GuildId { get; init; }
8+
public string SupportServerInvite { get; init; } = "https://discord.gg/your-server-invite";
9+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
using Serilog;
3+
using Serilog.Events;
4+
5+
namespace NotifierRedirecter.Configuration;
6+
7+
public sealed record LoggerConfiguration
8+
{
9+
public string Format { get; init; } = "[{Timestamp:O}] [{Level:u4}] {SourceContext}: {Message:lj}{NewLine}{Exception}";
10+
public string Path { get; init; } = "logs";
11+
public string FileName { get; init; } = "yyyy'-'MM'-'dd' 'HH'.'mm'.'ss";
12+
public LogEventLevel LogLevel { get; init; } = LogEventLevel.Information;
13+
public RollingInterval RollingInterval { get; init; } = RollingInterval.Day;
14+
public IReadOnlyDictionary<string, LogEventLevel> Overrides { get; init; } = new Dictionary<string, LogEventLevel>();
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace NotifierRedirecter.Configuration;
2+
3+
public sealed class NotifierConfiguration
4+
{
5+
public required DiscordConfiguration Discord { get; init; }
6+
public DatabaseConfiguration Database { get; init; } = new();
7+
public LoggerConfiguration Logger { get; init; } = new();
8+
}

src/Database.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
using System;
22
using System.Collections.Frozen;
33
using System.Collections.Generic;
4-
using System.Data;
54
using Microsoft.Data.Sqlite;
6-
using Microsoft.Extensions.Configuration;
75
using Microsoft.Extensions.Logging;
86
using Microsoft.Extensions.Logging.Abstractions;
7+
using NotifierRedirecter.Configuration;
98

109
namespace NotifierRedirecter;
1110

@@ -16,10 +15,10 @@ public sealed class Database
1615
private readonly ILogger<Database> _logger;
1716
private readonly FrozenDictionary<PreparedCommandType, SqliteCommand> _preparedCommands;
1817

19-
public Database(IConfiguration configuration, ILogger<Database>? logger = null)
18+
public Database(NotifierConfiguration configuration, ILogger<Database>? logger = null)
2019
{
2120
this._logger = logger ?? NullLogger<Database>.Instance;
22-
string? dataSource = configuration.GetValue("database:path", "database.db");
21+
string? dataSource = configuration.Database.Path;
2322
if (string.IsNullOrWhiteSpace(dataSource))
2423
{
2524
this._logger.LogCritical("Database path is not set.");
@@ -32,8 +31,9 @@ public Database(IConfiguration configuration, ILogger<Database>? logger = null)
3231
Cache = SqliteCacheMode.Private,
3332
DataSource = dataSource,
3433
Mode = SqliteOpenMode.ReadWriteCreate,
35-
Password = configuration.GetValue<string?>("database:password")
34+
Password = configuration.Database.Password
3635
}.ToString());
36+
3737
this._preparedCommands = this.PrepareCommands();
3838
this.PrepareDatabase();
3939
}

src/Events/Handlers/GuildDownloadCompletedEventHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ public sealed class GuildDownloadCompletedEventHandler
1313
public GuildDownloadCompletedEventHandler(ILogger<GuildDownloadCompletedEventHandler>? logger = null) => this._logger = logger ?? NullLogger<GuildDownloadCompletedEventHandler>.Instance;
1414

1515
[DiscordEvent(DiscordIntents.Guilds)]
16-
public Task ExecuteAsync(DiscordClient _, GuildDownloadCompletedEventArgs eventArgs)
16+
public Task ExecuteAsync(DiscordClient client, GuildDownloadCompletedEventArgs eventArgs)
1717
{
1818
foreach (DiscordGuild guild in eventArgs.Guilds.Values)
1919
{
2020
this._logger.LogDebug("Guild ({GuildId}) is available with {MemberCount:N0} members.", guild.Id, guild.MemberCount);
2121
}
2222

2323
this._logger.LogInformation("{GuildCount:N0} guilds are ready to go!", eventArgs.Guilds.Count);
24-
return Task.CompletedTask;
24+
return client.UpdateStatusAsync(new DiscordActivity("Messing around with your notifications...", ActivityType.Custom));
2525
}
2626
}

src/Program.cs

Lines changed: 80 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -6,139 +6,137 @@
66
using DSharpPlus.Commands;
77
using DSharpPlus.Commands.Processors.SlashCommands;
88
using DSharpPlus.Commands.Processors.TextCommands;
9+
using DSharpPlus.Commands.Processors.TextCommands.Parsing;
910
using Microsoft.Extensions.Configuration;
1011
using Microsoft.Extensions.DependencyInjection;
1112
using Microsoft.Extensions.Logging;
13+
using NotifierRedirecter.Configuration;
1214
using NotifierRedirecter.Events;
1315
using Serilog;
1416
using Serilog.Events;
1517
using Serilog.Sinks.SystemConsole.Themes;
18+
using DSharpPlusDiscordConfiguration = DSharpPlus.DiscordConfiguration;
19+
using SerilogLoggerConfiguration = Serilog.LoggerConfiguration;
1620

1721
namespace NotifierRedirecter;
1822

1923
public sealed class Program
2024
{
21-
internal static IServiceProvider ServiceProvider = null!;
22-
2325
public static async Task Main(string[] args)
2426
{
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);
2833
#if DEBUG
29-
configurationBuilder.AddJsonFile("config.debug.json", true, true);
34+
configurationBuilder.AddJsonFile("config.debug.json", true, true);
3035
#endif
31-
configurationBuilder.AddEnvironmentVariables("NOTIFIER_REDIRECTER__");
32-
configurationBuilder.AddCommandLine(args);
36+
configurationBuilder.AddEnvironmentVariables("NotifierRedirecter__");
37+
configurationBuilder.AddCommandLine(args);
3338

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)
4142
{
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);
4345
}
4446

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+
});
5049

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(
5557
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(
7863
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
8167
);
8268

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)
8572
{
86-
if (!string.IsNullOrWhiteSpace(logOverride.Value) && Enum.TryParse(logOverride.Value, out LogEventLevel logEventLevel))
73+
foreach ((string key, LogEventLevel value) in notifierConfiguration.Logger.Overrides)
8774
{
88-
loggerConfiguration.MinimumLevel.Override(logOverride.Key, logEventLevel);
75+
serilogLoggerConfiguration.MinimumLevel.Override(key, value);
8976
}
9077
}
9178

92-
logger.AddSerilog(loggerConfiguration.CreateLogger());
79+
logging.AddSerilog(serilogLoggerConfiguration.CreateLogger());
9380
});
9481

95-
serviceCollection.AddSingleton<Database>();
96-
serviceCollection.AddSingleton<UserActivityTracker>();
97-
serviceCollection.AddSingleton((serviceProvider) =>
82+
services.AddSingleton<Database>();
83+
services.AddSingleton<UserActivityTracker>();
84+
services.AddSingleton((serviceProvider) =>
9885
{
9986
DiscordEventManager eventManager = new(serviceProvider);
10087
eventManager.GatherEventHandlers(typeof(Program).Assembly);
10188
return eventManager;
10289
});
10390

104-
// Register the Discord sharded client to the service collection
105-
serviceCollection.AddSingleton((serviceProvider) =>
91+
services.AddSingleton(serviceProvider =>
10692
{
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))
11295
{
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...");
11497
Environment.Exit(1);
11598
}
11699

117-
DiscordClient client = new(new DiscordConfiguration()
100+
DiscordShardedClient discordClient = new(new DSharpPlusDiscordConfiguration
118101
{
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,
121104
LoggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>(),
122-
LogUnknownEvents = false
123105
});
124106

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()
126129
{
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
131131
});
132132

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+
}
138137

139-
ServiceProvider = serviceCollection.BuildServiceProvider();
140-
DiscordClient client = ServiceProvider.GetRequiredService<DiscordClient>();
141-
await client.ConnectAsync();
138+
eventManager.RegisterEventHandlers(discordClient);
139+
await discordClient.StartAsync();
142140
await Task.Delay(-1);
143141
}
144142
}

0 commit comments

Comments
 (0)