Removed module projects because it can't work like that atm. Commented out package commands.

This commit is contained in:
Master Kwoth
2017-10-15 09:39:46 +02:00
parent 90e71a3a30
commit 696a0eb2a7
180 changed files with 21625 additions and 1058 deletions

View File

@ -0,0 +1,74 @@
using NadekoBot.Core.Services;
using Newtonsoft.Json;
using NLog;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using NadekoBot.Modules.Searches.Common;
namespace NadekoBot.Modules.Searches.Services
{
public class AnimeSearchService : INService
{
private readonly Logger _log;
private readonly IDataCache _cache;
private readonly HttpClient _http;
public AnimeSearchService(IDataCache cache)
{
_log = LogManager.GetCurrentClassLogger();
_cache = cache;
_http = new HttpClient();
}
public async Task<AnimeResult> GetAnimeData(string query)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
try
{
var link = "https://aniapi.nadekobot.me/anime/" + Uri.EscapeDataString(query.Replace("/", " "));
link = link.ToLowerInvariant();
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
if (!ok)
{
data = await _http.GetStringAsync(link).ConfigureAwait(false);
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
}
return JsonConvert.DeserializeObject<AnimeResult>(data);
}
catch
{
return null;
}
}
public async Task<MangaResult> GetMangaData(string query)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
try
{
var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " "));
link = link.ToLowerInvariant();
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
if (!ok)
{
data = await _http.GetStringAsync(link).ConfigureAwait(false);
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
}
return JsonConvert.DeserializeObject<MangaResult>(data);
}
catch
{
return null;
}
}
}
}

View File

