/// <summary> /// Tag meta docs user command. /// </summary> public void CMD_Tag(string[] cmds, SocketMessage message) { List <string> secondarySearches = new List <string>(); if (cmds.Length > 0) { cmds[0] = MetaTag.CleanTag(cmds[0]); int dotIndex = cmds[0].IndexOf('.'); if (dotIndex > 0) { string tagBase = cmds[0].Substring(0, dotIndex); string tagSuffix = cmds[0].Substring(dotIndex); if (!tagBase.EndsWith("tag")) { secondarySearches.Add(tagBase + "tag" + tagSuffix); } string tagBaseLow = tagBase.ToLowerFast(); if (tagBaseLow == "player" || tagBaseLow == "npc" || tagBaseLow == "playertag" || tagBaseLow == "npctag") { secondarySearches.Add("entitytag" + tagSuffix); } secondarySearches.Add("elementtag" + tagSuffix); } } int getDistanceTo(MetaTag tag) { int dist1 = StringConversionHelper.GetLevenshteinDistance(cmds[0], tag.CleanedName); int dist2 = StringConversionHelper.GetLevenshteinDistance(cmds[0], tag.AfterDotCleaned); int dist = Math.Min(dist1, dist2); foreach (string secondSearch in secondarySearches) { int dist3 = StringConversionHelper.GetLevenshteinDistance(secondSearch, tag.CleanedName); dist = Math.Min(dist, dist3); } return(dist); } string findClosestTag() { int lowestDistance = 20; string lowestStr = null; foreach (MetaTag tag in Program.CurrentMeta.Tags.Values) { int currentDistance = getDistanceTo(tag); if (currentDistance < lowestDistance) { lowestDistance = currentDistance; lowestStr = tag.CleanedName; } } return(lowestStr); } AutoMetaCommand(Program.CurrentMeta.Tags, MetaDocs.META_TYPE_TAG, cmds, message, secondarySearches, altFindClosest: findClosestTag, altMatchOrderer: (list) => list.OrderBy(getDistanceTo).ToList()); }
/// <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, SocketMessage 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 Program.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; } if (strongMatch.IsEmpty() && partialStrongMatch.IsEmpty() && weakMatch.IsEmpty() && partialWeakMatch.IsEmpty()) { SendErrorMessageReply(message, "Search Command Has No Results", "Input search text could not be found."); 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) => $"!{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); } }