Skip to content

Commit

Permalink
use hosted services over whatever was before
Browse files Browse the repository at this point in the history
  • Loading branch information
loukylor committed Dec 20, 2024
1 parent d529c94 commit 7e8ab11
Show file tree
Hide file tree
Showing 18 changed files with 532 additions and 391 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ WORKDIR /data
# Run the app on container startup
EXPOSE 8080/tcp
ENV DOTNET_GCHeapHardLimit=0xF424000
ENTRYPOINT [ "dotnet", "/TrickFireDiscordBot/TrickFireDiscordBot.dll", "/TrickFireDiscordBot/secrets.txt" ]
ENTRYPOINT [ "dotnet", "/TrickFireDiscordBot/TrickFireDiscordBot.dll", "/TrickFireDiscordBot/" ]
16 changes: 0 additions & 16 deletions TrickFireDiscordBot/BotState.cs

This file was deleted.

123 changes: 0 additions & 123 deletions TrickFireDiscordBot/Config.cs

This file was deleted.

98 changes: 57 additions & 41 deletions TrickFireDiscordBot/Program.cs
Original file line number Diff line number Diff line change
@@ -1,64 +1,80 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TrickFireDiscordBot.Discord;
using DSharpPlus;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using TrickFireDiscordBot.Services;

namespace TrickFireDiscordBot
{
internal class Program
{
static async Task Main(string[] args)
private static async Task Main(string[] args)
{
string[] lines;
if (args.Length == 1)
string baseDir = args.Length > 0 ? args[0] : "";
string[] secrets = File.ReadAllLines(Path.Join(baseDir, "secrets.txt"));

IHost host;
try
{
lines = File.ReadAllLines(args[0]);
host = CreateHost(baseDir, secrets);
}
else
catch (Exception e)
{
lines = File.ReadAllLines("secrets.txt");
Console.WriteLine("Startup error. The bot is likely not in a working state: " + e.ToString());
await Task.Delay(-1);
return;
}


try
{
ServiceCollection services = new();

services
// Add notion client
.AddNotionClient(options =>
{
options.AuthToken = lines[1];
})
await host.StartAsync();

// Add webhook listener
.AddSingleton(container =>
{
// This is not good security practice, but fly.io requires us to
// expose 0.0.0.0:8080, which this is equivalent to
WebhookListener webhookListener = new(container.GetRequiredService<ILogger<WebhookListener>>(), "http://*:8080/");
webhookListener.Start();
return webhookListener;
})

// Add role syncer
.AddSingleton<RoleSyncer>()
// Hang the process forever so it doesn't quit after the bot
// connects
await host.WaitForShutdownAsync();
}
finally
{
// Dispose of discord client before host, since it needs the
// service provider
try
{
host.Services.GetService<DiscordClient>()?.Dispose();
}
finally
{
host.Dispose();
}
}
}

.AddSingleton<BotState>();
private static IHost CreateHost(string baseDir, string[] secrets)
{
HostApplicationBuilder builder = Host.CreateApplicationBuilder();

// Start the bot
DiscordBot bot = new(lines[0], services);
await bot.Start();
// Add config.json
builder.Configuration.AddJsonFile(Path.Join(baseDir, "config.json"));
builder.Configuration["BOT_TOKEN"] = secrets[0];
builder.Configuration["NOTION_SECRET"] = secrets[1];

// Start the role syncer
await bot.Client.ServiceProvider.GetRequiredService<RoleSyncer>().Start();
}
catch (Exception e)
// Get all types
Assembly asm = Assembly.GetExecutingAssembly();
foreach (Type type in asm.GetTypes())
{
Console.WriteLine("Startup error. The bot is likely not in a working state: " + e.ToString());
// Filter to those that implement auto registered service
if (!typeof(IAutoRegisteredService).IsAssignableFrom(type) || type == typeof(IAutoRegisteredService))
{
continue;
}

// Register type
MethodInfo registerMethod = type.GetMethod(nameof(IAutoRegisteredService.Register), [typeof(IHostApplicationBuilder)])!;
registerMethod.Invoke(null, [builder]);
}

// Hang the process forever so it doesn't quit after the bot
// connects
await Task.Delay(-1);
return builder.Build();
}
}
}
15 changes: 15 additions & 0 deletions TrickFireDiscordBot/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace TrickFireDiscordBot
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddInjectableHostedService<T>(this IServiceCollection services) where T : class, IHostedService
=> services.AddSingleton<T>().AddHostedService(provider => provider.GetRequiredService<T>());

public static IServiceCollection ConfigureTypeSection<T>(this IServiceCollection services, IConfiguration config) where T : class
=> services.Configure<T>(config.GetSection(typeof(T).Name));
}
}
38 changes: 38 additions & 0 deletions TrickFireDiscordBot/Services/AttendanceTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Notion.Client;
using System.Collections.Specialized;

namespace TrickFireDiscordBot.Services
{
public class AttendanceTracker
{
public NotionClient NotionClient { get; }
public BotState BotState { get; }

public AttendanceTracker(NotionClient notionClient, BotState botState)
{
NotionClient = notionClient;
BotState = botState;

BotState.Members.CollectionChanged += OnMembersChange;
}

private void OnMembersChange(object? _, NotifyCollectionChangedEventArgs ev)
{
OnMembersChangeAsync(ev).GetAwaiter().GetResult();
}

private async Task OnMembersChangeAsync(NotifyCollectionChangedEventArgs ev)
{

}
}

public class AttendanceTrackerOptions
{

/// <summary>
/// The id of the Members Attendance page database in Notion.
/// </summary>
public string MemberAttendanceDatabaseId { get; set; } = "";
}
}
59 changes: 59 additions & 0 deletions TrickFireDiscordBot/Services/BotState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using DSharpPlus.Entities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Collections.ObjectModel;

namespace TrickFireDiscordBot.Services
{
/// <summary>
/// A class that represents the state of the bot.
/// </summary>
public class BotState : IAutoRegisteredService
{
/// <summary>
/// The list of members checked in.
/// </summary>
public ObservableCollection<(DiscordMember member, DateTimeOffset time)> Members { get; } = [];

/// <summary>
/// The id of the message that has the list of members in the shop.
/// </summary>
public ulong ListMessageId { get; set; } = 0;

/// <summary>
/// The id of the channel that current attendance is sent to.
/// </summary>
public ulong CheckInChannelId { get; set; } = 0;

private BotStateOptions Options { get; }

public BotState(IOptions<BotStateOptions> options)
{
Options = options.Value;
if (!File.Exists(Options.FileLocation))
{
File.WriteAllText(Options.FileLocation, "{}");
}
JsonConvert.PopulateObject(File.ReadAllText(Options.FileLocation), this);
}

public void Save()
{
File.WriteAllText(Options.FileLocation, JsonConvert.SerializeObject(this, Formatting.Indented));
}

public static void Register(IHostApplicationBuilder builder)
{
builder.Services
.AddSingleton<BotState>()
.ConfigureTypeSection<BotStateOptions>(builder.Configuration); ;
}
}

public class BotStateOptions
{
public string FileLocation { get; set; } = "";
}
}
Loading

0 comments on commit 7e8ab11

Please sign in to comment.