/// <summary> /// Create a <see cref="XanBotMember"/> from a <seealso cref="DiscordUser"/> and a <seealso cref="DiscordGuild"/> /// </summary> /// <param name="server">The server that this <see cref="XanBotMember"/> exists in.</param> /// <param name="user">The <seealso cref="DiscordUser"/> to create the member from.</param> /// <returns></returns> public static XanBotMember GetMemberFromUser(DiscordGuild server, DiscordUser user) { if (user == null) { return(null); } BotContext ctxForServer = BotContextRegistry.GetContext(server); return(GetMemberFromUser(ctxForServer, user)); }
/// <summary> /// Runs the <see cref="PassiveHandler"/>s for the server that <paramref name="originalMessage"/> was sent in based on the content from the message. /// </summary> /// <param name="originalMessage">The chat message.</param> public static async Task RunPassiveHandlersForMessage(DiscordMessage originalMessage) { BotContext targetContext = BotContextRegistry.GetContext(originalMessage.Channel.Guild); XanBotMember sender = XanBotMember.GetMemberFromUser(targetContext, originalMessage.Author); if (!targetContext.Virtual) { foreach (PassiveHandler handler in targetContext.Handlers) { XanBotLogger.WriteDebugLine(handler.Name); if (await handler.RunHandlerAsync(targetContext, sender, originalMessage)) { XanBotLogger.WriteDebugLine("... returned true"); return; } XanBotLogger.WriteDebugLine("... returned false"); } } else { XanBotLogger.WriteDebugLine("Target context is virtual. Skipping passive handlers."); } }
/// <summary> /// Automatically creates <see cref="Client"/> and connects it, creates <see cref="VoiceClient"/> (if requested), instantiates all bot contexts automatically, starts the <see cref="XanBotConsoleCore"/> update loop, and binds the windows console CTRL+C/CTRL+BREAK to <see cref="Exit(int)>"/> /// </summary> /// <param name="botToken">The token of this discord bot.</param> /// <param name="createVoiceNextClient">If true, <see cref="VoiceClient"/> will be instantiated and allow this bot to connect to voice channels.</param> /// <param name="isTargettingWindows7">Whether or not this bot is running on Windows 7. This is necessary for some init code.</param> /// <param name="yieldUntilGuildsDownloaded">If true, this task will yield until <see cref="DiscordClient.GuildDownloadCompleted"/> is fired.</param> /// <returns></returns> public static async Task InitializeBotAsync(string botToken, bool createVoiceNextClient = false, bool isTargettingWindows7 = false, bool yieldUntilGuildsDownloaded = false) { Console.ForegroundColor = ConsoleColor.Green; XanBotLogger.IsPathLocked = true; if (IsDebugMode) { XanBotLogger.WriteLine("§eInitializing in Debug Mode..."); } else { XanBotLogger.WriteLine("§2Initializing..."); } if (isTargettingWindows7) { Client = new DiscordClient(new DiscordConfiguration { Token = botToken, TokenType = TokenType.Bot, WebSocketClientFactory = new WebSocketClientFactoryDelegate(WebSocket4NetClient.CreateNew), // Remove if this bot is running on machine with Windows 10. }); } else { Client = new DiscordClient(new DiscordConfiguration { Token = botToken, TokenType = TokenType.Bot }); } XanBotLogger.WriteDebugLine("Created DiscordClient."); if (createVoiceNextClient) { VoiceClient = Client.UseVoiceNext(new VoiceNextConfiguration { AudioFormat = new AudioFormat(48000, 2, VoiceApplication.LowLatency) }); XanBotLogger.WriteDebugLine("Created VoiceNextClient."); } XanBotLogger.WriteLine("§2Connecting to Discord..."); await Client.ConnectAsync(); // Client is connected. Create contexts! XanBotLogger.WriteLine("§2Initializing User-Defined Bot Contexts..."); BotContextRegistry.InitializeAllContexts(); XanBotLogger.WriteLine("§2Connecting CommandMarshaller to chat events..."); Client.MessageCreated += async evt => { //XanBotLogger.WriteLine("Hey the message created event fired"); if (evt.Author == Client.CurrentUser) { return; } if (evt.Author.IsBot) { return; } if (CommandMarshaller.IsCommand(evt.Message.Content)) { XanBotLogger.WriteDebugLine("Message was sent and was detected as a command."); await CommandMarshaller.HandleMessageCommand(evt.Message); } else { XanBotLogger.WriteDebugLine("Message was sent but was not a command. Throwing it into the passive handlers."); await CommandMarshaller.RunPassiveHandlersForMessage(evt.Message); } }; #pragma warning disable CS1998 if (yieldUntilGuildsDownloaded) { XanBotLogger.WriteLine("§2Downloading server data from Discord..."); ManualResetEvent completedSignal = new ManualResetEvent(false); Client.GuildDownloadCompleted += async evt => { HasFinishedGettingGuildData = true; completedSignal?.Set(); }; completedSignal.WaitOne(); completedSignal.Dispose(); completedSignal = null; } else { Client.GuildDownloadCompleted += async evt => { HasFinishedGettingGuildData = true; }; } #pragma warning restore CS1998 XanBotLogger.WriteLine("§2Setting up frontend console..."); Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCtrlCExit); XanBotConsoleCore.StartUpdateConsoleTask(); Created = true; XanBotLogger.WriteLine("§aInitialization complete!"); // This fixes an occasional formatting issue where it renders in the typing text color. }
/// <summary> /// Handles a command issued via a Discord message. This does not test if it is a command. Please test with <see cref="IsCommand(string)"/> before running this. /// </summary> /// <param name="originalMessage">The Discord message sent by the command.</param> public static async Task HandleMessageCommand(DiscordMessage originalMessage) { DiscordUser author = originalMessage.Author; BotContext commandContext = BotContextRegistry.GetContext(originalMessage.Channel.Guild); XanBotLogger.WriteDebugLine("Executing command in bot context. Info:"); XanBotLogger.WriteLine(commandContext.ToConsoleString(), isDebugModeOnly: true); XanBotMember member = XanBotMember.GetMemberFromUser(commandContext, author); string text = originalMessage.Content; if (text.ToLower().StartsWith(CommandPrefix.ToLower())) { text = text.Substring(CommandPrefix.Length); } while (AllowSpacesAfterPrefix && text.StartsWith(" ")) { text = text.Substring(1); } string[] allArgs = ArgumentSplitter.SplitArgs(text); string command = allArgs[0]; string[] args = new string[0]; if (allArgs.Length > 1) { args = allArgs.Skip(1).ToArray(); } // Catch case: Prevent excessive command length. if (command.Length > 32) { await ResponseUtil.RespondToAsync(originalMessage, "The command you input is too long."); XanBotLogger.WriteLine(string.Format("§aUser \"§6{0}§a\" issued a command that was considered too long to parse.", member.FullName)); return; } // Catch case: Strip color formatting command.Replace(XanBotLogger.COLOR_CODE_SYM.ToString(), ""); XanBotLogger.WriteDebugLine("Searching for command..."); Command execCommand = null; string cmdLower = command.ToLower(); // Search the context FIRST. That ensures that context commands can override stock commands. foreach (Command cmd in commandContext.Commands) { if (cmd.Name.ToLower() == cmdLower) { execCommand = cmd; XanBotLogger.WriteDebugLine($"Found command [{cmd.Name}] in context."); break; } else { if (cmd.AlternateNames != null) { foreach (string altName in cmd.AlternateNames) { if (altName.ToLower() == cmdLower) { execCommand = cmd; break; } } } } } if (execCommand == null) { foreach (Command cmd in Commands) { if (cmd.Name.ToLower() == command.ToLower()) { execCommand = cmd; XanBotLogger.WriteDebugLine($"Found command [{cmd.Name}] globally."); break; } } } if (execCommand != null) { UsagePermissionPacket usagePerms = execCommand.CanUseCommand(member); if (usagePerms.CanUse) { if (execCommand.CanUseCommandInThisChannel(member, originalMessage.Channel, out DiscordChannel optimalTargetChannel)) { try { string allArgsText = ""; if (args.Length > 0) { allArgsText = text.Substring(command.Length + 1); } originalMessage.Channel.TriggerTypingAsync().GetAwaiter().GetResult(); await execCommand.ExecuteCommandAsync(commandContext, member, originalMessage, args, allArgsText); XanBotLogger.WriteLine(string.Format("§aUser \"§6{0}§a\" issued command \"§6{1}§a\" with args {2}", member.FullName, command, ArrayToText(args))); } catch (CommandException commandErr) { string message = string.Format("§cFailed to issue command `{0}`: §1{1}", commandErr.Command.Name, commandErr.Message); await ResponseUtil.RespondToAsync(originalMessage, message); XanBotLogger.WriteLine(string.Format("§aUser \"§6{0}§a\" attempted to issue command \"§6{1}§a\" but it failed. The command gave the reason: §2{2}", member.FullName, commandErr.Command.Name, commandErr.Message)); } catch (ArchonCommandException commandErr) { string message = string.Format("§cFailed to issue Archon Command `{0}`: §1{1}", commandErr.Command.Name, commandErr.Message); await ResponseUtil.RespondToAsync(originalMessage, message); XanBotLogger.WriteLine(string.Format("§aUser \"§6{0}§a\" attempted to issue Archon Command \"§6{1}§a\" but it failed. The command gave the reason: §2{2}", member.FullName, commandErr.Command.Name, commandErr.Message)); } catch (TaskCanceledException taskCancel) { XanBotLogger.WriteException(taskCancel); } } else { string message = string.Format($"You can't use this command here. Go to <#{optimalTargetChannel.Id}> instead."); await ResponseUtil.RespondToAsync(originalMessage, message); } } else { //string message = string.Format("You are not authorized to use `{0}`. It is only available to permission level `{1}` and above (You are at `{2}`)", execCommand.Name, execCommand.RequiredPermissionLevel, member.PermissionLevel); await ResponseUtil.RespondToAsync(originalMessage, usagePerms.ErrorMessage); XanBotLogger.WriteLine(string.Format("§aUser \"§6{0}§a\" attempted to issue command \"§6{1}§a\" but it failed because they don't have a high enough permission level or because the command's CanUseCommand method returned false.", member.FullName, execCommand.Name)); } } else { await ResponseUtil.RespondToAsync(originalMessage, "The command `" + command + "` does not exist."); XanBotLogger.WriteLine(string.Format("§aUser \"§6{0}§a\" attempted to issue command \"§6{1}§a\" but it failed because it doesn't exist.", member.FullName, command)); } }