using Discord; using Discord.Commands; using Discord.WebSocket; using NerdsTeaserBot.Misc; using NerdsTeaserBot.Modules.Extensions; using NerdsTeaserBot.Modules.Models; using Nerd_STF.Extensions; using Nerd_STF.File.Saving; using Nerd_STF.Lists; using System; using System.IO; using System.Threading.Tasks; using static Discord.Format; using static NerdsTeaserBot.Const; namespace NerdsTeaserBot.Modules.Commands { [Group("mm")] [Name("Modmail")] [Summary("Commands about Modmail and it's functions")] public class ModmailModule : ModuleBase { [Command("anon")] [RequireUserPermission(GuildPermission.ManageMessages)] [Summary("Sends an anonymous message to the recipient")] public async Task Anon() { SocketTextChannel ch = (SocketTextChannel)Context.Channel; if (!ch.CategoryId.HasValue) { LogModule.LogMessage(LogSeverity.Error, "Channel is not a ModMail ticket channel (Channel is not in any category)"); return; } if (ch.CategoryId != Data.consts.Data.modmailCategory) { LogModule.LogMessage(LogSeverity.Error, "Channel is not a ModMail ticket channel (Channel category is not the specified ModMail category. Use " + Code("n;mm category set") + " to set it!)"); return; } if (ch.Topic.Trim().ToLower().Contains("archive")) { LogModule.LogMessage(LogSeverity.Error, "ModMail ticket has been archived"); return; } ulong userId = ulong.Parse(ch.Topic.Substring(9, 18)); EmbedAuthorBuilder a = new() { IconUrl = Context.Client.CurrentUser.GetAvatarUrl(), Name = "From ModMail Recipients", }; await SendModMailMessage(a, Context.Message, await Internals.client.GetUser(userId).GetOrCreateDMChannelAsync()); if (new List(await ch.GetMessagesAsync().FlattenAsync()).FindAll(x => x != null).Length < 4) await SuccessModMailMessage(ch, true); } [Command("archive")] [RequireUserPermission(GuildPermission.ManageMessages & GuildPermission.ManageChannels)] [Summary("Pauses communication in a ModMail thread and saves all communication in a text file (this does not delete the ticket)")] public async Task Archive([Remainder][Summary("The reason you have to archive the ticket")] string reason = "No Reason Specified") { if (Context.Channel is INestedChannel nest) { SocketTextChannel ch = (SocketTextChannel)Context.Channel; if (nest.CategoryId != Data.consts.Data.modmailCategory) { LogModule.LogMessage(LogSeverity.Error, "Channel is not a ModMail ticket channel (Channel category is not the specified ModMail category. Use " + Code("n;mm category set") + " to set it!)"); return; } if (ch.Topic.Trim().ToLower().Contains("archive")) { LogModule.LogMessage(LogSeverity.Error, "ModMail ticket has already been archived"); return; } ulong userId = ulong.Parse(ch.Topic.Substring(9, 18)); IUser user = Context.Client.GetUser(userId); User usr = Data.users.Data.FindOrDefault(x => x.userID == userId); int num = 0; if (usr != null) num = usr.tickets - 1; string folder = Data.appPath + "/data/files-" + Context.Guild.Id + "/tickets/"; TextFile save = new(folder + userId + "-" + num + ".ticket"); string add = "Nerd's Teaser Bot - Ticket Transcript\n" + "=========================\n" + "Ticket Info is below:\n" + "\n" + "Archiving Reason: " + reason + "\n" + "=========================\n" + "User Info is below:\n" + "\n" + "Full Name: "; if (user == null) add += "-- Unknown --"; else add += user.Username + "#" + user.Discriminator; add += "\n"; add += "User ID: "; if (user == null) add += "-- Unknown --"; else add += user.Id.ToString(); add += "\n"; add += "User Is Muted: "; if (usr == null || usr.currentMute == null) add += "False"; else add += usr.currentMute.IsMuted.ToString(); add += "\n"; add += "Ticket Count: "; if (usr == null) add += "1 Ticket"; else { add += usr.tickets + " Ticket"; if (usr.tickets != 1) add += "s"; } add += "\n"; add += "Warn Count: "; if (usr == null || usr.warns is null) add += "0 Warns"; else { add += usr.warns.Length + " Warns"; if (usr.warns.Length != 1) add += "s"; } add += "\n"; add += "=========================\n" + "-- Begin Transcript --\n" + "\n"; List messages = new List(await ch.GetMessagesAsync(1000).FlattenAsync()).FindAll(x => x != null); messages.Reverse(); foreach (IUserMessage msg in messages) { add += msg.Author.Username + "#" + msg.Author.Discriminator; if (msg.Author.IsBot) add += " [BOT]"; add += " (" + msg.Timestamp.DateTime + "):\n"; if (msg.Content != null && msg.Content != "") add += msg.Content.Replace("\n", "\n ") + "\n"; List embeds = new List(msg.Embeds).FindAll(x => x != null); for (int i = 0; i < embeds.Length; i++) { EmbedBuilder embed = embeds[i].ToEmbedBuilder(); add += "Embed " + (i + 1) + "/" + embeds.Length + ":\n"; if (embed.Author != null && embed.Author != new EmbedAuthorBuilder()) { add += " Author:\n" + " Icon Url: " + (embed.Author.IconUrl ?? "-- None --") + "\n" + " Name: " + (embed.Author.Name ?? "-- None --").Replace("\n", "\n ") + "\n" + " Url: " + (embed.Author.Url ?? "-- None --") + "\n"; } if (embed.Description != null && embed.Description != "") add += " Description: " + embed.Description + "\n"; if (embed.Fields != null && embed.Fields != new System.Collections.Generic.List()) { for (int j = 0; j < embed.Fields.Count; j++) { add += " Field " + (j + 1) + "/" + embed.Fields.Count + ":\n" + " Name: " + (embed.Fields[j].Name ?? "-- None --").Replace("\n", "\n ") + "\n" + " Value: " + (embed.Fields[j].Value ?? "-- None --").ToString().Replace("\n", "\n ") + "\n"; } } if (embed.Footer != null && embed.Footer != new EmbedFooterBuilder()) { add += " Footer:\n" + " Icon Url: " + (embed.Footer.IconUrl ?? "-- None --") + "\n" + " Text: " + (embed.Footer.Text ?? "-- None --").Replace("\n", "\n ") + "\n"; } if (embed.ImageUrl != null && embed.ImageUrl != "") add += " Image Url: " + embed.ImageUrl + "\n"; if (embed.ThumbnailUrl != null && embed.ThumbnailUrl != "") add += " Thumbnail Url: " + embed.ThumbnailUrl + "\n"; if (embed.Timestamp.HasValue && embed.Timestamp.Value.DateTime != DateTime.MinValue) add += " Timestamp: " + embed.Timestamp.Value.DateTime + "\n"; if (embed.Title != null && embed.Title != "") add += " Title: " + embed.Title.Replace("\n", "\n ") + "\n"; if (embed.Url != null && embed.Url != "") add += " Url: " + embed.Url + "\n"; } List attachments = new List(msg.Attachments).FindAll(x => x != null); for (int i = 0; i < attachments.Length; i++) { Attachment att = attachments[i]; add += "Attachment " + (i + 1) + "/" + attachments.Length + ":\n" + " Filename: " + att.Filename + "\n" + " ID: " + att.Id + "\n" + " Size: " + att.Size + " Byte"; if (att.Size != 1) add += "s"; add += "\n" + " Url: " + att.Url; } add += "\n\n"; } add += "-- End Transcript --\n" + "=========================\n"; save.Data = add; Directory.CreateDirectory(folder); save.Save(); EmbedBuilder e = new() { Color = Color.Red, Description = "This ticket has been archived and closed for reason: " + Code(reason), Footer = new() { Text = "Reply to create a new ticket" }, Timestamp = DateTime.Now, Title = "Ticket Archived", }; Modify(x => { x.Name = user.Username + user.Discriminator + "-archived"; x.Topic = "User: " + user.Mention + " (ARCHIVED)"; }); await user.DMUserAsync("", false, e.Build()); e.Description += ". The ticket transcript is attached above"; e.Footer = null; await Context.Channel.SendFileAsync(save.Path, "", false, e.Build()); void Modify(Action func) => ch.ModifyAsync(func); } else { LogModule.LogMessage(LogSeverity.Error, "Channel is not a ModMail ticket channel (Channel is not in any category)"); return; } } [Command("category get")] [Summary("The current modmail category for the bot")] public async Task CategoryGet() { SocketCategoryChannel c = null; if (Data.consts.Data.modmailCategory != default) { foreach (SocketGuild g in Context.Client.Guilds) { SocketCategoryChannel c2 = new List(g.CategoryChannels).FindOrDefault(x => x.Id == Data.consts.Data.modmailCategory); if (c2 != default) { c = c2; break; } } if (c == null) { Data.consts.Data.modmailCategory = default; LogModule.LogMessage(LogSeverity.Error, "The Modmail category found was no longer accessible by the bot, so it was reset"); return; } } EmbedBuilder e = new() { Color = Colors.DefaultColor, Description = "No current modmail category found. Use " + Code("n;mm category set") + " to set it!", Timestamp = DateTime.Now, Title = "Current Modmail Category", }; if (Data.consts.Data.modmailCategory != default && c != null) e.Description = "The current modmail category is " + Italics(c.Name) + " in the server " + Italics(c.Guild.Name + " (" + Code(c.Guild.Id.ToString()) + ")"); await ReplyAsync("", false, e.Build()); } [Command("category reset")] [RequireUserPermission(GuildPermission.ManageChannels)] [Summary("Resets the changelog channel of the bot back to none")] public async Task CategoryReset() { Data.consts.Data.modmailCategory = default; EmbedBuilder e = new() { Color = Color.Red, Description = "Successfully set the current modmail category to: " + Code("None"), Timestamp = DateTime.Now, Title = "Reset Modmail Category", }; await ReplyAsync("", false, e.Build()); } [Command("category set")] [RequireUserPermission(GuildPermission.ManageChannels)] [Summary("Sets the given category to use for modmail tickets")] public async Task CategorySet([Summary("The category to use for modmail tickets")] SocketCategoryChannel cat) { if (!new List(Context.Client.Guilds).Any(x => new List(x.CategoryChannels).Contains(cat))) { LogModule.LogMessage(LogSeverity.Error, "Category is not found in any of the bot's known servers"); return; } ulong old = Data.consts.Data.modmailCategory; Data.consts.Data.modmailCategory = cat.Id; EmbedBuilder e = new() { Color = Colors.DefaultColor, Description = "Successfully set modmail category to: " + Italics(cat.Name) + " in the server " + Italics(cat.Guild.Name + " (" + Code(cat.Guild.Id.ToString()) + ")"), Timestamp = DateTime.Now, Title = "Set modmail category", }; SocketCategoryChannel oldC = null; foreach (SocketGuild g in Context.Client.Guilds) { SocketCategoryChannel tempO = new List(g.CategoryChannels).FindOrDefault(x => x.Id == old); if (tempO == default) continue; oldC = tempO; break; } if (oldC != null) e.Description += "\n(Previous: " + Italics(oldC.Name) + " in the server " + Italics(oldC.Guild.Name + " (" + Code(oldC.Guild.Id.ToString()) + ")") + ")"; await ReplyAsync("", false, e.Build()); } [Command("unarchive")] [RequireUserPermission(GuildPermission.ManageChannels & GuildPermission.ManageChannels)] [Summary("Resumes communication in an archived ModMail thread and saves all communication in a text file")] public async Task Unarchive([Remainder][Summary("The reason you have to unarchive the ticket")] string reason = "No Reason Specified") { if (Context.Channel is INestedChannel nest) { SocketTextChannel ch = (SocketTextChannel)Context.Channel; if (nest.CategoryId != Data.consts.Data.modmailCategory) { LogModule.LogMessage(LogSeverity.Error, "Channel is not a ModMail ticket channel (Channel category is not the specified ModMail category. Use " + Code("n;mm category set") + " to set it!)"); return; } if (!ch.Topic.Trim().ToLower().Contains("archive")) { LogModule.LogMessage(LogSeverity.Error, "ModMail ticket has not been archived"); return; } ulong userId = ulong.Parse(ch.Topic.Substring(9, 18)); IUser user = Context.Client.GetUser(userId); SocketTextChannel other = new List(Context.Guild.TextChannels).FindAll(x => x.Category == ch.Category).FindOrDefault(x => x.Topic.Trim().StartsWith("User: " + user.Mention) && !x.Topic.Trim().ToLower().Contains("archive")); if (other != default) { LogModule.LogMessage(LogSeverity.Error, "There is already an open ticket for this user, " + other.Mention); return; } EmbedBuilder e = new() { Color = Color.Green, Description = "This ticket has been unarchived and reopened for reason: " + Code(reason), Footer = new() { Text = "Answering ModMail tickets will now go through this ticket" }, Timestamp = DateTime.Now, Title = "Ticket Unarchived", }; Modify(x => { x.Name = user.Username + user.Discriminator; x.Topic = "User: " + user.Mention; }); await user.DMUserAsync("", false, e.Build()); e.Footer = null; await ReplyAsync("", false, e.Build()); void Modify(Action func) => ch.ModifyAsync(func); } else { LogModule.LogMessage(LogSeverity.Error, "Channel is not a ModMail ticket channel (Channel is not in any category)"); return; } } // end commands public static async Task ModmailHandler(SocketMessage message) { if (message is SocketUserMessage msg) { if (msg.Author.Id == ID) return; if (msg.Content.Trim().StartsWith(";") || msg.Content.Trim().StartsWith("n;") || msg.Content.Trim().StartsWith("m;")) return; EmbedBuilder e; if (msg.Channel is SocketDMChannel) { SocketCategoryChannel cat = null; foreach (SocketGuild g in Internals.context.Client.Guilds) { SocketCategoryChannel tempCat = new List(g.CategoryChannels).FindOrDefault(x => x.Id == Data.consts.Data.modmailCategory); if (tempCat == default) continue; cat = tempCat; break; } if (cat == null) return; Data.TryLoadAll(cat.Guild.Id); ITextChannel ch = null; foreach (SocketGuildChannel tCh in cat.Channels) if (tCh is SocketTextChannel textCh && textCh.Topic == "User: " + msg.Author.Mention) ch = textCh; e = new() { Color = Colors.DefaultColor, Timestamp = DateTime.Now, }; if (ch == null) { ch = await cat.Guild.CreateTextChannelAsync(msg.Author.Username + msg.Author.Discriminator, x => { x.CategoryId = cat.Id; x.Topic = "User: " + msg.Author.Mention; }); e.Description = "Info on the thread creator is below"; e.Title = "New ModMail Thread Created"; e.AddField("User Full Name", Code(msg.Author.Username + "#" + msg.Author.Discriminator), true); e.AddField("User Id", Code(msg.Author.Id.ToString()), true); e.AddField("User Mention", msg.Author.Mention, true); int count = 0; User usr = Data.users.Data.FindOrDefault(x => x.userID == msg.Author.Id); if (usr == default) { usr = new() { currentMute = null, tickets = 1, userID = msg.Author.Id, }; count = 1; Data.users.Data.Add(usr); } else { usr.tickets++; count = usr.tickets; } bool muted; if (usr.currentMute == null) muted = false; else muted = usr.currentMute.IsMuted; e.AddField("User is Muted", Code(muted.ToString()), true); string s = ""; if (count != 1) s += "s"; e.AddField("Ticket Count", Code(count + " Ticket" + s), true); if (usr.warns is null) count = 0; else count = usr.warns.Length; s = ""; if (count != 1) s += "s"; e.AddField("Warn Count", Code(count + " Warn" + s), true); await ch.SendMessageAsync("", false, e.Build()); await ch.SyncPermissionsAsync(); await SuccessModMailMessage(await msg.Author.GetOrCreateDMChannelAsync(), true); } EmbedAuthorBuilder a = new() { IconUrl = msg.Author.GetAvatarUrl(), Name = "From " + msg.Author.Username + "#" + msg.Author.Discriminator, }; await SendModMailMessage(a, msg, ch); Data.SaveAll(cat.Guild.Id); } else if (msg.Channel is SocketTextChannel ch) { if (!ch.CategoryId.HasValue) return; if (ch.CategoryId != Data.consts.Data.modmailCategory) return; if (ch.Topic.Trim().ToLower().Contains("archive")) return; ulong userId = ulong.Parse(ch.Topic.Substring(9, 18)); EmbedAuthorBuilder a = new() { IconUrl = msg.Author.GetAvatarUrl(), Name = "From " + msg.Author.Username + msg.Author.Discriminator, }; await SendModMailMessage(a, msg, await Internals.client.GetUser(userId).GetOrCreateDMChannelAsync()); if (new List(await ch.GetMessagesAsync().FlattenAsync()).FindAll(x => x != null).Length < 4) await SuccessModMailMessage(ch, true); } } } public static async Task SendModMailMessage(EmbedAuthorBuilder a, IMessage msg, IMessageChannel ch) { EmbedBuilder e = new() { Author = a, Color = Colors.DefaultColor, Description = msg.Content, Title = "ModMail Message Recieved", }; List attachments = new(msg.Attachments); if (attachments.Length == 1) e.ImageUrl = attachments[0].Url; IUserMessage sent = await ch.SendMessageAsync("", false, e.Build()); if (attachments.Length > 1) { for (int i = 0; i < attachments.Length; i++) { e.Author = null; e.Description = "This ModMail message has been send more than one attachment, and as such must be broken down into several embeds"; e.ImageUrl = attachments[i].Url; e.Title = "ModMail Message Attachment " + (i + 1) + "/" + attachments.Length; await sent.ReplyAsync("", false, e.Build()); } } } public static async Task SuccessModMailMessage(IMessageChannel ch, bool appearsOnce = false) { EmbedBuilder e = new() { Color = Colors.DefaultColor, Description = "Your message has been sent to the ModMail recievers, and they will get back to you as soon as possible!", Timestamp = DateTime.Now, Title = "ModMail Message Sent", }; if (appearsOnce) e.WithFooter("This message will appear only once per ticket"); await ch.SendMessageAsync("", false, e.Build()); } } }