@ -0,0 +1,214 @@
using Discord;
using Microsoft.SyndicationFeed;
using Microsoft.SyndicationFeed.Rss;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Collections.Generic;
using NadekoBot.Core.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;
using Discord.WebSocket;
namespace NadekoBot.Modules.Searches.Services
{
public class FeedsService : INService
{
private readonly DbService _db;
private readonly ConcurrentDictionary<string, HashSet<FeedSub>> _subs;
private readonly DiscordSocketClient _client;
private readonly ConcurrentDictionary<string, DateTime> _lastPosts =
new ConcurrentDictionary<string, DateTime>();
public FeedsService(NadekoBot bot, DbService db, DiscordSocketClient client)
{
_db = db;
_subs = bot
.AllGuildConfigs
.SelectMany(x => x.FeedSubs)
.GroupBy(x => x.Url)
.ToDictionary(x => x.Key, x => x.ToHashSet())
.ToConcurrent();
_client = client;
foreach (var kvp in _subs)
{
// to make sure rss feeds don't post right away, but
// only the updates from after the bot has started
_lastPosts.AddOrUpdate(kvp.Key, DateTime.UtcNow, (k, old) => DateTime.UtcNow);
}
var _ = Task.Run(TrackFeeds);
}
public async Task<EmbedBuilder> TrackFeeds()
{
while (true)
{
foreach (var kvp in _subs)
{
if (kvp.Value.Count == 0)
continue;
if (!_lastPosts.TryGetValue(kvp.Key, out DateTime lastTime))
lastTime = _lastPosts.AddOrUpdate(kvp.Key, DateTime.UtcNow, (k, old) => DateTime.UtcNow);
var rssUrl = kvp.Key;
try
{
using (var xmlReader = XmlReader.Create(rssUrl, new XmlReaderSettings() { Async = true }))
{
var feedReader = new RssFeedReader(xmlReader);
var embed = new EmbedBuilder()
.WithAuthor(kvp.Key)
.WithOkColor();
while (await feedReader.Read() && feedReader.ElementType != SyndicationElementType.Item)
{
switch (feedReader.ElementType)
{
case SyndicationElementType.Link:
var uri = await feedReader.ReadLink();
embed.WithAuthor(kvp.Key, url: uri.Uri.AbsoluteUri);
break;
case SyndicationElementType.Content:
var content = await feedReader.ReadContent();
break;
case SyndicationElementType.Category:
break;
case SyndicationElementType.Image:
ISyndicationImage image = await feedReader.ReadImage();
embed.WithThumbnailUrl(image.Url.AbsoluteUri);
break;
default:
break;
}
}
ISyndicationItem item = await feedReader.ReadItem();
if (item.Published.UtcDateTime <= lastTime)
continue;
var desc = item.Description.StripHTML();
lastTime = item.Published.UtcDateTime;
var title = string.IsNullOrWhiteSpace(item.Title) ? "-" : item.Title;
desc = Format.Code(item.Published.ToString()) + Environment.NewLine + desc;
var link = item.Links.FirstOrDefault();
if (link != null)
desc = $"[link]({link.Uri}) " + desc;
var img = item.Links.FirstOrDefault(x => x.RelationshipType == "enclosure")?.Uri.AbsoluteUri
?? Regex.Match(item.Description, @"src=""(?<src>.*?)""").Groups["src"].ToString();
if (!string.IsNullOrWhiteSpace(img) && Uri.IsWellFormedUriString(img, UriKind.Absolute))
embed.WithImageUrl(img);
embed.AddField(title, desc);
//send the created embed to all subscribed channels
var sendTasks = kvp.Value
.Where(x => x.GuildConfig != null)
.Select(x => _client.GetGuild(x.GuildConfig.GuildId)
?.GetTextChannel(x.ChannelId))
.Where(x => x != null)
.Select(x => x.EmbedAsync(embed));
_lastPosts.AddOrUpdate(kvp.Key, item.Published.UtcDateTime, (k, old) => item.Published.UtcDateTime);
await Task.WhenAll(sendTasks).ConfigureAwait(false);
}
}
catch { }
}
await Task.Delay(10000);
}
}
public List<FeedSub> GetFeeds(ulong guildId)
{
using (var uow = _db.UnitOfWork)
{
return uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs))
.FeedSubs
.OrderBy(x => x.Id)
.ToList();
}
}
public bool AddFeed(ulong guildId, ulong channelId, string rssFeed)
{
rssFeed.ThrowIfNull(nameof(rssFeed));
var fs = new FeedSub()
{
ChannelId = channelId,
Url = rssFeed.Trim().ToLowerInvariant(),
};
using (var uow = _db.UnitOfWork)
{
var gc = uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs));
if (gc.FeedSubs.Contains(fs))
{
return false;
}
else if (gc.FeedSubs.Count >= 10)
{
return false;
}
gc.FeedSubs.Add(fs);
//adding all, in case bot wasn't on this guild when it started
foreach (var f in gc.FeedSubs)
{
_subs.AddOrUpdate(f.Url, new HashSet<FeedSub>(), (k, old) =>
{
old.Add(f);
return old;
});
}
uow.Complete();
}
return true;
}
public bool RemoveFeed(ulong guildId, int index)
{
if (index < 0)
return false;
using (var uow = _db.UnitOfWork)
{
var items = uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs))
.FeedSubs
.OrderBy(x => x.Id)
.ToList();
if (items.Count <= index)
return false;
var toRemove = items[index];
_subs.AddOrUpdate(toRemove.Url, new HashSet<FeedSub>(), (key, old) =>
{
old.Remove(toRemove);
return old;
});
uow._context.Remove(toRemove);
uow.Complete();
}
return true;
}
}
}

View File

