diff --git a/.gitignore b/.gitignore index 8f7d125..c94a765 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ appsettings.json +assets/exchangepairs.json # ---> VisualStudio ## Ignore Visual Studio temporary files, build results, and diff --git a/Conversion/ConversionConfig.cs b/Conversion/ConversionConfig.cs new file mode 100644 index 0000000..7ded3d0 --- /dev/null +++ b/Conversion/ConversionConfig.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace silverworker_discord.Conversion +{ + public class ConversionConfig + { + public class KnownUnit + { + public string Canonical { get; set; } + public IEnumerable Aliases { get; set; } + } + public class LinearPair + { + public string item1 { get; set; } + public string item2 { get; set; } + public decimal factor { get; set; } + } + public IEnumerable Units { get; set; } + public IEnumerable LinearPairs { get; set; } + } +} \ No newline at end of file diff --git a/Conversion/Converter.cs b/Conversion/Converter.cs new file mode 100644 index 0000000..c3ec105 --- /dev/null +++ b/Conversion/Converter.cs @@ -0,0 +1,179 @@ +using System; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using Newtonsoft.Json; +using QRCoder; + +namespace silverworker_discord.Conversion +{ + public static class Converter + { + + private delegate decimal Convert1Way(decimal input); + private static string currencyPath; + private static ExchangePairs currencyConf = null; + private static DateTime lastUpdatedCurrency = DateTime.UnixEpoch; + private static List> knownConversions = new List>() + { + new Tuple("℉", "°C", (f => {return(f- 32.0m) / 1.8m;}), (c => {return 1.8m*c + 32.0m;})), + }; + private static Dictionary, string> knownAliases = new Dictionary, string>(new List, string>>()); + + public static string convert(string message) + { + var theseMatches = Regex.Matches(message, "\\b([\\d]+\\.?\\d*) ?([^\\d\\s].*) (in|to|as) ([^\\d\\s].*)$", RegexOptions.IgnoreCase); + + if (theseMatches != null && theseMatches.Count > 0 && theseMatches[0].Groups != null && theseMatches[0].Groups.Count == 5) + { + decimal asNumeric = 0; + if (decimal.TryParse(theseMatches[0].Groups[1].Value, out asNumeric)) + { + return actualConvert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower()); + } + return "mysteriously semi-parsable"; + } + return "unparsable"; + } + + public static void Load(string currencyPath) + { + Converter.currencyPath = currencyPath; + var convConf = JsonConvert.DeserializeObject(File.ReadAllText("assets/conversion.json")); + foreach (var unit in convConf.Units) + { + knownAliases.Add(unit.Aliases.ToList(), unit.Canonical); + } + foreach (var lp in convConf.LinearPairs) + { + AddLinearPair(lp.item1, lp.item2, lp.factor); + } + loadCurrency(); + } + private static void loadCurrency() + { + if (File.Exists(currencyPath)) + { + currencyConf = JsonConvert.DeserializeObject(File.ReadAllText(currencyPath)); + + knownAliases.Add(new List() { currencyConf.Base.ToLower() }, currencyConf.Base); + foreach (var rate in currencyConf.rates) + { + knownAliases.Add(new List() { rate.Key.ToLower() }, rate.Key); + AddLinearPair(currencyConf.Base, rate.Key, rate.Value); + } + } + } + + private static string actualConvert(decimal numericTerm, string sourceunit, string destinationUnit) + { + var normalizedSourceUnit = normalizeUnit(sourceunit); + if (string.IsNullOrWhiteSpace(normalizedSourceUnit)) + { + return $"what's {sourceunit}?"; + } + var normalizedDestUnit = normalizeUnit(destinationUnit); + if (string.IsNullOrWhiteSpace(normalizedDestUnit)) + { + return $"what's {destinationUnit}?"; + } + if (normalizedSourceUnit == normalizedDestUnit) + { + return $"source and dest are the same, so... {numericTerm} {normalizedDestUnit}?"; + } + var foundPath = exhaustiveBreadthFirst(normalizedDestUnit, new List() { normalizedSourceUnit })?.ToList(); + + if (foundPath != null) + { + var accumulator = numericTerm; + 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) + { + accumulator = forwardConversion.Item3(accumulator); + } + else + { + var reverseConversion = knownConversions.First(kc => kc.Item2 == foundPath[j] && kc.Item1 == foundPath[j + 1]); + accumulator = reverseConversion.Item4(accumulator); + } + } + if (normalizedDestUnit == currencyConf.Base || currencyConf.rates.Select(r => r.Key).Contains(normalizedDestUnit)) + { + return $"{String.Format("approximately {0:0.00}", accumulator)} {normalizedDestUnit} as of {currencyConf.DateUpdated.ToLongDateString()}"; + } + else + { + return $"{String.Format("{0:0.####}", accumulator)} {normalizedDestUnit}"; + } + } + return "no conversion known"; + } + private static string normalizeUnit(string unit) + { + var normalizedUnit = unit.ToLower(); + if (normalizedUnit.EndsWith("es")) + { + normalizedUnit = normalizedUnit.Substring(0, normalizedUnit.Length - 2); + } + else if (normalizedUnit.EndsWith('s')) + { + normalizedUnit = normalizedUnit.Substring(0, normalizedUnit.Length - 1); + } + if (knownConversions.FirstOrDefault(c => c.Item1 == normalizedUnit || c.Item2 == normalizedUnit) != null) + { + return normalizedUnit; + } + if (!knownAliases.ContainsValue(normalizedUnit)) + { + var key = knownAliases.Keys.FirstOrDefault(listkey => listkey.Contains(normalizedUnit)); + if (key != null) + { + return knownAliases[key]; + } + } + return null; + } + private static IEnumerable exhaustiveBreadthFirst(string dest, IEnumerable currentPath) + { + var last = currentPath.Last(); + if (last == dest) + { + return currentPath; + } + + var toTest = new List>(); + foreach (var conv in knownConversions) + { + if (conv.Item1 == last && currentPath.Contains(conv.Item2) == false && conv.Item3 != null) + { + var test = exhaustiveBreadthFirst(dest, currentPath.Append(conv.Item2)); + if (test != null) + return test; + } + if (conv.Item2 == last && currentPath.Contains(conv.Item1) == false && conv.Item4 != null) + { + var test = exhaustiveBreadthFirst(dest, currentPath.Append(conv.Item1)); + if (test != null) + return test; + } + } + return null; + } + private static void AddLinearPair(string key1, string key2, decimal factor) + { + var reverseFactor = 1.0m / factor; + knownConversions.Add(new Tuple( + key1, key2, x => x * factor, y => y * reverseFactor + )); + } + } +} \ No newline at end of file diff --git a/Conversion/ExchangePairs.cs b/Conversion/ExchangePairs.cs new file mode 100644 index 0000000..b4d0a79 --- /dev/null +++ b/Conversion/ExchangePairs.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace silverworker_discord.Conversion +{ + public class ExchangePairs + { + public string disclaimer{ get; set; } + public string license{ get; set; } + public int timestamp{ get; set; } + public DateTime DateUpdated { get { return DateTime.UnixEpoch.AddSeconds(timestamp); }} + public string Base{ get; set; } + public Dictionary rates { get; set; } + } +} \ No newline at end of file diff --git a/Features.cs b/Features.cs index a981692..bd8e3ab 100644 --- a/Features.cs +++ b/Features.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; using System.Collections.Generic; using System.IO; using System.Linq; @@ -151,6 +152,10 @@ namespace silverworker_discord Console.Error.WriteLine($"convert failed :( qr{todaysnumber}"); } } + public static async void Convert(SocketUserMessage message) + { + await message.Channel.SendMessageAsync(Conversion.Converter.convert(message.Content)); + } public static async void Joke(SocketUserMessage message) { var jokes = File.ReadAllLines("assets/jokes.txt"); diff --git a/Program.cs b/Program.cs index 4b133ce..1698561 100644 --- a/Program.cs +++ b/Program.cs @@ -47,11 +47,14 @@ namespace silverworker_discord } } #endif + Conversion.Converter.Load(config["exchangePairsLocation"]); _client = new DiscordSocketClient(); _client.Log += Log; + Console.WriteLine("token, why u null?"); + Console.WriteLine(config["token"]); await _client.LoginAsync(TokenType.Bot, config["token"]); await _client.StartAsync(); @@ -200,6 +203,10 @@ namespace silverworker_discord { //Features.countdown(msgText.Substring("!countdown ".Length + msgText.IndexOf("!countdown ")), message); //converting human readable times is hard :/ } + if (msgText.Contains("!freedomunits ")) + { + Features.Convert(message); + } if (Regex.IsMatch(msgText, "!joke\\b")) { Features.Joke(message); diff --git a/appsettings.example.json b/appsettings.example.json index d5d8931..b06d933 100644 --- a/appsettings.example.json +++ b/appsettings.example.json @@ -1,3 +1,4 @@ { - "token": "59 chars" + "token": "59 chars", + "exchangePairsLocation": "assets/exchangepairs.json" } \ No newline at end of file diff --git a/assets/conversion.json b/assets/conversion.json new file mode 100644 index 0000000..83b659c --- /dev/null +++ b/assets/conversion.json @@ -0,0 +1,56 @@ +{ + "units":[ + { + "canonical": "f", + "aliases": ["degrees fahrenheit"] + } + ], + "linearPairs":[ + {"item1":"kg", "item2":"g", "factor":1000}, + {"item1":"lb", "item2":"oz", "factor":16}, + {"item1":"kg", "item2":"lb", "factor":2.204623}, + + {"item1":"km", "item2":"m", "factor":1000}, + {"item1":"mi", "item2":"ft", "factor":5280}, + {"item1":"m", "item2":"in", "factor":39.37008}, + {"item1":"m", "item2":"cm", "factor":100}, + {"item1":"km", "item2":"mi", "factor":0.6213712}, + {"item1":"ft", "item2":"in", "factor":12}, + {"item1":"yd", "item2":"ft", "factor":3}, + {"item1":"chain", "item2":"yd", "factor":22}, + {"item1":"chain", "item2":"link", "factor":100}, + {"item1":"furlong", "item2":"mi", "factor":8}, + {"item1":"rod", "item2":"ft", "factor":16.5}, + {"item1":"AU", "item2":"ly", "factor": 0.0000158125}, + {"item1":"ly", "item2":"km", "factor": 946070000000}, + {"item1":"pc", "item2":"AU", "factor":206266.3}, + + {"item1":"floz", "item2":"mL", "factor":29.57344}, + {"item1":"L", "item2":"mL", "factor":1000}, + {"item1":"L", "item2":"floz", "factor":33.81402}, + {"item1":"hhd", "item2":"gal", "factor":54}, + {"item1":"barrel", "item2":"kilderkin", "factor":2}, + {"item1":"barrel", "item2":"firkin", "factor":4}, + {"item1":"pint", "item2":"floz", "factor":16}, + {"item1":"cup", "item2":"floz", "factor":8}, + {"item1":"gill", "item2":"floz", "factor":4}, + {"item1":"tbsp", "item2":"tsp", "factor":3}, + {"item1":"tbsp", "item2":"floz", "factor":0.5}, + {"item1":"gal", "item2":"floz", "factor":128}, + {"item1":"gal", "item2":"qt", "factor":4}, + + + {"item1":"acre", "item2":"yd^2", "factor":4840}, + {"item1":"yd^2", "item2":"m^2", "factor":0.836127}, + + {"item1":"mph", "item2":"knot", "factor":0.868976}, + {"item1":"mph", "item2":"kph", "factor":1.609343550606653}, + + + {"item1":"kPa", "item2":"Pa", "factor":1000}, + {"item1":"Nm^2", "item2":"Pa", "factor":1}, + {"item1":"Pa", "item2":"bar", "factor":100}, + {"item1":"atm", "item2":"Pa", "factor":101325}, + {"item1":"bar", "item2":"psi", "factor":14.5038} + ] +} \ No newline at end of file diff --git a/shtikbot-discord.csproj b/shtikbot-discord.csproj index 9e75929..c57d539 100644 --- a/shtikbot-discord.csproj +++ b/shtikbot-discord.csproj @@ -16,14 +16,17 @@ - - Always - Always Always + + Always + + + Never +