From 354876985d83d517698c0de7cd6e53d8d46aa986 Mon Sep 17 00:00:00 2001 From: jetsparrow Date: Sun, 27 Feb 2022 18:21:35 +0300 Subject: [PATCH] Switching to NET6 --- .../AntiAntiSwearingBot.Tests.csproj | 2 +- AntiAntiSwearingBot/AntiAntiSwearingBot.cs | 133 +++++++-------- .../AntiAntiSwearingBot.csproj | 6 +- AntiAntiSwearingBot/CommandRouter.cs | 63 ++++--- AntiAntiSwearingBot/Commands/CommandString.cs | 60 ++++--- AntiAntiSwearingBot/Commands/LearnCommand.cs | 39 ++--- .../Commands/UnlearnCommand.cs | 43 +++-- AntiAntiSwearingBot/Config.cs | 55 +++---- AntiAntiSwearingBot/ConfigBase.cs | 123 +++++++------- .../Extensions/IListExtensions.cs | 31 ++-- .../IReadOnlyDictionaryExtensions.cs | 15 -- AntiAntiSwearingBot/GlobalSuppressions.cs | 6 + AntiAntiSwearingBot/GlobalUsings.cs | 5 + AntiAntiSwearingBot/Language.cs | 127 +++++++------- AntiAntiSwearingBot/Program.cs | 82 +++------ AntiAntiSwearingBot/SearchDictionary.cs | 155 +++++++++--------- AntiAntiSwearingBot/Unbleeper.cs | 99 ++++++----- 17 files changed, 487 insertions(+), 557 deletions(-) delete mode 100644 AntiAntiSwearingBot/Extensions/IReadOnlyDictionaryExtensions.cs create mode 100644 AntiAntiSwearingBot/GlobalSuppressions.cs create mode 100644 AntiAntiSwearingBot/GlobalUsings.cs diff --git a/AntiAntiSwearingBot.Tests/AntiAntiSwearingBot.Tests.csproj b/AntiAntiSwearingBot.Tests/AntiAntiSwearingBot.Tests.csproj index 0c26fbd..d6bbd71 100644 --- a/AntiAntiSwearingBot.Tests/AntiAntiSwearingBot.Tests.csproj +++ b/AntiAntiSwearingBot.Tests/AntiAntiSwearingBot.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + net6.0 false diff --git a/AntiAntiSwearingBot/AntiAntiSwearingBot.cs b/AntiAntiSwearingBot/AntiAntiSwearingBot.cs index f052156..9b8bf89 100644 --- a/AntiAntiSwearingBot/AntiAntiSwearingBot.cs +++ b/AntiAntiSwearingBot/AntiAntiSwearingBot.cs @@ -1,96 +1,97 @@ -using System; -using System.Linq; -using System.Net; -using System.Text; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; -using AntiAntiSwearingBot.Commands; using Telegram.Bot; -using Telegram.Bot.Args; +using Telegram.Bot.Exceptions; +using Telegram.Bot.Extensions.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; +using AntiAntiSwearingBot.Commands; -namespace AntiAntiSwearingBot +namespace AntiAntiSwearingBot; + +public class AntiAntiSwearingBot { - public class AntiAntiSwearingBot : IDisposable + Config Config { get; } + SearchDictionary Dict { get; } + Unbleeper Unbleeper { get; } + + public AntiAntiSwearingBot(Config cfg, SearchDictionary dict) { - Config Config { get; } - SearchDictionary Dict { get; } - Unbleeper Unbleeper { get; } + Config = cfg; + Dict = dict; + Unbleeper = new Unbleeper(dict, cfg.Unbleeper); + } - public AntiAntiSwearingBot(Config cfg, SearchDictionary dict) + TelegramBotClient TelegramBot { get; set; } + ChatCommandRouter Router { get; set; } + public User Me { get; private set; } + + public async Task Init() + { + if (string.IsNullOrWhiteSpace(Config.ApiKey)) + return; + + TelegramBot = new TelegramBotClient(Config.ApiKey); + + Me = await TelegramBot.GetMeAsync(); + + + Router = new ChatCommandRouter(Me.Username); + Router.Add(new LearnCommand(Dict), "learn"); + Router.Add(new UnlearnCommand(Dict), "unlearn"); + + var receiverOptions = new ReceiverOptions { AllowedUpdates = new[] { UpdateType.Message } }; + TelegramBot.StartReceiving( + HandleUpdateAsync, + HandleErrorAsync, + receiverOptions); + } + + Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) + { + var ErrorMessage = exception switch { - Config = cfg; - Dict = dict; - Unbleeper = new Unbleeper(dict, cfg.Unbleeper); - } + ApiRequestException apiRequestException => $"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}", + _ => exception.ToString() + }; + Console.WriteLine(ErrorMessage); + return Task.CompletedTask; + } - TelegramBotClient Client { get; set; } - ChatCommandRouter Router { get; set; } - public User Me { get; private set; } - - public async Task Init() + async Task HandleUpdateAsync(ITelegramBotClient sender, Update update, CancellationToken cancellationToken) + { + if (update.Type != UpdateType.Message || update?.Message?.Type != MessageType.Text) + return; + var msg = update.Message!; + try { - var httpProxy = new WebProxy($"{Config.Proxy.Url}:{Config.Proxy.Port}") - { - Credentials = new NetworkCredential(Config.Proxy.Login, Config.Proxy.Password) - }; - - Client = new TelegramBotClient(Config.ApiKey, httpProxy); - Me = await Client.GetMeAsync(); - Router = new ChatCommandRouter(Me.Username); - Router.Add(new LearnCommand(Dict), "learn"); - Router.Add(new UnlearnCommand(Dict), "unlearn"); - - Client.OnMessage += BotOnMessageReceived; - Client.StartReceiving(); - } - - public async Task Stop() - { - Dispose(); - } - - #region service - - void BotOnMessageReceived(object sender, MessageEventArgs args) - { - var msg = args.Message; - if (msg == null || msg.Type != MessageType.Text) - return; - string commandResponse = null; - try { commandResponse = Router.Execute(sender, args); } + try { commandResponse = Router.Execute(sender, update); } catch { } if (commandResponse != null) { - Client.SendTextMessageAsync( - args.Message.Chat.Id, + await TelegramBot.SendTextMessageAsync( + msg.Chat.Id, commandResponse, - replyToMessageId: args.Message.MessageId); + replyToMessageId: msg.MessageId); } else { var unbleepResponse = Unbleeper.UnbleepSwears(msg.Text); if (unbleepResponse != null) - Client.SendTextMessageAsync( - args.Message.Chat.Id, + await TelegramBot.SendTextMessageAsync( + msg.Chat.Id, unbleepResponse, - replyToMessageId: args.Message.MessageId); + replyToMessageId: msg.MessageId); } + } - - #endregion - - #region IDisposable - - public void Dispose() + catch (Exception e) { - Client.StopReceiving(); + Console.WriteLine(e); } - - #endregion } } diff --git a/AntiAntiSwearingBot/AntiAntiSwearingBot.csproj b/AntiAntiSwearingBot/AntiAntiSwearingBot.csproj index c87eaae..bb3cea5 100644 --- a/AntiAntiSwearingBot/AntiAntiSwearingBot.csproj +++ b/AntiAntiSwearingBot/AntiAntiSwearingBot.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.0 + net6.0 @@ -10,8 +10,8 @@ - - + + diff --git a/AntiAntiSwearingBot/CommandRouter.cs b/AntiAntiSwearingBot/CommandRouter.cs index 800a8de..e8a5353 100644 --- a/AntiAntiSwearingBot/CommandRouter.cs +++ b/AntiAntiSwearingBot/CommandRouter.cs @@ -1,47 +1,44 @@ -using System; -using System.Collections.Generic; -using Telegram.Bot.Args; +using Telegram.Bot.Types; using AntiAntiSwearingBot.Commands; -namespace AntiAntiSwearingBot +namespace AntiAntiSwearingBot; + +public interface IChatCommand { - public interface IChatCommand + string Execute(CommandString cmd, Update messageEventArgs); +} + +public class ChatCommandRouter +{ + string Username { get; } + Dictionary Commands { get; } + + public ChatCommandRouter(string username) { - string Execute(CommandString cmd, MessageEventArgs messageEventArgs); + Username = username; + Commands = new Dictionary(); } - public class ChatCommandRouter + public string Execute(object sender, Update args) { - string Username { get; } - Dictionary Commands { get; } - - public ChatCommandRouter(string username) + var text = args.Message.Text; + if (CommandString.TryParse(text, out var cmd)) { - Username = username; - Commands = new Dictionary(); + if (cmd.Username != null && cmd.Username != Username) + return null; + if (Commands.ContainsKey(cmd.Command)) + return Commands[cmd.Command].Execute(cmd, args); } + return null; + } - public string Execute(object sender, MessageEventArgs args) + public void Add(IChatCommand c, params string[] cmds) + { + foreach (var cmd in cmds) { - var text = args.Message.Text; - if (CommandString.TryParse(text, out var cmd)) - { - if (cmd.UserName != null && cmd.UserName != Username) - return null; - if (Commands.ContainsKey(cmd.Command)) - return Commands[cmd.Command].Execute(cmd, args); - } - return null; - } - - public void Add(IChatCommand c, params string[] cmds) - { - foreach (var cmd in cmds) - { - if (Commands.ContainsKey(cmd)) - throw new ArgumentException($"collision for {cmd}, commands {Commands[cmd].GetType()} and {c.GetType()}"); - Commands[cmd] = c; - } + if (Commands.ContainsKey(cmd)) + throw new ArgumentException($"collision for {cmd}, commands {Commands[cmd].GetType()} and {c.GetType()}"); + Commands[cmd] = c; } } } diff --git a/AntiAntiSwearingBot/Commands/CommandString.cs b/AntiAntiSwearingBot/Commands/CommandString.cs index dc1d512..5603f33 100644 --- a/AntiAntiSwearingBot/Commands/CommandString.cs +++ b/AntiAntiSwearingBot/Commands/CommandString.cs @@ -1,44 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; -namespace AntiAntiSwearingBot.Commands +namespace AntiAntiSwearingBot.Commands; + +public class CommandString { - public class CommandString + public CommandString(string command, string username, params string[] parameters) { - public CommandString(string command, string username, params string[] parameters) - { - Command = command; - Parameters = parameters; - } + Command = command; + Username = username; + Parameters = parameters; + } - public string Command { get; } - public string UserName { get; } - public string[] Parameters { get; } + public string Command { get; } + public string Username { get; } + public string[] Parameters { get; } - static readonly char[] WS_CHARS = new[] { ' ', '\r', '\n', '\n' }; + static readonly char[] WS_CHARS = new[] { ' ', '\r', '\n', '\n' }; - public static bool TryParse(string s, out CommandString result) - { - result = null; - if (string.IsNullOrWhiteSpace(s) || s[0] != '/') - return false; + public static bool TryParse(string s, out CommandString result) + { + result = null; + if (string.IsNullOrWhiteSpace(s) || s[0] != '/') + return false; - string[] words = s.Split(WS_CHARS, StringSplitOptions.RemoveEmptyEntries); + string[] words = s.Split(WS_CHARS, StringSplitOptions.RemoveEmptyEntries); - var cmdRegex = new Regex(@"/(?\w+)(@(?\w+))?"); - var match = cmdRegex.Match(words.First()); - if (!match.Success) - return false; + var cmdRegex = new Regex(@"/(?\w+)(@(?\w+))?"); + var match = cmdRegex.Match(words.First()); + if (!match.Success) + return false; - string cmd = match.Groups["cmd"].Captures[0].Value; - string username = match.Groups["name"].Captures.Count > 0 ? match.Groups["name"].Captures[0].Value : null; - string[] parameters = words.Skip(1).ToArray(); + string cmd = match.Groups["cmd"].Captures[0].Value; + string username = match.Groups["name"].Captures.Count > 0 ? match.Groups["name"].Captures[0].Value : null; + string[] parameters = words.Skip(1).ToArray(); - result = new CommandString(cmd, username, parameters); - return true; - } + result = new CommandString(cmd, username, parameters); + return true; } } diff --git a/AntiAntiSwearingBot/Commands/LearnCommand.cs b/AntiAntiSwearingBot/Commands/LearnCommand.cs index 296e8eb..5edb980 100644 --- a/AntiAntiSwearingBot/Commands/LearnCommand.cs +++ b/AntiAntiSwearingBot/Commands/LearnCommand.cs @@ -1,29 +1,26 @@ -using System.Linq; -using System.Text.RegularExpressions; -using Telegram.Bot.Args; +using System.Text.RegularExpressions; +using Telegram.Bot.Types; -namespace AntiAntiSwearingBot.Commands +namespace AntiAntiSwearingBot.Commands; +public class LearnCommand : IChatCommand { - public class LearnCommand : IChatCommand + SearchDictionary Dict { get; } + + public LearnCommand(SearchDictionary dict) { - SearchDictionary Dict { get; } + Dict = dict; + } - public LearnCommand(SearchDictionary dict) - { - Dict = dict; - } + public string Execute(CommandString cmd, Update args) + { + var word = cmd.Parameters.FirstOrDefault(); + if (string.IsNullOrWhiteSpace(word)) + return null; - public string Execute(CommandString cmd, MessageEventArgs args) - { - var word = cmd.Parameters.FirstOrDefault(); - if (string.IsNullOrWhiteSpace(word)) - return null; + if (!Regex.IsMatch(word, @"[а-яА-Я]+")) + return null; - if (!Regex.IsMatch(word, @"[а-яА-Я]+")) - return null; - - bool newWord = Dict.Learn(word); - return newWord ? $"Принято слово \"{word}\"" : $"Поднял рейтинг слову \"{word}\""; - } + bool newWord = Dict.Learn(word); + return newWord ? $"Принято слово \"{word}\"" : $"Поднял рейтинг слову \"{word}\""; } } diff --git a/AntiAntiSwearingBot/Commands/UnlearnCommand.cs b/AntiAntiSwearingBot/Commands/UnlearnCommand.cs index 1754208..f699649 100644 --- a/AntiAntiSwearingBot/Commands/UnlearnCommand.cs +++ b/AntiAntiSwearingBot/Commands/UnlearnCommand.cs @@ -1,30 +1,27 @@ -using System.Linq; -using System.Text.RegularExpressions; -using Telegram.Bot.Args; +using System.Text.RegularExpressions; +using Telegram.Bot.Types; -namespace AntiAntiSwearingBot.Commands +namespace AntiAntiSwearingBot.Commands; +public class UnlearnCommand : IChatCommand { - public class UnlearnCommand : IChatCommand + SearchDictionary Dict { get; } + + public UnlearnCommand(SearchDictionary dict) { - SearchDictionary Dict { get; } + Dict = dict; + } - public UnlearnCommand(SearchDictionary dict) - { - Dict = dict; - } + public string Execute(CommandString cmd, Update args) + { + var word = cmd.Parameters.FirstOrDefault(); + if (string.IsNullOrWhiteSpace(word)) + return null; - public string Execute(CommandString cmd, MessageEventArgs args) - { - var word = cmd.Parameters.FirstOrDefault(); - if (string.IsNullOrWhiteSpace(word)) - return null; - - if (!Regex.IsMatch(word, @"[а-яА-Я]+")) - return null; - if (Dict.Unlearn(word)) - return $"Удалил слово \"{word}\""; - else - return $"Не нашел слово \"{word}\""; - } + if (!Regex.IsMatch(word, @"[а-яА-Я]+")) + return null; + if (Dict.Unlearn(word)) + return $"Удалил слово \"{word}\""; + else + return $"Не нашел слово \"{word}\""; } } diff --git a/AntiAntiSwearingBot/Config.cs b/AntiAntiSwearingBot/Config.cs index 0a164d4..e9bbf07 100644 --- a/AntiAntiSwearingBot/Config.cs +++ b/AntiAntiSwearingBot/Config.cs @@ -1,32 +1,29 @@ -namespace AntiAntiSwearingBot +namespace AntiAntiSwearingBot; + +public class Config : ConfigBase { - public class Config : ConfigBase - { - public string ApiKey { get; private set; } - public ProxySettings Proxy { get; private set; } - public SearchDictionarySettings SearchDictionary { get; private set; } - public UnbleeperSettings Unbleeper { get; private set; } - } - - public struct UnbleeperSettings - { - public string BleepedSwearsRegex { get; private set; } - public int MinAmbiguousWordLength { get; private set; } - public int MinWordLength { get; private set; } - } - - public struct SearchDictionarySettings - { - public string DictionaryPath { get; private set; } - } - - public struct ProxySettings - { - public string Url { get; private set; } - public int Port { get; private set; } - public string Login { get; private set; } - public string Password { get; private set; } - } - + public string ApiKey { get; private set; } + public ProxySettings Proxy { get; private set; } + public SearchDictionarySettings SearchDictionary { get; private set; } + public UnbleeperSettings Unbleeper { get; private set; } } +public struct UnbleeperSettings +{ + public string BleepedSwearsRegex { get; private set; } + public int MinAmbiguousWordLength { get; private set; } + public int MinWordLength { get; private set; } +} + +public struct SearchDictionarySettings +{ + public string DictionaryPath { get; private set; } +} + +public struct ProxySettings +{ + public string Url { get; private set; } + public int Port { get; private set; } + public string Login { get; private set; } + public string Password { get; private set; } +} diff --git a/AntiAntiSwearingBot/ConfigBase.cs b/AntiAntiSwearingBot/ConfigBase.cs index c1e1707..58b6c95 100644 --- a/AntiAntiSwearingBot/ConfigBase.cs +++ b/AntiAntiSwearingBot/ConfigBase.cs @@ -1,76 +1,75 @@ using System.IO; +using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System.Reflection; using Newtonsoft.Json.Serialization; -namespace AntiAntiSwearingBot +namespace AntiAntiSwearingBot; + +public abstract class ConfigBase { - public abstract class ConfigBase + public static T Load(params string[] paths) where T : ConfigBase, new() { - public static T Load(params string[] paths) where T : ConfigBase, new() + var result = new T(); + var configJson = new JObject(); + var mergeSettings = new JsonMergeSettings { - var result = new T(); - var configJson = new JObject(); - var mergeSettings = new JsonMergeSettings + MergeArrayHandling = MergeArrayHandling.Union + }; + + foreach (var path in paths) + { + try { configJson.Merge(JObject.Parse(File.ReadAllText(path)), mergeSettings);} + catch { } + } + + using (var sr = configJson.CreateReader()) + { + var settings = new JsonSerializerSettings { - MergeArrayHandling = MergeArrayHandling.Union + ContractResolver = new PrivateSetterContractResolver() }; - - foreach (var path in paths) - { - try { configJson.Merge(JObject.Parse(File.ReadAllText(path)), mergeSettings);} - catch { } - } - - using (var sr = configJson.CreateReader()) - { - var settings = new JsonSerializerSettings - { - ContractResolver = new PrivateSetterContractResolver() - }; - JsonSerializer.CreateDefault(settings).Populate(sr, result); - } - - return result; + JsonSerializer.CreateDefault(settings).Populate(sr, result); } - } - public class PrivateSetterContractResolver : DefaultContractResolver - { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var jProperty = base.CreateProperty(member, memberSerialization); - if (jProperty.Writable) - return jProperty; - - jProperty.Writable = member.IsPropertyWithSetter(); - - return jProperty; - } - } - - public class PrivateSetterCamelCasePropertyNamesContractResolver : CamelCasePropertyNamesContractResolver - { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var jProperty = base.CreateProperty(member, memberSerialization); - if (jProperty.Writable) - return jProperty; - - jProperty.Writable = member.IsPropertyWithSetter(); - - return jProperty; - } - } - - internal static class MemberInfoExtensions - { - internal static bool IsPropertyWithSetter(this MemberInfo member) - { - var property = member as PropertyInfo; - - return property?.GetSetMethod(true) != null; - } + return result; + } +} + +public class PrivateSetterContractResolver : DefaultContractResolver +{ + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var jProperty = base.CreateProperty(member, memberSerialization); + if (jProperty.Writable) + return jProperty; + + jProperty.Writable = member.IsPropertyWithSetter(); + + return jProperty; + } +} + +public class PrivateSetterCamelCasePropertyNamesContractResolver : CamelCasePropertyNamesContractResolver +{ + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var jProperty = base.CreateProperty(member, memberSerialization); + if (jProperty.Writable) + return jProperty; + + jProperty.Writable = member.IsPropertyWithSetter(); + + return jProperty; + } +} + +internal static class MemberInfoExtensions +{ + internal static bool IsPropertyWithSetter(this MemberInfo member) + { + var property = member as PropertyInfo; + + return property?.GetSetMethod(true) != null; } } diff --git a/AntiAntiSwearingBot/Extensions/IListExtensions.cs b/AntiAntiSwearingBot/Extensions/IListExtensions.cs index e352511..0a5b439 100644 --- a/AntiAntiSwearingBot/Extensions/IListExtensions.cs +++ b/AntiAntiSwearingBot/Extensions/IListExtensions.cs @@ -1,23 +1,18 @@ -using System; -using System.Collections.Generic; - -namespace AntiAntiSwearingBot +namespace AntiAntiSwearingBot; +public static class IListExtensions { - public static class IListExtensions + public static void Move(this IList list, int from, int to) { - public static void Move(this IList list, int from, int to) - { - if (from < 0 || from > list.Count) - throw new ArgumentOutOfRangeException("from"); - if (to < 0 || to > list.Count) - throw new ArgumentOutOfRangeException("to"); - if (from == to) - return; + if (from < 0 || from > list.Count) + throw new ArgumentOutOfRangeException("from"); + if (to < 0 || to > list.Count) + throw new ArgumentOutOfRangeException("to"); + if (from == to) + return; - var item = list[from]; - list.RemoveAt(from); - if (to > from) --to; // the actual index could have shifted due to the removal - list.Insert(to, item); - } + var item = list[from]; + list.RemoveAt(from); + if (to > from) --to; // the actual index could have shifted due to the removal + list.Insert(to, item); } } diff --git a/AntiAntiSwearingBot/Extensions/IReadOnlyDictionaryExtensions.cs b/AntiAntiSwearingBot/Extensions/IReadOnlyDictionaryExtensions.cs deleted file mode 100644 index 5155778..0000000 --- a/AntiAntiSwearingBot/Extensions/IReadOnlyDictionaryExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace AntiAntiSwearingBot -{ - public static class IReadOnlyDictionaryExtensions - { - public static TValue GetOrDefault(this IReadOnlyDictionary dict, TKey key) - { - TValue res = default(TValue); - if (key != null) - dict.TryGetValue(key, out res); - return res; - } - } -} diff --git a/AntiAntiSwearingBot/GlobalSuppressions.cs b/AntiAntiSwearingBot/GlobalSuppressions.cs new file mode 100644 index 0000000..b6f22e9 --- /dev/null +++ b/AntiAntiSwearingBot/GlobalSuppressions.cs @@ -0,0 +1,6 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; diff --git a/AntiAntiSwearingBot/GlobalUsings.cs b/AntiAntiSwearingBot/GlobalUsings.cs new file mode 100644 index 0000000..e5252f6 --- /dev/null +++ b/AntiAntiSwearingBot/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System; +global using System.Text; +global using System.Linq; +global using System.Collections.Generic; + diff --git a/AntiAntiSwearingBot/Language.cs b/AntiAntiSwearingBot/Language.cs index 3ad7ded..2d1d986 100644 --- a/AntiAntiSwearingBot/Language.cs +++ b/AntiAntiSwearingBot/Language.cs @@ -1,76 +1,69 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; -namespace AntiAntiSwearingBot +namespace AntiAntiSwearingBot; +public static class Language { - public static class Language + static int min(int a, int b, int c) { return Math.Min(Math.Min(a, b), c); } + + public static int HammingDistance(string a, string b) { - static int min(int a, int b, int c) { return Math.Min(Math.Min(a, b), c); } - - public static int HammingDistance(string a, string b) + if (string.IsNullOrEmpty(a)) { - if (string.IsNullOrEmpty(a)) - { - if (string.IsNullOrEmpty(b)) - return 0; - return b.Length; - } - - int dist = 0; - int len = Math.Min(a.Length, b.Length); - int leftover = Math.Max(a.Length, b.Length) - len; - for (int i = 0; i < len; ++i) - if (!CharMatch(a[i], b[i])) - ++dist; - - return leftover + dist; + if (string.IsNullOrEmpty(b)) + return 0; + return b.Length; } - public static int LevenshteinDistance(string a, string b) - { - int[] prevRow = new int[b.Length + 1]; - int[] thisRow = new int[b.Length + 1]; - - // init thisRow as - for (int i = 0; i < prevRow.Length; i++) prevRow[i] = i; - - for (int i = 0; i < a.Length; i++) - { - thisRow[0] = i + 1; - for (int j = 0; j < b.Length; j++) - { - var cost = CharMatch(a[i], b[j]) ? 0 : 1; - thisRow[j + 1] = min(thisRow[j] + 1, prevRow[j + 1] + 1, prevRow[j] + cost); - } - - var t = prevRow; - prevRow = thisRow; - thisRow = t; - } - return prevRow[b.Length]; - } - - public static bool CharMatch(char a, char b) - => a == b || !char.IsLetterOrDigit(a) || !char.IsLetterOrDigit(b); - - static readonly Regex MentionRegex = new Regex("^@[a-zA-Z0-9_]+$", RegexOptions.Compiled); - static readonly Regex EmailPartRegex = new Regex("^\\w+@\\w+$", RegexOptions.Compiled); - - static readonly Regex HashTagRegex = new Regex("^#\\w+$", RegexOptions.Compiled); - - public static bool IsTelegramMention(string word) => MentionRegex.IsMatch(word); - - public static bool IsEmailPart(string word) => EmailPartRegex.IsMatch(word); - - public static bool IsHashTag(string word) => HashTagRegex.IsMatch(word); - - public static bool HasNonWordChars(string arg) => arg.Any(c => !char.IsLetterOrDigit(c)); - - public static bool HasWordChars(string arg) => arg.Any(char.IsLetter); - + int dist = 0; + int len = Math.Min(a.Length, b.Length); + int leftover = Math.Max(a.Length, b.Length) - len; + for (int i = 0; i < len; ++i) + if (!CharMatch(a[i], b[i])) + ++dist; + return leftover + dist; } + + public static int LevenshteinDistance(string a, string b) + { + int[] prevRow = new int[b.Length + 1]; + int[] thisRow = new int[b.Length + 1]; + + // init thisRow as + for (int i = 0; i < prevRow.Length; i++) prevRow[i] = i; + + for (int i = 0; i < a.Length; i++) + { + thisRow[0] = i + 1; + for (int j = 0; j < b.Length; j++) + { + var cost = CharMatch(a[i], b[j]) ? 0 : 1; + thisRow[j + 1] = min(thisRow[j] + 1, prevRow[j + 1] + 1, prevRow[j] + cost); + } + + var t = prevRow; + prevRow = thisRow; + thisRow = t; + } + return prevRow[b.Length]; + } + + public static bool CharMatch(char a, char b) + => a == b || !char.IsLetterOrDigit(a) || !char.IsLetterOrDigit(b); + + static readonly Regex MentionRegex = new Regex("^@[a-zA-Z0-9_]+$", RegexOptions.Compiled); + static readonly Regex EmailPartRegex = new Regex("^\\w+@\\w+$", RegexOptions.Compiled); + + static readonly Regex HashTagRegex = new Regex("^#\\w+$", RegexOptions.Compiled); + + public static bool IsTelegramMention(string word) => MentionRegex.IsMatch(word); + + public static bool IsEmailPart(string word) => EmailPartRegex.IsMatch(word); + + public static bool IsHashTag(string word) => HashTagRegex.IsMatch(word); + + public static bool HasNonWordChars(string arg) => arg.Any(c => !char.IsLetterOrDigit(c)); + + public static bool HasWordChars(string arg) => arg.Any(char.IsLetter); + } diff --git a/AntiAntiSwearingBot/Program.cs b/AntiAntiSwearingBot/Program.cs index a981e6b..e65c6d3 100644 --- a/AntiAntiSwearingBot/Program.cs +++ b/AntiAntiSwearingBot/Program.cs @@ -1,59 +1,31 @@ -using System; -using System.Threading; +using System.Threading; +using AntiAntiSwearingBot; -namespace AntiAntiSwearingBot +static void Log(string m) => Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}|{m}"); + +Log("AntiAntiSwearBot starting...."); + +var cfg = Config.Load("aasb.cfg.json", "aasb.cfg.secret.json"); +var dict = new SearchDictionary(cfg); +Log($"{dict.Count} words loaded."); +var bot = new AntiAntiSwearingBot.AntiAntiSwearingBot(cfg, dict); +bot.Init().Wait(); +Log($"Connected to Telegram as @{bot.Me.Username}"); +Log("AntiAntiSwearBot started! Press Ctrl-C to exit."); + +ManualResetEvent quitEvent = new ManualResetEvent(false); +try { - public static class Program + Console.CancelKeyPress += (sender, eArgs) => // ctrl-c { - public enum ExitCode : int - { - Ok = 0, - ErrorNotStarted = 0x80, - ErrorRunning = 0x81, - ErrorException = 0x82, - ErrorInvalidCommandLine = 0x100 - }; - - static void Log(string m) => Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}|{m}"); - - public static int Main(string[] args) - { - try - { - Log("AntiAntiSwearBot starting...."); - - var cfg = Config.Load("aasb.cfg.json", "aasb.cfg.secret.json"); - var dict = new SearchDictionary(cfg); - Log($"{dict.Count} words loaded."); - var bot = new AntiAntiSwearingBot(cfg, dict); - bot.Init().Wait(); - Log($"Connected to Telegram as @{bot.Me.Username}"); - Log("AntiAntiSwearBot started! Press Ctrl-C to exit."); - Environment.ExitCode = (int)ExitCode.ErrorRunning; - - ManualResetEvent quitEvent = new ManualResetEvent(false); - try - { - Console.CancelKeyPress += (sender, eArgs) => // ctrl-c - { - eArgs.Cancel = true; - quitEvent.Set(); - }; - } - catch { } - - quitEvent.WaitOne(Timeout.Infinite); - - Console.WriteLine("Waiting for exit..."); - bot.Stop().Wait(); - dict.Save(); - return (int)ExitCode.Ok; - } - catch (Exception ex) - { - Console.WriteLine(ex); - return (int)ExitCode.ErrorException; - } - } - } + eArgs.Cancel = true; + quitEvent.Set(); + }; } +catch { } + +quitEvent.WaitOne(Timeout.Infinite); + +Console.WriteLine("Waiting for exit..."); +dict.Save(); + diff --git a/AntiAntiSwearingBot/SearchDictionary.cs b/AntiAntiSwearingBot/SearchDictionary.cs index f6ff071..08fb16f 100644 --- a/AntiAntiSwearingBot/SearchDictionary.cs +++ b/AntiAntiSwearingBot/SearchDictionary.cs @@ -1,86 +1,81 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.IO; -namespace AntiAntiSwearingBot +namespace AntiAntiSwearingBot; +public class SearchDictionary { - public class SearchDictionary + public SearchDictionary(Config cfg) { - public SearchDictionary(Config cfg) - { - var s = cfg.SearchDictionary; - path = s.DictionaryPath; - tmppath = path + ".tmp"; + var s = cfg.SearchDictionary; + path = s.DictionaryPath; + tmppath = path + ".tmp"; - words = File.ReadAllLines(path).ToList(); - } - - public int Count => words.Count; - - public void Save() - { - if (File.Exists(tmppath)) - File.Delete(tmppath); - File.WriteAllLines(tmppath, words); - if (File.Exists(path)) - File.Delete(path); - File.Move(tmppath, path); - } - - public struct WordMatch - { - public string Word; - public int Distance; - public int Rating; - } - - public WordMatch Match(string pattern) - => AllMatches(pattern).First(); - - public IEnumerable AllMatches(string pattern) - { - lock (SyncRoot) - { - pattern = pattern.ToLowerInvariant(); - return words - .Select((w, i) => new WordMatch { Word = w, Distance = Language.LevenshteinDistance(pattern, w), Rating = i }) - .OrderBy(m => m.Distance) - .ThenBy(m => m.Rating); - } - } - - public bool Learn(string word) - { - lock (SyncRoot) - { - int index = words.IndexOf(word); - if (index > 0) - { - words.Move(index, 0); - return false; - } - else - { - words.Insert(0, word); - return true; - } - } - } - - public bool Unlearn(string word) - { - lock (SyncRoot) - return words.Remove(word); - } - - #region service - - readonly string path, tmppath; - - object SyncRoot = new object(); - List words; - - #endregion + words = File.ReadAllLines(path).ToList(); } + + public int Count => words.Count; + + public void Save() + { + if (File.Exists(tmppath)) + File.Delete(tmppath); + File.WriteAllLines(tmppath, words); + if (File.Exists(path)) + File.Delete(path); + File.Move(tmppath, path); + } + + public struct WordMatch + { + public string Word; + public int Distance; + public int Rating; + } + + public WordMatch Match(string pattern) + => AllMatches(pattern).First(); + + public IEnumerable AllMatches(string pattern) + { + lock (SyncRoot) + { + pattern = pattern.ToLowerInvariant(); + return words + .Select((w, i) => new WordMatch { Word = w, Distance = Language.LevenshteinDistance(pattern, w), Rating = i }) + .OrderBy(m => m.Distance) + .ThenBy(m => m.Rating); + } + } + + public bool Learn(string word) + { + lock (SyncRoot) + { + int index = words.IndexOf(word); + if (index > 0) + { + words.Move(index, 0); + return false; + } + else + { + words.Insert(0, word); + return true; + } + } + } + + public bool Unlearn(string word) + { + lock (SyncRoot) + return words.Remove(word); + } + + #region service + + readonly string path, tmppath; + + object SyncRoot = new object(); + List words; + + #endregion } diff --git a/AntiAntiSwearingBot/Unbleeper.cs b/AntiAntiSwearingBot/Unbleeper.cs index be83309..2b14dae 100644 --- a/AntiAntiSwearingBot/Unbleeper.cs +++ b/AntiAntiSwearingBot/Unbleeper.cs @@ -1,62 +1,57 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; -namespace AntiAntiSwearingBot +namespace AntiAntiSwearingBot; + +public class Unbleeper { - public class Unbleeper + SearchDictionary Dict { get; } + UnbleeperSettings Cfg { get; } + + public Unbleeper(SearchDictionary dict, UnbleeperSettings cfg) { - SearchDictionary Dict { get; } - UnbleeperSettings Cfg { get; } + Dict = dict; + Cfg = cfg; + BleepedSwearsRegex = new Regex("^" + Cfg.BleepedSwearsRegex + "$", RegexOptions.Compiled); + } - public Unbleeper(SearchDictionary dict, UnbleeperSettings cfg) + Regex BleepedSwearsRegex { get; } + + static readonly char[] WORD_SEPARATORS = { ' ', '\t', '\r', '\n', '.', ',', '!', '?', ';', ':', '-', '—' }; + + public string UnbleepSwears(string text) + { + if (string.IsNullOrWhiteSpace(text)) + return null; + + text = text.Trim(); + + if (text.StartsWith('/')) // is chat command + return null; + + var words = text.Split(WORD_SEPARATORS, StringSplitOptions.RemoveEmptyEntries); + var candidates = words + .Where(w => + !Language.IsTelegramMention(w) + && !Language.IsEmailPart(w) + && Language.HasNonWordChars(w) + && !Language.IsHashTag(w) + && (Language.HasWordChars(w) || w.Length >= Cfg.MinAmbiguousWordLength) + && w.Length >= Cfg.MinWordLength + && BleepedSwearsRegex.IsMatch(w) + ) + .ToArray(); + + if (candidates.Any()) { - Dict = dict; - Cfg = cfg; - BleepedSwearsRegex = new Regex("^" + Cfg.BleepedSwearsRegex + "$", RegexOptions.Compiled); - } - - Regex BleepedSwearsRegex { get; } - - static readonly char[] WORD_SEPARATORS = { ' ', '\t', '\r', '\n', '.', ',', '!', '?', ';', ':', '-', '—' }; - - public string UnbleepSwears(string text) - { - if (string.IsNullOrWhiteSpace(text)) - return null; - - text = text.Trim(); - - if (text.StartsWith('/')) // is chat command - return null; - - var words = text.Split(WORD_SEPARATORS, StringSplitOptions.RemoveEmptyEntries); - var candidates = words - .Where(w => - !Language.IsTelegramMention(w) - && !Language.IsEmailPart(w) - && Language.HasNonWordChars(w) - && !Language.IsHashTag(w) - && (Language.HasWordChars(w) || w.Length >= Cfg.MinAmbiguousWordLength) - && w.Length >= Cfg.MinWordLength - && BleepedSwearsRegex.IsMatch(w) - ) - .ToArray(); - - if (candidates.Any()) + var response = new StringBuilder(); + for (int i = 0; i < candidates.Length; ++i) { - var response = new StringBuilder(); - for (int i = 0; i < candidates.Length; ++i) - { - var m = Dict.Match(candidates[i]); - response.AppendLine(new string('*', i + 1) + m.Word + new string('?', m.Distance)); - } - return response.ToString(); + var m = Dict.Match(candidates[i]); + response.AppendLine(new string('*', i + 1) + m.Word + new string('?', m.Distance)); } - else - return null; + return response.ToString(); } + else + return null; } }