@ -0,0 +1,251 @@
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;
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;
public ConcurrentDictionary<ulong, bool> TranslatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
public ConcurrentDictionary<UserChannelPair, string> UserLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>();
public readonly string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json";
public readonly string PokemonListFile = "data/pokemon/pokemon_list7.json";
public Dictionary<string, SearchPokemon> Pokemons { get; } = new Dictionary<string, SearchPokemon>();
public Dictionary<string, SearchPokemonAbility> PokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
public List<WoWJoke> WowJokes { get; } = new List<WoWJoke>();
public List<MagicItem> MagicItems { get; } = new List<MagicItem>();
private readonly ConcurrentDictionary<ulong, SearchImageCacher> _imageCacher = new ConcurrentDictionary<ulong, SearchImageCacher>();
public ConcurrentDictionary<ulong, Timer> AutoHentaiTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
public ConcurrentDictionary<ulong, Timer> AutoBoobTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
public ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
private readonly ConcurrentDictionary<ulong, HashSet<string>> _blacklistedTags = new ConcurrentDictionary<ulong, HashSet<string>>();
public SearchesService(DiscordSocketClient client, IGoogleApiService google,
DbService db, NadekoBot bot)
{
Http = new HttpClient();
Http.AddFakeHeaders();
_client = client;
_google = google;
_db = db;
_log = LogManager.GetCurrentClassLogger();
_blacklistedTags = new ConcurrentDictionary<ulong, HashSet<string>>(
bot.AllGuildConfigs.ToDictionary(
x => x.GuildId,
x => new HashSet<string>(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<Dictionary<string, SearchPokemon>>(File.ReadAllText(PokemonListFile));
}
else
_log.Warn(PokemonListFile + " is missing. Pokemon abilities not loaded.");
if (File.Exists(PokemonAbilitiesFile))
PokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(PokemonAbilitiesFile));
else
_log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded.");
//joke commands
if (File.Exists("data/wowjokes.json"))
{
WowJokes = JsonConvert.DeserializeObject<List<WoWJoke>>(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<List<MagicItem>>(File.ReadAllText("data/magicitems.json"));
}
else
_log.Warn("data/magicitems.json is missing. Magic items are not loaded.");
}
public async Task<string> 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<ImageCacherObject> DapiSearch(string tag, DapiSearchType type, ulong? guild, bool isExplicit = false)
{
if (guild.HasValue)
{
var blacklistedTags = GetBlacklistedTags(guild.Value);
if (blacklistedTags
.Any(x => tag.ToLowerInvariant().Contains(x)))
{
throw new TagBlacklistedException();
}
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<string> GetBlacklistedTags(ulong guildId)
{
if (_blacklistedTags.TryGetValue(guildId, out var tags))
return tags;
return new HashSet<string>();
}
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<string>(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<string> 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 = 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<string> 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; }
}
}

View File

@ -0,0 +1,184 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Modules.Searches.Common;
using NadekoBot.Modules.Searches.Common.Exceptions;
using NadekoBot.Core.Services.Impl;
namespace NadekoBot.Modules.Searches.Services
{
public class StreamNotificationService : INService
{
private readonly Timer _streamCheckTimer;
private bool firstStreamNotifPass { get; set; } = true;
private readonly ConcurrentDictionary<string, IStreamResponse> _cachedStatuses = new ConcurrentDictionary<string, IStreamResponse>();
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings;
private readonly HttpClient _http;
public StreamNotificationService(DbService db, DiscordSocketClient client, NadekoStrings strings)
{
_db = db;
_client = client;
_strings = strings;
_http = new HttpClient();
_streamCheckTimer = new Timer(async (state) =>
{
var oldCachedStatuses = new ConcurrentDictionary<string, IStreamResponse>(_cachedStatuses);
_cachedStatuses.Clear();
IEnumerable<FollowedStream> streams;
using (var uow = _db.UnitOfWork)
{
streams = uow.GuildConfigs.GetAllFollowedStreams(client.Guilds.Select(x => (long)x.Id).ToList());
}
await Task.WhenAll(streams.Select(async fs =>
{
try
{
var newStatus = await GetStreamStatus(fs).ConfigureAwait(false);
if (firstStreamNotifPass)
{
return;
}
IStreamResponse oldResponse;
if (oldCachedStatuses.TryGetValue(newStatus.Url, out oldResponse) &&
oldResponse.Live != newStatus.Live)
{
var server = _client.GetGuild(fs.GuildId);
var channel = server?.GetTextChannel(fs.ChannelId);
if (channel == null)
return;
try
{
await channel.EmbedAsync(GetEmbed(fs, newStatus, channel.Guild.Id)).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
catch
{
// ignored
}
}));
firstStreamNotifPass = false;
}, null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60));
}
public async Task<IStreamResponse> GetStreamStatus(FollowedStream stream, bool checkCache = true)
{
string response;
IStreamResponse result;
switch (stream.Type)
{
case FollowedStream.FollowedStreamType.Smashcast:
var smashcastUrl = $"https://api.smashcast.tv/user/{stream.Username.ToLowerInvariant()}";
if (checkCache && _cachedStatuses.TryGetValue(smashcastUrl, out result))
return result;
response = await _http.GetStringAsync(smashcastUrl).ConfigureAwait(false);
var scData = JsonConvert.DeserializeObject<SmashcastResponse>(response);
if (!scData.Success)
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
scData.Url = smashcastUrl;
_cachedStatuses.AddOrUpdate(smashcastUrl, scData, (key, old) => scData);
return scData;
case FollowedStream.FollowedStreamType.Twitch:
var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username.ToLowerInvariant())}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6";
if (checkCache && _cachedStatuses.TryGetValue(twitchUrl, out result))
return result;
response = await _http.GetStringAsync(twitchUrl).ConfigureAwait(false);
var twData = JsonConvert.DeserializeObject<TwitchResponse>(response);
if (twData.Error != null)
{
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
}
twData.Url = twitchUrl;
_cachedStatuses.AddOrUpdate(twitchUrl, twData, (key, old) => twData);
return twData;
case FollowedStream.FollowedStreamType.Mixer:
var beamUrl = $"https://mixer.com/api/v1/channels/{stream.Username.ToLowerInvariant()}";
if (checkCache && _cachedStatuses.TryGetValue(beamUrl, out result))
return result;
response = await _http.GetStringAsync(beamUrl).ConfigureAwait(false);
var bmData = JsonConvert.DeserializeObject<MixerResponse>(response);
if (bmData.Error != null)
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
bmData.Url = beamUrl;
_cachedStatuses.AddOrUpdate(beamUrl, bmData, (key, old) => bmData);
return bmData;
default:
break;
}
return null;
}
public EmbedBuilder GetEmbed(FollowedStream fs, IStreamResponse status, ulong guildId)
{
var embed = new EmbedBuilder()
.WithTitle(fs.Username)
.WithUrl(GetLink(fs))
.WithDescription(GetLink(fs))
.AddField(efb => efb.WithName(GetText(fs, "status"))
.WithValue(status.Live ? "Online" : "Offline")
.WithIsInline(true))
.AddField(efb => efb.WithName(GetText(fs, "viewers"))
.WithValue(status.Live ? status.Viewers.ToString() : "-")
.WithIsInline(true))
.WithColor(status.Live ? NadekoBot.OkColor : NadekoBot.ErrorColor);
if (!string.IsNullOrWhiteSpace(status.Title))
embed.WithAuthor(status.Title);
if (!string.IsNullOrWhiteSpace(status.Game))
embed.AddField(GetText(fs, "streaming"),
status.Game,
true);
embed.AddField(GetText(fs, "followers"),
status.FollowerCount.ToString(),
true);
if (!string.IsNullOrWhiteSpace(status.Icon))
embed.WithThumbnailUrl(status.Icon);
return embed;
}
public string GetText(FollowedStream fs, string key, params object[] replacements) =>
_strings.GetText(key,
fs.GuildId,
"Searches".ToLowerInvariant(),
replacements);
public string GetLink(FollowedStream fs)
{
if (fs.Type == FollowedStream.FollowedStreamType.Smashcast)
return $"https://www.smashcast.tv/{fs.Username}/";
if (fs.Type == FollowedStream.FollowedStreamType.Twitch)
return $"https://www.twitch.tv/{fs.Username}/";
if (fs.Type == FollowedStream.FollowedStreamType.Mixer)
return $"https://www.mixer.com/{fs.Username}/";
return "??";
}
}
}