From 2b8fd15e8e23fcd63cd81a9e9403c32fd5015872 Mon Sep 17 00:00:00 2001 From: jetsparrow Date: Wed, 14 Aug 2019 00:05:56 +0300 Subject: [PATCH] Initial release --- .gitignore | 46 + AntiAntiSwearingBot.sln | 25 + AntiAntiSwearingBot/AntiAntiSwearingBot.cs | 127 ++ .../AntiAntiSwearingBot.csproj | 29 + AntiAntiSwearingBot/CommandRouter.cs | 50 + AntiAntiSwearingBot/Commands/CommandString.cs | 44 + AntiAntiSwearingBot/Commands/LearnCommand.cs | 29 + .../Commands/UnlearnCommand.cs | 38 + AntiAntiSwearingBot/Config.cs | 32 + AntiAntiSwearingBot/ConfigBase.cs | 76 + .../Extensions/IListExtensions.cs | 23 + .../IReadOnlyDictionaryExtensions.cs | 15 + AntiAntiSwearingBot/Language.cs | 100 ++ AntiAntiSwearingBot/Program.cs | 38 + .../PublishProfiles/FolderProfile.pubxml | 16 + AntiAntiSwearingBot/SearchDictionary.cs | 108 ++ AntiAntiSwearingBot/aasb.cfg.json | 10 + .../dict/ObsceneDictionaryRu.txt | 1313 +++++++++++++++++ 18 files changed, 2119 insertions(+) create mode 100644 .gitignore create mode 100644 AntiAntiSwearingBot.sln create mode 100644 AntiAntiSwearingBot/AntiAntiSwearingBot.cs create mode 100644 AntiAntiSwearingBot/AntiAntiSwearingBot.csproj create mode 100644 AntiAntiSwearingBot/CommandRouter.cs create mode 100644 AntiAntiSwearingBot/Commands/CommandString.cs create mode 100644 AntiAntiSwearingBot/Commands/LearnCommand.cs create mode 100644 AntiAntiSwearingBot/Commands/UnlearnCommand.cs create mode 100644 AntiAntiSwearingBot/Config.cs create mode 100644 AntiAntiSwearingBot/ConfigBase.cs create mode 100644 AntiAntiSwearingBot/Extensions/IListExtensions.cs create mode 100644 AntiAntiSwearingBot/Extensions/IReadOnlyDictionaryExtensions.cs create mode 100644 AntiAntiSwearingBot/Language.cs create mode 100644 AntiAntiSwearingBot/Program.cs create mode 100644 AntiAntiSwearingBot/Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 AntiAntiSwearingBot/SearchDictionary.cs create mode 100644 AntiAntiSwearingBot/aasb.cfg.json create mode 100644 AntiAntiSwearingBot/dict/ObsceneDictionaryRu.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dbcbd8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Autosave files +*~ + +# build +[Oo]bj/ +[Bb]in/ +packages/ +TestResults/ + +# globs +Makefile.in +*.DS_Store +*.sln.cache +*.suo +*.cache +*.pidb +*.userprefs +*.usertasks +config.log +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.user +*.tar.gz +tarballs/ +test-results/ +Thumbs.db +.vs/ + +# Mac bundle stuff +*.dmg +*.app + +# resharper +*_Resharper.* +*.Resharper + +# dotCover +*.dotCover + +#secret config +karma.cfg.json +*secrets.ini +*.secret.json diff --git a/AntiAntiSwearingBot.sln b/AntiAntiSwearingBot.sln new file mode 100644 index 0000000..7ea08cc --- /dev/null +++ b/AntiAntiSwearingBot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2036 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntiAntiSwearingBot", "AntiAntiSwearingBot\AntiAntiSwearingBot.csproj", "{66AFFD7B-5B2D-4C85-8523-770702255511}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {66AFFD7B-5B2D-4C85-8523-770702255511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66AFFD7B-5B2D-4C85-8523-770702255511}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66AFFD7B-5B2D-4C85-8523-770702255511}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66AFFD7B-5B2D-4C85-8523-770702255511}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F0B6EAE6-9D61-4E40-B5B8-3269484DB0D2} + EndGlobalSection +EndGlobal diff --git a/AntiAntiSwearingBot/AntiAntiSwearingBot.cs b/AntiAntiSwearingBot/AntiAntiSwearingBot.cs new file mode 100644 index 0000000..5dbfe7b --- /dev/null +++ b/AntiAntiSwearingBot/AntiAntiSwearingBot.cs @@ -0,0 +1,127 @@ +using System; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using AntiAntiSwearingBot.Commands; +using Telegram.Bot; +using Telegram.Bot.Args; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; + +namespace AntiAntiSwearingBot +{ + public class AntiAntiSwearingBot : IDisposable + { + Config Config { get; } + SearchDictionary Dict { get; } + + public AntiAntiSwearingBot(Config cfg, SearchDictionary dict) + { + Config = cfg; + Dict = dict; + BleepedSwearsRegex = new Regex(cfg.BleepedSwearsRegex, RegexOptions.Compiled); + NonWordRegex = new Regex("\\W", RegexOptions.Compiled); + MentionRegex = new Regex("@[a-zA-Z0-9_]+", RegexOptions.Compiled); + } + + TelegramBotClient Client { get; set; } + ChatCommandRouter Router { get; set; } + User Me { get; set; } + + public async Task Init() + { + 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() + { + Dict.Save(); + Dispose(); + } + + #region service + + Regex BleepedSwearsRegex { get; } + Regex NonWordRegex { get; } + Regex MentionRegex { get; } + + string UnbleepSwears(string text) + { + if (string.IsNullOrWhiteSpace(text)) + return null; + + var words = BleepedSwearsRegex.Matches(text) + .Select(m => m.Value) + .Where(m => NonWordRegex.IsMatch(m)) + .Where(m => !MentionRegex.IsMatch(m)) + .ToArray(); + + if (words.Any()) + { + var response = new StringBuilder(); + for (int i = 0; i < words.Length; ++i) + { + var m = Dict.Match(words[i]); + response.AppendLine(new string('*', i + 1) + m.Word + new string('?', m.Distance)); + } + return response.ToString(); + } + else + return null; + } + + 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); } + catch { } + + if (commandResponse != null) + { + Client.SendTextMessageAsync( + args.Message.Chat.Id, + commandResponse, + replyToMessageId: args.Message.MessageId); + } + else + { + var unbleepResponse = UnbleepSwears(msg.Text); + if (unbleepResponse != null) + Client.SendTextMessageAsync( + args.Message.Chat.Id, + unbleepResponse, + replyToMessageId: args.Message.MessageId); + } + } + + #endregion + + #region IDisposable + + public void Dispose() + { + Client.StopReceiving(); + } + + #endregion + } +} diff --git a/AntiAntiSwearingBot/AntiAntiSwearingBot.csproj b/AntiAntiSwearingBot/AntiAntiSwearingBot.csproj new file mode 100644 index 0000000..8469ed9 --- /dev/null +++ b/AntiAntiSwearingBot/AntiAntiSwearingBot.csproj @@ -0,0 +1,29 @@ + + + + Exe + netcoreapp2.1 + + + + + + + + + + + + + + Always + + + Always + + + Always + + + + diff --git a/AntiAntiSwearingBot/CommandRouter.cs b/AntiAntiSwearingBot/CommandRouter.cs new file mode 100644 index 0000000..eae4a84 --- /dev/null +++ b/AntiAntiSwearingBot/CommandRouter.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using Telegram.Bot.Args; +using AntiAntiSwearingBot.Commands; + +namespace AntiAntiSwearingBot +{ + public interface IChatCommand + { + string Execute(CommandString cmd, MessageEventArgs messageEventArgs); + } + + public class ChatCommandRouter + { + string Username { get; } + Dictionary Commands { get; } + + public ChatCommandRouter(string username) + { + Username = username; + Commands = new Dictionary(); + } + + public string Execute(object sender, MessageEventArgs args) + { + 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)) + { + try { return Commands[cmd.Command].Execute(cmd, args); } + catch { } + } + } + 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; + } + } + } +} diff --git a/AntiAntiSwearingBot/Commands/CommandString.cs b/AntiAntiSwearingBot/Commands/CommandString.cs new file mode 100644 index 0000000..dc1d512 --- /dev/null +++ b/AntiAntiSwearingBot/Commands/CommandString.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace AntiAntiSwearingBot.Commands +{ + public class CommandString + { + public CommandString(string command, string username, params string[] parameters) + { + Command = command; + Parameters = parameters; + } + + public string Command { get; } + public string UserName { get; } + public string[] Parameters { get; } + + 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; + + 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; + + 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; + } + } +} diff --git a/AntiAntiSwearingBot/Commands/LearnCommand.cs b/AntiAntiSwearingBot/Commands/LearnCommand.cs new file mode 100644 index 0000000..296e8eb --- /dev/null +++ b/AntiAntiSwearingBot/Commands/LearnCommand.cs @@ -0,0 +1,29 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Telegram.Bot.Args; + +namespace AntiAntiSwearingBot.Commands +{ + public class LearnCommand : IChatCommand + { + SearchDictionary Dict { get; } + + public LearnCommand(SearchDictionary dict) + { + Dict = dict; + } + + public string Execute(CommandString cmd, MessageEventArgs args) + { + var word = cmd.Parameters.FirstOrDefault(); + if (string.IsNullOrWhiteSpace(word)) + return null; + + if (!Regex.IsMatch(word, @"[а-яА-Я]+")) + return null; + + bool newWord = Dict.Learn(word); + return newWord ? $"Принято слово \"{word}\"" : $"Поднял рейтинг слову \"{word}\""; + } + } +} diff --git a/AntiAntiSwearingBot/Commands/UnlearnCommand.cs b/AntiAntiSwearingBot/Commands/UnlearnCommand.cs new file mode 100644 index 0000000..ea76503 --- /dev/null +++ b/AntiAntiSwearingBot/Commands/UnlearnCommand.cs @@ -0,0 +1,38 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Telegram.Bot.Args; + +namespace AntiAntiSwearingBot.Commands +{ + public class UnlearnCommand : IChatCommand + { + SearchDictionary Dict { get; } + + public UnlearnCommand(SearchDictionary dict) + { + Dict = dict; + } + + public string Execute(CommandString cmd, MessageEventArgs args) + { + var word = cmd.Parameters.FirstOrDefault(); + if (string.IsNullOrWhiteSpace(word)) + return null; + + if (!Regex.IsMatch(word, @"[а-яА-Я]+")) + return null; + var res = Dict.Unlearn(word); + + switch (res) + { + case SearchDictionary.UnlearnResult.Demoted: + return $"Понизил слово \"{word}\""; + case SearchDictionary.UnlearnResult.Removed: + return $"Удалил слово \"{word}\""; + case SearchDictionary.UnlearnResult.NotFound: + default: + return $"Не нашел слово \"{word}\""; + } + } + } +} diff --git a/AntiAntiSwearingBot/Config.cs b/AntiAntiSwearingBot/Config.cs new file mode 100644 index 0000000..bb86862 --- /dev/null +++ b/AntiAntiSwearingBot/Config.cs @@ -0,0 +1,32 @@ +namespace AntiAntiSwearingBot +{ + public class Config : ConfigBase + { + public string ApiKey { get; private set; } + + public string BleepedSwearsRegex { 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 ProxySettings Proxy { get; private set; } + + public struct SearchDictionarySettings + { + public string DictionaryPath { get; private set; } + + public double LearnNudgeFactor { get; private set; } + public double LearnInitialRating { get; private set; } + public int MinUnlearnNudge { get; private set; } + public double UnlearnNudgeFactor { get; private set; } + } + + public SearchDictionarySettings SearchDictionary { get; private set; } + } +} + diff --git a/AntiAntiSwearingBot/ConfigBase.cs b/AntiAntiSwearingBot/ConfigBase.cs new file mode 100644 index 0000000..c1e1707 --- /dev/null +++ b/AntiAntiSwearingBot/ConfigBase.cs @@ -0,0 +1,76 @@ +using System.IO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Reflection; +using Newtonsoft.Json.Serialization; + +namespace AntiAntiSwearingBot +{ + public abstract class ConfigBase + { + public static T Load(params string[] paths) where T : ConfigBase, new() + { + 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 + { + ContractResolver = new PrivateSetterContractResolver() + }; + JsonSerializer.CreateDefault(settings).Populate(sr, result); + } + + 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 new file mode 100644 index 0000000..e352511 --- /dev/null +++ b/AntiAntiSwearingBot/Extensions/IListExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace AntiAntiSwearingBot +{ + public static class IListExtensions + { + 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; + + 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 new file mode 100644 index 0000000..5155778 --- /dev/null +++ b/AntiAntiSwearingBot/Extensions/IReadOnlyDictionaryExtensions.cs @@ -0,0 +1,15 @@ +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/Language.cs b/AntiAntiSwearingBot/Language.cs new file mode 100644 index 0000000..76e6b59 --- /dev/null +++ b/AntiAntiSwearingBot/Language.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AntiAntiSwearingBot +{ + static class Language + { + public static int HammingDistance(string a, string b) + { + 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; + } + + static int min(int a, int b, int c) { return Math.Min(Math.Min(a, b), c); } + + 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); + + /// + /// Compute the distance between two strings. + /// + public static int Compute(string s, string t) + { + int n = s.Length; + int m = t.Length; + int[,] d = new int[n + 1, m + 1]; + + if (n == 0) + return m; + if (m == 0) + return n; + + // Step 2 + for (int i = 0; i <= n; d[i, 0] = i++) + { + } + + for (int j = 0; j <= m; d[0, j] = j++) + { + } + + // Step 3 + for (int i = 1; i <= n; i++) + { + //Step 4 + for (int j = 1; j <= m; j++) + { + // Step 5 + int cost = (t[j - 1] == s[i - 1]) ? 0 : 1; + + // Step 6 + d[i, j] = Math.Min( + Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), + d[i - 1, j - 1] + cost); + } + } + // Step 7 + return d[n, m]; + } + } +} diff --git a/AntiAntiSwearingBot/Program.cs b/AntiAntiSwearingBot/Program.cs new file mode 100644 index 0000000..622d6d0 --- /dev/null +++ b/AntiAntiSwearingBot/Program.cs @@ -0,0 +1,38 @@ +using System; + +namespace AntiAntiSwearingBot +{ + public static class Program + { + public enum ExitCode : int + { + Ok = 0, + ErrorNotStarted = 0x80, + ErrorRunning = 0x81, + ErrorException = 0x82, + ErrorInvalidCommandLine = 0x100 + }; + + public static int Main(string[] args) + { + try + { + var cfg = Config.Load("aasb.cfg.json", "aasb.cfg.secret.json"); + var dict = new SearchDictionary(cfg); + var bot = new AntiAntiSwearingBot(cfg, dict); + bot.Init().Wait(); + Console.WriteLine("AntiAntiSwear started. Press any key to exit..."); + Environment.ExitCode = (int)ExitCode.ErrorRunning; + Console.ReadKey(); + Console.WriteLine("Waiting for exit..."); + bot.Stop().Wait(); + return (int)ExitCode.Ok; + } + catch (Exception ex) + { + Console.WriteLine(ex); + return (int)ExitCode.ErrorException; + } + } + } +} diff --git a/AntiAntiSwearingBot/Properties/PublishProfiles/FolderProfile.pubxml b/AntiAntiSwearingBot/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..302034d --- /dev/null +++ b/AntiAntiSwearingBot/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,16 @@ + + + + + FileSystem + Release + Any CPU + netcoreapp2.1 + i:\aasb + win-x64 + false + <_IsPortable>true + + \ No newline at end of file diff --git a/AntiAntiSwearingBot/SearchDictionary.cs b/AntiAntiSwearingBot/SearchDictionary.cs new file mode 100644 index 0000000..76eaca8 --- /dev/null +++ b/AntiAntiSwearingBot/SearchDictionary.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AntiAntiSwearingBot +{ + public class SearchDictionary + { + public SearchDictionary(Config cfg) + { + var s = cfg.SearchDictionary; + path = s.DictionaryPath; + learnInitialRating = Math.Clamp(s.LearnInitialRating, 0,1); + learnNudgeFactor = Math.Clamp(s.LearnNudgeFactor, 0, 1); + unlearnNudgeFactor = Math.Clamp(s.UnlearnNudgeFactor, 0, 1); + minUnlearnNudge = Math.Max(s.MinUnlearnNudge, 0); + + words = File.ReadAllLines(path).ToList(); + } + + public void Save() + { + File.WriteAllLines(path + ".tmp", words); + File.Move(path + ".tmp", 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) + { + int newIndex = (int)(index * learnNudgeFactor); + words.Move(index, newIndex); + return false; + } + else + { + words.Insert((int)(words.Count * learnInitialRating), word); + return true; + } + } + } + + public enum UnlearnResult { NotFound, Demoted, Removed } + public UnlearnResult Unlearn(string word) + { + lock (SyncRoot) + { + int index = words.IndexOf(word); + if (index < 0) + return UnlearnResult.NotFound; + + int indexFromEnd = words.Count - 1 - index; + int change = Math.Max(minUnlearnNudge, (int)(indexFromEnd * unlearnNudgeFactor )); + int newIndex = index + change; + if (newIndex > words.Count) + { + words.RemoveAt(index); + return UnlearnResult.Removed; + } + else + { + words.Move(index, newIndex); + return UnlearnResult.Demoted; + } + } + } + + #region service + + string path; + + double learnInitialRating = 0.75; + double learnNudgeFactor = 0.5; + double unlearnNudgeFactor = 0.66; + int minUnlearnNudge = 5; + + object SyncRoot = new object(); + List words; + + #endregion + } +} diff --git a/AntiAntiSwearingBot/aasb.cfg.json b/AntiAntiSwearingBot/aasb.cfg.json new file mode 100644 index 0000000..4898bb5 --- /dev/null +++ b/AntiAntiSwearingBot/aasb.cfg.json @@ -0,0 +1,10 @@ +{ + "BleepedSwearsRegex": "[а-яА-Я@\\*#]+", + "SearchDictionary": { + "DictionaryPath": "dict/ObsceneDictionaryRu.txt", + "LearnNudgeFactor": 0.5, + "LearnInitialRating": 0.75, + "MinUnlearnNudge": 5, + "UnlearnNudgeFactor": 0.66 + } +} \ No newline at end of file diff --git a/AntiAntiSwearingBot/dict/ObsceneDictionaryRu.txt b/AntiAntiSwearingBot/dict/ObsceneDictionaryRu.txt new file mode 100644 index 0000000..0847a2f --- /dev/null +++ b/AntiAntiSwearingBot/dict/ObsceneDictionaryRu.txt @@ -0,0 +1,1313 @@ +еб +бля +хуй +блядь +пиздец +пидр +пидор +охуеть +бздун +беспезды +блудилище +блядво +блядеха +блядина +блядистка +блядище +блядки +блядование +блядовать +блядовитый +блядовозка +блядолиз +блядоход +блядский +блядство +блядствовать +блядун +бляди +бляд +блядюга +блядюра +блядюшка +блядюшник +бордель +вагина +вафлист +вжопить +вжопиться +вздрачивание +вздрачивать +вздрачиваться +вздрочить +вздрочиться +вздрючить +вздрючивание +вздрючивать +взъебка +взъебщик +взъебнуть +влагалище +вхуйнуть +вхуйнуться +вхуякать +вхуякаться +вхуя +вхуякивать +вхуякиваться +вхуякнуть +вхуякнуться +вхуяривание +вхуяривать +вхуяриваться +вхуярить +вхуяриться +вхуячивание +вхуячивать +вхуячиваться +вхуячить +вхуячиться +вхуяшивать +вхуяшиваться +вхуяшить +вхуяшиться +въебать +въебаться +въебашивать +въебашиваться +въебашить +въебашиться +въебенивать +въебениваться +въебенить +въебениться +выблядок +выебанный +выебат +выебаться +высрать +высраться +выссать +выссаться +высераться +выссереть +говнецо +говнистый +говниться +говно +говновоз +говнодав +говноеб +говноед +говномес +говномер +говносерка +говнюк +голожопая +гомик +гомосек +гондон +гонорея +давалка +двужопник +дерьмо +дерьмоед +дерьмовый +дилдо +додрочить +додрочиться +доебать +доебаться +доебенивать +доебениваться +доебенить +доебениться +долбоеб +допиздить +допиздиться +допиздовать +допиздоваться +допиздовывать +допиздовываться +допиздохать +допиздохаться +допиздохивать +допиздохиваться +допиздошить +допиздошиться +допиздошивать +допиздошиваться +допиздюлить +допиздюлиться +допиздюливать +допиздюливаться +допиздюрить +допиздюриться +допиздюривать +допиздюриваться +допиздюхать +допиздюхаться +допиздюхивать +допиздюхиваться +допиздякать +допиздякаться +допиздякивать +допиздякиваться +допиздярить +допиздяриться +допиздяривать +допиздяриваться +допиздяхать +допиздяхаться +допиздяхивать +допиздяхиваться +допиздячить +допиздячиться +допиздячивать +допиздячиваться +допиздяшить +допиздяшиться +допиздяшивать +допиздяшиваться +допиздоболивать +допиздоболиваться +допиздоболиться +допиздюкать +допиздюкаться +допиздюкивать +допиздюкиваться +допизживать +дотрахать +дотрахаться +дохуйнуть +дохуякать +дохуякаться +дохуякивать +дохуякиваться +дохуяривать +дохуяриваться +дохуярить +дохуяриться +дохуячить +дохуячиться +дохуячивать +дохуячиваться +дрисня +дристать +дристун +дроченье +дрочилыцик +дрочить +дрочиться +дрочка +дрючить +дрючиться +дурак +дуроеб +выебать +ебало +ебальник +ебальные +ебальный +ебанатик +ебанашка +ебанутый +ебануть +ебануться +ебать +ебат +ебаться +ебатьс +ебитесь +ебло +еблом +еблысь +ебля +ебнуть +ебнуться +ебня +ебучий +заебла +надроченный +объебешь +поебать +жирнозадый +жопа +жопой +жопастая +жопоеб +жопенци +жопища +жопка +жопник +жополиз +жополизание +жопоногий +жопочка +жопочник +жопство +жопу +забздеть +заблядовать +заблядоваться +задница +задрачивать +задрачиваться +задроченный +задрочить +задрочиться +задрючить +задрючиться +заебанный +заебать +заебаться +заебательская +заебашивать +заебашиваться +заебашить +заебашиться +заебенивать +заебениваться +заебенить +заебениться +залупа +залупу +залупаться +залупенить +залупень +залупить +залупляться +залупистый +запиздарить +запизденная +запизденелый +запиздить +запиздиться +запиздоболивать +запиздоболиваться +запиздоболить +запиздоболиться +запиздовать +запиздоваться +запиздовывать +запиздовываться +запиздохать +запиздошить +запиздошиться +запиздошивать +запиздошиваться +запиздюкать +запиздюкаться +запиздюкивать +запиздюкиваться +запиздюлить +запиздюлиться +запиздюливать +запиздюливаться +запиздюрить +запиздюриться +запиздюривать +запиздюриваться +запиздюхать +запиздюхаться +запиздюхивать +запиздюхиваться +запиздючить +запиздючиться +запиздючивать +запиздючиваться +засранец +засранка +засранный +засратый +засрать +засраться +зассать +затраханный +затрахать +затрахаться +затрахивать +затрахиваться +захуить +захуйнуть +захуйнуться +захуякать +захуякаться +захуякивать +захуякиваться +захуярить +захуяриться +захуяривать +захуяриваться +захуячить +захуячиться +захуячивать +захуячиваться +захуяшить +захуяшиться +захуяшивать +захуяшиваться +злоебучий +издрочиться +измандить +измандиться +измандовать +измандоваться +измандовывать +измандовываться +изъебать +изъебаться +изъебашить +изъебашиться +изъебашивать +изъебашиваться +изъебенить +изъебениться +изъебенивать +изъебениваться +изъеб +испиздеться +испиздить +испражнение +испражняться +исхуякать +исхуякаться +исхуякивать +исхуякиваться +исхуярить +исхуяриться +исхуяривать +какать +какашка +кастрат +кастрировать +клитор +клоака +кнахт +кончить +косоебить +косоебиться +кривохуй +курва +курвиный +лахудра +лох +лохудра +лохматка +манда +мандавошка +мандавоха +мандить +мандиться +мандоватая +мандовать +мандохать +мандохаться +мандохивать +мандохиваться +мандошить +мастурбатор +минет +минетить +минетка +минетчик +минетчица +мозгоеб +мозгоебатель +мозгоебать +мозгоебка +мокрожопый +мокропиздая +моча +мочиться +мудак +мудашвили +мудило +мудильщик +мудистый +мудить +мудоеб +наебанный +наебка +наебщик +наебывать +наебываться +наебыш +набздеть +наблядоваться +надрочивать +надрочить +надрочиться +надристать +наебать +наебаться +наебнуть +наебнуться +накакать +накакаться +накакивать +напиздить +напиздошить +напиздюрить +напиздюриться +насрать +насраться +нассать +нассаться +натрахать +натрахаться +натрахивать +натрахиваться +нахуякать +нахуякаться +нахуякивать +нахуякиваться +нахуярить +нахуяриться +нахуяривать +нахуяриваться +нахуячить +нахуячиться +нахуячивать +нахуячиваться +нахуяшить +недоебанный +недоносок +неебущий +нищеебство +оебыват +обдристанный +обдристать +обдрочиться +обосранец +обосранная +обосраный +обосрать +обосраться +обоссанец +обоссаный +обоссать +обоссаться +обоссывать +обоссываться +пизду +обпиздить +обпиздиться +обпиздовать +обпиздоваться +обпиздовывать +обпиздовываться +обпиздохать +обпиздохаться +обпиздохивать +обпиздохиваться +обпиздошить +обтрахать +обтрахаться +обтрахивать +обтрахиваться +обхуярить +обхуяриться +обхуячить +объебать +объебаться +объебенить +объебнуть +объебон +одинхуй +однапизда +однохуйственно +оебать +оебашивать +оебашить +оебенивать +оебенить +опедерастить +опизденеть +опизденный +опизденно +опиздеть +опиздить +остоебеть +остоебенить +остоебенило +остопиздеть +остопиздело +остохуело +остохуеть +отдрачивать +отдрачиваться +отдрочить +отдрочиться +отпиздить +отпиздошить +отпиздяшить +отпиздяшиться +отпиздяшивание +отпиздяшивать +отпиздяшиваться +отсасывать +отсасываться +отсосать +отсосаться +оттраханная +оттрахать +оттрахаться +оттрахивать +оттрахиваться +отхерачить +отхуякать +отхуякаться +отхуякивать +отхуякиваться +отхуярить +отхуяриться +отхуяривать +отхуяриваться +отхуячить +отхуячиться +отхуячивать +отхуячиваться +отхуяшить +отхуяшиться +отхуяшивать +отхуяшиваться +отъебать +отъебывание +отъебывать +отъебываться +отъебашить +отъебашивание +отъебашивать +отъебашиваться +отъебенить +отъебениться +отъебенивать +отъебениваться +отъебнуть +отьебаться +отьебашиться +отьебенивание +отьебнуться +охуевать +охуевающий +охуевший +охуение +охуенно +охуенные +охуительно +охуительный +охуякать +охуякаться +охуякивать +охуякиваться +охуякнуть +охуякнуться +охуярить +охуяриться +охуяривать +охуяриваться +охуячить +охуячиться +охуячивать +охуячиваться +охуяшить +охуяшиться +охуяшивать +охуяшиваться +очко +перднуть +падла +падлюка +педераст +педерастина +педерастический +педерастия +педик +педрило +пежить +пенис +пердеж +пердеть +пердун +перебздеть +передрачивать +передрочить +передрочиться +переебаться +переебашить +перетрахать +перетрахаться +перетрахивать +перетрахиваться +перехуйнуть +перехуйнуться +перехуякнуть +перехуякнуться +перехуякать +перехуякаться +перехуякивать +перехуякиваться +перехуярить +перехуяриться +перехуяривать +перехуяриваться +перехуячить +юнити +перехуячиться +наебениться +перехуячивать +пидорас +пизда +пизданутая +пиздануть +пиздануться +пиздато +пизденка +пизденочка +пиздень +пизденыш +пиздеть +пиздища +пиздобол +пиздовать +пиздолиз +пиздомол +пиздосос +пиздоход +пиздуй +пиздун +пиздюга +пиздюлей +пиздюли +пиздюлина +пиздюк +пиздюкать +пиздюкаться +пиздюшка +пиздякать +пиздятина +пиздятиной +пиздячий +писька +писюлек +плоскозадая +поебочка +поебывать +поебываться +поблудить +поблядовать +поблядушка +подосрать +подосраться +подоссать +подпиздить +подпиздовать +подпиздоваться +подпиздовывать +подпиздовываться +подпиздохать +подпиздохаться +подпиздохивать +подпиздохиваться +подпиздошить +подпиздошиться +подпиздошивать +подпиздякать +подпиздякаться +подпиздякивать +подпиздякиваться +подпиздярить +подпиздяриться +подпиздяривать +подпиздяриваться +подпиздяхать +подпиздяхаться +подпиздяхивать +подпиздяхиваться +подпиздячить +подпиздячиться +подпиздячивать +подпиздячиваться +подпиздяшить +подпиздяшиться +подпиздяшивать +подпиздяшиваться +подристывать +подрочить +подсирать +подхуякнуть +подхуякнуться +подхуякать +подхуякаться +подхуякивать +подхуякиваться +подхуярить +подхуяриться +подхуяривать +подхуяриваться +подхуячивать +подхуячиться +подхуячиваться +подхуяшить +подхуяшиться +подхуяшивать +подхуяшиваться +подъеб +подъебать +подъебаться +подъебашить +подъебнуть +подъебка +подъебывать +подъябывать +поебанный +поебаться +поебень +поебистика +поебон +поебончик +попердеть +попердеться +попердывать +попизденная +попиздеть +попиздистее +попиздить +попиздиться +попиздоватей +попиздоболивать +попиздоболиваться +попиздоболить +попиздоболиться +попиздовать +попиздоваться +попиздовывать +попиздовываться +попиздохать +попиздохаться +попиздохивать +попиздохиваться +попиздошить +попиздошиться +попиздошивать +попиздошиваться +попиздюкать +попиздюкаться +попиздюкивать +попиздюкиваться +попиздюлить +попиздюлиться +попиздюливать +попиздюливаться +попиздюрить +попиздюриться +попиздюривать +попиздюриваться +попиздюхать +попиздюхаться +попиздюхивать +попиздюхиваться +попиздякать +попиздякаться +попиздякивать +попиздякиваться +попиздярить +попиздяриться +попиздяривать +попиздяриваться +попиздяхать +попиздяхаться +попиздяхивать +попиздяхиваться +попиздячить +попиздячиться +попиздячивать +попиздячиваться +попиздяшить +попиздяшиться +попиздяшивать +попиздяшиваться +попизживать +попизживаться +потаскун +потаскуха +потраханная +потрахать +потрахаться +потрахивать +потрахиваться +похер +похуист +похуякать +похуякаться +похуякивать +похуякиваться +похуярить +похуяриться +похуяривать +похуяриваться +похуячить +похуячиться +похуячивать +похуячиваться +похуяшить +похуяшиться +похуяшивать +похуяшиваться +поц +пошмариться +поябывать +приебать +приебаться +приебывать +приебываться +приебашить +приебашиться +приебашивать +приебашиваться +приебенить +приебениться +приебенивать +приебениваться +приебехать +приебехаться +приебехивать +приебехиваться +приебистый +приебурить +приебуриться +приебуривать +приебуриваться +прижопить +прижопывать +прикинуть +примандовать +примандоваться +примавдовывать +примандовываться +примандохать +примандохаться +примандохивать +примандохиваться +примандошить +примандошиться +примандошивать +примандошиваться +примандюкать +примандюкаться +примандюкивать +примандюкиваться +примандехать +примандехаться +примандехивать +примандехиваться +примандюлить +примандюлиться +примандюливать +примандюливаться +примандюрить +примандюриться +примандюривать +примандюриваться +примандякать +примандякаться +примандякивать +примандякиваться +примандярить +примандяриться +примандяривать +примандяриваться +примандяхать +примандяхаться +примандяхивать +примандяхиваться +примандячить +примандячиться +примандячивать +примандячиваться +примандяшить +примандяшиться +примандяшивать +примандяшиваться +примудохать +примудохаться +примудохивать +примудохиваться +примандить +примандиться +припизденный +припиздень +припиздить +припиздиться +припиздывать +припиздываться +припиздовать +припиздоваться +припиздовывать +припиздовываться +припиздохать +припиздохаться +припиздохивать +припиздохиваться +припиздошить +припиздошиться +припиздошивать +припиздошиваться +припиздюкать +припиздюкаться +припиздюкивать +припиздюкиваться +припиздюлить +припиздюлиться +припиздюливать +припиздюливаться +припиздюрить +припиздюриться +припиздюривать +припиздюхать +припиздюриваться +припиздюхаться +припиздюхивать +припиздюхиваться +припиздякать +припиздякаться +припиздякивать +припиздякиваться +припиздярить +припиздяриться +припиздяривать +припиздяриваться +припиздяхать +припиздяхаться +припиздяхивать +припиздяхиваться +припиздячить +припиздячиться +припиздячивать +припиздячиваться +припиздяшить +припиздяшиться +припиздяшивать +припиздяшиваться +припиздронить +припиздрониться +припиздронивать +припиздрониваться +припизживать +припизживаться +прихуеть +прихуякать +прихуякаться +прихуякивать +прихуякиваться +прихуярить +прихуяриться +прихуяривать +прихуяриваться +прихуячить +прихуячиться +прихуячивать +прихуячиваться +прихуяшить +прихуяшиться +прихуяшивать +прихуяшиваться +притрахаться +проблядовать +проблядь +проблядушка +продрачивать +продрачиваться +продрочить +продрочиться +проебать +проебаться +проебашить +проебашиться +проебашивать +проебашиваться +проебенить +проебениться +проебывать +проебываться +пропиздить +пропиздиться +пропиздоболивать +пропиздоболиваться +пропиздоболить +пропиздоболиться +пропиздовать +пропиздоваться +пропиздовывать +пропиздовываться +пропиздохать +пропиздохаться +пропиздохивать +пропиздохиваться +пропиздошить +пропиздошиться +пропиздошивать +пропиздошиваться +пропиздюкать +пропиздюкаться +пропиздюкивать +пропиздюкиваться +пропиздюлить +пропиздюлиться +пропиздюливать +пропиздюливаться +пропиздюрить +пропиздюриться +пропиздюривать +пропиздюриваться +пропиздюхать +пропиздюхаться +пропиздюхивать +пропиздюхиваться +пропиздякать +пропиздякаться +пропиздякивать +пропиздякиваться +пропиздярить +пропиздяриться +пропиздяривать +пропиздяриваться +пропиздяхать +пропиздяхивать +пропиздяхиваться +пропиздячить +пропиздячиться +пропиздячивать +пропиздячиваться +пропиздяшить +пропиздяшиться +пропиздяшивать +пропиздяшиваться +пропизживать +пропизживаться +пропиздон +прохуякать +прохуякаться +прохуякивать +прохуякиваться +прохуярить +прохуяриться +прохуяривать +прохуяриваться +прохуячить +прохуячиться +прохуячивать +прохуячиваться +прохуяшить +прохуяшиться +прохуяшивать +прохуяшиваться +разблядоваться +раздрочить +раздрочиться +раззалупаться +разнохуйственно +разъебать +разъебаться +разъебашить +разъебашиться +разъебашивать +разъебашиваться +разъебенить +разъебениться +разъебенивать +разъебениваться +распиздить +распиздиться +распиздовать +распиздоваться +распиздовывать +распиздовываться +распиздохать +распиздохаться +распиздохивать +распиздохиваться +распиздошить +распиздошиться +распиздошивать +распиздошиваться +распиздон +распиздяй +расхуярить +расхуяриться +расхуяривать +расхуяриваться +расхуячить +расхуячиться +расхуячивать +расхуячиваться +сдрочить +сестроеб +сифилитик +сифилюга +скурвиться +смандить +смандиться +сперматозавр +спиздеть +стерва +стервоза +сука +суки +сукин +сукины +суходрочка +суходрочкой +сучара +сучий +сучка +сучье +схуякать +схуякаться +схуякивать +схуякиваться +схуярить +схуяриться +схуяривать +схуяриваться +схуячить +схуячиться +схуячивать +съебывать +съебываться +съебать +съебаться +съебашить +съебашиться +съебашивать +съебашиваться +съебенить +съебениться +съебенивать +спиздить +тварь +толстожопый +толстозадая +торчило +траханье +трахать +трахаться +трахнуть +трахнуться +трепак +триппер +уебывать +уебываться +уебыш +ублюдок +уебать +уебашить +уебашивать +уебенить +уебище +усраться +усрачка +уссать +уссаться +ухуякать +ухуякаться +ухуякивать +ухуякиваться +ухуярить +ухуяриться +ухуяривать +ухуяриваться +ухуячить +ухуячиться +ухуячивать +ухуячиваться +ухуяшить +ухуяшиться +ухуяшивать +ухуяшиваться +фаллос +фекал +фекалий +фекалии +хер +херами +херня +херовина +херов +хрен +хреново +хреновое +хреновый +хуевина +хуев +хуево +хуевый +хуек +хуечек +худоебина +хуебень +хуева +хуевато +хуеватый +хуеглот +хуегрыз +хуедрыга +хуемудрие +хуемыслие +хуеньки +хуеплет +хуесос +хуета +хуетень +хуец +хуила +хуиный +хуистый +хуишко +хуище +хуи +хуило +хуйло +хуйство +хуйнуть +хуйня +хуйню +хули +хуюжить +хуюжиться +хуюживать +хуюживаться +хуюшки +хуя +хуяк +хуякать +хуями +хуярить +хуяриться +хуястый +хуячий +хуячить +хуячиться +хуяшить +целка +целку +целочка +черножопые +чернозадый +член +шалава +шлюха +шмара +шмарить +шмариться +отъебись +отьебись +спам +spam +мудила +пидарасы +пиздоблядская +мудопроебина +пиздострадание +наебенюсь