/// <summary> /// User command to see information on how to update projects. /// </summary> public void CMD_Update(string[] cmds, SocketMessage message) { if (cmds.Length == 0) { if (!Bot.ChannelToDetails.TryGetValue(message.Channel.Id, out ChannelDetails details) || details.Updates.Length == 0) { SendErrorMessageReply(message, "Unknown input for Update command", "Please specify which project(s) you want the update link for."); return; } foreach (ProjectDetails proj in details.Updates) { SendReply(message, proj.GetUpdateEmbed()); } return; } foreach (string projectName in cmds) { string projectNameLower = projectName.ToLowerFast(); if (Bot.ProjectToDetails.TryGetValue(projectNameLower, out ProjectDetails detail)) { SendReply(message, detail.GetUpdateEmbed()); } else { string closeName = StringConversionHelper.FindClosestString(Bot.ProjectToDetails.Keys, projectName, 20); SendErrorMessageReply(message, "Unknown project name for Update command", $"Unknown project name `{projectName.Replace('`', '\'')}`." + (closeName == null ? "" : $" Did you mean `{closeName}`?")); } } }
/// <summary> /// User command to see a link to the GitHub. /// </summary> public void CMD_Issues(string[] cmds, IUserMessage message) { if (DenizenMetaBot.ProjectToDetails.IsEmpty()) { return; } if (cmds.Length == 0) { if (!DenizenMetaBot.ChannelToDetails.TryGetValue(message.Channel.Id, out ChannelDetails details) || details.Updates.Length == 0) { SendErrorMessageReply(message, "Unknown input for Issues command", $"Please specify which project(s) you want the Issues link for, like `{DenizenMetaBotConstants.COMMAND_PREFIX}issues denizen`."); return; } SendReply(message, details.Updates[0].GetIssuesEmbed()); return; } foreach (string projectName in cmds) { string projectNameLower = projectName.ToLowerFast(); if (DenizenMetaBot.ProjectToDetails.TryGetValue(projectNameLower, out ProjectDetails detail)) { SendReply(message, detail.GetIssuesEmbed()); } else { string closeName = StringConversionHelper.FindClosestString(DenizenMetaBot.ProjectToDetails.Keys, projectName, 20); SendErrorMessageReply(message, "Unknown project name for Issues command", $"Unknown project name `{projectName.Replace('`', '\'')}`." + (closeName == null ? "" : $" Did you mean `{closeName}`?")); } } }
/// <summary> /// User command to get some predefined informational output. /// </summary> public void CMD_Info(string[] cmds, IUserMessage message) { if (DenizenMetaBot.InformationalData.IsEmpty()) { return; } if (cmds.Length == 0) { SendErrorMessageReply(message, "Command Syntax Incorrect", "`!info <info item or 'list'>`"); return; } if (cmds.Length > 5) { SendErrorMessageReply(message, "Command Syntax Incorrect", "Please request no more than 5 info items at a time."); return; } foreach (string searchRaw in cmds) { string commandSearch = searchRaw.ToLowerFast().Trim(); if (commandSearch == "list" || commandSearch == "all") { string fullList = "`" + string.Join("`, `", DenizenMetaBot.InformationalDataNames) + "`"; SendReply(message, new EmbedBuilder().WithThumbnailUrl(Constants.INFO_ICON).WithTitle("Available Info Names").WithDescription($"Available info names: {fullList}").Build()); } else if (DenizenMetaBot.InformationalData.TryGetValue(commandSearch, out string infoOutput)) { infoOutput = infoOutput.Trim(); if (infoOutput.StartsWith("NO_BOX:")) { infoOutput = infoOutput.Substring("NO_BOX:".Length).Trim(); message.Channel.SendMessageAsync("+++ Info `" + commandSearch + "`: " + infoOutput); } else { SendReply(message, new EmbedBuilder().WithThumbnailUrl(Constants.INFO_ICON).WithTitle($"Info: {commandSearch}").WithDescription(infoOutput).Build()); } } else { string closeName = StringConversionHelper.FindClosestString(DenizenMetaBot.InformationalData.Keys, commandSearch, 20); SendErrorMessageReply(message, "Cannot Display Info", "Unknown info name." + (closeName == null ? "" : $" Did you mean `{closeName}`?")); } } }
/// <summary>User command to see information on how to update projects.</summary> public void CMD_Update(CommandData command) { if (DenizenMetaBot.ProjectToDetails.IsEmpty()) { return; } if (command.CleanedArguments.Length == 0) { ulong chanId = command.Message.Channel is SocketThreadChannel thread ? thread.ParentChannel.Id : command.Message.Channel.Id; if (!DenizenMetaBot.ChannelToDetails.TryGetValue(chanId, out ChannelDetails details) || details.Updates.Length == 0) { SendErrorMessageReply(command.Message, "Unknown input for Update command", $"Please specify which project(s) you want the update link for, like `{DenizenMetaBotConstants.COMMAND_PREFIX}update denizen`."); return; } foreach (ProjectDetails proj in details.Updates) { SendReply(command.Message, proj.GetUpdateEmbed()); } return; } if (command.CleanedArguments.Length > 5) { SendErrorMessageReply(command.Message, "Command Syntax Incorrect", "Please request no more than 5 info items at a time."); return; } foreach (string projectName in command.CleanedArguments) { string projectNameLower = projectName.ToLowerFast(); if (DenizenMetaBot.ProjectToDetails.TryGetValue(projectNameLower, out ProjectDetails detail)) { SendReply(command.Message, detail.GetUpdateEmbed()); } else { string closeName = StringConversionHelper.FindClosestString(DenizenMetaBot.ProjectToDetails.Keys, projectName, 20); SendErrorMessageReply(command.Message, "Unknown project name for Update command", $"Unknown project name `{projectName.Replace('`', '\'')}`." + (closeName == null ? "" : $" Did you mean `{closeName}`?")); } } }
/// <summary> /// Mechanism meta docs user command. /// </summary> public void CMD_Mechanism(string[] cmds, SocketMessage message) { List <string> secondarySearches = new List <string>(); if (cmds.Length > 0) { int dotIndex = cmds[0].IndexOf('.'); if (dotIndex > 0) { secondarySearches.Add(cmds[0].Substring(0, dotIndex) + "tag" + cmds[0].Substring(dotIndex)); } } int closeness = AutoMetaCommand(Program.CurrentMeta.Mechanisms, MetaDocs.META_TYPE_MECHANISM, cmds, message, secondarySearches); if (closeness > 0) { string closeCmd = StringConversionHelper.FindClosestString(Program.CurrentMeta.Commands.Keys, cmds[0].ToLowerFast(), 7); if (closeCmd != null) { SendGenericPositiveMessageReply(message, "Possible Confusion", $"Did you mean to search for `command {closeCmd}`?"); } } }
/// <summary> /// Mechanism meta docs user command. /// </summary> public void CMD_Mechanism(string[] cmds, IUserMessage message) { List <string> secondarySearches = new List <string>(); if (cmds.Length > 0) { int dotIndex = cmds[0].IndexOf('.'); if (dotIndex > 0) { secondarySearches.Add(cmds[0].Substring(0, dotIndex) + "tag" + cmds[0].Substring(dotIndex)); } } int closeness = AutoMetaCommand(MetaDocs.CurrentMeta.Mechanisms, MetaDocs.META_TYPE_MECHANISM, cmds, message, secondarySearches); if (closeness > 0) { string closeCmd = StringConversionHelper.FindClosestString(MetaDocs.CurrentMeta.Commands.Keys, cmds[0].ToLowerFast(), 7); if (closeCmd != null) { SendDidYouMeanReply(message, "Possible Confusion", $"Did you mean to search for `command {closeCmd}`?", $"{DenizenMetaBotConstants.COMMAND_PREFIX}command {closeCmd}"); } } }
/// <summary> /// Command meta docs user command. /// </summary> public void CMD_Command(string[] cmds, SocketMessage message) { void singleReply(MetaCommand cmd) { if (cmds.Length >= 2) { string outputType = cmds[1].ToLowerFast(); if (outputType.StartsWith("u")) { SendReply(message, cmd.GetUsagesEmbed().Build()); } else if (outputType.StartsWith("t")) { SendReply(message, cmd.GetTagsEmbed().Build()); } else { SendErrorMessageReply(message, "Bad Command Syntax", "Second argument is unknown.\n\nUsage: `command [name] [usage/tags]`."); } } else { SendReply(message, cmd.GetEmbed().Build()); } } int closeness = AutoMetaCommand(Program.CurrentMeta.Commands, MetaDocs.META_TYPE_COMMAND, cmds, message, altSingleOutput: singleReply); if (closeness > 0) { string closeMech = StringConversionHelper.FindClosestString(Program.CurrentMeta.Mechanisms.Keys.Select(s => s.After('.')), cmds[0].ToLowerFast(), 10); if (closeMech != null) { SendGenericPositiveMessageReply(message, "Possible Confusion", $"Did you mean to search for `mechanism {closeMech}`?"); } } }
/// <summary> /// Automatically processes a meta search command. /// </summary> /// <typeparam name="T">The meta object type.</typeparam> /// <param name="docs">The docs mapping.</param> /// <param name="type">The meta type.</param> /// <param name="cmds">The command args.</param> /// <param name="message">The Discord message object.</param> /// <param name="secondarySearches">A list of secondary search strings if the first fails.</param> /// <param name="secondaryMatcher">A secondary matching function if needed.</param> /// <param name="altSingleOutput">An alternate method of processing the single-item-result.</param> /// <param name="altFindClosest">Alternate method to find the closest result.</param> /// <returns>How close of an answer was gotten (0 = perfect, -1 = no match needed, 1000 = none).</returns> public int AutoMetaCommand <T>(Dictionary <string, T> docs, MetaType type, string[] cmds, SocketMessage message, List <string> secondarySearches = null, Func <T, bool> secondaryMatcher = null, Action <T> altSingleOutput = null, Func <string> altFindClosest = null, Func <List <T>, List <T> > altMatchOrderer = null) where T : MetaObject { if (CheckMetaDenied(message)) { return(-1); } if (cmds.Length == 0) { SendErrorMessageReply(message, $"Need input for '{type.Name}' command", $"Please specify a {type.Name} to search, like `!{type.Name} Some{type.Name}Here`. Or, use `!{type.Name} all` to view all documented {type.Name.ToLowerFast()}s."); return(-1); } string search = cmds[0].ToLowerFast(); if (search == "all") { SendGenericPositiveMessageReply(message, $"All {type.Name}s", $"Find all {type.Name}s at {Constants.DOCS_URL_BASE}{type.WebPath}/"); return(-1); } if (altSingleOutput == null) { altSingleOutput = (singleObj) => SendReply(message, singleObj.GetEmbed().Build()); } if (altFindClosest == null) { altFindClosest = () => { string initialPossibleResult = StringConversionHelper.FindClosestString(docs.Keys, search, out int lowestDistance, 20); string lowestStr = null; foreach (string possibleName in docs.Values.Where(o => o.HasMultipleNames).SelectMany(o => o.MultiNames)) { int currentDistance = StringConversionHelper.GetLevenshteinDistance(search, possibleName); if (currentDistance < lowestDistance) { lowestDistance = currentDistance; lowestStr = possibleName; } } return(lowestStr); }; } if (altMatchOrderer == null) { altMatchOrderer = (list) => list.OrderBy((mat) => StringConversionHelper.GetLevenshteinDistance(search, mat.CleanName)).ToList(); } if (docs.TryGetValue(search, out T obj)) { string multiNameData = string.Join("', '", obj.MultiNames); Console.WriteLine($"Meta-Command for '{type.Name}' found perfect match for search '{search}': '{obj.CleanName}', multi={obj.HasMultipleNames}='{multiNameData}'"); altSingleOutput(obj); return(0); } if (secondarySearches != null) { secondarySearches = secondarySearches.Select(s => s.ToLowerFast()).ToList(); foreach (string secondSearch in secondarySearches) { if (docs.TryGetValue(secondSearch, out obj)) { Console.WriteLine($"Meta-Command for '{type.Name}' found perfect match for secondary search '{secondSearch}': '{obj.CleanName}', multi={obj.HasMultipleNames}"); altSingleOutput(obj); return(0); } } } List <T> matched = new List <T>(); List <T> strongMatched = new List <T>(); int tryProcesSingleMatch(T objVal, string objName, int min) { if (objName.Contains(search)) { Console.WriteLine($"Meta-Command for '{type.Name}' found a strong match (main contains) for search '{search}': '{objName}'"); strongMatched.Add(objVal); return(2); } if (secondarySearches != null) { foreach (string secondSearch in secondarySearches) { if (objName.Contains(secondSearch)) { Console.WriteLine($"Meta-Command for '{type.Name}' found a strong match (secondary contains) for search '{secondSearch}': '{objName}'"); strongMatched.Add(objVal); return(2); } } } if (min < 1 && secondaryMatcher != null && secondaryMatcher(objVal)) { Console.WriteLine($"Meta-Command for '{type.Name}' found a weak match (secondaryMatcher) for search '{search}': '{objName}'"); matched.Add(objVal); return(1); } return(min); } foreach (KeyValuePair <string, T> objPair in docs) { if (objPair.Value.HasMultipleNames) { int matchQuality = 0; foreach (string name in objPair.Value.MultiNames) { matchQuality = tryProcesSingleMatch(objPair.Value, name, matchQuality); if (matchQuality == 2) { break; } } } else { tryProcesSingleMatch(objPair.Value, objPair.Key, 0); } } if (strongMatched.Count > 0) { matched = strongMatched; } if (matched.Count == 0) { string closeName = altFindClosest(); SendErrorMessageReply(message, $"Cannot Find Searched {type.Name}", $"Unknown {type.Name.ToLowerFast()}." + (closeName == null ? "" : $" Did you mean `{closeName}`?")); return(closeName == null ? 1000 : StringConversionHelper.GetLevenshteinDistance(search, closeName)); } else if (matched.Count > 1) { matched = altMatchOrderer(matched); string suffix = "."; if (matched.Count > 20) { matched = matched.GetRange(0, 20); suffix = ", ..."; } string listText = string.Join("`, `", matched.Select((m) => m.Name)); SendErrorMessageReply(message, $"Cannot Specify Searched {type.Name}", $"Multiple possible {type.Name.ToLowerFast()}s: `{listText}`{suffix}"); return(StringConversionHelper.GetLevenshteinDistance(search, matched[0].CleanName)); } else // Count == 1 { obj = matched[0]; Console.WriteLine($"Meta-Command for '{type.Name}' found imperfect single match for search '{search}': '{obj.CleanName}', multi={obj.HasMultipleNames}"); altSingleOutput(obj); return(0); } }
/// <summary> /// Meta docs total search command. /// </summary> public void CMD_Search(string[] cmds, IUserMessage message) { if (CheckMetaDenied(message)) { return; } if (cmds.Length == 0) { SendErrorMessageReply(message, "Need input for Search command", "Please specify some text to search, like `!search someobjecthere`."); return; } for (int i = 0; i < cmds.Length; i++) { cmds[i] = cmds[i].ToLowerFast(); } string fullSearch = string.Join(' ', cmds); List <MetaObject> strongMatch = new List <MetaObject>(); List <MetaObject> partialStrongMatch = new List <MetaObject>(); List <MetaObject> weakMatch = new List <MetaObject>(); List <MetaObject> partialWeakMatch = new List <MetaObject>(); foreach (MetaObject obj in MetaDocs.CurrentMeta.AllMetaObjects()) { if (obj.CleanName.Contains(fullSearch)) { strongMatch.Add(obj); continue; } foreach (string word in cmds) { if (obj.CleanName.Contains(word)) { partialStrongMatch.Add(obj); goto fullContinue; } } if (obj.Searchable.Contains(fullSearch)) { weakMatch.Add(obj); continue; } if (fullSearch.Contains(obj.CleanName)) { partialWeakMatch.Add(obj); continue; } foreach (string word in cmds) { if (obj.Searchable.Contains(word)) { partialWeakMatch.Add(obj); goto fullContinue; } } fullContinue: continue; } void backupMatchCheck() { string possible = StringConversionHelper.FindClosestString(MetaDocs.CurrentMeta.AllMetaObjects().SelectMany(obj => obj.MultiNames), fullSearch, 10); if (!string.IsNullOrWhiteSpace(possible)) { SendDidYouMeanReply(message, "Possible Confusion", $"Did you mean to search for `{possible}`?", $"{DenizenMetaBotConstants.COMMAND_PREFIX}search {possible}"); } } if (strongMatch.IsEmpty() && partialStrongMatch.IsEmpty() && weakMatch.IsEmpty() && partialWeakMatch.IsEmpty()) { SendErrorMessageReply(message, "Search Command Has No Results", "Input search text could not be found."); backupMatchCheck(); return; } string suffix = "."; void listWrangle(string typeShort, string typeLong, List <MetaObject> objs) { objs = objs.OrderBy((obj) => StringConversionHelper.GetLevenshteinDistance(fullSearch, obj.CleanName)).ToList(); suffix = "."; if (objs.Count > 20) { objs = objs.GetRange(0, 20); suffix = ", ..."; } string listText = string.Join("`, `", objs.Select((obj) => $"{DenizenMetaBotConstants.COMMAND_PREFIX}{obj.Type.Name} {obj.CleanName}")); SendGenericPositiveMessageReply(message, $"{typeShort} Search Results", $"{typeShort} ({typeLong}) search results: `{listText}`{suffix}"); } if (strongMatch.Any()) { listWrangle("Best", "very close", strongMatch); } if (partialStrongMatch.Any()) { listWrangle("Probable", "close but imperfect", partialStrongMatch); if (strongMatch.Any()) { return; } } if (weakMatch.Any()) { listWrangle("Possible", "might be related", weakMatch); if (strongMatch.Any() || partialStrongMatch.Any()) { return; } } if (partialWeakMatch.Any()) { listWrangle("Weak", "if nothing else, some chance of being related", partialWeakMatch); if (!weakMatch.Any() && !partialStrongMatch.Any() && !strongMatch.Any()) { backupMatchCheck(); } } }