using Discord; using Discord.Commands; using Discord.WebSocket; using RMSoftware.IO; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Net; using System.IO; using System.Net.Http; namespace RMSoftware.ModularBot { public class CoreModule : ModuleBase { DiscordSocketClient Client { get; set; } CommandService cmdsvr { get; set; } public CoreModule(DiscordSocketClient client, CommandService cmdservice) { Client = client; cmdsvr = cmdservice; } [Command("about"), Summary("Display information about the bot")] public async Task ShowAbout() { EmbedBuilder builder = new EmbedBuilder(); builder.Title = "About"; builder.WithAuthor(Context.Client.CurrentUser); builder.Color = Color.Blue; builder.Description = "A Multi-purpose, multi-module bot designed for discord. Tailor it for your specific server, create your own modules and plug-ins. Includes a core module for custom text-based commands & EXEC functionality"; builder.AddField("Copyright", "Copyright © 2017 RMSoftware Development"); builder.AddField("Version", Assembly.GetExecutingAssembly().GetName().Version.ToString(3)); builder.WithFooter("RMSoftware.ModularBot, created by rmsoft1"); await Retry.Do(async () => await Context.Channel.SendMessageAsync("", false, builder.Build()), TimeSpan.FromMilliseconds(140)); } [Command("uptime"),Summary("Displays how long the bot has been connected")] public async Task ShowUptime([Remainder]string args=null) { var delta = DateTime.Now - Program.StartTime; string format = string.Format("I've been alive and well for **{0}** hours, **{1}** minutes, and **{2}** seconds!", Math.Floor(delta.TotalHours).ToString("n0"), delta.Minutes, delta.Seconds); await Retry.Do(async () => await Context.Channel.SendMessageAsync(format + " " + args), TimeSpan.FromMilliseconds(140)); } [Command("status"), Summary("Set the bot's 'Playing' status"), Remarks("[CMDMgmt]")] public async Task setStatus([Remainder]string args = null) { await Client.SetGameAsync(args); } [Command("streamstatus"), Summary("Set the bot's status to streaming on twitch, with twitch url and custom text."), Remarks("[CMDMgmt]")] public async Task setStatus(string streamurl, [Remainder]string args) { await Client.SetGameAsync(args, streamurl, StreamType.Twitch); } [Command("STOPBOT",RunMode=RunMode.Async), Summary("Shutdown the bot"), RequireOwner, Remarks("[CMDMgmt]")] public async Task StopBot() { if (Program.LOG_ONLY_MODE) { await Context.Channel.SendMessageAsync("Unable to run command. Origin instance started with `-log_only` parameter. Stop it from console or UI Host instead."); return; } await Context.Channel.SendMessageAsync("**[BotMaster]** called ***StopBot***... *Ending Session*"); DiscordSocketClient c = (DiscordSocketClient)Context.Client; Program.BCMDStarted = false; await c.SetGameAsync(""); await Task.Delay(1000); await c.SetStatusAsync(UserStatus.Invisible); await Task.Delay(1000); Program.BCMDStarted = false; await c.StopAsync(); await Task.Delay(3000);//Allow the bot to shut down fully before telling Main() to scream at user to finger the keyboard to close the console. Program.discon = true; } #if DEBUG [Command("Session", RunMode = RunMode.Async), Summary("Shutdown the bot with a session error."), RequireOwner, Remarks("[CMDMgmt]")] public async Task InvalidateSession() { if (Program.LOG_ONLY_MODE) { await Context.Channel.SendMessageAsync("Unable to run command. Origin instance started with `-log_only` parameter. Stop it from console or UI Host instead."); return; } await Context.Channel.SendMessageAsync("`CRITICAL: Instance owner triggered a session invalidation. Immediately terminating session and calling for restart.`"); await Task.Delay(200); await Context.Channel.SendMessageAsync("`CRITICAL: If restart fails, please verify the install & ensure you can properly contact discord API.`"); await Task.Delay(400); await Context.Channel.SendMessageAsync("`CRITICAL: This command is for DEBUG builds only!`"); DiscordSocketClient c = (DiscordSocketClient)Context.Client; Program.BCMDStarted = false; await c.SetGameAsync(""); await Task.Delay(1000); await c.SetStatusAsync(UserStatus.Invisible); await Task.Delay(1000); Program.BCMDStarted = false; await c.StopAsync(); await Task.Delay(3000);//Allow the bot to shut down fully before telling Main() to scream at user to finger the keyboard to close the console. Program.discon = true; Program.CriticalError = true; Program.crashException = new Discord.Net.WebSocketClosedException(4007, "Forced invalidate session."); } [Command("Throw", RunMode = RunMode.Async), Summary("Shutdown the bot with a session error."), RequireOwner, Remarks("[CMDMgmt]")] public async Task crash() { if (Program.LOG_ONLY_MODE) { await Context.Channel.SendMessageAsync("Unable to run command. Origin instance started with `-log_only` parameter. Stop it from console or UI Host instead."); return; } await Context.Channel.SendMessageAsync("`CRITICAL: Instance owner triggered a manual crash!`"); await Task.Delay(200); await Context.Channel.SendMessageAsync("`CRITICAL: If restart fails, please verify the install & ensure you can properly contact discord API.`"); await Task.Delay(400); await Context.Channel.SendMessageAsync("`CRITICAL: This command is for DEBUG builds only!`"); DiscordSocketClient c = (DiscordSocketClient)Context.Client; Program.BCMDStarted = false; await c.SetGameAsync(""); await Task.Delay(1000); await c.SetStatusAsync(UserStatus.Invisible); await Task.Delay(1000); Program.BCMDStarted = false; await c.StopAsync(); await Task.Delay(3000);//Allow the bot to shut down fully before telling Main() to scream at user to finger the keyboard to close the console. Program.discon = true; Program.CriticalError = true; Program.crashException = new Exception("THIS IS A MANUAL DEBUG CRASH!"); throw (Program.crashException); } #endif [Command("RESTARTBOT", RunMode = RunMode.Async), Summary("Restart the bot"), RequireOwner, Remarks("[CMDMgmt]")] public async Task RestartBot() { if(Program.LOG_ONLY_MODE) { await Context.Channel.SendMessageAsync("Unable to run command. Origin instance started with `-log_only` parameter. Restart it from console or UI Host instead."); return; } await Context.Channel.SendMessageAsync("**[BotMaster]** called ***RestartBot***... *Ending Session, then restarting the program...*"); DiscordSocketClient c = (DiscordSocketClient)Context.Client; Program.BCMDStarted = false; await c.SetGameAsync(""); await Task.Delay(1000); await c.SetStatusAsync(UserStatus.Invisible); await Task.Delay(1000); Program.BCMDStarted = false; await c.StopAsync(); await Task.Delay(3000);//Allow the bot to shut down fully before telling Main() to scream at user to finger the keyboard to close the console. Program.RestartRequested = true; Program.discon = true; } [Command("addcmd"), Summary("Add a custom command to the bot"),RequireContext(ContextType.Guild), Remarks("[CMDMgmt]")] public async Task AddCmd(string cmdTag, bool DevCmdOnly, bool lockToGuild, [Remainder]string action) { SocketGuildUser user = ((SocketGuildUser)Context.Message.Author); SocketMessage arg = Context.Message as SocketMessage; if (Program.rolemgt.CheckUserRole(user))//If user has a role that is in the database, it is good. { try { Program.LogToConsole(new LogMessage(LogSeverity.Info,"CmdMgmt","User has a role in cmdMgrDB")); string tosend = ""; if (action.StartsWith(Program.CommandPrefix.ToString())) { tosend = "Haha, you're funny. This bot will not run commands with nested commands. *That's dumb*."; await Retry.Do(async () => await Context.Channel.SendMessageAsync(tosend), TimeSpan.FromMilliseconds(140)); return; } if (!lockToGuild) { tosend = Program.ccmg.AddCommand(cmdTag, action, DevCmdOnly); } if (lockToGuild) { tosend = Program.ccmg.AddCommand(cmdTag, action, DevCmdOnly, Context.Guild.Id); } IUserMessage a = await Retry.Do(async () => await Context.Channel.SendMessageAsync(tosend), TimeSpan.FromMilliseconds(140));//This is something to try, if it works, then boom! return; } catch (System.Net.Http.HttpRequestException ex) { await arg.Channel.SendMessageAsync("The request failed due to an API related http error that I can't sort out right now... please forgive me... (The command was most likely added anyway~)"); Program.LogToConsole(new LogMessage(LogSeverity.Error,"CritERR",ex.Message,ex)); } catch (Exception ex) { Program.LogToConsole(new LogMessage(LogSeverity.Error, "CritERR", ex.Message, ex)); ConsoleColor Last = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = Last; } return; } await Retry.Do(async () => await Context.Channel.SendMessageAsync("HEY! You don't have permission to do that!"), TimeSpan.FromMilliseconds(140)); return; } [Command("addcmds"), Summary("Add a series of commands from attached file."), RequireContext(ContextType.Guild), Remarks("[CMDMgmt]")] public async Task AddCmds() { SocketGuildUser user = ((SocketGuildUser)Context.Message.Author); SocketMessage arg = Context.Message as SocketMessage; if (Program.rolemgt.CheckUserRole(user))//If user has a role that is in the database, it is good. { if (Context.Message.Attachments.Count < 1) { await Retry.Do(async () => await Context.Channel.SendMessageAsync("You must attach a file."), TimeSpan.FromMilliseconds(140)); return; } var a = Context.Message.Attachments.First(); //TODO: Download, process and merge into command database. return; } await Retry.Do(async () => await Context.Channel.SendMessageAsync("HEY! You don't have permission to do that!"), TimeSpan.FromMilliseconds(140)); return; } [Command("editcmd"), Summary("edit an existing custom command"), RequireContext(ContextType.Guild), Remarks("[CMDMgmt]")] public async Task editcmd(string cmdTag, bool newDevCmdOnly, [Remainder]string newaction) { SocketGuildUser user = ((SocketGuildUser)Context.Message.Author); SocketMessage arg = Context.Message as SocketMessage; if (Program.rolemgt.CheckUserRole(user)) { try { Program.LogToConsole(new LogMessage(LogSeverity.Info,"CmdMgmt","User has required permission.")); string tosend = ""; if (newaction.StartsWith(Program.CommandPrefix.ToString())) { tosend = "Haha, you're funny. This bot will not run commands with nested commands. *That's dumb*."; await Retry.Do(async () => await Context.Channel.SendMessageAsync(tosend), TimeSpan.FromMilliseconds(140)); return; } tosend = Program.ccmg.EditCommand(cmdTag, newaction, newDevCmdOnly); RequestOptions op = new RequestOptions(); op.Timeout = 256; op.RetryMode = RetryMode.AlwaysRetry; IUserMessage a = await Retry.Do(async () => await Context.Channel.SendMessageAsync(tosend), TimeSpan.FromMilliseconds(140)); return; } catch (AggregateException ex) { await arg.Channel.SendMessageAsync("The request failed (MANY TIMES) due to an API related http error that I can't sort out right now... please forgive me... (The command was most likely changed anyway~)"); Program.LogToConsole(new LogMessage(LogSeverity.Error,"CritERR",ex.Message,ex)); ConsoleColor Last = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = Last; } catch (Exception ex) { Program.LogToConsole(new LogMessage(LogSeverity.Error, "CritERR", ex.Message, ex)); ConsoleColor Last = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = Last; } return; } await Retry.Do(async () => await Context.Channel.SendMessageAsync("Hey " + arg.Author.Mention + ", You don't have permission to use this command!"), TimeSpan.FromMilliseconds(140)); return; } [Command("delcmd"), Summary("Remove specified custom command."), RequireContext(ContextType.Guild), Remarks("[CMDMgmt]")] public async Task delcmd(string cmdTag) { SocketGuildUser user = ((SocketGuildUser)Context.Message.Author); SocketMessage arg = Context.Message as SocketMessage; if (Program.rolemgt.CheckUserRole(user)) { try { Program.ccmg.DeleteCommand(cmdTag); RequestOptions op = new RequestOptions(); op.Timeout = 256; op.RetryMode = RetryMode.AlwaysRetry; await Retry.Do(async () => await Context.Channel.SendMessageAsync("Command removed!make sure to save."), TimeSpan.FromMilliseconds(140)); } catch (AggregateException ex) { await arg.Channel.SendMessageAsync("The request failed (MANY TIMES) due to an API related http error that I can't sort out right now... please forgive me... (The command probably still got removed though)"); Program.LogToConsole(new LogMessage(LogSeverity.Error, "CritERR", ex.Message, ex)); ConsoleColor Last = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = Last; } catch (Exception ex) { await arg.Channel.SendMessageAsync("Command is probably removed, but I threw some kind of error... My master will look into it..."); Program.LogToConsole(new LogMessage(LogSeverity.Error, "CritERR", ex.Message, ex)); ConsoleColor Last = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = Last; } } return; } //TODO: remove the attachment property! -- STREAM IT [Command("listcmd"), Summary("Shows a list of available commands."), RequireContext(ContextType.Guild)] public async Task listCmds() { CommandList commandList = new CommandList(Client.CurrentUser.Username); INICategory[] cmdcats = Program.ccmg.GetAllCommand(); #region CORE foreach (CommandInfo item in cmdsvr.Commands) { string group = item.Module.Aliases[0] + " "; string sum = item.Summary; if (string.IsNullOrWhiteSpace(group)) { group = "";//Command's groupAttribute? } if (item.Module.Name == "CoreModule") { if (string.IsNullOrEmpty(sum)) { sum = "No summary was provided."; } } string usage = Program.CommandPrefix + group + item.Name + " "; foreach (var param in item.Parameters) { if (param.IsOptional) { usage += $"[{param.Type.Name} {param.Name}] "; } if (!param.IsOptional) { usage += $"<{param.Type.Name} {param.Name}> "; } } commandList.AddCommand(Program.CommandPrefix + group + item.Name, item.Remarks == "[CMDMgmt]", item.Module.Name == "CoreModule", sum, usage); } #endregion #region Custom Commands foreach (INICategory item in cmdcats) { if (item.CheckForEntry("guildID")) { if (item.GetEntryByName("guildID").GetAsUlong() != Context.Guild.Id) { continue;//if the entry exists, and it doesn't match the guild listcmd was called in, don't add it to the list. } } string commandSummary = ""; string usage = ""; if (item.CheckForEntry("summary")) { commandSummary = item.GetEntryByName("summary").GetAsString(); } if (item.CheckForEntry("usage")) { usage = item.GetEntryByName("usage").GetAsString(); } commandList.AddCommand(Program.CommandPrefix + item.Name, item.GetEntryByName("restricted").GetAsBool(), false, commandSummary, usage); } #endregion try { using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) { using (System.IO.StreamWriter sw = new System.IO.StreamWriter(ms)) { sw.WriteLine(commandList.GetFullHTML()); sw.Flush(); ms.Position = 0; await Context.Channel.SendMessageAsync("**See the attached web document for a full list of commands.**", false); await Context.Channel.SendFileAsync(ms, $"{Context.Guild.Name}_{Context.Client.CurrentUser.Username}_AllCommands.html"); } } } catch (AggregateException ex) { await Context.Channel.SendMessageAsync("Tried to do this THREE different times, and Quite honestly, I just could not do it... I'm sorry..."); Program.LogToConsole(new LogMessage(LogSeverity.Error, "CritERR", ex.Message, ex)); } return; } [Command("save"), Summary("Save the command database."), RequireContext(ContextType.Guild), Remarks("[CMDMgmt]")] public async Task SaveCmd() { SocketMessage arg = Context.Message as SocketMessage; SocketGuildUser user = ((SocketGuildUser)arg.Author); if (Program.rolemgt.CheckUserRole(user)) { Program.LogToConsole(new LogMessage(LogSeverity.Info, "CmdEXEC","User has required permission")); await arg.Channel.SendMessageAsync("Command DB saved."); Program.ccmg.Save(); } return; } [Command("changes"), Summary("Shows what changed in this version")] public async Task ShowChanges() { EmbedBuilder eb = new EmbedBuilder(); eb.WithAuthor("What's New", Client.CurrentUser.GetAvatarUrl(), ""); eb.AddField($"v{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)} (Beta release)", "• Adjusting logging feature... Making logging a globally-accessible system.\r\n• Fixed ERRORS.LOG. It will actually be created again.\r\n Re-worded save prompts."); eb.WithFooter("RMSoftware.ModularBOT"); eb.Color = Color.DarkBlue; RequestOptions op = new RequestOptions(); op.RetryMode = RetryMode.AlwaysRetry; await Context.Channel.SendMessageAsync("**Full version history/change log: http://rms0.org?a=mbChanges**", false, eb.Build(), op); } [Command("shadowban"), RequireContext(ContextType.Guild), RequireOwner, Remarks("[CMDMgmt]")] public async Task IgnoreUser(params IUser[] users) { int count = 0; foreach (var item in users) { if (Program.rolemgt.AddUserToBlacklist(item as SocketUser)) { count++; } } await Context.Channel.SendMessageAsync($"Added `{count}` user(s) to the blacklist. they can no longer access my commands."); } [Command("shadowunban"), RequireContext(ContextType.Guild), RequireOwner, Remarks("[CMDMgmt]")] public async Task UnIgnoreUser(params IUser[] users) { int count = 0; foreach (var item in users) { if (Program.rolemgt.DeleteUserFromBlacklist(item as SocketUser)) { count++; } } await Context.Channel.SendMessageAsync($"Removed `{count}` user(s) from the blacklist. they can now access my commands."); } [Command("addmgrole"), Summary("add a role to CommandMGMT database"), RequireContext(ContextType.Guild),RequireOwner, Remarks("[CMDMgmt]")] public async Task AddRoletoDB(params IRole[] roles) { foreach (var item in roles) { Program.rolemgt.AddCommandManagerRole(item as SocketRole); } await Context.Channel.SendMessageAsync($"Successfully added `{roles.Length}` role(s) to the CommandMGMT database for guild: `{Context.Guild.Name}`."); } [Command("delmgrole"), Summary("Delete a role from CommandMGMT database"), RequireContext(ContextType.Guild), RequireOwner, Remarks("[CMDMgmt]")] public async Task DelRoleFromDB(params IRole[] roles) { foreach (var item in roles) { Program.rolemgt.DeleteCommandManager(item as SocketRole); } await Context.Channel.SendMessageAsync($"Successfully removed `{roles.Length}` role(s) from the CommandMGMT database for guild: `{Context.Guild.Name}`."); } [Command("listmgrole",RunMode=RunMode.Async), Summary("lists the authorized management roles for the guild where the command was called."), RequireContext(ContextType.Guild), RequireOwner, Remarks("[CMDMgmt]")] public async Task listmgroledbguild() { await Context.Channel.SendMessageAsync($"This could take a while..."); await Task.Delay(500); using (Context.Channel.EnterTypingState()) { SocketRole[] items = Program.rolemgt.GetRolesForGuild(Context.Guild as SocketGuild); string roles = ""; foreach (var item in items) { roles += $"{item.Id} - {item.Name}{Environment.NewLine}"; } await Context.Channel.SendMessageAsync($"All authorized management roles for `{Context.Guild.Name}`:\r\n```\r\n{roles}\r\n```"); } } [Command("listallmgroles", RunMode = RunMode.Async), Summary("lists the authorized management roles for all guilds"), RequireContext(ContextType.Guild), RequireOwner, Remarks("[CMDMgmt]")] public async Task listmgroledball() { int zint = 0; await Context.Channel.SendMessageAsync($"This could take a while..."); await Task.Delay(500); using (Context.Channel.EnterTypingState()) { SocketRole[] items = Program.rolemgt.GetAllRoles(); List book = new List(); string roles = ""; foreach (var item in items) { string toAddTo = $"{item.Id} - {item.Name}{Environment.NewLine}"; int len = roles.Length + toAddTo.Length; if(len >600) { len = 0; book.Add(new String(roles.ToCharArray()));//This is probably super dumb, but I want to add the string to the list and change the roles string without affecting the entry in the list. Im doing this to be safe. roles = ""; } roles += $"{item.Guild.Name} - {item.Id} - {item.Name}{Environment.NewLine}"; } book.Add(roles); foreach (string item in book) { if(zint <1) { await Context.Channel.SendMessageAsync($"All authorized management roles in the database:\r\n```\r\n{item}\r\n```"); } if (zint >= 1) { await Context.Channel.SendMessageAsync($"```\r\n{item}\r\n```"); } zint++; SocketGuildUser r = Context.User as SocketGuildUser; string display = r.Nickname ?? r.Username; } } } } }