From f7b18303fc9968b42f38e64bc38f0db13716e706 Mon Sep 17 00:00:00 2001 From: Sam <72096531+conaticus@users.noreply.github.com> Date: Mon, 20 May 2024 20:19:56 +0100 Subject: [PATCH 01/14] Update CONTRIBUTING.md (#55) --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc5fbc2..4d7d9b0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,8 @@ Hello and thank you for checking out the contributing guidelines for Boolean! Pl ## Issues All tasks for the project are listed in the repo's [issues](https://github.com/conaticusgrp/boolean-revamp/issues). Feel free to work on any issue that does not have an assignee. There is no guarantee this issue has not already been worked on - it's good to check the PRs page first. +**Ensure that you remote the develop branch and not the main branch.** + ## Command Conventions - Use the necessary attribute or create one in Precondition.cs for commands/command groups that require an item from the database - Prefer using discord embeds instead of standard text replies From 383f3b8c104faa73b93f67c0ecc3c204c8cd60fb Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 21 May 2024 10:47:42 +0100 Subject: [PATCH 02/14] prevent building bot at design time & fix event handler injection error --- Boolean/EventHandlers.cs | 5 ++++- Boolean/Modules/UserInfo.cs | 1 - Boolean/Program.cs | 34 +++++++++++++++++++++------------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Boolean/EventHandlers.cs b/Boolean/EventHandlers.cs index e2f482d..5137a3e 100644 --- a/Boolean/EventHandlers.cs +++ b/Boolean/EventHandlers.cs @@ -5,11 +5,11 @@ using Discord.Interactions; using Discord.WebSocket; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Boolean; public class EventHandlers( - DataContext db, IServiceProvider serviceProvider, Config config, DiscordSocketClient client, @@ -84,6 +84,9 @@ public async Task UserJoined(IGuildUser user) if (user.IsBot) return; + // We can't pass in data context to the class because EventHandlers is a singleton and data context is scoped + var db = serviceProvider.GetRequiredService(); + var guild = await db.Guilds.FirstOrDefaultAsync(g => g.Snowflake == user.Guild.Id); if (guild?.JoinRoleSnowflake != null) await user.AddRoleAsync(guild.JoinRoleSnowflake ?? 0); diff --git a/Boolean/Modules/UserInfo.cs b/Boolean/Modules/UserInfo.cs index 4fab172..fbe48e2 100644 --- a/Boolean/Modules/UserInfo.cs +++ b/Boolean/Modules/UserInfo.cs @@ -1,7 +1,6 @@ using Boolean.Util; using Discord; using Discord.Interactions; -using Discord.WebSocket; using Microsoft.EntityFrameworkCore; namespace Boolean; diff --git a/Boolean/Program.cs b/Boolean/Program.cs index 2711a30..5573429 100644 --- a/Boolean/Program.cs +++ b/Boolean/Program.cs @@ -16,8 +16,20 @@ class Program public static async Task Main() { - // Dependency injection & application setup - _client = new DiscordSocketClient(new DiscordSocketConfig() + HostApplicationBuilder builder = Host.CreateApplicationBuilder(); + Config config = builder.Configuration.Get() + ?? throw new Exception("Failed to load valid config from appsettings.json, please refer to the README.md for instructions."); + + builder.Services + .AddDbContext(options => options.UseNpgsql(config.GetConnectionString())); + + // Only start the bot outside of design time (avoids app running during dotnet ef commands) + if (EF.IsDesignTime) { + _serviceProvider = builder.Services.BuildServiceProvider(); + goto buildApp; + } + + _client = new DiscordSocketClient(new DiscordSocketConfig { GatewayIntents = GatewayIntents.All, UseInteractionSnowflakeDate = false // Prevents a funny from happening when your OS clock is out of sync @@ -25,27 +37,23 @@ public static async Task Main() var interactionService = new InteractionService(_client.Rest); - HostApplicationBuilder builder = Host.CreateApplicationBuilder(); - Config config = builder.Configuration.Get() - ?? throw new Exception("Failed to load valid config from appsettings.json, please refer to the README.md for instructions."); - - _serviceProvider = builder.Services - .AddDbContext(options => options.UseNpgsql(config.GetConnectionString())) + builder.Services .AddSingleton(interactionService) .AddSingleton(_client) .AddSingleton(config) - .AddSingleton() - .BuildServiceProvider(); + .AddSingleton(); + + _serviceProvider = builder.Services.BuildServiceProvider(); - // Start the bot await _client.LoginAsync(TokenType.Bot, config.DiscordToken); await _client.StartAsync(); await interactionService.AddModulesAsync(Assembly.GetEntryAssembly(), _serviceProvider); AttachEventHandlers(); - IHost app = builder.Build(); - await app.RunAsync(); + buildApp: + IHost app = builder.Build(); + await app.RunAsync(); } private static void AttachEventHandlers() From 8e8c736ceef0c204c3733ba1c6eba399d3ca6692 Mon Sep 17 00:00:00 2001 From: Sam <72096531+conaticus@users.noreply.github.com> Date: Tue, 28 May 2024 14:42:39 +0100 Subject: [PATCH 03/14] Starboard #14 (#57) --- Boolean/Config.cs | 7 ++ Boolean/DataContext.cs | 23 ++++ Boolean/EventHandlers.cs | 102 +++++++++++++++++- Boolean/Modules/ServerConfig/JoinRole.cs | 6 +- Boolean/Modules/ServerConfig/ServerConfig.cs | 6 +- .../Modules/ServerConfig/SpecialChannels.cs | 6 +- Boolean/Modules/ServerConfig/Starboard.cs | 94 ++++++++++++++++ Boolean/Modules/Warnings/Commands.cs | 4 +- Boolean/Program.cs | 5 +- Boolean/Utils/Cache.cs | 16 +++ Boolean/Utils/Database.cs | 22 +++- Boolean/Utils/{ => UI}/EmbedColors.cs | 0 Boolean/Utils/{ => UI}/Modals.cs | 0 Boolean/Utils/{ => UI}/Paginator.cs | 15 --- 14 files changed, 277 insertions(+), 29 deletions(-) create mode 100644 Boolean/Modules/ServerConfig/Starboard.cs create mode 100644 Boolean/Utils/Cache.cs rename Boolean/Utils/{ => UI}/EmbedColors.cs (100%) rename Boolean/Utils/{ => UI}/Modals.cs (100%) rename Boolean/Utils/{ => UI}/Paginator.cs (87%) diff --git a/Boolean/Config.cs b/Boolean/Config.cs index d54daca..6a17398 100644 --- a/Boolean/Config.cs +++ b/Boolean/Config.cs @@ -26,6 +26,13 @@ public static string WelcomeMsg(string userDisplay, string serverName) => $"Hello {userDisplay}, thank you for joining the **{serverName}** server!"; } + // Each represents the unicode for that emoji + // This should be compared with IEmote.Name + public static class Emojis + { + public const string Star = "\u2b50"; + } + #if DEBUG public required ulong TestGuildId { get; set; } #endif diff --git a/Boolean/DataContext.cs b/Boolean/DataContext.cs index aae1d96..f7a58c9 100644 --- a/Boolean/DataContext.cs +++ b/Boolean/DataContext.cs @@ -15,6 +15,8 @@ public DataContext(DbContextOptions options) : base(options) {} public DbSet Members { get; set; } public DbSet Warnings { get; set; } public DbSet SpecialChannels { get; set; } + + public DbSet StarReactions { get; set; } } public class Guild @@ -25,6 +27,10 @@ public class Guild public ICollection SpecialChannels { get; } = new List(); + public ICollection StarReactions { get; } = new List(); + + public uint? StarboardThreshold { get; set; } + public ulong? JoinRoleSnowflake { get; set; } } @@ -74,3 +80,20 @@ public class Warning public bool HasAppealed { get; set; } } + +// In future we should set expiry dates for these to be removed from the database +public class StarReaction +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } + public Guild Guild { get; set; } + + public ulong GuildId { get; set; } + + public bool IsOnStarboard { get; set; } + + public uint ReactionCount { get; set; } = 1; + + public ulong MessageSnowflake { get; set; } + + +} diff --git a/Boolean/EventHandlers.cs b/Boolean/EventHandlers.cs index 5137a3e..e8ea4cf 100644 --- a/Boolean/EventHandlers.cs +++ b/Boolean/EventHandlers.cs @@ -22,7 +22,7 @@ public Task LogMessage(LogMessage message) + $"failed to execute in {exception.Context.Channel}"); Console.WriteLine(exception); return Task.CompletedTask; - } + } Console.WriteLine($"[General/{message.Severity}] {message}"); return Task.CompletedTask; @@ -102,4 +102,104 @@ public async Task UserJoined(IGuildUser user) Color = EmbedColors.Normal, }.Build())!; } + + public async Task ReactionAdded(Cacheable cachedMessage, + Cacheable originChannel, SocketReaction reaction) + { + if (reaction.Emote.Name != Config.Emojis.Star) + return; + + // Message IDs are unique across discord yay + var db = serviceProvider.GetRequiredService(); + var starReaction = await db.StarReactions + .Include(sr => sr.Guild.SpecialChannels) + .FirstOrDefaultAsync(sr => sr.MessageSnowflake == cachedMessage.Id); + + if (starReaction != null) { + var message = await cachedMessage.GetOrDownloadAsync(); + await HandleExistingStarReaction(db, message, starReaction); + return; + } + + var channel = await originChannel.GetOrDownloadAsync(); + if (channel is not SocketTextChannel textChannel) + return; + + var guild = await GuildTools.FindOrCreate(db, textChannel.Guild.Id); + if (guild.StarboardThreshold == null) + return; + + await db.StarReactions.AddAsync(new StarReaction + { + GuildId = textChannel.Guild.Id, + MessageSnowflake = cachedMessage.Id + }); + + await db.SaveChangesAsync(); + } + + private async Task HandleExistingStarReaction(DataContext db, IUserMessage message, StarReaction starReaction) + { + if (starReaction.IsOnStarboard || starReaction.Guild.StarboardThreshold == null) + return; + + var dbStarboardChannel = starReaction.Guild.SpecialChannels + .FirstOrDefault(sc => sc.Type == SpecialChannelType.Starboard); + + if (dbStarboardChannel == null) + return; + + starReaction.ReactionCount++; + + if (starReaction.ReactionCount != starReaction.Guild.StarboardThreshold) { + await db.SaveChangesAsync(); + return; + } + + var starboardChannel = await client.GetChannelAsync(dbStarboardChannel.Snowflake) as ITextChannel; + var embed = new EmbedBuilder + { + Title = "Message Link", + ThumbnailUrl = message.Author.GetAvatarUrl(), + Color = EmbedColors.Normal, + Fields = [ + new EmbedFieldBuilder + { + Name = "Author", + Value = message.Author.Username, + IsInline = true, + }, + + new EmbedFieldBuilder + { + Name = "Message", + Value = message.Content, + IsInline = true, + }, + ], + Url = message.GetJumpUrl(), + }; + + await starboardChannel.SendMessageAsync(embed: embed.Build()); + + starReaction.IsOnStarboard = true; + await db.SaveChangesAsync(); + } + + public async Task ReactionRemoved(Cacheable cachedMessage, + Cacheable originChannel, SocketReaction reaction) + { + if (reaction.Emote.Name != Config.Emojis.Star) + return; + + var db = serviceProvider.GetRequiredService(); + var starReaction = await db.StarReactions + .FirstOrDefaultAsync(sr => sr.MessageSnowflake == cachedMessage.Id); + + if (starReaction == null || starReaction.IsOnStarboard == true) + return; + + starReaction.ReactionCount--; + await db.SaveChangesAsync(); + } } \ No newline at end of file diff --git a/Boolean/Modules/ServerConfig/JoinRole.cs b/Boolean/Modules/ServerConfig/JoinRole.cs index c3293a6..5077bff 100644 --- a/Boolean/Modules/ServerConfig/JoinRole.cs +++ b/Boolean/Modules/ServerConfig/JoinRole.cs @@ -7,7 +7,7 @@ namespace Boolean; -public partial class ServerSet +public partial class GuildSet { [RequireGuild] [SlashCommand("joinrole", "Sets role members are assigned when joining the server")] @@ -44,7 +44,7 @@ public async Task JoinRoleSet(SocketRole joinRole) } } -public partial class ServerGet +public partial class GuildGet { [RequireGuild] [SlashCommand("joinrole", "Gets the role members are assigned when joining the server")] @@ -68,7 +68,7 @@ public async Task JoinRoleGet() } } -public partial class ServerUnset +public partial class GuildUnset { [RequireGuild] [SlashCommand("joinrole", "Unsets the role members are assigned when joining the server")] diff --git a/Boolean/Modules/ServerConfig/ServerConfig.cs b/Boolean/Modules/ServerConfig/ServerConfig.cs index b30c66f..24d150f 100644 --- a/Boolean/Modules/ServerConfig/ServerConfig.cs +++ b/Boolean/Modules/ServerConfig/ServerConfig.cs @@ -6,12 +6,12 @@ namespace Boolean; // Used to set configuration options [DefaultMemberPermissions(GuildPermission.Administrator)] [Group("set", "Set configuration options")] -public partial class ServerSet(DataContext db) : InteractionModuleBase { } +public partial class GuildSet(DataContext db) : InteractionModuleBase { } [DefaultMemberPermissions(GuildPermission.Administrator)] [Group("get", "Get configuration options")] -public partial class ServerGet(DataContext db) : InteractionModuleBase { } +public partial class GuildGet(DataContext db) : InteractionModuleBase { } [DefaultMemberPermissions(GuildPermission.Administrator)] [Group("unset", "Unset configuration options")] -public partial class ServerUnset(DataContext db) : InteractionModuleBase { } +public partial class GuildUnset(DataContext db) : InteractionModuleBase { } diff --git a/Boolean/Modules/ServerConfig/SpecialChannels.cs b/Boolean/Modules/ServerConfig/SpecialChannels.cs index ed1e291..0660746 100644 --- a/Boolean/Modules/ServerConfig/SpecialChannels.cs +++ b/Boolean/Modules/ServerConfig/SpecialChannels.cs @@ -7,7 +7,7 @@ namespace Boolean; -public partial class ServerSet +public partial class GuildSet { // Channel configuration, welcome messages, starboard, etc [RequireGuild] @@ -46,7 +46,7 @@ await db.SpecialChannels.AddAsync(new SpecialChannel await RespondAsync(embed: embed.Build(), ephemeral: true); } } -public partial class ServerGet +public partial class GuildGet { [SlashCommand("channel", "Get the current configuration for channels")] public async Task ChannelGet(SpecialChannelType specialChannelType) @@ -67,7 +67,7 @@ public async Task ChannelGet(SpecialChannelType specialChannelType) } } -public partial class ServerUnset +public partial class GuildUnset { [SlashCommand("channel", "Unmarks a channel for a certain purpose")] public async Task ChannelUnset(SpecialChannelType specialChannelType) diff --git a/Boolean/Modules/ServerConfig/Starboard.cs b/Boolean/Modules/ServerConfig/Starboard.cs new file mode 100644 index 0000000..9211c79 --- /dev/null +++ b/Boolean/Modules/ServerConfig/Starboard.cs @@ -0,0 +1,94 @@ +using Boolean.Util; +using Boolean.Util.Preconditions; +using Discord; +using Discord.Interactions; +using Microsoft.EntityFrameworkCore; + +namespace Boolean; + +[RequireGuild] +public partial class GuildSet +{ + [SlashCommand("starboard-threshold", + "Set the number of star reactions a message needs to be eligible for the starboard")] + public async Task ThresholdSet(int threshold) + { + EmbedBuilder embed; + + if (threshold is < 2 or > 500) { + embed = new EmbedBuilder + { + Description = "Starboard threshold must be 2-500", + Color = EmbedColors.Fail, + }; + + await RespondAsync(embed: embed.Build(), ephemeral: true); + return; + } + + var guild = await db.Guilds.FirstAsync(g => g.Snowflake == Context.Guild.Id); + guild.StarboardThreshold = (uint)threshold; + await db.SaveChangesAsync(); + + embed = new EmbedBuilder + { + Description = $"Successfully set starboard threshold to {threshold} stars", + Color = EmbedColors.Success, + }; + + await RespondAsync(embed: embed.Build(), ephemeral: true); + } +} + +public partial class GuildGet +{ + [SlashCommand("starboard-threshold", + "Get the number of star reactions a message needs to be eligible for the starboard")] + public async Task ThresholdSet() + { + EmbedBuilder embed; + + var guild = await db.Guilds.FirstOrDefaultAsync(g => g.Snowflake == Context.Guild.Id); + if (guild is not { StarboardThreshold: not null }) { + embed = new EmbedBuilder + { + Description = "No starboard threshold has been set", + Color = EmbedColors.Fail, + }; + + await RespondAsync(embed: embed.Build(), ephemeral: true); + return; + } + + embed = new EmbedBuilder + { + Description = $"The current starboard threshold is {guild.StarboardThreshold}", + Color = EmbedColors.Normal, + }; + + await RespondAsync(embed: embed.Build(), ephemeral: true); + } +} + +public partial class GuildUnset +{ + [SlashCommand("starboard-threshold", + "Get the number of star reactions a message needs to be eligible for the starboard")] + public async Task ThresholdUnset() + { + var guild = await db.Guilds.FirstOrDefaultAsync(g => g.Snowflake == Context.Guild.Id); + + if (guild is { StarboardThreshold: not null }) { + guild.StarboardThreshold = null; + await db.SaveChangesAsync(); + } + + var embed = new EmbedBuilder + { + Description = $"Starboard threshold has been unset", + Color = EmbedColors.Success, + }; + + await RespondAsync(embed: embed.Build(), ephemeral: true); + } +} \ No newline at end of file diff --git a/Boolean/Modules/Warnings/Commands.cs b/Boolean/Modules/Warnings/Commands.cs index ea1787d..9471dd1 100644 --- a/Boolean/Modules/Warnings/Commands.cs +++ b/Boolean/Modules/Warnings/Commands.cs @@ -19,8 +19,8 @@ public async Task Warn( [Summary(description: "Whether a public chat notification will be sent.")] bool silent = false) { var moderator = Context.Interaction.User; - var dbOffender = await MemberTools.FindOrCreateMember(db, offender.Id, Context.Guild.Id); - var dbModerator = await MemberTools.FindOrCreateMember(db, moderator.Id, Context.Guild.Id); + var dbOffender = await MemberTools.FindOrCreate(db, offender.Id, Context.Guild.Id); + var dbModerator = await MemberTools.FindOrCreate(db, moderator.Id, Context.Guild.Id); var warning = await db.Warnings.AddAsync(new Warning { diff --git a/Boolean/Program.cs b/Boolean/Program.cs index 5573429..6baca26 100644 --- a/Boolean/Program.cs +++ b/Boolean/Program.cs @@ -32,7 +32,8 @@ public static async Task Main() _client = new DiscordSocketClient(new DiscordSocketConfig { GatewayIntents = GatewayIntents.All, - UseInteractionSnowflakeDate = false // Prevents a funny from happening when your OS clock is out of sync + UseInteractionSnowflakeDate = false, // Prevents a funny from happening when your OS clock is out of sync + MessageCacheSize = 100, }); var interactionService = new InteractionService(_client.Rest); @@ -65,6 +66,8 @@ private static void AttachEventHandlers() _client.InteractionCreated += eventHandlers.InteractionCreated; _client.JoinedGuild += eventHandlers.GuildCreate; _client.ButtonExecuted += eventHandlers.ButtonExecuted; + _client.ReactionAdded += eventHandlers.ReactionAdded; + _client.ReactionRemoved += eventHandlers.ReactionRemoved; _client.UserJoined += eventHandlers.UserJoined; } } \ No newline at end of file diff --git a/Boolean/Utils/Cache.cs b/Boolean/Utils/Cache.cs new file mode 100644 index 0000000..d5553f2 --- /dev/null +++ b/Boolean/Utils/Cache.cs @@ -0,0 +1,16 @@ +namespace Boolean.Utils; + +// In the future, we may wish to store paginators in the db with an expiry - to save memory & allow use after bot restarts +public static class PaginatorCache +{ + public static readonly Dictionary Paginators = new(); + + public static Task Add(string id, IPaginator paginator) + { + Paginators.Add(id, paginator); + + // Remove paginator from cache after 2 minutes + return Task.Delay(TimeSpan.FromMinutes(2)) + .ContinueWith(t => Paginators.Remove(id)); + } +} diff --git a/Boolean/Utils/Database.cs b/Boolean/Utils/Database.cs index 9893b4a..70445a3 100644 --- a/Boolean/Utils/Database.cs +++ b/Boolean/Utils/Database.cs @@ -5,7 +5,7 @@ namespace Boolean.Util; public static class MemberTools { // Finds a member from the database, if one does not exist a member object is created, this DOES NOT insert the member into the database - public static async Task FindOrCreateMember(DataContext db, ulong memberId, ulong serverId) + public static async Task FindOrCreate(DataContext db, ulong memberId, ulong serverId) { var member = await db.Members.FirstOrDefaultAsync(m => m.Snowflake == memberId && m.Guild.Snowflake == serverId); if (member != null) @@ -31,3 +31,23 @@ public static class SpecialChannelTools sc.Guild.Snowflake == guildId && sc.Type == specialChannelType); } } + +public static class GuildTools +{ + public static async Task FindOrCreate(DataContext db, ulong id) + { + var guild = await db.Guilds.FirstOrDefaultAsync(g => g.Snowflake == id); + if (guild != null) + return guild; + + guild = new Guild + { + Snowflake = id, + }; + + await db.Guilds.AddAsync(guild); + + await db.SaveChangesAsync(); + return guild; + } +} \ No newline at end of file diff --git a/Boolean/Utils/EmbedColors.cs b/Boolean/Utils/UI/EmbedColors.cs similarity index 100% rename from Boolean/Utils/EmbedColors.cs rename to Boolean/Utils/UI/EmbedColors.cs diff --git a/Boolean/Utils/Modals.cs b/Boolean/Utils/UI/Modals.cs similarity index 100% rename from Boolean/Utils/Modals.cs rename to Boolean/Utils/UI/Modals.cs diff --git a/Boolean/Utils/Paginator.cs b/Boolean/Utils/UI/Paginator.cs similarity index 87% rename from Boolean/Utils/Paginator.cs rename to Boolean/Utils/UI/Paginator.cs index 12aa2b5..8676cb4 100644 --- a/Boolean/Utils/Paginator.cs +++ b/Boolean/Utils/UI/Paginator.cs @@ -4,21 +4,6 @@ namespace Boolean.Utils; -// In the future, we may wish to store paginators in the db with an expiry - to save memory & allow use after bot restarts -public static class PaginatorCache -{ - public static readonly Dictionary Paginators = new(); - - public static Task Add(string id, IPaginator paginator) - { - Paginators.Add(id, paginator); - - // Remove paginator from cache after 2 minutes - return Task.Delay(TimeSpan.FromMinutes(2)) - .ContinueWith(t => Paginators.Remove(id)); - } -} - public static class PaginatorComponentIds { public const string NextId = "pagination_next_btn"; From a097cb44832beef93a20323d65d97a618856c04c Mon Sep 17 00:00:00 2001 From: conaticus Date: Wed, 29 May 2024 12:25:13 +0100 Subject: [PATCH 04/14] feat: Contribute command Closes #41 --- Boolean/Config.cs | 4 ++++ Boolean/DataContext.cs | 4 +--- Boolean/Modules/BotInfo.cs | 13 +++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Boolean/Config.cs b/Boolean/Config.cs index 6a17398..9f9fe61 100644 --- a/Boolean/Config.cs +++ b/Boolean/Config.cs @@ -22,6 +22,10 @@ public static class Strings "__**/set channel **__\n" + "\u2800Marks a channel for a specific purpose, such as for welcoming messages, starboard, server logs, etc.\n"; + public const string ContributeMsg = "Hello and thank you for your interest in contributing to Boolean!\n\n" + + "To get started, please visit our open source [repository](https://github.com/conaticusgrp/boolean) and check out our [contributing guide](https://github.com/conaticusgrp/boolean/blob/main/CONTRIBUTING.md). " + + "Once completed, you may review our [issues](https://github.com/conaticusgrp/boolean/issues) and choose whichever task you are interested in."; + public static string WelcomeMsg(string userDisplay, string serverName) => $"Hello {userDisplay}, thank you for joining the **{serverName}** server!"; } diff --git a/Boolean/DataContext.cs b/Boolean/DataContext.cs index f7a58c9..224a478 100644 --- a/Boolean/DataContext.cs +++ b/Boolean/DataContext.cs @@ -94,6 +94,4 @@ public class StarReaction public uint ReactionCount { get; set; } = 1; public ulong MessageSnowflake { get; set; } - - -} +} \ No newline at end of file diff --git a/Boolean/Modules/BotInfo.cs b/Boolean/Modules/BotInfo.cs index 0588e74..82fc997 100644 --- a/Boolean/Modules/BotInfo.cs +++ b/Boolean/Modules/BotInfo.cs @@ -44,4 +44,17 @@ public async Task Help() }; await RespondAsync(embed: embed.Build(), ephemeral: true); } + + [SlashCommand("contribute", "Information on how to contribute to the discord bot.")] + public async Task Contribute() + { + var embed = new EmbedBuilder + { + Title = "Contributing to Boolean", + Description = Config.Strings.ContributeMsg, + Color = EmbedColors.Normal + }; + + await RespondAsync(embed: embed.Build(), ephemeral: true); + } } \ No newline at end of file From ea48dcc4a2abc7677f30decb860b7eb89a82f1f8 Mon Sep 17 00:00:00 2001 From: conaticus Date: Wed, 29 May 2024 12:34:08 +0100 Subject: [PATCH 05/14] fix(starboard): check author is bot in reaction events --- Boolean/EventHandlers.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Boolean/EventHandlers.cs b/Boolean/EventHandlers.cs index e8ea4cf..da94224 100644 --- a/Boolean/EventHandlers.cs +++ b/Boolean/EventHandlers.cs @@ -106,7 +106,9 @@ public async Task UserJoined(IGuildUser user) public async Task ReactionAdded(Cacheable cachedMessage, Cacheable originChannel, SocketReaction reaction) { - if (reaction.Emote.Name != Config.Emojis.Star) + var message = await cachedMessage.GetOrDownloadAsync(); + + if (reaction.Emote.Name != Config.Emojis.Star || message.Author.IsBot) return; // Message IDs are unique across discord yay @@ -116,7 +118,6 @@ public async Task ReactionAdded(Cacheable cachedMessage, .FirstOrDefaultAsync(sr => sr.MessageSnowflake == cachedMessage.Id); if (starReaction != null) { - var message = await cachedMessage.GetOrDownloadAsync(); await HandleExistingStarReaction(db, message, starReaction); return; } @@ -189,7 +190,9 @@ private async Task HandleExistingStarReaction(DataContext db, IUserMessage messa public async Task ReactionRemoved(Cacheable cachedMessage, Cacheable originChannel, SocketReaction reaction) { - if (reaction.Emote.Name != Config.Emojis.Star) + var message = await cachedMessage.GetOrDownloadAsync(); + + if (reaction.Emote.Name != Config.Emojis.Star || message.Author.IsBot) return; var db = serviceProvider.GetRequiredService(); From 13b18070fee8489533bda0da0f59e46f83253ec0 Mon Sep 17 00:00:00 2001 From: conaticus Date: Wed, 29 May 2024 12:43:16 +0100 Subject: [PATCH 06/14] feat: repeat command solves #8 --- Boolean/Modules/Repeat.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Boolean/Modules/Repeat.cs diff --git a/Boolean/Modules/Repeat.cs b/Boolean/Modules/Repeat.cs new file mode 100644 index 0000000..23416c1 --- /dev/null +++ b/Boolean/Modules/Repeat.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices.ObjectiveC; +using Boolean.Util; +using Discord; +using Discord.Interactions; +using Discord.WebSocket; + +namespace Boolean; + +public class Repeat(DiscordSocketClient client) : InteractionModuleBase +{ + [SlashCommand("repeat", "Repeats the message you send into the channel")] + public async Task RepeatMsg(string msg) + { + await ReplyAsync(msg, allowedMentions: AllowedMentions.None); + + // Annoying we have to do this to end the interaction. + await RespondAsync(embed: new EmbedBuilder + { + Description = "Repeated your message", + Color = EmbedColors.Normal + }.Build(), ephemeral: true); + } +} \ No newline at end of file From bfda65a8abdf5cf7cab3864a1b7085b88bbbdbda Mon Sep 17 00:00:00 2001 From: conaticus Date: Wed, 29 May 2024 12:56:02 +0100 Subject: [PATCH 07/14] Resolves #24: clear command --- Boolean/Modules/AdminUtils.cs | 58 +++++++++++++++++++++++++++++++++++ Boolean/Modules/Repeat.cs | 23 -------------- 2 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 Boolean/Modules/AdminUtils.cs delete mode 100644 Boolean/Modules/Repeat.cs diff --git a/Boolean/Modules/AdminUtils.cs b/Boolean/Modules/AdminUtils.cs new file mode 100644 index 0000000..061ea1f --- /dev/null +++ b/Boolean/Modules/AdminUtils.cs @@ -0,0 +1,58 @@ +using System.Runtime.InteropServices.ObjectiveC; +using Boolean.Util; +using Discord; +using Discord.Interactions; +using Discord.WebSocket; + +namespace Boolean; + +[DefaultMemberPermissions(GuildPermission.Administrator)] +public class AdminUtils(DiscordSocketClient client) : InteractionModuleBase +{ + [SlashCommand("repeat", "Repeats the message you send into the channel")] + public async Task Repeat(string msg) + { + await ReplyAsync(msg, allowedMentions: AllowedMentions.None); + + // Annoying we have to do this to end the interaction. + await RespondAsync(embed: new EmbedBuilder + { + Description = "Repeated your message", + Color = EmbedColors.Normal + }.Build(), ephemeral: true); + } + + [SlashCommand("clear", "Clears a specified number of messages in a channel")] + public async Task Clear(int messageCount) + { + if (messageCount is < 1 or > 500) { + await RespondAsync(embed: new EmbedBuilder + { + Description = "Number of messages to delete must be between 1-500.", + Color = EmbedColors.Fail, + }.Build(), ephemeral: true); + + return; + } + + if (Context.Channel is not SocketTextChannel textChannel) { + await RespondAsync(embed: new EmbedBuilder + { + Description = "Can only delete messages in a text channel.", + Color = EmbedColors.Fail, + }.Build(), ephemeral: true); + + return; + } + + var messages = await Context.Channel.GetMessagesAsync(messageCount).FlattenAsync(); + await textChannel.DeleteMessagesAsync(messages); + + await RespondAsync(embed: new EmbedBuilder + { + Description = $"Deleted `{messages.Count()}` messages.", + Color = EmbedColors.Success, + }.Build()); + } + +} \ No newline at end of file diff --git a/Boolean/Modules/Repeat.cs b/Boolean/Modules/Repeat.cs deleted file mode 100644 index 23416c1..0000000 --- a/Boolean/Modules/Repeat.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Runtime.InteropServices.ObjectiveC; -using Boolean.Util; -using Discord; -using Discord.Interactions; -using Discord.WebSocket; - -namespace Boolean; - -public class Repeat(DiscordSocketClient client) : InteractionModuleBase -{ - [SlashCommand("repeat", "Repeats the message you send into the channel")] - public async Task RepeatMsg(string msg) - { - await ReplyAsync(msg, allowedMentions: AllowedMentions.None); - - // Annoying we have to do this to end the interaction. - await RespondAsync(embed: new EmbedBuilder - { - Description = "Repeated your message", - Color = EmbedColors.Normal - }.Build(), ephemeral: true); - } -} \ No newline at end of file From f47ce67551f4f90eba753b0d6c725fd840121e16 Mon Sep 17 00:00:00 2001 From: conaticus Date: Wed, 29 May 2024 13:05:47 +0100 Subject: [PATCH 08/14] feat: execute command --- Boolean/Modules/AdminUtils.cs | 2 +- Boolean/Modules/FunUtils.cs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 Boolean/Modules/FunUtils.cs diff --git a/Boolean/Modules/AdminUtils.cs b/Boolean/Modules/AdminUtils.cs index 061ea1f..f7910b0 100644 --- a/Boolean/Modules/AdminUtils.cs +++ b/Boolean/Modules/AdminUtils.cs @@ -7,7 +7,7 @@ namespace Boolean; [DefaultMemberPermissions(GuildPermission.Administrator)] -public class AdminUtils(DiscordSocketClient client) : InteractionModuleBase +public class AdminUtils : InteractionModuleBase { [SlashCommand("repeat", "Repeats the message you send into the channel")] public async Task Repeat(string msg) diff --git a/Boolean/Modules/FunUtils.cs b/Boolean/Modules/FunUtils.cs new file mode 100644 index 0000000..92530b0 --- /dev/null +++ b/Boolean/Modules/FunUtils.cs @@ -0,0 +1,19 @@ +using Boolean.Util; +using Discord; +using Discord.Interactions; + +namespace Boolean; + +public class FunUtils : InteractionModuleBase +{ + [SlashCommand("execute", "Executes a fiendish individual.")] + public async Task Execute(IUser user, string? reason = null) + { + await RespondAsync(embed: new EmbedBuilder + { + Description = $"{user.Mention} has been executed by {Context.User.Mention}" + + (reason != null ? $"\nReason: `{reason}`" : ""), + Color = EmbedColors.Normal, + }.Build()); + } +} \ No newline at end of file From b203b3f98c25602a171de31768ac89632cfa0594 Mon Sep 17 00:00:00 2001 From: Sam <72096531+conaticus@users.noreply.github.com> Date: Wed, 29 May 2024 16:34:34 +0100 Subject: [PATCH 09/14] Fork/status command (#62) Co-authored-by: JatoMixo --- Boolean/Modules/BotInfo.cs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Boolean/Modules/BotInfo.cs b/Boolean/Modules/BotInfo.cs index 82fc997..a1b9e3f 100644 --- a/Boolean/Modules/BotInfo.cs +++ b/Boolean/Modules/BotInfo.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Boolean.Util; using Discord; using Discord.Interactions; @@ -57,4 +58,39 @@ public async Task Contribute() await RespondAsync(embed: embed.Build(), ephemeral: true); } + + [DefaultMemberPermissions(GuildPermission.Administrator)] + [SlashCommand("status", "Shows the bot's compute usage (CPU, RAM, etc)")] + public async Task Status() + { + var botProcess = Process.GetCurrentProcess(); + var ramUsageGb = botProcess.WorkingSet64 / (float) Math.Pow(1024, 3); + + // Calculate the CPU usage + var startTime = DateTime.UtcNow; + var startCpuUsage = botProcess.TotalProcessorTime; + + await Task.Delay(500); + + var endTime = DateTime.UtcNow; + var endCpuUsage = botProcess.TotalProcessorTime; + + var cpuUsage = (float) (endCpuUsage - startCpuUsage).TotalMilliseconds + / (float) (Environment.ProcessorCount * (endTime - startTime).TotalMilliseconds); + + var embed = new EmbedBuilder + { + Title = "Bot Status", + Color = EmbedColors.Normal, + }; + + var uptime = DateTime.Now - botProcess.StartTime; + + embed + .AddField("RAM", $"`{Math.Round(ramUsageGb, 2)} GB`", true) + .AddField("CPU Usage", $"`{Math.Round(cpuUsage * 100, 2)}%`", true) + .AddField("Up Time", $"{uptime.Days} days, {uptime.Hours} hours, {uptime.Minutes} minutes, {uptime.Seconds} seconds", false); + + await RespondAsync(embed: embed.Build(), ephemeral: true); + } } \ No newline at end of file From a9a5bb047959d797df799946e8c844f577f1cc8f Mon Sep 17 00:00:00 2001 From: conaticus Date: Wed, 29 May 2024 16:39:08 +0100 Subject: [PATCH 10/14] Remove admin permission from status command --- Boolean/Modules/BotInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Boolean/Modules/BotInfo.cs b/Boolean/Modules/BotInfo.cs index a1b9e3f..fdef5d8 100644 --- a/Boolean/Modules/BotInfo.cs +++ b/Boolean/Modules/BotInfo.cs @@ -59,7 +59,7 @@ public async Task Contribute() await RespondAsync(embed: embed.Build(), ephemeral: true); } - [DefaultMemberPermissions(GuildPermission.Administrator)] + // We might later want to consider making this maintainers only (as people could use this to exploit the bot) [SlashCommand("status", "Shows the bot's compute usage (CPU, RAM, etc)")] public async Task Status() { From 13bb5c7f90cbc05bbb5bbd743647c938030c50c3 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 31 May 2024 18:13:10 +0100 Subject: [PATCH 11/14] support attachments in starboard --- Boolean/EventHandlers.cs | 43 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/Boolean/EventHandlers.cs b/Boolean/EventHandlers.cs index da94224..412437a 100644 --- a/Boolean/EventHandlers.cs +++ b/Boolean/EventHandlers.cs @@ -157,31 +157,32 @@ private async Task HandleExistingStarReaction(DataContext db, IUserMessage messa return; } + // Send to starboard channel as it has reached the minimum stars threshold + var starboardChannel = await client.GetChannelAsync(dbStarboardChannel.Snowflake) as ITextChannel; var embed = new EmbedBuilder { - Title = "Message Link", - ThumbnailUrl = message.Author.GetAvatarUrl(), Color = EmbedColors.Normal, - Fields = [ - new EmbedFieldBuilder - { - Name = "Author", - Value = message.Author.Username, - IsInline = true, - }, - - new EmbedFieldBuilder - { - Name = "Message", - Value = message.Content, - IsInline = true, - }, - ], - Url = message.GetJumpUrl(), - }; - - await starboardChannel.SendMessageAsync(embed: embed.Build()); + Description = $"{message.Content}\n\n[Message Link]({message.GetJumpUrl()})", + ImageUrl = message.Attachments.FirstOrDefault()?.Url, + }.WithAuthor(message.Author); + + // Add attachments + var attachments = message.Attachments.Skip(1); + var attachmentsLength = attachments.Count(); + + Embed[] embeds = new Embed[attachmentsLength + 1]; + embeds[0] = embed.Build(); + + for (int i = 0; i < attachmentsLength; i++) { + embeds[i + 1] = new EmbedBuilder + { + ImageUrl = attachments.ElementAt(i).Url, + Color = EmbedColors.Normal, + }.Build(); + } + + await starboardChannel.SendMessageAsync(embeds: embeds); starReaction.IsOnStarboard = true; await db.SaveChangesAsync(); From 7b46647d7633e375ac80f5c6aaa4da29dc461a8e Mon Sep 17 00:00:00 2001 From: JatoMixo <97539106+JatoMixo@users.noreply.github.com> Date: Sun, 16 Jun 2024 22:32:05 +0200 Subject: [PATCH 12/14] Added Network Traffic data to the status command (#67) --- Boolean/Modules/BotInfo.cs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Boolean/Modules/BotInfo.cs b/Boolean/Modules/BotInfo.cs index fdef5d8..c76b185 100644 --- a/Boolean/Modules/BotInfo.cs +++ b/Boolean/Modules/BotInfo.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Net.NetworkInformation; using Boolean.Util; using Discord; using Discord.Interactions; @@ -70,6 +71,16 @@ public async Task Status() var startTime = DateTime.UtcNow; var startCpuUsage = botProcess.TotalProcessorTime; + NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + + float startBytesSent = 0; + float startBytesReceived = 0; + + foreach (NetworkInterface networkInterface in networkInterfaces) { + startBytesSent += networkInterface.GetIPv4Statistics().BytesSent; + startBytesReceived += networkInterface.GetIPv4Statistics().BytesReceived; + } + await Task.Delay(500); var endTime = DateTime.UtcNow; @@ -78,6 +89,20 @@ public async Task Status() var cpuUsage = (float) (endCpuUsage - startCpuUsage).TotalMilliseconds / (float) (Environment.ProcessorCount * (endTime - startTime).TotalMilliseconds); + float endBytesSent = 0; + float endBytesReceived = 0; + + foreach (NetworkInterface networkInterface in networkInterfaces) { + endBytesSent += networkInterface.GetIPv4Statistics().BytesSent; + endBytesReceived += networkInterface.GetIPv4Statistics().BytesReceived; + } + + float megabytesSentPerSecond = ((endBytesSent - startBytesSent) / (float) Math.Pow(1024, 2)) + / (float) (endTime - startTime).TotalSeconds; + + float megabytesReceivedPerSecond = ((endBytesReceived - startBytesReceived) / (float) Math.Pow(1024, 2)) + / (float) (endTime - startTime).TotalSeconds; + var embed = new EmbedBuilder { Title = "Bot Status", @@ -89,6 +114,9 @@ public async Task Status() embed .AddField("RAM", $"`{Math.Round(ramUsageGb, 2)} GB`", true) .AddField("CPU Usage", $"`{Math.Round(cpuUsage * 100, 2)}%`", true) + .AddField("** **", "** **") // Empty line to display the following fields in a separate row + .AddField("MB/S Sent", $"`{Math.Round(megabytesSentPerSecond, 2)} MB/S`", true) + .AddField("MB/S Received", $"`{Math.Round(megabytesReceivedPerSecond, 2)} MB/S`", true) .AddField("Up Time", $"{uptime.Days} days, {uptime.Hours} hours, {uptime.Minutes} minutes, {uptime.Seconds} seconds", false); await RespondAsync(embed: embed.Build(), ephemeral: true); From 3471b60aac5f8cf2552a1c0404c607c44a16ab39 Mon Sep 17 00:00:00 2001 From: JatoMixo <97539106+JatoMixo@users.noreply.github.com> Date: Sun, 16 Jun 2024 23:03:39 +0200 Subject: [PATCH 13/14] Fix Pagination Buttons Bugs (#68) --- Boolean/Utils/UI/Paginator.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Boolean/Utils/UI/Paginator.cs b/Boolean/Utils/UI/Paginator.cs index 8676cb4..95d11f1 100644 --- a/Boolean/Utils/UI/Paginator.cs +++ b/Boolean/Utils/UI/Paginator.cs @@ -32,10 +32,21 @@ public class Paginator( public async Task SendAsync() { var embed = pageChangeFunc(SlicePage(), CreateDefaultEmbed()); - await interaction.RespondAsync(embed: embed.Build(), components: CreateButtons(true), ephemeral: true); + await interaction.RespondAsync(embed: embed.Build(), components: CreateButtonsForCurrentPage(), ephemeral: true); await PaginatorCache.Add(_id, this); } + + private MessageComponent CreateButtonsForCurrentPage() + { + bool previousButtonDisabled = _currentPage == 0; + + int numberOfPages = (int) MathF.Ceiling(data.Count / (float) itemsPerPage); + bool nextButtonDisabled = numberOfPages - 1 <= _currentPage; + + return CreateButtons(previousButtonDisabled, nextButtonDisabled); + } + private MessageComponent CreateButtons(bool isPreviousDisabled = false, bool isNextDisabled = false) { return new ComponentBuilder() @@ -72,13 +83,7 @@ public async Task HandleChange(bool isNext, SocketMessageComponent component) await interaction.ModifyOriginalResponseAsync(m => { m.Embed = embed.Build(); - - if (_currentPage + itemsPerPage + 1 > data.Count) - m.Components = CreateButtons(false, true); - else if (_currentPage == 0) - m.Components = CreateButtons(true); - else - m.Components = CreateButtons(); + m.Components = CreateButtonsForCurrentPage(); }); await component.DeferAsync(); From 9d2e26200d0f27b9026cb0e2aa001f37fbcd5638 Mon Sep 17 00:00:00 2001 From: Sam <72096531+conaticus@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:06:13 +0100 Subject: [PATCH 14/14] Drake meme generator (#69) Co-authored-by: JatoMixo --- Boolean/Boolean.csproj | 74 +++++++++-------- Boolean/Modules/FunUtils.cs | 74 +++++++++++++---- Boolean/Program.cs | 145 +++++++++++++++++---------------- Boolean/Utils/Drawing/Text.cs | 25 ++++++ Boolean/images/Conatidrake.jpg | Bin 0 -> 58885 bytes 5 files changed, 192 insertions(+), 126 deletions(-) create mode 100644 Boolean/Utils/Drawing/Text.cs create mode 100644 Boolean/images/Conatidrake.jpg diff --git a/Boolean/Boolean.csproj b/Boolean/Boolean.csproj index 7b2302e..836bba5 100644 --- a/Boolean/Boolean.csproj +++ b/Boolean/Boolean.csproj @@ -1,36 +1,38 @@ - - - - Exe - net8.0 - enable - enable - 4043f2b4-1120-4946-b098-003bba4cbbe4 - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - Always - - - - + + + + Exe + net8.0 + enable + enable + 4043f2b4-1120-4946-b098-003bba4cbbe4 + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + Always + + + + \ No newline at end of file diff --git a/Boolean/Modules/FunUtils.cs b/Boolean/Modules/FunUtils.cs index 92530b0..474b723 100644 --- a/Boolean/Modules/FunUtils.cs +++ b/Boolean/Modules/FunUtils.cs @@ -1,19 +1,57 @@ -using Boolean.Util; -using Discord; -using Discord.Interactions; - -namespace Boolean; - -public class FunUtils : InteractionModuleBase -{ - [SlashCommand("execute", "Executes a fiendish individual.")] - public async Task Execute(IUser user, string? reason = null) - { - await RespondAsync(embed: new EmbedBuilder - { - Description = $"{user.Mention} has been executed by {Context.User.Mention}" - + (reason != null ? $"\nReason: `{reason}`" : ""), - Color = EmbedColors.Normal, - }.Build()); - } +using Boolean.Util; +using Discord; +using Discord.Interactions; +using SkiaSharp; + +namespace Boolean; + +public class FunUtils : InteractionModuleBase +{ + [SlashCommand("execute", "Executes a fiendish individual.")] + public async Task Execute(IUser user, string? reason = null) + { + await RespondAsync(embed: new EmbedBuilder + { + Description = $"{user.Mention} has been executed by {Context.User.Mention}" + + (reason != null ? $"\nReason: `{reason}`" : ""), + Color = EmbedColors.Normal, + }.Build()); + } + + [SlashCommand("conatidrake", "Creates the drake preference meme BUT conaticus")] + public async Task Conatidrake(string top, string bottom) + { + try + { + var templateImage = SKImage.FromEncodedData("./images/Conatidrake.jpg"); + using (var surface = SKSurface.Create(templateImage.Info)) + { + surface.Canvas.DrawImage(templateImage, 0, 0); + + var font = new SKPaint + { + TextSize = 50, + IsAntialias = true, + Color = new SKColor(0, 0, 0), + Style = SKPaintStyle.Fill, + }; + + var width = templateImage.Width; + var height = templateImage.Height; + + var topRect = new SKRect(width / 2, 0, width, height / 2); + var bottomRect = new SKRect(width / 2, height / 2, width, height); + + Text.DrawTextBox(surface.Canvas, top, topRect, font); + Text.DrawTextBox(surface.Canvas, bottom, bottomRect, font); + + var stream = surface.Snapshot().Encode().AsStream(); + await Context.Channel.SendFileAsync(stream, "conatidrake.png"); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } } \ No newline at end of file diff --git a/Boolean/Program.cs b/Boolean/Program.cs index 4105562..416d49c 100644 --- a/Boolean/Program.cs +++ b/Boolean/Program.cs @@ -1,73 +1,74 @@ -using System.Reflection; -using Discord; -using Discord.Interactions; -using Discord.WebSocket; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Boolean; - -class Program -{ - private static ServiceProvider _serviceProvider; - private static DiscordSocketClient _client; - - public static async Task Main() - { - HostApplicationBuilder builder = Host.CreateApplicationBuilder(); - Config config = builder.Configuration.Get() - ?? throw new Exception("Failed to load valid config from appsettings.json, please refer to the README.md for instructions."); - - builder.Services - .AddDbContext(options => options.UseNpgsql(config.GetConnectionString())); - - // Only start the bot outside of design time (avoids app running during dotnet ef commands) - if (EF.IsDesignTime) { - _serviceProvider = builder.Services.BuildServiceProvider(); - goto buildApp; - } - - _client = new DiscordSocketClient(new DiscordSocketConfig - { - GatewayIntents = GatewayIntents.All, - UseInteractionSnowflakeDate = false, // Prevents a funny from happening when your OS clock is out of sync - MessageCacheSize = 100, - }); - - var interactionService = new InteractionService(_client.Rest); - - builder.Services - .AddSingleton(interactionService) - .AddSingleton(_client) - .AddSingleton(config) - .AddSingleton(); - - _serviceProvider = builder.Services.BuildServiceProvider(); - - await _client.LoginAsync(TokenType.Bot, config.DiscordToken); - await _client.StartAsync(); - - await interactionService.AddModulesAsync(Assembly.GetEntryAssembly(), _serviceProvider); - AttachEventHandlers(); - - buildApp: - IHost app = builder.Build(); - await app.RunAsync(); - } - - private static void AttachEventHandlers() - { - var eventHandlers = _serviceProvider.GetRequiredService(); - - _client.Log += eventHandlers.LogMessage; - _client.Ready += eventHandlers.Ready; - _client.InteractionCreated += eventHandlers.InteractionCreated; - _client.JoinedGuild += eventHandlers.GuildCreate; - _client.ButtonExecuted += eventHandlers.ButtonExecuted; - _client.ReactionAdded += eventHandlers.ReactionAdded; - _client.ReactionRemoved += eventHandlers.ReactionRemoved; - _client.UserJoined += eventHandlers.UserJoined; - } +using System.Reflection; +using Discord; +using Discord.Interactions; +using Discord.WebSocket; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Boolean; + +class Program +{ + private static ServiceProvider _serviceProvider; + private static DiscordSocketClient _client; + + public static async Task Main() + { + HostApplicationBuilder builder = Host.CreateApplicationBuilder(); + Config config = builder.Configuration.Get() + ?? throw new Exception("Failed to load valid config from appsettings.json, please refer to the README.md for instructions."); + + builder.Services + .AddDbContext(options => options.UseNpgsql(config.GetConnectionString())); + + // Only start the bot outside of design time (avoids app running during dotnet ef commands) + if (EF.IsDesignTime) + { + _serviceProvider = builder.Services.BuildServiceProvider(); + goto buildApp; + } + + _client = new DiscordSocketClient(new DiscordSocketConfig + { + GatewayIntents = GatewayIntents.All, + UseInteractionSnowflakeDate = false, // Prevents a funny from happening when your OS clock is out of sync + MessageCacheSize = 100, + }); + + var interactionService = new InteractionService(_client.Rest); + + builder.Services + .AddSingleton(interactionService) + .AddSingleton(_client) + .AddSingleton(config) + .AddSingleton(); + + _serviceProvider = builder.Services.BuildServiceProvider(); + + await _client.LoginAsync(TokenType.Bot, config.DiscordToken); + await _client.StartAsync(); + + await interactionService.AddModulesAsync(Assembly.GetEntryAssembly(), _serviceProvider); + AttachEventHandlers(); + + buildApp: + IHost app = builder.Build(); + await app.RunAsync(); + } + + private static void AttachEventHandlers() + { + var eventHandlers = _serviceProvider.GetRequiredService(); + + _client.Log += eventHandlers.LogMessage; + _client.Ready += eventHandlers.Ready; + _client.InteractionCreated += eventHandlers.InteractionCreated; + _client.JoinedGuild += eventHandlers.GuildCreate; + _client.ButtonExecuted += eventHandlers.ButtonExecuted; + _client.ReactionAdded += eventHandlers.ReactionAdded; + _client.ReactionRemoved += eventHandlers.ReactionRemoved; + _client.UserJoined += eventHandlers.UserJoined; + } } \ No newline at end of file diff --git a/Boolean/Utils/Drawing/Text.cs b/Boolean/Utils/Drawing/Text.cs new file mode 100644 index 0000000..5a522a7 --- /dev/null +++ b/Boolean/Utils/Drawing/Text.cs @@ -0,0 +1,25 @@ +using SkiaSharp; + +public static class Text +{ + public static void DrawTextBox(SKCanvas canvas, string text, SKRect rect, SKPaint paint) + { + var spaceWidth = paint.MeasureText(" "); + var wordX = rect.Left; + var wordY = rect.Top + paint.TextSize; + + foreach (var word in text.Split(' ')) + { + var wordWidth = paint.MeasureText(word); + + if (wordWidth > rect.Right - wordX) + { + wordY += paint.FontSpacing; + wordX = rect.Left; + } + + canvas.DrawText(word, wordX, wordY, paint); + wordX += wordWidth + spaceWidth; + } + } +} \ No newline at end of file diff --git a/Boolean/images/Conatidrake.jpg b/Boolean/images/Conatidrake.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6bb37c101121c6f6980b2f3b5b01969e90837372 GIT binary patch literal 58885 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