using AngleSharp; using AngleSharp.Dom.Html; using Discord; using Discord.WebSocket; using NadekoBot.Common; using NadekoBot.Common.Replacements; using NadekoBot.Core.Services.Database.Models; using NadekoBot.Extensions; using NadekoBot.Modules.CustomReactions.Services; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace NadekoBot.Modules.CustomReactions.Extensions { public static class Extensions { private static readonly Regex imgRegex = new Regex("%(img|image):(?.*?)%", RegexOptions.Compiled); private static readonly NadekoRandom rng = new NadekoRandom(); public static Dictionary>> regexPlaceholders = new Dictionary>>() { { imgRegex, async (match) => { var tag = match.Groups["tag"].ToString(); if(string.IsNullOrWhiteSpace(tag)) return ""; var fullQueryLink = $"http://imgur.com/search?q={ tag }"; var config = Configuration.Default.WithDefaultLoader(); var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); var elems = document.QuerySelectorAll("a.image-list-link").ToArray(); if (!elems.Any()) return ""; var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Length))?.Children?.FirstOrDefault() as IHtmlImageElement); if (img?.Source == null) return ""; return " " + img.Source.Replace("b.", ".") + " "; } } }; private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordSocketClient client) { var rep = new ReplacementBuilder() .WithUser(ctx.Author) .WithClient(client) .Build(); str = rep.Replace(str.ToLowerInvariant()); return str; } private static async Task ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordSocketClient client, string resolvedTrigger, bool containsAnywhere) { var substringIndex = resolvedTrigger.Length; if (containsAnywhere) { var pos = ctx.Content.GetWordPosition(resolvedTrigger); if (pos == WordPosition.Start) substringIndex += 1; else if (pos == WordPosition.End) substringIndex = ctx.Content.Length; else if (pos == WordPosition.Middle) substringIndex += ctx.Content.IndexOf(resolvedTrigger); } var rep = new ReplacementBuilder() .WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client) .WithOverride("%target%", () => ctx.Content.Substring(substringIndex).Trim()) .Build(); str = rep.Replace(str); foreach (var ph in regexPlaceholders) { str = await ph.Key.ReplaceAsync(str, ph.Value); } return str; } public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client) => cr.Trigger.ResolveTriggerString(ctx, client); public static Task ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, bool containsAnywhere) => cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client), containsAnywhere); public static async Task Send(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, CustomReactionsService crs) { var channel = cr.DmResponse ? await ctx.Author.GetOrCreateDMChannelAsync() : ctx.Channel; crs.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old); if (CREmbed.TryParse(cr.Response, out CREmbed crembed)) { var trigger = cr.Trigger.ResolveTriggerString(ctx, client); var substringIndex = trigger.Length; if (cr.ContainsAnywhere) { var pos = ctx.Content.GetWordPosition(trigger); if (pos == WordPosition.Start) substringIndex += 1; else if (pos == WordPosition.End) substringIndex = ctx.Content.Length; else if (pos == WordPosition.Middle) substringIndex += ctx.Content.IndexOf(trigger); } var rep = new ReplacementBuilder() .WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client) .WithOverride("%target%", () => ctx.Content.Substring(substringIndex).Trim()) .Build(); rep.Replace(crembed); return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? ""); } return await channel.SendMessageAsync((await cr.ResponseWithContextAsync(ctx, client, cr.ContainsAnywhere)).SanitizeMentions()); } public static WordPosition GetWordPosition(this string str, string word) { var indexOfWord = str.IndexOf(word); if (indexOfWord == -1) return WordPosition.None; if (indexOfWord == 0) // on start { if (word.Length < str.Length && str.isInvalidWordChar(word.Length)) // filter char after word index return WordPosition.Start; } else if ((indexOfWord + word.Length) == str.Length) // on end { if (str.isInvalidWordChar(indexOfWord - 1)) // filter char before word index return WordPosition.End; } else if (str.isInvalidWordChar(indexOfWord - 1) && str.isInvalidWordChar(indexOfWord + word.Length)) // on middle return WordPosition.Middle; return WordPosition.None; } private static bool isInvalidWordChar(this string str, int index) { var ch = (ushort)str[index]; if (ch >= 65 && ch <= 90) // must be A-Z return false; if (ch >= 97 && ch <= 122) // a-z return false; return true; } } public enum WordPosition { None, Start, Middle, End, } }