From 0ea798186d41a6c4bb606f4ef6d3b63f49b25b46 Mon Sep 17 00:00:00 2001 From: copokbl Date: Thu, 18 Jul 2024 22:52:25 +1000 Subject: [PATCH] Initial commit --- .gitignore | 41 +++++++++++ .idea/.idea.QuoteBot/.idea/.gitignore | 13 ++++ .idea/.idea.QuoteBot/.idea/encodings.xml | 4 ++ .idea/.idea.QuoteBot/.idea/indexLayout.xml | 8 +++ Commands/ConfigureQuotes.cs | 35 +++++++++ Commands/QuoteCommand.cs | 58 +++++++++++++++ Data/Quote.cs | 3 + Data/Storage/IStorageService.cs | 11 +++ Data/Storage/SqliteStorageService.cs | 80 +++++++++++++++++++++ DefaultConfig.cs | 9 +++ Program.cs | 37 ++++++++++ QuoteBot.csproj | 22 ++++++ QuoteBot.sln | 16 +++++ Quoting.cs | 65 +++++++++++++++++ README.md | 2 + ReplyQuoteListener.cs | 39 ++++++++++ SimpleDiscordNet.dll | Bin 0 -> 59904 bytes Utils.cs | 31 ++++++++ 18 files changed, 474 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.idea.QuoteBot/.idea/.gitignore create mode 100644 .idea/.idea.QuoteBot/.idea/encodings.xml create mode 100644 .idea/.idea.QuoteBot/.idea/indexLayout.xml create mode 100644 Commands/ConfigureQuotes.cs create mode 100644 Commands/QuoteCommand.cs create mode 100644 Data/Quote.cs create mode 100644 Data/Storage/IStorageService.cs create mode 100644 Data/Storage/SqliteStorageService.cs create mode 100644 DefaultConfig.cs create mode 100644 Program.cs create mode 100644 QuoteBot.csproj create mode 100644 QuoteBot.sln create mode 100644 Quoting.cs create mode 100644 README.md create mode 100644 ReplyQuoteListener.cs create mode 100644 SimpleDiscordNet.dll create mode 100644 Utils.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79098dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +db.dat +config.json + +# Common IntelliJ Platform excludes + +# User specific +**/.idea/**/workspace.xml +**/.idea/**/tasks.xml +**/.idea/shelf/* +**/.idea/dictionaries +**/.idea/httpRequests/ + +# Sensitive or high-churn files +**/.idea/**/dataSources/ +**/.idea/**/dataSources.ids +**/.idea/**/dataSources.xml +**/.idea/**/dataSources.local.xml +**/.idea/**/sqlDataSources.xml +**/.idea/**/dynamic.xml + +# Rider +# Rider auto-generates .iml files, and contentModel.xml +**/.idea/**/*.iml +**/.idea/**/contentModel.xml +**/.idea/**/modules.xml + +*.suo +*.user +.vs/ +[Bb]in/ +[Oo]bj/ +_UpgradeReport_Files/ +[Pp]ackages/ +Logs/ + +Thumbs.db +Desktop.ini +.DS_Store + +bin/ +obj/ \ No newline at end of file diff --git a/.idea/.idea.QuoteBot/.idea/.gitignore b/.idea/.idea.QuoteBot/.idea/.gitignore new file mode 100644 index 0000000..229aca8 --- /dev/null +++ b/.idea/.idea.QuoteBot/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/.idea.QuoteBot.iml +/modules.xml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.QuoteBot/.idea/encodings.xml b/.idea/.idea.QuoteBot/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.QuoteBot/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.QuoteBot/.idea/indexLayout.xml b/.idea/.idea.QuoteBot/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.QuoteBot/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Commands/ConfigureQuotes.cs b/Commands/ConfigureQuotes.cs new file mode 100644 index 0000000..a92612a --- /dev/null +++ b/Commands/ConfigureQuotes.cs @@ -0,0 +1,35 @@ +using Discord; +using Discord.WebSocket; +using SimpleDiscordNet.Commands; + +namespace QuoteBot.Commands; + +public class ConfigureQuotes { + private static readonly string[] ValidForms = ["embed", "fake-msg", "bot-msg"]; + + [SlashCommand("configure-quotes", "Change quote settings", GuildPermission.ManageGuild)] + [SlashCommandArgument("channel", "The quotes channel to send quotes to", true, ApplicationCommandOptionType.Channel)] + [SlashCommandArgument("quote-form", "How to display quotes: 'embed', 'fake-msg' or 'bot-msg'", true, ApplicationCommandOptionType.String)] + public async Task Execute(SocketSlashCommand cmd, DiscordSocketClient client) { + IGuildChannel channel = cmd.GetArgument("channel")!; + string form = cmd.GetArgument("quote-form")!; + + if (!ValidForms.Contains(form)) { + await cmd.RespondWithEmbedAsync("Configure Quotes", "Invalid quote form, must be 'embed', 'fake-msg' or 'bot-msg'.", ResponseType.Error, ephemeral: false); + return; + } + + if (cmd.Channel.GetChannelType() == ChannelType.DM) { + await cmd.RespondWithEmbedAsync("Configure Quotes", "You can't do this in your DMs.", ResponseType.Error, ephemeral: false); + return; + } + + if (channel is not ITextChannel textChannel) { + await cmd.RespondWithEmbedAsync("Configure Quotes", "Channel must be a text channel.", ResponseType.Error, ephemeral: true); + return; + } + + Program.Storage.SetQuoteSettings(cmd.GuildId!.Value, textChannel.Id, form); + await cmd.RespondWithEmbedAsync("Configure Quotes", "Quotes have been configured in this server.", ResponseType.Success, ephemeral: true); + } +} \ No newline at end of file diff --git a/Commands/QuoteCommand.cs b/Commands/QuoteCommand.cs new file mode 100644 index 0000000..40c3cea --- /dev/null +++ b/Commands/QuoteCommand.cs @@ -0,0 +1,58 @@ +using Discord; +using Discord.WebSocket; +using GeneralPurposeLib; +using QuoteBot.Data; +using SimpleDiscordNet.Commands; + +namespace QuoteBot.Commands; + +public class QuoteCommand { + + [SlashCommand("quote-user", "Quote something someone said and attribute quote to user")] + [SlashCommandArgument("person", "The person you are quoting", true, ApplicationCommandOptionType.User)] + [SlashCommandArgument("message", "What they said", true, ApplicationCommandOptionType.String)] + public Task WithUser(SocketSlashCommand cmd, DiscordSocketClient client) { + IUser quotee = cmd.GetArgument("person")!; + string message = cmd.GetArgument("message")!; + return Execute(cmd, client, message, quotee: quotee); + } + + [SlashCommand("quote", "Quote something someone said")] + [SlashCommandArgument("person", "The person you are quoting", true, ApplicationCommandOptionType.String)] + [SlashCommandArgument("message", "What they said", true, ApplicationCommandOptionType.String)] + public Task WithString(SocketSlashCommand cmd, DiscordSocketClient client) { + string quotee = cmd.GetArgument("person")!; + string message = cmd.GetArgument("message")!; + return Execute(cmd, client, message, quoteeName: quotee); + } + + public async Task Execute(SocketSlashCommand cmd, DiscordSocketClient client, string msg, string? quoteeName = null, IUser? quotee = null) { + if (cmd.Channel.GetChannelType() == ChannelType.DM) { + await cmd.RespondWithEmbedAsync("Quote", "You can't do this in your DMs.", ResponseType.Error, ephemeral: false); + return; + } + + ITextChannel? quotesChannel = await cmd.GetQuotesChannel(client); + + if (quotesChannel == null) { + await cmd.RespondWithEmbedAsync("Error", + "There is no quotes channel configured, ask a staff member to configure it.", ResponseType.Error, + ephemeral: true); + return; + } + + await cmd.DeferAsync(); + + await Quoting.QuoteMessage(client, + cmd.GuildId!.Value, + quoteeName ?? quotee!.Username, + cmd.User.Username, + cmd.User.Id, + quoteeName ?? quotee!.Id.ToString(), + msg, + quotee); + + await cmd.ModifyWithEmbedAsync("Quotes", $"Quote has been created in {quotesChannel.Mention}.", + ResponseType.Success); + } +} \ No newline at end of file diff --git a/Data/Quote.cs b/Data/Quote.cs new file mode 100644 index 0000000..b063d00 --- /dev/null +++ b/Data/Quote.cs @@ -0,0 +1,3 @@ +namespace QuoteBot.Data; + +public record Quote(ulong Quoter, string Quotee, ulong Guild, ulong MessageId, string Text, ulong? QuotedMessageChannel = null, ulong? QuotedMessageId = null); \ No newline at end of file diff --git a/Data/Storage/IStorageService.cs b/Data/Storage/IStorageService.cs new file mode 100644 index 0000000..209dfec --- /dev/null +++ b/Data/Storage/IStorageService.cs @@ -0,0 +1,11 @@ +namespace QuoteBot.Data.Storage; + +public interface IStorageService { + void Init(); + void Deinit(); + + void CreateQuote(Quote quote); + void SetQuoteSettings(ulong guild, ulong channel, string form); + ulong? GetQuoteChannel(ulong guild); + string? GetQuoteForm(ulong guild); +} diff --git a/Data/Storage/SqliteStorageService.cs b/Data/Storage/SqliteStorageService.cs new file mode 100644 index 0000000..2e9f87a --- /dev/null +++ b/Data/Storage/SqliteStorageService.cs @@ -0,0 +1,80 @@ +using System.Data.SQLite; + +namespace QuoteBot.Data.Storage; + +public class SqliteStorageService : IStorageService { + private const string ConnectionString = "Data Source=db.dat"; + private SQLiteConnection _connection = null!; + + public void Init() { + _connection = new SQLiteConnection(ConnectionString); + _connection.Open(); + CreateTables(); + } + + private void CreateTables() { + SQLiteCommand cmd = new(@" +CREATE TABLE IF NOT EXISTS quotes ( + quoter VARCHAR(64), + quotee VARCHAR(64), + guild VARCHAR(64), + message_id VARCHAR(64), + quote TEXT, + quoted_message_channel VARCHAR(64), + quoted_message_id VARCHAR(64) +); + +CREATE TABLE IF NOT EXISTS guild_configs ( + guild VARCHAR(64) PRIMARY KEY, + channel_id VARCHAR(64), + form VARCHAR(16) +) +", _connection); + cmd.ExecuteNonQuery(); + } + + public void Deinit() { + _connection.Dispose(); + } + + public void CreateQuote(Quote quote) { + using SQLiteCommand cmd = new("INSERT INTO quotes (quoter, quotee, guild, message_id, quote, quoted_message_channel, quoted_message_id) " + + "VALUES (@quoter, @quotee, @guild, @message_id, @quote, @quoted_message_channel, @quoted_message_id);", _connection); + cmd.Parameters.AddWithValue("quoter", quote.Quoter.ToString()); + cmd.Parameters.AddWithValue("quotee", quote.Quotee); + cmd.Parameters.AddWithValue("guild", quote.Guild.ToString()); + cmd.Parameters.AddWithValue("message_id", quote.MessageId.ToString()); + cmd.Parameters.AddWithValue("quote", quote.Text); + cmd.Parameters.AddWithValue("quoted_message_channel", quote.QuotedMessageChannel.ToString()); + cmd.Parameters.AddWithValue("quoted_message_id", quote.QuotedMessageId.ToString()); + cmd.ExecuteNonQuery(); + } + + public void SetQuoteSettings(ulong guild, ulong channel, string form) { + using SQLiteCommand cmd = new("INSERT OR REPLACE INTO guild_configs (guild, channel_id, form) VALUES (@guild, @channel, @form);", _connection); + cmd.Parameters.AddWithValue("guild", guild.ToString()); + cmd.Parameters.AddWithValue("channel", channel.ToString()); + cmd.Parameters.AddWithValue("form", form); + cmd.ExecuteNonQuery(); + } + + public ulong? GetQuoteChannel(ulong guild) { + using SQLiteCommand cmd = new("SELECT channel_id FROM guild_configs WHERE guild = @guild;", _connection); + cmd.Parameters.AddWithValue("guild", guild.ToString()); + using SQLiteDataReader reader = cmd.ExecuteReader(); + if (!reader.Read()) { + return null; + } + return ulong.Parse(reader.GetString(0)); + } + + public string? GetQuoteForm(ulong guild) { + using SQLiteCommand cmd = new("SELECT form FROM guild_configs WHERE guild = @guild;", _connection); + cmd.Parameters.AddWithValue("guild", guild.ToString()); + using SQLiteDataReader reader = cmd.ExecuteReader(); + if (!reader.Read()) { + return null; + } + return reader.GetString(0); + } +} \ No newline at end of file diff --git a/DefaultConfig.cs b/DefaultConfig.cs new file mode 100644 index 0000000..f174ac2 --- /dev/null +++ b/DefaultConfig.cs @@ -0,0 +1,9 @@ +using GeneralPurposeLib; + +namespace QuoteBot; + +public static class DefaultConfig { + public static Dictionary Values = new() { + { "token", "xxxxxxxxxxxxxxx" } + }; +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..7cd2890 --- /dev/null +++ b/Program.cs @@ -0,0 +1,37 @@ +using GeneralPurposeLib; +using QuoteBot.Data.Storage; +using SimpleDiscordNet; + +namespace QuoteBot; + +public static class Program { + public static IStorageService Storage = null!; + + public static async Task Main(string[] args) { + Logger.Init(LogLevel.Debug); + Config config = new(DefaultConfig.Values); + + Storage = new SqliteStorageService(); + Storage.Init(); + + SimpleDiscordBot bot = new(config["token"]); + + bot.Log += message => { + Logger.Info(message.Message); + if (message.Exception != null) { + Logger.Info(message.Exception); + } + return Task.CompletedTask; + }; + + bot.Client.Ready += () => { + bot.UpdateCommands(); + bot.Client.SetCustomStatusAsync("Watching for funny quotes"); + Logger.Info("Bot ready"); + return Task.CompletedTask; + }; + await bot.StartBot(); + Logger.Info("Bot started"); + await bot.WaitAsync(); + } +} \ No newline at end of file diff --git a/QuoteBot.csproj b/QuoteBot.csproj new file mode 100644 index 0000000..e58d448 --- /dev/null +++ b/QuoteBot.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + SimpleDiscordNet.dll + + + + diff --git a/QuoteBot.sln b/QuoteBot.sln new file mode 100644 index 0000000..063dc59 --- /dev/null +++ b/QuoteBot.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuoteBot", "QuoteBot.csproj", "{132496D3-908A-4419-BB25-A08E11448FDE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {132496D3-908A-4419-BB25-A08E11448FDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {132496D3-908A-4419-BB25-A08E11448FDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {132496D3-908A-4419-BB25-A08E11448FDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {132496D3-908A-4419-BB25-A08E11448FDE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Quoting.cs b/Quoting.cs new file mode 100644 index 0000000..ef2d532 --- /dev/null +++ b/Quoting.cs @@ -0,0 +1,65 @@ +using Discord; +using Discord.Webhook; +using Discord.WebSocket; +using QuoteBot.Data; + +namespace QuoteBot; + +public class Quoting { + + public static async Task QuoteMessage(DiscordSocketClient client, + ulong guild, + string quoteeName, + string quoterName, + ulong quoterId, + string quoteeData, + string msg, + IUser? quotee = null, + ulong? quotedMsgChannel = null, + ulong? quotedMsgId = null) { + ITextChannel? quotesChannel = await client.GetQuotesChannel(guild); + if (quotesChannel == null) { + throw new Exception("Quotes channel isn't defined"); + } + + string form = Program.Storage.GetQuoteForm(guild)!; // Should be defined if channel is + + IMessage createdMsg; + switch (form) { + case "embed": { + EmbedBuilder embedBuilder = new(); + embedBuilder.WithTitle(quoteeName); + embedBuilder.WithDescription($"\"{msg}\""); + embedBuilder.WithFooter("Quoted by " + quoterName); + embedBuilder.WithColor(Color.Green); + + createdMsg = await quotesChannel.SendMessageAsync(embed: embedBuilder.Build()); + break; + } + + case "fake-msg": { + IWebhook? webhook = (await quotesChannel.GetWebhooksAsync()).FirstOrDefault() ?? await quotesChannel.CreateWebhookAsync("Quotes"); + + DiscordWebhookClient webhookClient = new(webhook); + ulong newMsgId = await webhookClient.SendMessageAsync(text: msg, + username: quoteeName, + avatarUrl: quotee?.GetAvatarUrl()); + + createdMsg = await quotesChannel.GetMessageAsync(newMsgId); + break; + } + + case "bot-msg": { + createdMsg = await quotesChannel.SendMessageAsync($"{quoteeName}: \"{msg}\""); + break; + } + + default: + throw new Exception("Invalid quote form in db."); + } + + + Quote quote = new(quoterId, quoteeData, guild, createdMsg.Id, msg, quotedMsgChannel, quotedMsgId); + Program.Storage.CreateQuote(quote); + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..540b9e7 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Quote Bot +Quote people in Discord. \ No newline at end of file diff --git a/ReplyQuoteListener.cs b/ReplyQuoteListener.cs new file mode 100644 index 0000000..d3ec733 --- /dev/null +++ b/ReplyQuoteListener.cs @@ -0,0 +1,39 @@ +using Discord; +using Discord.WebSocket; +using SimpleDiscordNet.MessageReceived; + +namespace QuoteBot; + +public class ReplyQuoteListener { + + [MessageListener] + public async Task MessageListen(SocketMessage msg, DiscordSocketClient client) { + if (msg.Channel is not IGuildChannel channel) { + return; + } + + if (msg.MentionedUsers.All(u => u.Id != client.CurrentUser.Id)) { + return; + } + + if (msg.Reference == null || !msg.Reference.MessageId.IsSpecified) { + await msg.Channel.SendMessageAsync("Reply to a message and ping me to quote it.", + messageReference: msg.ToReference()); + return; + } + + IMessage quotedMsg = await msg.Channel.GetMessageAsync(msg.Reference.MessageId.Value); + await Quoting.QuoteMessage(client, + channel.GuildId, + quotedMsg.Author.Username, + msg.Author.Username, + msg.Author.Id, + quotedMsg.Author.Id.ToString(), + quotedMsg.Content, + quotedMsg.Author, + quotedMsg.Channel.Id, + quotedMsg.Id); + + await msg.AddReactionAsync(Emoji.Parse(":white_check_mark:")); + } +} \ No newline at end of file diff --git a/SimpleDiscordNet.dll b/SimpleDiscordNet.dll new file mode 100644 index 0000000000000000000000000000000000000000..18726198db6b09c527aff0bae0ee5ec576264ea3 GIT binary patch literal 59904 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ïa-E3c2IC5P)f===(m;b1HH7=4|-{7 z9`3M8af@`O`eZ1XOj&Ak^(=sX7sl}uFfF7FrMMyLOt;inCHgO0KHEat^WKPzPI8B~ zv$Qn-QziGMCqp0H7RH0yU;BdDQd=ff(>C_LU#>>a=E+y;=)>pstHE2grI2M!zwC?t z={8unSIyY-6^%y5fQ>srw#CGQp}eF0bsQ{rup84PcsFke~1*~yZ_3Yg-jq(~owuWYCGj1RNhXl5ak;!^;W%Gfrz z-ocd5);k%UUj2ke;XP~wPXnIOcn-xg22T^7v3MrpnS^Hwo~d|_!7~ldbUd^0wBR`w z&+&Lpz%w7u0z3=x6o|R?n_ZKKJ2k=$NJG%c8>%UqHfuUh(~~qkUDK$h?V6s0v=Z;i zsi9vfHzBRXn`crUMcN=?Yj({98G+Q2)Aba3x}H8O^sQutzLorv%Gb#|^(*iV!l`u~ z_`BPL4^p^JtYI4fGyRg@vi92-)L)0z@?GXMeU~{~_%3s{@LlG7;k(TF!grZ>s^%p~ z_sC85q{s*Iye!52XEW?6l`VEt=>gWA_TpjVtu1!E;sC2iB`vk~O!=sOv1M5;^=DXn zKwoa%XY-!3O=TXnZzxPz9S%HDc)iu_z`F~-ZEbMidkdem&ar~wUmz`t{1$0NyVVXEzum4fQ>?2iYwh1aLxcSryQXSBQh)8q_C>1mCj0WpYWoTY zexdXR`+9|M(ew@(Tl1EEw|r;R$M!D!!qR`*d+cxHqmz&9WLduNL6v!2?7~UDf7r7l z)A9Nl9fZG3!`}Ig_4~@xeZR2+g-lPXINdkGCVhnc)UdMvkFI9AcSH^FH`(`9KJE+q=+zxs%L}N*?|(rxzoPIf3cu#0d;oyVmsZ^658Jmze&`=z?;7!(|4`pQDyHT2+vnBKLTXjc z%bTdyPPG3sYysfW)lB^(m*o8$I{p4BzQ-d;P#&u4L&|%$3iPtyUx$>jab^8q^A1vY ztfoh5I?drmBgf~@R(Oy6wBVwA*87$G`AS)0(Vt5ITZpbVaRbzE-w>IWcc0x78XMdt zzZ`K?Flx2(PR^QNI1O;wD6Fj3FRB&=+pRjlkJ?w^R{R|0o}=`0ls->&o&&G$k@db; zN3IXfv)-@lgU-)KE(vb2uBp8-SYSOfa=Nd;`cci-0dE@ijo>c(rrLXhH`%^Xj|9)O zUatRX@ILFsQNIn|tg@F$Hbf(k;G2Ldyy|tY41USkSF4>PA~Wwzv2gvD@vc(ANt#DEA`e zUZmVB92-J~e=g`p4^GSb$U3zC)X+y(RY@!~#rliZ`|+shzU!?Wp^xoH?Z-xZGxUPI zI%;0tEn4p#mPWP3sNSL0PO+XGb%lK7Fub!q^h;}bo z3vR6)EicQs@OXS@c4oEPBId(Pp2;QauSP3nrczq0w;vQ0@Tg{b1xpfYEBD+^*c)9hkLmRd~B< z*sdC$Qkkce{!>!hIGuW%kxv`e`M+HA#vN*S%`Or>M&plq{E z|Fme?Im&HS`Bn#}RWXJ8RlZ;42UKQ2=@)CdS#@qvnJp@FyUN_I+^w2!Q2`%6DpSSNb;4$5d@o`cq2Tt*I}MRvoBmi>6U0)mjuD&~!^4 zeSW*b+ZBFF;oX|PoyWR-0cwi`Hc7N963})9Sn5EfH!IwtaI2<$nht1syQbSv_QtAh zT6SB2y|7(NJ*C{8;J#M1Q@J~pyIbj!zez?_OFnCqe3tU%t0kaEt0PK}DE&aC&s4ZM zpSHCqC8}_%!hI;UwYpDB4Jc)ca&K37d;Y8PXzf!9?^611g(ax{8zk4Kus_K8qyZ^B zpp==xP4ag2%pmP-R(gxlqYAew+^_I}!kdF^%NC{Fu9T-V-2uv?`kl~LHhiaQ+o_Vf zl`93CM7a^=HYoQ%<<126{oyl}J5#yM%572Za&V6v zv0S;!l^a!Vt8&{47z2F@4`{ks(=94UR1wL;hmc9QZD}MWwbLyJ0l7=C_Gc) zW`$=X#r&+4K27_TzFFZd3U5<*r>0gRwKZtktm*PX))G~?PvOm)ZqszT(swGnOJONe ztBTYrg=cEotdyvxo0YOn)9p&xsqij^rC2o-s|JNT;V=V`&DMA!cwB8 zO0<;14GJ$W*(4iAEH7ctMwQYB%8n6zO6f0Q>}^r{c7=C>TU4`Cxx18Z4O5?JI&&Cn zX;wI@Y2UC-GN-0*81?rJW90NJeKY8n)ofP!W~FZf<;9w9O4+8AouGtkcPeG4Qg&%s zt5j>$v{}>TrJH1F?ebD>g;M%8-Bx-zYTT~yu2Qze3abagq&I{~X;w;9(>72LZ_4dg z$`(y`hTlTJOBqwEOf@LnpzutEn-yNJaDUmW_-c0x(l=|jD}4vjMfDcmQXm@3wRVM@ zHC>LI_;KN=!foZR$|?1mm9j-CJCw3hDN>;}RHz3uZPs*o1$9Oh-lp(&g?DLcRcgJJ zlxbEts%f86?x#yB3eT>lhN!}utEqX5!rK(ysi_Pny6YQ_scpllZM#x-DMd!8g(I|e z3O6Wx&^tuu^7fx?Iz!a@&;KrO*hxG)ms$arKyZkKa8Sev!=^OQA1SWK85=g-mLI8O}8s$r@{>pEvso%(|%1i zN2qycgxYo~#cH5rq=A$Mg`=AGHBho&;hhTaQrJ3x@(r5KJb*IM1L(=k3U5)m9H{mm zs5WT2UDI8fS_hH7NG_Cb$Yc23!*3+tnrr>k`iu36HOwAoA89xH7Wle-eZDJvH~Q}I zz32=1tNb(l^ZnoPzvO@2zsLWv-^we_do}OO{MGs0`J3{8o&R?Jhxz}`4+blOk>Ju` zNASJi-h%qjj8J#zve3(+#=_$YR~KGX__e~rie?p^RrIT(Wbre_zbg)u98fYDzf-bs z;rT9p17!p9;eUSo_An2>LMp=DVkv$}RDp9{CB7@HLRyVG##;UL-z_EOcqdR=IUH$) zP5A0C;mLl&_tg^qR~})zj_^-IBayDCZ$N655YkTBBZvQ2!B}kEl(}rll6C2i;>#01a(^Y@9?tb zrMQ!|4N?WT0l)hk2P-B39xaChK2&A{K2+uc9wWyC9wYMsH_3^Bo8)A`V`VAev2q&V ziIM<3QMv(7lFI;3k}CjDky`;zkKvHJcNZMb^2Dvr%~xLz6UA3r(>kL|6W$P?6MEy%~-C!2E1p=EqhRewCu9&c&n33 zyB^~&m-Z0e&GElAU4W4mVw@qeLWrmkez8}GC@;iqe<7l(5Wht#hR=)fooq28s~8bg zj7Ta*1QjEqOYmFb5=3)}Ou#b{&*2z@N8mXU&m=rYVKg3%XEL5Cc&6ex2G2A+)A7u} zfBpS)cxK{h#xn~~3!d5df|$Q8oQvmJJjY@Fnuq6jJct=YNgbl24pE_Q#r*d+e!`k4lc5GAg?W$WL=Brw|GiJldK7$Pvj)z zYw+}kHpxTCpT|>BILRu*GZoJnczW?%kLRw!d#&dS@56Ji^?G4Bo&qaSRBja)y#)Qw z!=6djrA4n>4;D?bzK`emq5{h<-fdMEm*Z)&PAr~eZNYOFo=5Qf8qeSHj3}989ahq0 z&B6OT{F^pE9&KxhcXmd*+LAL?EL%2lf=pZ79Zhu4ig(XwGx&_wWy@x_C)ajF*SB;; zlgSCoAT(nNm;?rCaES|I$z*g@>^P|Ih$W2nG%wy3?HH0XyK_kLte)=fc-J6Su4!r! zO*@=5E$&&-+1|}&DVi&6h!8%4!l$fli+0DnzEE;*p&^M!QbDfdBqc&Ucnjif?JL)x z+TOi-PUnhPTXS-KS1auT>(pp_w-+~}v*K;*m&DG?;$&Iql%8{5thFa?wnIEN*1a^^ z(GzpflY6={jON6up3Yd8dIhyZbY9om9tT|&>s~f(#_DCumL#HG$+gi01n0FOXl26Sb%!`)ue-d#z3y;wd)*-q)$3s8dV1nA5Id=SUfYaGh)bEaLgOWkCK=L8 z+B;(oyEWdqHr|DX&FX3IXp1Er@{Gw^c(7BU5uWn;&QQ|61A1~S37u^@oy;)=i<2hn zG1=e&wS1y1j<>Ffbvpq#E1HbWj&?^uU)a;p5na&{12}I^7dkKDU`wLOH6Sip+ua`T zigo~OZY4;;lVj1gMO_{1TjCuZF_${$oStY$dpC%k?ArtcsiXN^grH)s{_Z$=^V^f% z$c$Zc5Cq-pf$l#>CQ8fVgQn4s9r4y^N0M#Vm^pl?m~n_h#*E7w95W6lH)b62P%#6R znG^sF?%czdfsBYSlOW;;=sr6WydeiO`gyQ&L*)1~kTW3}gfW34GY}KZRxVrC+!gOy z-x=>oE?K`eHevZt)QLk<4}LVo87KK5KXKHw*M|Pr3`9-@1sk=F%=v&R0SaJ(=_l*$)krV38LXn z9*PU%j47@JfWi9~C9Z1M!3r4G_jJT&;7GcTi|Mju5{>H6;k@k3ii@!yTJ22P9O$UwWzSCP{)+R`jiB_5Bi7{@UX0&OK%rlv~ z9dIYdjYDhcNp{CO9mI?gAS;~h0shN1ZE#7OB(d{R_X^f|1I+8fqKH*&4cMG9PmZ0_ z)1Gi-(@4Ul>MD9lS2DUXwumlY+Y#$_O3q7WaP$k@J2#%_jCPYBZEIVmcAK@qC3yqO znf{EdX+oN`xSbVYcitLLv@MKvk9RummMtbAgnL`K9RI1TLj5wj~%x&|!u#@j7sxj`V0E zHs`!ntn|R=b*)3S-km!)V>l@!xr>+C@g8Q_B6j0+!T>U7Tuwv8<|g9KfmjwJAR(Gw zGP;s+pq#sxw08r-{v0W1Dn_Svfdc41&B<{BT5*<4GuW&y7Q@mM<3Y)-$FYp#DoL^cl}T**|TG<9Z~*$KBct4Z(Xqtg|){N z8XP3%*rUvDsy%fzy4($y@tg`<5a5GPRR|qUMLb|p8XbX7(}h!J$2wxGV7oN0T9q)+ z?AVH)RjXo&S&8_$+GE;MY$GNvTc+)l=43L~xuRn|)JEH|Gf2->qRU7dPW$dWr@_&V z%Me}TQIUs5M|kH%@s5;?hBRc>>$GNLCqpVNkv=$PD1%y)VY$uFSn`r_Q{nC149iuv zxI4CX$?8PBXVvNqa|x#ISohonLh9UjVoio|ERMLkBxgvjqT|}zaMzHb&pEF<*2S4E zLvpR03okCkrP7ehd0i`Yc?C1tu}`U%c-PAIRXquv8V1+knVU0AXQ=aF%w3v?!5zn7 z^6bvRG_xEHrgP$GHxt##v5x3@YF9E-h1>KK6WEuwc4rt_fs(smrh@>@?dE+_PQ=6# z90R!7%P`!EbSCTaFr2AC>D}!s+If|bDK=|82F@&OlywTy`zKCf2GAWeMt)~}9cO}$ zl`^*@y2>4`?#%}~#GQ8#QMkguq~H>q+Q&N`O3sqFIetjDS*~?6g=t(8TG29sOXKZr z%n^^$nM9Qw&@wN%Allu!T1}lF>sr-qKgDU z_NU%L!j*=}d)O!r=FrE?FZX1IGIT@}*EN@u6!X!Qm*mZhkf@wsJpAa;szXRHki zZcKMlT#hn`y93Hl=#+SmM})5PjCM82i*udy;%%8-8yd@vg>#(XD(c9H`|1fZ&c@yAiP6OVgeHC#p~Evm&el7v*}KYQq`2`;i(s(i?z_FbWLnZ4 zfz4fQEgdn~JB*HS;Q*gVb4;&3!CZRgl@g*<^1>m>_WV z=wdrJ9>*YaNgTZ1OJ1zn-OHA(aB4{67zP99E$(i|5igNtoY{jV(1YGr7??X&MteHU z&TwrUb9JoEOh9RQ&xMVoZ)wC#`K}OVV(#2b;Lcfx5$1AMn+q)+A>5F;;{fxe2USp~ z3NtmTMT+X^K$k;Vbk(KHj)!p2K9Z*8Ij<|UMG$)ZjssjNh$wYf6&iS|lPVZU? zL#uQ9OpG_p>r`fZEAjEInhlzpW-my(Q^4%@D9#)>SG9taJBOjK^!gRM@XQOT>|;k# zmbJQ4G<8Y5JKC{0)`~m#B&{=%lCI8OFBk>fb1jY~*0s0Bl4b?KT(%0cHBgjvR=jp# z-T*@ouZ<+5l_Zfa`W64YsUU*eG+76`bw|K z;~8#4^rEhRNzwNVtXBKI!_`iNe{N#PnV;V+?jP<4=yQ z>cQDz&UtGS7^;|xjhnm*ox`zC>qF7a^sWQjOxT&#EnS!Aeon9i$Ip#ny26RgtAzhSk=5m?ao2nW)4aps1trK259 zbPaI!t%gRaEJCZHduJ5azdGqSo|kz}45a<*ZYI%b(N%mSZF)f);_S(!)y(~d#;rZ9 zVpqVk3Y-4JtwOdxao?jw<|PT?T&FMkWrd4iXP8Vk52MS2(Ai2*kFf>%@)HAlU2Gx7 zqUv23KNo{wRqQ;TrK9VyLvKzbkgve&Z1#FL_LfORq^!Z?9yFx6YrWI^3!(tghdP7c zc%l8-g;AyNEqO(=IEH&=a^`fdMT&pN^x}6&5$VJ?U)}i1D~`YI9Kn|@5l|;c3g5{@ zKwE)tzFg`=P$PI!BIla2)9@9{YJ5w!443^0d`H%WUn;Fdo`B9SXq^Y025QGQW$l0y z(CzATYiWfIZTNjFY;0=~)z+Zzlmb5}&CptI|EyiOJ{Gm10 zyc*@0M%A7!M_)xL$lS814ikNmeE@_9k<3ZV1QA&5hS*$*SsG>m=Q8_D(NUNI5sBHz`I3xns zuyJ&jaTVnVaV&I!yIxBY?gpKIbM=F;@n#C z(gKv#N*^!QVYzM04c#1v_2W-Res+iLTiO4QT-cQBLQYLdbQ3#kExNN^yEwuRWBV@6 zxDRV1{+Ef2p(1W*RYN;(g3f?LM;{}BHPJ8*PiuOv(Sy5c=(l)_}~G295|VIN{(0zhY)w?F%Fqd{LdXM)Dyv=<=|ZbD*hhQfh{_yI3Jl=ExXKA z$VV z*10 z8rw=Z0T}xQ{D)O=0KzzmVQ;ZOHM{trNEw&Q&+T)s&!m`|S(EyqJiDx+wKxkXM@@&3 ztDKDwr3ck~prP)!zP_y)H*>vBqJi9yaG+!cd1ebk2bfSlS0&ih5xA9f{EYyVXNVXH z6M%FeHIido#&zLD4H8aAEYA?7%gJ~53Y_%J#DYM|i}1gUTW~03xbGGp{*cu4{SCqF z5YHa$>c&G_w56G;(GC8bp@-=)dkP&aC(GR7mtD{Qu_4$r$1{y(55U|SWuMzaZT0Bf z)(koPs+-Xt-tB(UgxL{pX~siqx}S4x=s`Si+GOv^^xP$h6Ltrt9uBaKoEf0!fW}Da z#^j+J?kR4K?gExw#?-u4W652<*z<@GOh$|?N^n8Mrh4QoXy?g^ltF{#nEgdl_JnSh z9HU3QIw@Oo?tmK{F3Qeu}|1>!3y>z`9HvJUEtl&NYB2wG`|W$`1di*L6KZd~% zdO~Qc@k(AkoYEht46nDsDfou1RL2n^s}PwhjKA{^i~6oDJEzE@D&mm8;w1xd$Y1l4 zfjH!wyksD1$VGV=dVP=PPr%>wf~|RIVBbZ4{HE<3zc7KZ?8kDLNqB>bKSV!l(DHp+ zq7U+p$GP11i zr4rN1myG1awWYSLZigE(&Nq}`2x?zKRG}Z~nTzczbdxKWk+#*Yiyh4uQvh{q>5B=N z($5>Q=h5-XFNmP0VP?5N;Qwjw>O~J)lP$?|P#oJ) zKHSExuvOQT&>-pL#FcFMLv9n2(A!{#lqMkt8!8$@4GuWO1sf7bpoIn;T0y_e*=cB^b0B zNGTALjO9C%3Dau7sZQx#r}SHAwKK$)t@c}Xm@R{ZN?%yc5JxKH!7+SFYCKh|hd*>m zf6hn=BMPA*rOrsHGwsPZvOW^kg6O?C)T@))E@^f`H{ca?gLi=0eoJ5jTnkDT*FA`G zr02z(&?%;UpafxO7bN49uE-Jt$5;!QeL#w4X_g!}ZaMKyDBQ1y$l&fhKfjiz2d1kyfAiFnFssA7+ERyPQ zyg3;k?McQnUZ#s59b{Y&`_b4HmkUGVjE% z^jgjchnAP(&_YPKMc|es%%~T0MxYrmcO!P23)0?ifN-zl^*i243@nVO8cOKh=6H)p zd)imzxoQ(7Nm5@{>dOjT5V#<4x4_*3=LF6P+$V6Kz=Hx0HYa1LOzEWa91MS>+u-cV zc<-b!nPP%D*;KG?PB3kqRy`RDy zyDLS&;O^722-X>#Oirg1sP4~rAEaEngZA2P7K5#Y-OyxZG530>t#q@p1mFbR$k|m= z6Rw@jc4TwOmXv1YpfGeqV(ppIC!l`LJTHJZ7(^^5d|nQs(uK0T5CuD%R52=ida>K+ z>`H^ZOEf~CIHl_V(R+?}jx)_!5dCYr-?gA`l7K-Ai0&?(qXlI3oSO_AP(mMcN(b+C zn+@)TG!34R|HSdY<#nk1CDxwtU=dlR(7fPNx=^MB?1M;#fPgMb5d&w?PvzQ{k)D z$H^`OX>dg*bGl4qe)%(H382UslK+SWB9b}}Vr4u?>U=c+VrBlt3Q1iiXJ)Kl#;g`d z@k%J;N+76fq10>WX>FltakK`Qh0EiZ7s6JRmSRhLO>4*{5T*`Fdy6xgfaS-7_@mi+ zW{#$z^iSXT9H8+@CGCv&O-x{1VW;pK0fp{J6tMQ}RDs5Mx`Fd*oRNi>YLY&|EF4cS zYO@VHVIbJA+=4KNOJ32bOLV37(1%D8FeWBF8AayX5 zI+!}~z!N{qoNc-{%LFB4WMT>Kbr501PNW(VnPNq4GEst`TrA;~-r$1$9R?>E zyodCsnf}rORLZxKzv$J}e$71RAV0@{6Y+CS+l2H4KoY0zM3&E&860A81PI372has( zyw2d4aqwKaO7L?AZ!>t8!C}fq3?yT}7fI3hf!QIZ{E@+j5L2afWBLIGpECFuf!9dr zA8n>}!Lsj8N$^cf)Sl!_Uf9Oev@S*xFewA3$Sp3&?lQ&dEx4M2gW(~zqDk;Yk2?U) zUyLnR92mV6ve%#19pS4hv)dTlM(NBEx|@N^=$mm4XoXKCc9!2s;(T%Hh&N%iCSzkV z-NX+kvi|0aKNd^dlYBt-=6T3G=gjl4d}_%yKt=E_L(D@?4j(`;Pp8wFVw*}x8et9z zyIIA81X(npKpJQD;O+cS+E6%*0S(6NM)}r(y3;Kvr3s?j@%=?O3TPrNt>OV$3_4TG z0iu{%4)Em-s>TH#9x{{4Yeo|i%o|5q-n6N(qJ9gQqZL@d)bk48vyY=M&tsUb|9{>WX$0#CWL-t=bW4o(p=CW!@eXK_UHID3!j1b=8pB_ zn|MVt>I}t3-O%s-K^i`1@BOdHKIkDy+yEQ3*Lub_4;^Hw>k^>VP;3*lg-^DKiy*iFDZI- zKYlAazu}=#Zs39EWw&wpzyBu#`2q-}ZwvO7n9U2^#*|_S7Z@!d^WeeF@j{7KG_(OHO<8bIIx-&c6 z*{3G`9PTS}cVc@X9JBC;4_4KiFYupI%Pp0cNLOL`*1@rffw8fHtrNXlCkMuhD+;}Z z;?}Y8mBqgCv4It1#btvN#g+K}X{okKFF~bNS*ms8Bz7ikgKOwevy)Y%Rk2je1&MTFJ-8eJnBi_gr$~tVR2j(p(-FOq)T2tfw+QUTV>tBqyni@YpjjGOF zu5h&9t+yrGvlwR^EbKU`lw>49%xmQdM>tEWtH#*}3R9-OFwgOI%0?WMlDuTe8F?n1 za4V)lBIEr#ge8&Hmb&ZlXI;Hh$i3fvRzuHsyVw@QI3CZt&vWjLaf3qJP;gX@%lNA2 zEcFBk%zt7)fmG^bqpI+}Bw~l-bt^BuWT~mTwfEMqy%)7(BSgiPCzh#Bt;@+Yj<>AK z$g=v3EYpnaF;(xWTiLfQwGY3ohch$nR@s!W9&YQ~uw4-ejq7$`J2ZA278TGQ!vTah zLc0S7kb|R&8kc3vQKUqmnvxZ%5NjHuky3~aEJ|!yjv?e|l3fqUN< zxc{c}7!q$G?t5@A_!sU^0fer$5KM66rr}K+r0o=gidjbkEvtPr7Sow)G6pOz6!ISWI!NY0raqRV}Bg6QBz{vZUgzv3tNiO}!mb~SK7Sm-QwnbW zyN9Qz*K?=lb}m^bC-d9|s;_@90hLP8M9F;}`#(zn>w46HgDp$ZiezaH&)Mp9;ysFc z3r2lR8*nKai(%SOo7E;bK5m9<=0-s4aEk5*#MdEw7%;Ej#XiFr;b`a0OF!j@su3ee z>RI?2Mg+gK25+F9e_$_Kvj zHz&h0b9aD?4S3Rvju(BRMsQEzBvoqUcW?%)706^5Sk=zjEVl_<(_NK5d(jqXUWlXX z_T{@7m~^o$_eU={ECe^}Zb#khQSGb8%?Q&WZf{g)9+H}tRw#wHeb9Ogb GetQuotesChannel(this SocketSlashCommand cmd, DiscordSocketClient client) { + ulong? channelId = Program.Storage.GetQuoteChannel(cmd.GuildId!.Value); + if (channelId == null) { + return null; + } + + IChannel channel = await client.GetChannelAsync(channelId.Value); + return channel as ITextChannel; + } + + public static async Task GetQuotesChannel(this DiscordSocketClient client, ulong guild) { + ulong? channelId = Program.Storage.GetQuoteChannel(guild); + if (channelId == null) { + return null; + } + + IChannel channel = await client.GetChannelAsync(channelId.Value); + return channel as ITextChannel; + } + + public static MessageReference ToReference(this SocketMessage msg) { + return new MessageReference(msg.Id); + } +} \ No newline at end of file