diff --git a/TrickFireDiscordBot/Discord/Bot.cs b/TrickFireDiscordBot/Discord/DiscordBot.cs similarity index 99% rename from TrickFireDiscordBot/Discord/Bot.cs rename to TrickFireDiscordBot/Discord/DiscordBot.cs index 4a30ea1..c08f90a 100644 --- a/TrickFireDiscordBot/Discord/Bot.cs +++ b/TrickFireDiscordBot/Discord/DiscordBot.cs @@ -14,7 +14,7 @@ namespace TrickFireDiscordBot.Discord /// A class representing the Discord bot. /// /// The token of the bot - public class Bot + public class DiscordBot { private const string SadCatASCII = "       />----フ\r\n" + @@ -34,7 +34,7 @@ public class Bot private bool _needToUpdateEmbed = true; - public Bot(string token) + public DiscordBot(string token) { DiscordClientBuilder builder = DiscordClientBuilder .CreateDefault(token, DiscordIntents.None) diff --git a/TrickFireDiscordBot/Notion/Automation.cs b/TrickFireDiscordBot/Notion/Automation.cs new file mode 100644 index 0000000..ba2570b --- /dev/null +++ b/TrickFireDiscordBot/Notion/Automation.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; +using Notion.Client; + +namespace TrickFireDiscordBot.Notion +{ + public class Automation + { + [JsonProperty("source")] + public AutomationSource? Source { get; set; } = null; + + [JsonProperty("data")] + public IObject? Data { get; set; } = null; + } + + public class AutomationSource + { + [JsonProperty("type")] + public string Type { get; set; } = ""; + + [JsonProperty("automation_id")] + public string AutomationId { get; set; } = ""; + + + [JsonProperty("action_id")] + public string ActionId { get; set; } = ""; + + [JsonProperty("event_id")] + public string EventId { get; set; } = ""; + + [JsonProperty("attempt")] + public int Attempt { get; set; } = 0; + } +} diff --git a/TrickFireDiscordBot/Program.cs b/TrickFireDiscordBot/Program.cs index 8ded268..6e1be1b 100644 --- a/TrickFireDiscordBot/Program.cs +++ b/TrickFireDiscordBot/Program.cs @@ -1,4 +1,5 @@ -using TrickFireDiscordBot.Discord; +using Notion.Client; +using TrickFireDiscordBot.Discord; namespace TrickFireDiscordBot { @@ -6,21 +7,37 @@ internal class Program { static async Task Main(string[] args) { - // The token is on the first line of the secrets.txt file - string token; + string[] lines; if (args.Length == 1) { - token = File.ReadAllLines(args[0])[0]; + lines = File.ReadAllLines(args[0]); } else { - token = File.ReadAllLines("secrets.txt")[0]; + lines = File.ReadAllLines("secrets.txt"); } // Start the bot - Bot bot = new(token); + DiscordBot bot = new(lines[0]); await bot.Start(); + // Start notion client + //NotionClient notionClient = NotionClientFactory.Create(new ClientOptions() + //{ + // AuthToken = lines[1] + //}); + + // Start the webhook listener + + // 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(bot.Client.Logger, "http://*:8080/"); + webhookListener.Start(); + + // Start role syncer + RoleSyncer syncer = new(null, bot, webhookListener); + syncer.Start(); + // Hang the process forever so it doesn't quit after the bot // connects await Task.Delay(-1); diff --git a/TrickFireDiscordBot/RoleSyncer.cs b/TrickFireDiscordBot/RoleSyncer.cs new file mode 100644 index 0000000..e18d762 --- /dev/null +++ b/TrickFireDiscordBot/RoleSyncer.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Notion.Client; +using System.Net; +using TrickFireDiscordBot.Discord; +using TrickFireDiscordBot.Notion; + +namespace TrickFireDiscordBot +{ + public class RoleSyncer(NotionClient notionClient, DiscordBot discordBot, WebhookListener listener) + { + public const string WebhookEndpoint = "/members"; + + public NotionClient NotionClient { get; } = notionClient; + public DiscordBot DiscordBot { get; } = discordBot; + public WebhookListener WebhookListener { get; } = listener; + + public void Start() + { + WebhookListener.OnWebhookReceived += OnWebhook; + } + + public void Stop() + { + WebhookListener.OnWebhookReceived -= OnWebhook; + } + + private void OnWebhook(HttpListenerRequest request) + { + if (request.RawUrl == null || !request.RawUrl.StartsWith(WebhookEndpoint)) + { + return; + } + + using StreamReader reader = new(request.InputStream); + Automation? automation = JsonConvert.DeserializeObject(reader.ReadToEnd()); + if (automation == null || automation.Data is not Page page) + { + return; + } + + Console.WriteLine(JsonConvert.SerializeObject(page, Formatting.Indented)); + } + } +} diff --git a/TrickFireDiscordBot/TrickFireDiscordBot.csproj b/TrickFireDiscordBot/TrickFireDiscordBot.csproj index acb6975..6c6d530 100644 --- a/TrickFireDiscordBot/TrickFireDiscordBot.csproj +++ b/TrickFireDiscordBot/TrickFireDiscordBot.csproj @@ -10,6 +10,7 @@ + diff --git a/TrickFireDiscordBot/WebhookListener.cs b/TrickFireDiscordBot/WebhookListener.cs new file mode 100644 index 0000000..836f524 --- /dev/null +++ b/TrickFireDiscordBot/WebhookListener.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.Logging; +using System.Net; +using System.Text; + +namespace TrickFireDiscordBot +{ + public class WebhookListener : IDisposable + { + public event Action? OnWebhookReceived; + public ILogger Logger { get; } + + private readonly HttpListener _listener = new(); + private bool _isRunning = false; + + public WebhookListener(ILogger logger, params string[] prefixes) + { + Logger = logger; + foreach (string prefix in prefixes) + { + _listener.Prefixes.Add(prefix); + } + } + + public void Start() + { + _ = Task.Run(async () => + { + try + { + await LongThread(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Webhook Listener error"); + } + }); + } + + public void Stop() + { + _listener.Stop(); + } + + void IDisposable.Dispose() + { + GC.SuppressFinalize(this); + _listener.Close(); + } + + public void Close() + { + ((IDisposable)this).Dispose(); + } + + private async Task LongThread() + { + _isRunning = true; + _listener.Start(); + + while (_isRunning) + { + try + { + // Wait for request + HttpListenerContext ctx = await _listener.GetContextAsync(); + + // Read request body + OnWebhookReceived?.Invoke(ctx.Request); + + // Return ok response on request + ctx.Response.StatusCode = (int)HttpStatusCode.OK; + ctx.Response.OutputStream.Write(Encoding.UTF8.GetBytes("Ok")); + ctx.Response.OutputStream.Close(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Webhook Listener loop error"); + } + } + } + } +}