using Discord; using Discord.WebSocket; using NadekoBot.Extensions; using NadekoBot.Core.Services; using Newtonsoft.Json; using NLog; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using NadekoBot.Modules.Searches.Common; using NadekoBot.Core.Services.Database.Models; using System.Linq; using Microsoft.EntityFrameworkCore; using System.Net.Http; using Newtonsoft.Json.Linq; using AngleSharp; using System.Threading; using NadekoBot.Modules.Searches.Exceptions; using ImageSharp; using Image = ImageSharp.Image; using SixLabors.Primitives; using SixLabors.Fonts; using NadekoBot.Core.Services.Impl; namespace NadekoBot.Modules.Searches.Services { public class SearchesService : INService, IUnloadableService { public HttpClient Http { get; } private readonly DiscordSocketClient _client; private readonly IGoogleApiService _google; private readonly DbService _db; private readonly Logger _log; private readonly IImagesService _imgs; private readonly IDataCache _cache; private readonly FontProvider _fonts; private readonly HttpClient http; public ConcurrentDictionary TranslatedChannels { get; } = new ConcurrentDictionary(); public ConcurrentDictionary UserLanguages { get; } = new ConcurrentDictionary(); public readonly string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json"; public readonly string PokemonListFile = "data/pokemon/pokemon_list7.json"; public Dictionary Pokemons { get; } = new Dictionary(); public Dictionary PokemonAbilities { get; } = new Dictionary(); public List WowJokes { get; } = new List(); public List MagicItems { get; } = new List(); private readonly ConcurrentDictionary _imageCacher = new ConcurrentDictionary(); public ConcurrentDictionary AutoHentaiTimers { get; } = new ConcurrentDictionary(); public ConcurrentDictionary AutoBoobTimers { get; } = new ConcurrentDictionary(); public ConcurrentDictionary AutoButtTimers { get; } = new ConcurrentDictionary(); private readonly ConcurrentDictionary> _blacklistedTags = new ConcurrentDictionary>(); public SearchesService(DiscordSocketClient client, IGoogleApiService google, DbService db, NadekoBot bot, IImagesService imgs, IDataCache cache, FontProvider fonts) { Http = new HttpClient(); Http.AddFakeHeaders(); _client = client; _google = google; _db = db; _log = LogManager.GetCurrentClassLogger(); _imgs = imgs; _cache = cache; _fonts = fonts; http = new HttpClient(); _blacklistedTags = new ConcurrentDictionary>( bot.AllGuildConfigs.ToDictionary( x => x.GuildId, x => new HashSet(x.NsfwBlacklistedTags.Select(y => y.Tag)))); //translate commands _client.MessageReceived += (msg) => { var _ = Task.Run(async () => { try { var umsg = msg as SocketUserMessage; if (umsg == null) return; if (!TranslatedChannels.TryGetValue(umsg.Channel.Id, out var autoDelete)) return; var key = new UserChannelPair() { UserId = umsg.Author.Id, ChannelId = umsg.Channel.Id, }; if (!UserLanguages.TryGetValue(key, out string langs)) return; var text = await Translate(langs, umsg.Resolve(TagHandling.Ignore)) .ConfigureAwait(false); if (autoDelete) try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { } await umsg.Channel.SendConfirmAsync($"{umsg.Author.Mention} `:` " + text.Replace("<@ ", "<@").Replace("<@! ", "<@!")).ConfigureAwait(false); } catch { } }); return Task.CompletedTask; }; //pokemon commands if (File.Exists(PokemonListFile)) { Pokemons = JsonConvert.DeserializeObject>(File.ReadAllText(PokemonListFile)); } else _log.Warn(PokemonListFile + " is missing. Pokemon abilities not loaded."); if (File.Exists(PokemonAbilitiesFile)) PokemonAbilities = JsonConvert.DeserializeObject>(File.ReadAllText(PokemonAbilitiesFile)); else _log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded."); //joke commands if (File.Exists("data/wowjokes.json")) { WowJokes = JsonConvert.DeserializeObject>(File.ReadAllText("data/wowjokes.json")); } else _log.Warn("data/wowjokes.json is missing. WOW Jokes are not loaded."); if (File.Exists("data/magicitems.json")) { MagicItems = JsonConvert.DeserializeObject>(File.ReadAllText("data/magicitems.json")); } else _log.Warn("data/magicitems.json is missing. Magic items are not loaded."); } public async Task> GetRipPictureAsync(string text, string imgUrl) { var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl); if (!succ) { using (var temp = await http.GetAsync(imgUrl, HttpCompletionOption.ResponseHeadersRead)) { if (temp.Content.Headers.ContentType.MediaType != "image/png" && temp.Content.Headers.ContentType.MediaType != "image/jpeg" && temp.Content.Headers.ContentType.MediaType != "image/gif") data = null; else { using (var tempDraw = ImageSharp.Image.Load(await temp.Content.ReadAsStreamAsync()).Resize(69, 70)) { tempDraw.ApplyRoundedCorners(35); data = tempDraw.ToStream().ToArray(); } } } await _cache.SetImageDataAsync(imgUrl, data); } var bg = ImageSharp.Image.Load(_imgs.Rip.ToArray()); //avatar 82, 139 if (data != null) { var avatar = Image.Load(data).Resize(85, 85); bg.DrawImage(avatar, default, new Point(82, 139), GraphicsOptions.Default); } //text 63, 241 bg.DrawText(text, _fonts.RipNameFont, Rgba32.Black, new PointF(25, 225), new ImageSharp.Drawing.TextGraphicsOptions() { HorizontalAlignment = HorizontalAlignment.Center, WrapTextWidth = 190, }); //flowa var flowers = Image.Load(_imgs.FlowerCircle.ToArray()); bg.DrawImage(flowers, default, new Point(0, 0), GraphicsOptions.Default); return bg; } public async Task Translate(string langs, string text = null) { var langarr = langs.ToLowerInvariant().Split('>'); if (langarr.Length != 2) throw new ArgumentException(); var from = langarr[0]; var to = langarr[1]; text = text?.Trim(); if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException(); return (await _google.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions(); } public Task DapiSearch(string tag, DapiSearchType type, ulong? guild, bool isExplicit = false) { if (guild.HasValue) { var blacklistedTags = GetBlacklistedTags(guild.Value); var cacher = _imageCacher.GetOrAdd(guild.Value, (key) => new SearchImageCacher()); return cacher.GetImage(tag, isExplicit, type, blacklistedTags); } else { var cacher = _imageCacher.GetOrAdd(guild ?? 0, (key) => new SearchImageCacher()); return cacher.GetImage(tag, isExplicit, type); } } public HashSet GetBlacklistedTags(ulong guildId) { if (_blacklistedTags.TryGetValue(guildId, out var tags)) return tags; return new HashSet(); } public bool ToggleBlacklistedTag(ulong guildId, string tag) { var tagObj = new NsfwBlacklitedTag { Tag = tag }; bool added; using (var uow = _db.UnitOfWork) { var gc = uow.GuildConfigs.For(guildId, set => set.Include(y => y.NsfwBlacklistedTags)); if (gc.NsfwBlacklistedTags.Add(tagObj)) added = true; else { gc.NsfwBlacklistedTags.Remove(tagObj); added = false; } var newTags = new HashSet(gc.NsfwBlacklistedTags.Select(x => x.Tag)); _blacklistedTags.AddOrUpdate(guildId, newTags, delegate { return newTags; }); uow.Complete(); } return added; } public void ClearCache() { foreach (var c in _imageCacher) { c.Value?.Clear(); } } public async Task GetYomamaJoke() { var response = await Http.GetStringAsync("http://api.yomomma.info/").ConfigureAwait(false); return JObject.Parse(response)["joke"].ToString() + " 😆"; } public async Task<(string Text, string BaseUri)> GetRandomJoke() { var config = AngleSharp.Configuration.Default.WithDefaultLoader(); var document = await BrowsingContext.New(config).OpenAsync("http://www.goodbadjokes.com/random"); var html = document.QuerySelector(".post > .joke-content"); var part1 = html.QuerySelector("dt").TextContent; var part2 = html.QuerySelector("dd").TextContent; return (part1 + "\n\n" + part2, document.BaseUri); } public async Task GetChuckNorrisJoke() { var response = await Http.GetStringAsync("http://api.icndb.com/jokes/random/").ConfigureAwait(false); return JObject.Parse(response)["value"]["joke"].ToString() + " 😆"; } public Task Unload() { AutoBoobTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite)); AutoBoobTimers.Clear(); AutoButtTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite)); AutoButtTimers.Clear(); AutoHentaiTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite)); AutoHentaiTimers.Clear(); _imageCacher.Clear(); return Task.CompletedTask; } } public struct UserChannelPair { public ulong UserId { get; set; } public ulong ChannelId { get; set; } } }