From 0c1370bd04cb24320e8708a31ad95f1467f6bcba Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 8 May 2025 00:30:30 -0400 Subject: [PATCH] unit converter attempts to resolve ambiguity e.g., converting 28 pounds to 28 dollars? :shrug:, *which* pounds and *which* dollars? 28 pounds to kilograms? shtikbot gotchu. see #28 also, discord will self-truncate outgoing messages. --- Behavior/UnitConvert.cs | 3 +- Conversion/Converter.cs | 107 ++++++++++++++---- .../DiscordInterface/DiscordInterface.cs | 17 ++- 3 files changed, 100 insertions(+), 27 deletions(-) diff --git a/Behavior/UnitConvert.cs b/Behavior/UnitConvert.cs index 77df052..969353d 100644 --- a/Behavior/UnitConvert.cs +++ b/Behavior/UnitConvert.cs @@ -22,6 +22,7 @@ public class UnitConvert : Behavior decimal asNumeric = 0; if (decimal.TryParse(theseMatches[0].Groups[1].Value, out asNumeric)) { + Console.WriteLine("let's try and convert..."); await message.Channel.SendMessage(Conversion.Converter.Convert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower())); return true; } @@ -31,4 +32,4 @@ public class UnitConvert : Behavior await message.Channel.SendMessage( "unparsable"); return true; } -} \ No newline at end of file +} diff --git a/Conversion/Converter.cs b/Conversion/Converter.cs index 220e577..f3d4e20 100644 --- a/Conversion/Converter.cs +++ b/Conversion/Converter.cs @@ -46,7 +46,7 @@ namespace vassago.Conversion { knownConversions = new List>(); knownAliases = new Dictionary, string>(new List, string>>()); - var convConf = JsonConvert.DeserializeObject(File.ReadAllText("assets/conversion.json")); + var convConf = JsonConvert.DeserializeObject(File.ReadAllText("assets/conversion.json").ToLower()); foreach (var unit in convConf.Units) { knownAliases.Add(unit.Aliases.ToList(), unit.Canonical); @@ -66,11 +66,11 @@ namespace vassago.Conversion } if (File.Exists(currencyPath)) { - currencyConf = JsonConvert.DeserializeObject(File.ReadAllText(currencyPath)); + currencyConf = JsonConvert.DeserializeObject(File.ReadAllText(currencyPath).ToLower()); if (!knownAliases.ContainsValue(currencyConf.Base)) { - knownAliases.Add(new List() { currencyConf.Base.ToLower() }, currencyConf.Base); + knownAliases.Add(new List() { }, currencyConf.Base); } foreach (var rate in currencyConf.rates) { @@ -85,30 +85,89 @@ namespace vassago.Conversion public static string Convert(decimal numericTerm, string sourceunit, string destinationUnit) { - var normalizationAttempt = NormalizeUnit(sourceunit); - if (normalizationAttempt.Item2 != null) + //normalize units + var normalizationAttemptSource = NormalizeUnit(sourceunit.ToLower()); + if (normalizationAttemptSource?.Count() == 0) { - return $"problem with {sourceunit}: {normalizationAttempt.Item2}"; + return $"can't find {sourceunit}"; } - var normalizedSourceUnit = normalizationAttempt.Item1; + var normalizedSourceUnit = normalizationAttemptSource.First(); - normalizationAttempt = NormalizeUnit(destinationUnit); - if (normalizationAttempt.Item2 != null) + var normalizationAttemptDest = NormalizeUnit(destinationUnit.ToLower()); + if (normalizationAttemptDest?.Count() == 0) { - return $"problem with {destinationUnit}: {normalizationAttempt.Item2}"; + return $"can't find {destinationUnit}"; } - var normalizedDestUnit = normalizationAttempt.Item1; + var normalizedDestUnit = normalizationAttemptDest.First(); if (normalizedSourceUnit == normalizedDestUnit) { return $"source and dest are the same, so... {numericTerm} {normalizedDestUnit}?"; } - var foundPath = exhaustiveBreadthFirst(normalizedDestUnit, new List() { normalizedSourceUnit })?.ToList(); + //resolve ambiguity + var disambiguationPaths = new List>(); + if (normalizationAttemptSource.Count() > 1 && normalizationAttemptDest.Count() > 1) + { + foreach (var possibleSourceUnit in normalizationAttemptSource) + { + foreach (var possibleDestUnit in normalizationAttemptDest) + { + foundPath = exhaustiveBreadthFirst(possibleDestUnit, new List() { possibleSourceUnit })?.ToList(); + if (foundPath != null) + { + disambiguationPaths.Add(foundPath.ToList()); + normalizedSourceUnit = possibleSourceUnit; + normalizedDestUnit = possibleDestUnit; + } + } + } + } + else if (normalizationAttemptSource.Count() > 1) + { + foreach (var possibleSourceUnit in normalizationAttemptSource) + { + foundPath = exhaustiveBreadthFirst(normalizedDestUnit, new List() { possibleSourceUnit })?.ToList(); + if (foundPath != null) + { + disambiguationPaths.Add(foundPath.ToList()); + normalizedSourceUnit = possibleSourceUnit; + } + } + } + else if (normalizationAttemptDest.Count() > 1) + { + foreach (var possibleDestUnit in normalizationAttemptDest) + { + foundPath = exhaustiveBreadthFirst(possibleDestUnit, new List() { normalizedSourceUnit })?.ToList(); + if (foundPath != null) + { + disambiguationPaths.Add(foundPath.ToList()); + normalizedDestUnit = possibleDestUnit; + } + } + } + if (disambiguationPaths.Count() > 1) + { + var sb = new StringBuilder(); + sb.Append("unresolvable ambiguity."); + foreach(var possibility in disambiguationPaths) + { + sb.Append($" {possibility.First()} -> {possibility.Last()}?"); + } + return sb.ToString(); + } + + if (disambiguationPaths.Count() == 1) + { + //TODO: I'm not entirely sure this is necessary. + foundPath = disambiguationPaths.First(); + } + //actually do the math. if (foundPath != null) { var accumulator = numericTerm; - for (int j = 0; j < foundPath.Count - 1; j++) + for (int j = 0; j < foundPath.Count() - 1; j++) { var forwardConversion = knownConversions.FirstOrDefault(kc => kc.Item1 == foundPath[j] && kc.Item2 == foundPath[j + 1]); if (forwardConversion != null) @@ -139,34 +198,34 @@ namespace vassago.Conversion } return "dimensional analysis failure - I know those units but can't find a path between them."; } - private static Tuple NormalizeUnit(string unit) + private static List NormalizeUnit(string unit) { if (string.IsNullOrWhiteSpace(unit)) - return new(null, "no unit provided"); - var normalizedUnit = unit.ToLower(); + return new(); + var normalizedUnit = unit; + //first, if it does exist in conversions, that's the canonical name. if (knownConversions.FirstOrDefault(c => c.Item1 == normalizedUnit || c.Item2 == normalizedUnit) != null) { - return new(normalizedUnit, null); + return new List() { normalizedUnit }; } - //if "unit" isn't a canonical name... + //if "unit" isn't a canonical name... actually it never should be; a conversion should use it. if (!knownAliases.ContainsValue(normalizedUnit)) { //then we look through aliases... var keys = knownAliases.Keys.Where(listkey => listkey.Contains(normalizedUnit)); if (keys?.Count() > 1) { - var sb = new StringBuilder(); - sb.Append($"{normalizedUnit} could refer to any of"); + var toReturn = new List(); foreach (var key in keys) { - sb.Append($" {knownAliases[key]}"); + toReturn.Add(knownAliases[key]); } - return new(null, sb.ToString()); + return toReturn; } else if (keys.Count() == 1) { //for the canonical name. - return new(knownAliases[keys.First()], null); + return new List() { knownAliases[keys.First()] }; } } if (normalizedUnit.EndsWith("es")) @@ -177,7 +236,7 @@ namespace vassago.Conversion { return NormalizeUnit(normalizedUnit.Substring(0, normalizedUnit.Length - 1)); } - return new(null, "couldn't find unit"); + return new(); } private static IEnumerable exhaustiveBreadthFirst(string dest, IEnumerable currentPath) { diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index 31c97e7..7698838 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -233,7 +233,7 @@ public class DiscordInterface m.MentionsMe = (dMessage.Author.Id != client.CurrentUser.Id && (dMessage.MentionedUserIds?.FirstOrDefault(muid => muid == client.CurrentUser.Id) > 0)); - m.Reply = (t) => { return dMessage.ReplyAsync(t); }; + m.Reply = (t) => { return dMessage.ReplyAsync(TruncateText(t, m.Channel.MaxTextChars)); }; m.React = (e) => { return AttemptReact(dMessage, e); }; Rememberer.RememberMessage(m); return m; @@ -314,7 +314,7 @@ public class DiscordInterface parentChannel.SubChannels.Add(c); } - c.SendMessage = (t) => { return channel.SendMessageAsync(t); }; + c.SendMessage = (t) => { return channel.SendMessageAsync(TruncateText(t, c.MaxTextChars));}; c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); }; c = Rememberer.RememberChannel(c); @@ -409,4 +409,17 @@ public class DiscordInterface return msg.AddReactionAsync(emote); } + private static string TruncateText(string msg, uint? chars) + { + chars ??= 500; + if(msg?.Length > chars) + { + return msg.Substring(0, (int)chars-2) + "✂"; + } + else + { + return msg; + } + } + }