/// <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> /// Strictly gets a user from their user ID. Unlike <see cref="GetMemberFromDataAsync(DiscordGuild, string)"/>, this code will return null if a name or discriminator is passed in. /// </summary> /// <param name="data">The query to get a XanBotMember from. This can either be a ulong as a string or a user ping (<@ulong>)</param> /// <returns></returns> public static async Task <XanBotMember> GetMemberFromDataIDStrictAsync(DiscordGuild server, string data) { string newdata = data; if (data.StartsWith("<@") && data.EndsWith(">")) { newdata = data.Substring(2, data.Length - 3); } if (ulong.TryParse(newdata, out ulong uuid)) { DiscordUser user = await XanBotCoreSystem.Client.GetUserAsync(uuid); // Catch case: Someone's username is a bunch of numbers OR they link a user ID that isn't in the server. if (user != null) { XanBotMember member = XanBotMember.GetMemberFromUser(server, user); if (member != null) { return(member); } } } return(null); }
/// <summary> /// Attempts to get a member via three distinct methods: <para/> /// First, it will attempt to cast <paramref name="data"/> into a ulong and see if it is a user GUID (this includes processing pings, like <@GUID_HERE>)<para/> /// Second, it will attempt to see if <paramref name="data"/> is a discriminator (only if data is formatted as #XXXX where XXXX is four digits)<para/> /// Finally, and only if the second method had no results or wasn't used, it will attempt to find <paramref name="data"/> as a nickname or username.<para/> /// This will throw a NonSingularResultException if the query can return more than one user. /// </summary> /// <param name="server">The Discord server to target.</param> /// <param name="data">The query to get a XanBotMember from. This can be a ulong as a string, a user ping (<@ulong>), a server nickname, a username (or optionally username#discriminator)</param> /// <exception cref="NonSingularResultException"/> /// <returns></returns> public static async Task <XanBotMember> GetMemberFromDataAsync(DiscordGuild server, string data) { // Wait! If it's a ping, it will start with <@ and end with > string newdata = data; if (data.StartsWith("<@") && data.EndsWith(">")) { newdata = data.Substring(2, data.Length - 3); } // I don't know if this method is used but it's one Discord supports so I have to support it too. if (data.StartsWith("<@!") && data.EndsWith(">")) { newdata = data.Substring(3, data.Length - 4); } if (ulong.TryParse(newdata, out ulong uuid)) { try { DiscordUser user = await XanBotCoreSystem.Client.GetUserAsync(uuid); // Catch case: Someone's username is a bunch of numbers OR they link a user ID that isn't in the server. if (user != null) { XanBotMember member = XanBotMember.GetMemberFromUser(server, user); if (member != null) { return(member); } } } catch (NotFoundException) { // The person typed in a number, it doesn't correspond to a discord GUID, so we'll catch the NotFoundException and let the code continue. } } List <XanBotMember> potentialReturns = new List <XanBotMember>(); string dataLower = data.ToLower(); // Discriminator searching: if (dataLower.Length == 5 && dataLower.First() == '#' && ushort.TryParse(dataLower.Substring(1), out ushort _)) { // This is a discriminator -- Length is 5, starts with #, and the last 4 chars are numbers. foreach (DiscordUser user in server.Members.Values) { string ud = "#" + user.Discriminator; if (dataLower == ud) { potentialReturns.Add(XanBotMember.GetMemberFromUser(server, user)); } } } if (potentialReturns.Count == 0) { // ONLY if discriminator searching found nothing will we search by display name or username. if (NeedsNewMemberCache) { LastCheckTime = DateTime.Now; Members = await server.GetAllMembersAsync(); } foreach (DiscordMember member in Members) { //foreach (DiscordMember member in server.Members.Values) { // DO NOT USE THIS. It's not fully populated in large servers. string fullName = member.Username + "#" + member.Discriminator; string nickName = member.Nickname ?? ""; fullName = fullName.ToLower(); nickName = nickName.ToLower(); if (nickName.Length >= dataLower.Length && dataLower == nickName.Substring(0, dataLower.Length)) { potentialReturns.Add(XanBotMember.GetMemberFromUser(server, member)); } else if (fullName.Length >= dataLower.Length && dataLower == fullName.Substring(0, dataLower.Length)) { potentialReturns.Add(XanBotMember.GetMemberFromUser(server, member)); } // Do NOT break if there are multiple. This is necessary for the values in a potential NonSingularResultException } } XanBotMember[] potentialReturnsArray = potentialReturns.ToArray(); if (potentialReturnsArray.Length == 0) { return(null); } else if (potentialReturnsArray.Length == 1) { return(potentialReturnsArray[0]); } else { throw new NonSingularResultException(string.Format("More than one member of the server was found with the search query `{0}`!", data), potentialReturnsArray); } }
/// <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)); } }