Removed module projects because it can't work like that atm. Commented out package commands.
This commit is contained in:
176
NadekoBot.Core/Modules/Searches/AnimeSearchCommands.cs
Normal file
176
NadekoBot.Core/Modules/Searches/AnimeSearchCommands.cs
Normal file
@ -0,0 +1,176 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom.Html;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class AnimeSearchCommands : NadekoSubmodule<AnimeSearchService>
|
||||
{
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Mal([Remainder] string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
var fullQueryLink = "https://myanimelist.net/profile/" + name;
|
||||
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
|
||||
var imageElem = document.QuerySelector("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-left > div.user-profile > div.user-image > img");
|
||||
var imageUrl = ((IHtmlImageElement)imageElem)?.Source ?? "http://icecream.me/uploads/870b03f36b59cc16ebfe314ef2dde781.png";
|
||||
|
||||
var stats = document.QuerySelectorAll("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-right > div#statistics > div.user-statistics-stats > div.stats > div.clearfix > ul.stats-status > li > span").Select(x => x.InnerHtml).ToList();
|
||||
|
||||
var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
|
||||
|
||||
var favAnime = GetText("anime_no_fav");
|
||||
if (favorites[0].QuerySelector("p") == null)
|
||||
favAnime = string.Join("\n", favorites[0].QuerySelectorAll("ul > li > div.di-tc.va-t > a")
|
||||
.Shuffle()
|
||||
.Take(3)
|
||||
.Select(x =>
|
||||
{
|
||||
var elem = (IHtmlAnchorElement)x;
|
||||
return $"[{elem.InnerHtml}]({elem.Href})";
|
||||
}));
|
||||
|
||||
var info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li.clearfix")
|
||||
.Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml))
|
||||
.ToList();
|
||||
|
||||
var daysAndMean = document.QuerySelectorAll("div.anime:nth-child(1) > div:nth-child(2) > div")
|
||||
.Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray())
|
||||
.ToArray();
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText("mal_profile", name))
|
||||
.AddField(efb => efb.WithName("💚 " + GetText("watching")).WithValue(stats[0]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("💙 " + GetText("completed")).WithValue(stats[1]).WithIsInline(true));
|
||||
if (info.Count < 3)
|
||||
embed.AddField(efb => efb.WithName("💛 " + GetText("on_hold")).WithValue(stats[2]).WithIsInline(true));
|
||||
embed
|
||||
.AddField(efb => efb.WithName("💔 " + GetText("dropped")).WithValue(stats[3]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("⚪ " + GetText("plan_to_watch")).WithValue(stats[4]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("🕐 " + daysAndMean[0][0]).WithValue(daysAndMean[0][1]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("📊 " + daysAndMean[1][0]).WithValue(daysAndMean[1][1]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1).WithValue(info[0].Item2.TrimTo(20)).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1).WithValue(info[1].Item2.TrimTo(20)).WithIsInline(true));
|
||||
if (info.Count > 2)
|
||||
embed.AddField(efb => efb.WithName(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1).WithValue(info[2].Item2.TrimTo(20)).WithIsInline(true));
|
||||
//if(info.Count > 3)
|
||||
// embed.AddField(efb => efb.WithName(MalInfoToEmoji(info[3].Item1) + " " + info[3].Item1).WithValue(info[3].Item2).WithIsInline(true))
|
||||
embed
|
||||
.WithDescription($@"
|
||||
** https://myanimelist.net/animelist/{ name } **
|
||||
|
||||
**{GetText("top_3_fav_anime")}**
|
||||
{favAnime}"
|
||||
|
||||
//**[Manga List](https://myanimelist.net/mangalist/{name})**
|
||||
//💚`Reading:` {stats[5]}
|
||||
//💙`Completed:` {stats[6]}
|
||||
//💔`Dropped:` {stats[8]}
|
||||
//⚪`Plan to read:` {stats[9]}
|
||||
|
||||
//**Top 3 Favorite Manga:**
|
||||
//{favManga}"
|
||||
|
||||
)
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithImageUrl(imageUrl);
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string MalInfoToEmoji(string info)
|
||||
{
|
||||
info = info.Trim().ToLowerInvariant();
|
||||
switch (info)
|
||||
{
|
||||
case "gender":
|
||||
return "🚁";
|
||||
case "location":
|
||||
return "🗺";
|
||||
case "last online":
|
||||
return "👥";
|
||||
case "birthday":
|
||||
return "📆";
|
||||
default:
|
||||
return "❔";
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task Mal(IGuildUser usr) => Mal(usr.Username);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Anime([Remainder] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var animeData = await _service.GetAnimeData(query).ConfigureAwait(false);
|
||||
|
||||
if (animeData == null)
|
||||
{
|
||||
await ReplyErrorLocalized("failed_finding_anime").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
.WithDescription(animeData.Synopsis.Replace("<br>", Environment.NewLine))
|
||||
.WithTitle(animeData.title_english)
|
||||
.WithUrl(animeData.Link)
|
||||
.WithImageUrl(animeData.image_url_lge)
|
||||
.AddField(efb => efb.WithName(GetText("episodes")).WithValue(animeData.total_episodes.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("status")).WithValue(animeData.AiringStatus.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("genres")).WithValue(string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : new[] { "none" })).WithIsInline(true))
|
||||
.WithFooter(efb => efb.WithText(GetText("score") + " " + animeData.average_score + " / 100"));
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Manga([Remainder] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var mangaData = await _service.GetMangaData(query).ConfigureAwait(false);
|
||||
|
||||
if (mangaData == null)
|
||||
{
|
||||
await ReplyErrorLocalized("failed_finding_manga").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
.WithDescription(mangaData.Synopsis.Replace("<br>", Environment.NewLine))
|
||||
.WithTitle(mangaData.title_english)
|
||||
.WithUrl(mangaData.Link)
|
||||
.WithImageUrl(mangaData.image_url_lge)
|
||||
.AddField(efb => efb.WithName(GetText("chapters")).WithValue(mangaData.total_chapters.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("status")).WithValue(mangaData.publishing_status.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("genres")).WithValue(string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : new[] { "none" })).WithIsInline(true))
|
||||
.WithFooter(efb => efb.WithText(GetText("score") + " " + mangaData.average_score + " / 100"));
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
NadekoBot.Core/Modules/Searches/Common/AnimeResult.cs
Normal file
20
NadekoBot.Core/Modules/Searches/Common/AnimeResult.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using NadekoBot.Extensions;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class AnimeResult
|
||||
{
|
||||
public int id;
|
||||
public string AiringStatus => airing_status.ToTitleCase();
|
||||
public string airing_status;
|
||||
public string title_english;
|
||||
public int total_episodes;
|
||||
public string description;
|
||||
public string image_url_lge;
|
||||
public string[] Genres;
|
||||
public string average_score;
|
||||
|
||||
public string Link => "http://anilist.co/anime/" + id;
|
||||
public string Synopsis => description?.Substring(0, description.Length > 500 ? 500 : description.Length) + "...";
|
||||
}
|
||||
}
|
39
NadekoBot.Core/Modules/Searches/Common/DefineModel.cs
Normal file
39
NadekoBot.Core/Modules/Searches/Common/DefineModel.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class Audio
|
||||
{
|
||||
public string url { get; set; }
|
||||
}
|
||||
|
||||
public class Example
|
||||
{
|
||||
public List<Audio> audio { get; set; }
|
||||
public string text { get; set; }
|
||||
}
|
||||
|
||||
public class GramaticalInfo
|
||||
{
|
||||
public string type { get; set; }
|
||||
}
|
||||
|
||||
public class Sens
|
||||
{
|
||||
public object Definition { get; set; }
|
||||
public List<Example> Examples { get; set; }
|
||||
public GramaticalInfo Gramatical_info { get; set; }
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
public string Part_of_speech { get; set; }
|
||||
public List<Sens> Senses { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
public class DefineModel
|
||||
{
|
||||
public List<Result> Results { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common.Exceptions
|
||||
{
|
||||
public class StreamNotFoundException : Exception
|
||||
{
|
||||
public StreamNotFoundException(string message) : base($"Stream '{message}' not found.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
16
NadekoBot.Core/Modules/Searches/Common/GoogleSearchResult.cs
Normal file
16
NadekoBot.Core/Modules/Searches/Common/GoogleSearchResult.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public struct GoogleSearchResult
|
||||
{
|
||||
public string Title { get; }
|
||||
public string Link { get; }
|
||||
public string Text { get; }
|
||||
|
||||
public GoogleSearchResult(string title, string link, string text)
|
||||
{
|
||||
this.Title = title;
|
||||
this.Link = link;
|
||||
this.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
8
NadekoBot.Core/Modules/Searches/Common/MagicItem.cs
Normal file
8
NadekoBot.Core/Modules/Searches/Common/MagicItem.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class MagicItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
17
NadekoBot.Core/Modules/Searches/Common/MangaResult.cs
Normal file
17
NadekoBot.Core/Modules/Searches/Common/MangaResult.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class MangaResult
|
||||
{
|
||||
public int id;
|
||||
public string publishing_status;
|
||||
public string image_url_lge;
|
||||
public string title_english;
|
||||
public int total_chapters;
|
||||
public int total_volumes;
|
||||
public string description;
|
||||
public string[] Genres;
|
||||
public string average_score;
|
||||
public string Link => "http://anilist.co/manga/" + id;
|
||||
public string Synopsis => description?.Substring(0, description.Length > 500 ? 500 : description.Length) + "...";
|
||||
}
|
||||
}
|
57
NadekoBot.Core/Modules/Searches/Common/OmdbProvider.cs
Normal file
57
NadekoBot.Core/Modules/Searches/Common/OmdbProvider.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public static class OmdbProvider
|
||||
{
|
||||
private const string queryUrl = "https://omdbapi.nadekobot.me/?t={0}&y=&plot=full&r=json";
|
||||
|
||||
public static async Task<OmdbMovie> FindMovie(string name, IGoogleApiService google)
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var res = await http.GetStringAsync(String.Format(queryUrl,name.Trim().Replace(' ','+'))).ConfigureAwait(false);
|
||||
var movie = JsonConvert.DeserializeObject<OmdbMovie>(res);
|
||||
if (movie?.Title == null)
|
||||
return null;
|
||||
movie.Poster = await google.ShortenUrl(movie.Poster);
|
||||
return movie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OmdbMovie
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Year { get; set; }
|
||||
public string ImdbRating { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public string Genre { get; set; }
|
||||
public string Plot { get; set; }
|
||||
public string Poster { get; set; }
|
||||
|
||||
public EmbedBuilder GetEmbed() =>
|
||||
new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(Title)
|
||||
.WithUrl($"http://www.imdb.com/title/{ImdbId}/")
|
||||
.WithDescription(Plot.TrimTo(1000))
|
||||
.AddField(efb => efb.WithName("Rating").WithValue(ImdbRating).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("Genre").WithValue(Genre).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("Year").WithValue(Year).WithIsInline(true))
|
||||
.WithImageUrl(Poster);
|
||||
|
||||
public override string ToString() =>
|
||||
$@"`Title:` {Title}
|
||||
`Year:` {Year}
|
||||
`Rating:` {ImdbRating}
|
||||
`Genre:` {Genre}
|
||||
`Link:` http://www.imdb.com/title/{ImdbId}/
|
||||
`Plot:` {Plot}";
|
||||
}
|
||||
}
|
60
NadekoBot.Core/Modules/Searches/Common/OverwatchApiModel.cs
Normal file
60
NadekoBot.Core/Modules/Searches/Common/OverwatchApiModel.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class OverwatchApiModel
|
||||
{
|
||||
public OverwatchPlayer Player { get; set; }
|
||||
|
||||
public class OverwatchResponse
|
||||
{
|
||||
public object _request;
|
||||
public OverwatchPlayer Eu { get; set; }
|
||||
public OverwatchPlayer Kr { get; set; }
|
||||
public OverwatchPlayer Us { get; set; }
|
||||
}
|
||||
public class OverwatchPlayer
|
||||
{
|
||||
public StatsField Stats { get; set; }
|
||||
}
|
||||
|
||||
public class StatsField
|
||||
{
|
||||
public Stats Quickplay { get; set; }
|
||||
public Stats Competitive { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class Stats
|
||||
{
|
||||
[JsonProperty("overall_stats")]
|
||||
public OverallStats OverallStats { get; set; }
|
||||
[JsonProperty("game_stats")]
|
||||
public GameStats GameStats { get; set; }
|
||||
}
|
||||
|
||||
public class OverallStats
|
||||
{
|
||||
public int? comprank;
|
||||
public int level;
|
||||
public int prestige;
|
||||
public string avatar;
|
||||
public int wins;
|
||||
public int losses;
|
||||
public int games;
|
||||
public string rank_image;
|
||||
}
|
||||
|
||||
public class GameStats
|
||||
{
|
||||
[JsonProperty("time_played")]
|
||||
public float timePlayed;
|
||||
}
|
||||
|
||||
|
||||
public class Competitive
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
223
NadekoBot.Core/Modules/Searches/Common/SearchImageCacher.cs
Normal file
223
NadekoBot.Core/Modules/Searches/Common/SearchImageCacher.cs
Normal file
@ -0,0 +1,223 @@
|
||||
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 System.Xml;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class SearchImageCacher
|
||||
{
|
||||
private readonly NadekoRandom _rng;
|
||||
private readonly ConcurrentDictionary<DapiSearchType, SemaphoreSlim> _locks = new ConcurrentDictionary<DapiSearchType, SemaphoreSlim>();
|
||||
|
||||
private readonly SortedSet<ImageCacherObject> _cache;
|
||||
private readonly Logger _log;
|
||||
private readonly HttpClient _http;
|
||||
|
||||
public SearchImageCacher()
|
||||
{
|
||||
_http = new HttpClient();
|
||||
_http.AddFakeHeaders();
|
||||
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_rng = new NadekoRandom();
|
||||
_cache = new SortedSet<ImageCacherObject>();
|
||||
}
|
||||
|
||||
public async Task<ImageCacherObject> GetImage(string tag, bool forceExplicit, DapiSearchType type,
|
||||
HashSet<string> blacklistedTags = null)
|
||||
{
|
||||
tag = tag?.ToLowerInvariant();
|
||||
|
||||
blacklistedTags = blacklistedTags ?? new HashSet<string>();
|
||||
|
||||
if (type == DapiSearchType.E621)
|
||||
tag = tag?.Replace("yuri", "female/female");
|
||||
|
||||
var _lock = GetLock(type);
|
||||
await _lock.WaitAsync();
|
||||
try
|
||||
{
|
||||
ImageCacherObject[] imgs;
|
||||
if (!string.IsNullOrWhiteSpace(tag))
|
||||
{
|
||||
imgs = _cache.Where(x => x.Tags.IsSupersetOf(tag.Split('+')) && x.SearchType == type && (!forceExplicit || x.Rating == "e")).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
tag = null;
|
||||
imgs = _cache.Where(x => x.SearchType == type).ToArray();
|
||||
}
|
||||
ImageCacherObject img;
|
||||
if (imgs.Length == 0)
|
||||
img = null;
|
||||
else
|
||||
img = imgs[_rng.Next(imgs.Length)];
|
||||
|
||||
if (img != null)
|
||||
{
|
||||
_cache.Remove(img);
|
||||
return img;
|
||||
}
|
||||
else
|
||||
{
|
||||
var images = await DownloadImages(tag, forceExplicit, type).ConfigureAwait(false);
|
||||
images = images
|
||||
.Where(x => x.Tags.All(t => !blacklistedTags.Contains(t)))
|
||||
.ToArray();
|
||||
if (images.Length == 0)
|
||||
return null;
|
||||
var toReturn = images[_rng.Next(images.Length)];
|
||||
#if !GLOBAL_NADEKO
|
||||
foreach (var dledImg in images)
|
||||
{
|
||||
if(dledImg != toReturn)
|
||||
_cache.Add(dledImg);
|
||||
}
|
||||
#endif
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private SemaphoreSlim GetLock(DapiSearchType type)
|
||||
{
|
||||
return _locks.GetOrAdd(type, _ => new SemaphoreSlim(1, 1));
|
||||
}
|
||||
|
||||
public async Task<ImageCacherObject[]> DownloadImages(string tag, bool isExplicit, DapiSearchType type)
|
||||
{
|
||||
tag = tag?.Replace(" ", "_").ToLowerInvariant();
|
||||
if (isExplicit)
|
||||
tag = "rating%3Aexplicit+" + tag;
|
||||
var website = "";
|
||||
switch (type)
|
||||
{
|
||||
case DapiSearchType.Safebooru:
|
||||
website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=1000&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.E621:
|
||||
website = $"https://e621.net/post/index.json?limit=1000&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Danbooru:
|
||||
website = $"http://danbooru.donmai.us/posts.json?limit=100&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Gelbooru:
|
||||
website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Rule34:
|
||||
website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Konachan:
|
||||
website = $"https://konachan.com/post.json?s=post&q=index&limit=100&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Yandere:
|
||||
website = $"https://yande.re/post.json?limit=100&tags={tag}";
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == DapiSearchType.Konachan || type == DapiSearchType.Yandere ||
|
||||
type == DapiSearchType.E621 || type == DapiSearchType.Danbooru)
|
||||
{
|
||||
var data = await _http.GetStringAsync(website).ConfigureAwait(false);
|
||||
return JsonConvert.DeserializeObject<DapiImageObject[]>(data)
|
||||
.Where(x => x.File_Url != null)
|
||||
.Select(x => new ImageCacherObject(x, type))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
return (await LoadXmlAsync(website, type)).ToArray();
|
||||
}
|
||||
|
||||
private async Task<ImageCacherObject[]> LoadXmlAsync(string website, DapiSearchType type)
|
||||
{
|
||||
var list = new List<ImageCacherObject>();
|
||||
using (var reader = XmlReader.Create(await _http.GetStreamAsync(website), new XmlReaderSettings()
|
||||
{
|
||||
Async = true,
|
||||
}))
|
||||
{
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element &&
|
||||
reader.Name == "post")
|
||||
{
|
||||
list.Add(new ImageCacherObject(new DapiImageObject()
|
||||
{
|
||||
File_Url = reader["file_url"],
|
||||
Tags = reader["tags"],
|
||||
Rating = reader["rating"] ?? "e"
|
||||
|
||||
}, type));
|
||||
}
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public class ImageCacherObject : IComparable<ImageCacherObject>
|
||||
{
|
||||
public DapiSearchType SearchType { get; }
|
||||
public string FileUrl { get; }
|
||||
public HashSet<string> Tags { get; }
|
||||
public string Rating { get; }
|
||||
|
||||
public ImageCacherObject(DapiImageObject obj, DapiSearchType type)
|
||||
{
|
||||
if (type == DapiSearchType.Danbooru)
|
||||
this.FileUrl = "https://danbooru.donmai.us" + obj.File_Url;
|
||||
else
|
||||
this.FileUrl = obj.File_Url.StartsWith("http") ? obj.File_Url : "https:" + obj.File_Url;
|
||||
this.SearchType = type;
|
||||
this.Rating = obj.Rating;
|
||||
this.Tags = new HashSet<string>((obj.Tags ?? obj.Tag_String).Split(' '));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return FileUrl;
|
||||
}
|
||||
|
||||
public int CompareTo(ImageCacherObject other)
|
||||
{
|
||||
return FileUrl.CompareTo(other.FileUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public class DapiImageObject
|
||||
{
|
||||
public string File_Url { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public string Tag_String { get; set; }
|
||||
public string Rating { get; set; }
|
||||
}
|
||||
|
||||
public enum DapiSearchType
|
||||
{
|
||||
Safebooru,
|
||||
E621,
|
||||
Gelbooru,
|
||||
Konachan,
|
||||
Rule34,
|
||||
Yandere,
|
||||
Danbooru
|
||||
}
|
||||
}
|
55
NadekoBot.Core/Modules/Searches/Common/SearchPokemon.cs
Normal file
55
NadekoBot.Core/Modules/Searches/Common/SearchPokemon.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class SearchPokemon
|
||||
{
|
||||
public class GenderRatioClass
|
||||
{
|
||||
public float M { get; set; }
|
||||
public float F { get; set; }
|
||||
}
|
||||
public class BaseStatsClass
|
||||
{
|
||||
public int HP { get; set; }
|
||||
public int ATK { get; set; }
|
||||
public int DEF { get; set; }
|
||||
public int SPA { get; set; }
|
||||
public int SPD { get; set; }
|
||||
public int SPE { get; set; }
|
||||
|
||||
public override string ToString() => $@"**HP:** {HP,-4} **ATK:** {ATK,-4} **DEF:** {DEF,-4}
|
||||
**SPA:** {SPA,-4} **SPD:** {SPD,-4} **SPE:** {SPE,-4}";
|
||||
}
|
||||
public int Id { get; set; }
|
||||
public string Species { get; set; }
|
||||
public string[] Types { get; set; }
|
||||
public GenderRatioClass GenderRatio { get; set; }
|
||||
public BaseStatsClass BaseStats { get; set; }
|
||||
public Dictionary<string, string> Abilities { get; set; }
|
||||
public float HeightM { get; set; }
|
||||
public float WeightKg { get; set; }
|
||||
public string Color { get; set; }
|
||||
public string[] Evos { get; set; }
|
||||
public string[] EggGroups { get; set; }
|
||||
|
||||
// public override string ToString() => $@"`Name:` {Species}
|
||||
//`Types:` {string.Join(", ", Types)}
|
||||
//`Stats:` {BaseStats}
|
||||
//`Height:` {HeightM,4}m `Weight:` {WeightKg}kg
|
||||
//`Abilities:` {string.Join(", ", Abilities.Values)}";
|
||||
|
||||
}
|
||||
|
||||
public class SearchPokemonAbility
|
||||
{
|
||||
public string Desc { get; set; }
|
||||
public string ShortDesc { get; set; }
|
||||
public string Name { get; set; }
|
||||
public float Rating { get; set; }
|
||||
|
||||
// public override string ToString() => $@"`Name:` : {Name}
|
||||
//`Rating:` {Rating}
|
||||
//`Description:` {Desc}";
|
||||
}
|
||||
}
|
95
NadekoBot.Core/Modules/Searches/Common/StreamResponses.cs
Normal file
95
NadekoBot.Core/Modules/Searches/Common/StreamResponses.cs
Normal file
@ -0,0 +1,95 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public interface IStreamResponse
|
||||
{
|
||||
int Viewers { get; }
|
||||
string Title { get; }
|
||||
bool Live { get; }
|
||||
string Game { get; }
|
||||
int FollowerCount { get; }
|
||||
string Url { get; }
|
||||
string Icon { get; }
|
||||
}
|
||||
|
||||
public class SmashcastResponse : IStreamResponse
|
||||
{
|
||||
public bool Success { get; set; } = true;
|
||||
public int Followers { get; set; }
|
||||
[JsonProperty("user_logo")]
|
||||
public string UserLogo { get; set; }
|
||||
[JsonProperty("is_live")]
|
||||
public string IsLive { get; set; }
|
||||
|
||||
public int Viewers => 0;
|
||||
public string Title => "";
|
||||
public bool Live => IsLive == "1";
|
||||
public string Game => "";
|
||||
public int FollowerCount => Followers;
|
||||
public string Icon => !string.IsNullOrWhiteSpace(UserLogo)
|
||||
? "https://edge.sf.hitbox.tv" + UserLogo
|
||||
: "";
|
||||
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
public class TwitchResponse : IStreamResponse
|
||||
{
|
||||
public string Error { get; set; } = null;
|
||||
public bool IsLive => Stream != null;
|
||||
public StreamInfo Stream { get; set; }
|
||||
|
||||
public class StreamInfo
|
||||
{
|
||||
public int Viewers { get; set; }
|
||||
public string Game { get; set; }
|
||||
public ChannelInfo Channel { get; set; }
|
||||
|
||||
public class ChannelInfo
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Logo { get; set; }
|
||||
public int Followers { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public int Viewers => Stream?.Viewers ?? 0;
|
||||
public string Title => Stream?.Channel?.Status;
|
||||
public bool Live => IsLive;
|
||||
public string Game => Stream?.Game;
|
||||
public int FollowerCount => Stream?.Channel?.Followers ?? 0;
|
||||
public string Url { get; set; }
|
||||
public string Icon => Stream?.Channel?.Logo;
|
||||
}
|
||||
|
||||
public class MixerResponse : IStreamResponse
|
||||
{
|
||||
public class MixerType
|
||||
{
|
||||
public string Parent { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
public class MixerThumbnail
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
||||
public string Url { get; set; }
|
||||
public string Error { get; set; } = null;
|
||||
|
||||
[JsonProperty("online")]
|
||||
public bool IsLive { get; set; }
|
||||
public int ViewersCurrent { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int NumFollowers { get; set; }
|
||||
public MixerType Type { get; set; }
|
||||
public MixerThumbnail Thumbnail { get; set; }
|
||||
|
||||
public int Viewers => ViewersCurrent;
|
||||
public string Title => Name;
|
||||
public bool Live => IsLive;
|
||||
public string Game => Type?.Name ?? "";
|
||||
public int FollowerCount => NumFollowers;
|
||||
public string Icon => Thumbnail?.Url;
|
||||
}
|
||||
}
|
37
NadekoBot.Core/Modules/Searches/Common/TimeModels.cs
Normal file
37
NadekoBot.Core/Modules/Searches/Common/TimeModels.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class TimeZoneResult
|
||||
{
|
||||
public double DstOffset { get; set; }
|
||||
public double RawOffset { get; set; }
|
||||
|
||||
//public string TimeZoneId { get; set; }
|
||||
public string TimeZoneName { get; set; }
|
||||
}
|
||||
|
||||
public class GeolocationResult
|
||||
{
|
||||
|
||||
public class GeolocationModel
|
||||
{
|
||||
public class GeometryModel
|
||||
{
|
||||
public class LocationModel
|
||||
{
|
||||
public float Lat { get; set; }
|
||||
public float Lng { get; set; }
|
||||
}
|
||||
|
||||
public LocationModel Location { get; set; }
|
||||
}
|
||||
|
||||
[JsonProperty("formatted_address")]
|
||||
public string FormattedAddress { get; set; }
|
||||
public GeometryModel Geometry { get; set; }
|
||||
}
|
||||
|
||||
public GeolocationModel[] results;
|
||||
}
|
||||
}
|
66
NadekoBot.Core/Modules/Searches/Common/WeatherModels.cs
Normal file
66
NadekoBot.Core/Modules/Searches/Common/WeatherModels.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class Coord
|
||||
{
|
||||
public double Lon { get; set; }
|
||||
public double Lat { get; set; }
|
||||
}
|
||||
|
||||
public class Weather
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Main { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Icon { get; set; }
|
||||
}
|
||||
|
||||
public class Main
|
||||
{
|
||||
public double Temp { get; set; }
|
||||
public float Pressure { get; set; }
|
||||
public float Humidity { get; set; }
|
||||
[JsonProperty("temp_min")]
|
||||
public double TempMin { get; set; }
|
||||
[JsonProperty("temp_max")]
|
||||
public double TempMax { get; set; }
|
||||
}
|
||||
|
||||
public class Wind
|
||||
{
|
||||
public double Speed { get; set; }
|
||||
public double Deg { get; set; }
|
||||
}
|
||||
|
||||
public class Clouds
|
||||
{
|
||||
public int All { get; set; }
|
||||
}
|
||||
|
||||
public class Sys
|
||||
{
|
||||
public int Type { get; set; }
|
||||
public int Id { get; set; }
|
||||
public double Message { get; set; }
|
||||
public string Country { get; set; }
|
||||
public double Sunrise { get; set; }
|
||||
public double Sunset { get; set; }
|
||||
}
|
||||
|
||||
public class WeatherData
|
||||
{
|
||||
public Coord Coord { get; set; }
|
||||
public List<Weather> Weather { get; set; }
|
||||
public Main Main { get; set; }
|
||||
public int Visibility { get; set; }
|
||||
public Wind Wind { get; set; }
|
||||
public Clouds Clouds { get; set; }
|
||||
public int Dt { get; set; }
|
||||
public Sys Sys { get; set; }
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Cod { get; set; }
|
||||
}
|
||||
}
|
18
NadekoBot.Core/Modules/Searches/Common/WikipediaApiModel.cs
Normal file
18
NadekoBot.Core/Modules/Searches/Common/WikipediaApiModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class WikipediaApiModel
|
||||
{
|
||||
public WikipediaQuery Query { get; set; }
|
||||
|
||||
public class WikipediaQuery
|
||||
{
|
||||
public WikipediaPage[] Pages { get; set; }
|
||||
|
||||
public class WikipediaPage
|
||||
{
|
||||
public bool Missing { get; set; } = false;
|
||||
public string FullUrl { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
NadekoBot.Core/Modules/Searches/Common/WoWJoke.cs
Normal file
9
NadekoBot.Core/Modules/Searches/Common/WoWJoke.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class WoWJoke
|
||||
{
|
||||
public string Question { get; set; }
|
||||
public string Answer { get; set; }
|
||||
public override string ToString() => $"`{Question}`\n\n**{Answer}**";
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Exceptions
|
||||
{
|
||||
public class TagBlacklistedException : Exception
|
||||
{
|
||||
public TagBlacklistedException() : base("Tag you used is blacklisted.")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
110
NadekoBot.Core/Modules/Searches/FeedCommands.cs
Normal file
110
NadekoBot.Core/Modules/Searches/FeedCommands.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.SyndicationFeed.Rss;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class FeedCommands : NadekoSubmodule<FeedsService>
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public FeedCommands(DiscordSocketClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task Feed(string url, [Remainder] ITextChannel channel = null)
|
||||
{
|
||||
var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
|
||||
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
if (success)
|
||||
{
|
||||
channel = channel ?? (ITextChannel)Context.Channel;
|
||||
using (var xmlReader = XmlReader.Create(url, new XmlReaderSettings() { Async = true }))
|
||||
{
|
||||
var reader = new RssFeedReader(xmlReader);
|
||||
try
|
||||
{
|
||||
await reader.Read();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Console.WriteLine(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
success = _service.AddFeed(Context.Guild.Id, channel.Id, url);
|
||||
if (success)
|
||||
{
|
||||
await ReplyConfirmLocalized("feed_added").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ReplyErrorLocalized("feed_not_valid").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task FeedRemove(int index)
|
||||
{
|
||||
if (_service.RemoveFeed(Context.Guild.Id, --index))
|
||||
{
|
||||
await ReplyConfirmLocalized("feed_removed").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ReplyErrorLocalized("feed_out_of_range").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task FeedList()
|
||||
{
|
||||
var feeds = _service.GetFeeds(Context.Guild.Id);
|
||||
|
||||
if (!feeds.Any())
|
||||
{
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText("feed_no_feed")))
|
||||
.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await Context.Channel.SendPaginatedConfirmAsync(_client, 0, (cur) =>
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor();
|
||||
var i = 0;
|
||||
var fs = string.Join("\n", feeds.Skip(cur * 10)
|
||||
.Take(10)
|
||||
.Select(x => $"`{(cur * 10) + (++i)}.` <#{x.ChannelId}> {x.Url}"));
|
||||
|
||||
return embed.WithDescription(fs);
|
||||
|
||||
}, feeds.Count / 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
NadekoBot.Core/Modules/Searches/JokeCommands.cs
Normal file
62
NadekoBot.Core/Modules/Searches/JokeCommands.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class JokeCommands : NadekoSubmodule<SearchesService>
|
||||
{
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Yomama()
|
||||
{
|
||||
await Context.Channel.SendConfirmAsync(await _service.GetYomamaJoke()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Randjoke()
|
||||
{
|
||||
var jokeInfo = await _service.GetRandomJoke();
|
||||
await Context.Channel.SendConfirmAsync("", jokeInfo.Text, footer: jokeInfo.BaseUri).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ChuckNorris()
|
||||
{
|
||||
await Context.Channel.SendConfirmAsync(await _service.GetChuckNorrisJoke()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task WowJoke()
|
||||
{
|
||||
if (!_service.WowJokes.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("jokes_not_loaded").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var joke = _service.WowJokes[new NadekoRandom().Next(0, _service.WowJokes.Count)];
|
||||
await Context.Channel.SendConfirmAsync(joke.Question, joke.Answer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task MagicItem()
|
||||
{
|
||||
if (!_service.WowJokes.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("magicitems_not_loaded").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var item = _service.MagicItems[new NadekoRandom().Next(0, _service.MagicItems.Count)];
|
||||
|
||||
await Context.Channel.SendConfirmAsync("✨" + item.Name, item.Description).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
NadekoBot.Core/Modules/Searches/LoLCommands.cs
Normal file
68
NadekoBot.Core/Modules/Searches/LoLCommands.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using Discord;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
private class ChampionNameComparer : IEqualityComparer<JToken>
|
||||
{
|
||||
public bool Equals(JToken a, JToken b) => a["name"].ToString() == b["name"].ToString();
|
||||
|
||||
public int GetHashCode(JToken obj) =>
|
||||
obj["name"].GetHashCode();
|
||||
}
|
||||
|
||||
private static readonly string[] trashTalk = { "Better ban your counters. You are going to carry the game anyway.",
|
||||
"Go with the flow. Don't think. Just ban one of these.",
|
||||
"DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.",
|
||||
"Ask your teammates what would they like to play, and ban that.",
|
||||
"If you consider playing teemo, do it. If you consider teemo, you deserve him.",
|
||||
"Doesn't matter what you ban really. Enemy will ban your main and you will lose." };
|
||||
|
||||
private static readonly Lazy<Dictionary<int, string>> champData = new Lazy<Dictionary<int, string>>(() =>
|
||||
((IDictionary<string, JToken>)JObject.Parse(File.ReadAllText("data/lolchamps.json")))
|
||||
.ToDictionary(x => (int)x.Value["id"], x => x.Value["name"].ToString()), true);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Lolban()
|
||||
{
|
||||
//const int showCount = 8;
|
||||
try
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var data = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/v2/champions?champData=general&limit=200&api_key={_creds.LoLApiKey}"));
|
||||
|
||||
var champs = data.OrderByDescending(x => (double)x["banRate"]).Distinct(x => x["championId"]).Take(6);
|
||||
|
||||
//_log.Info(string.Join("\n", champs.Select(x => x["championId"] + " | " + x["banRate"] + " | " + x["normalized"]["banRate"])));
|
||||
var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline(GetText("x_most_banned_champs", champs.Count())));
|
||||
foreach (var champ in champs)
|
||||
{
|
||||
var lChamp = champ;
|
||||
if (!champData.Value.TryGetValue((int)champ["championId"], out var champName))
|
||||
champName = "UNKNOWN";
|
||||
eb.AddField(efb => efb.WithName(champName).WithValue(((double)lChamp["banRate"] * 100).ToString("F2") + "%").WithIsInline(true));
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(eb, Format.Italics(trashTalk[new NadekoRandom().Next(0, trashTalk.Length)])).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
await ReplyErrorLocalized("something_went_wrong").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
NadekoBot.Core/Modules/Searches/MemegenCommands.cs
Normal file
78
NadekoBot.Core/Modules/Searches/MemegenCommands.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class MemegenCommands : NadekoSubmodule
|
||||
{
|
||||
private static readonly ImmutableDictionary<char, string> _map = new Dictionary<char, string>()
|
||||
{
|
||||
{'?', "~q"},
|
||||
{'%', "~p"},
|
||||
{'#', "~h"},
|
||||
{'/', "~s"},
|
||||
{' ', "-"},
|
||||
{'-', "--"},
|
||||
{'_', "__"},
|
||||
{'"', "''"}
|
||||
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Memelist()
|
||||
{
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = false
|
||||
};
|
||||
|
||||
|
||||
using (var http = new HttpClient(handler))
|
||||
{
|
||||
var rawJson = await http.GetStringAsync("https://memegen.link/api/templates/").ConfigureAwait(false);
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(rawJson)
|
||||
.Select(kvp => Path.GetFileName(kvp.Value));
|
||||
|
||||
await Context.Channel.SendTableAsync(data, x => $"{x,-17}", 3).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Memegen(string meme, string topText, string botText)
|
||||
{
|
||||
var top = Replace(topText);
|
||||
var bot = Replace(botText);
|
||||
await Context.Channel.SendMessageAsync($"http://memegen.link/{meme}/{top}/{bot}.jpg")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string Replace(string input)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var c in input)
|
||||
{
|
||||
string tmp;
|
||||
if (_map.TryGetValue(c, out tmp))
|
||||
sb.Append(tmp);
|
||||
else
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
271
NadekoBot.Core/Modules/Searches/OsuCommands.cs
Normal file
271
NadekoBot.Core/Modules/Searches/OsuCommands.cs
Normal file
@ -0,0 +1,271 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class OsuCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly IGoogleApiService _google;
|
||||
private readonly IBotCredentials _creds;
|
||||
|
||||
public OsuCommands(IGoogleApiService google, IBotCredentials creds)
|
||||
{
|
||||
_google = google;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Osu(string usr, [Remainder] string mode = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(usr))
|
||||
return;
|
||||
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
var m = 0;
|
||||
if (!string.IsNullOrWhiteSpace(mode))
|
||||
{
|
||||
m = ResolveGameMode(mode);
|
||||
}
|
||||
http.AddFakeHeaders();
|
||||
var res = await http.GetStreamAsync(new Uri($"http://lemmmy.pw/osusig/sig.php?uname={ usr }&flagshadow&xpbar&xpbarhex&pp=2&mode={m}")).ConfigureAwait(false);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
res.CopyTo(ms);
|
||||
ms.Position = 0;
|
||||
await Context.Channel.SendFileAsync(ms, $"{usr}.png", $"🎧 **{GetText("profile_link")}** <https://new.ppy.sh/u/{Uri.EscapeDataString(usr)}>\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ReplyErrorLocalized("osu_failed").ConfigureAwait(false);
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Osub([Remainder] string map)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
|
||||
{
|
||||
await ReplyErrorLocalized("osu_api_key").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(map))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var mapId = ResolveMap(map);
|
||||
var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={_creds.OsuApiKey}&{mapId}";
|
||||
var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false))[0];
|
||||
var sb = new System.Text.StringBuilder();
|
||||
var starRating = Math.Round(double.Parse($"{obj["difficultyrating"]}", CultureInfo.InvariantCulture), 2);
|
||||
var time = TimeSpan.FromSeconds(double.Parse($"{obj["total_length"]}")).ToString(@"mm\:ss");
|
||||
sb.AppendLine($"{obj["artist"]} - {obj["title"]}, mapped by {obj["creator"]}. https://osu.ppy.sh/s/{obj["beatmapset_id"]}");
|
||||
sb.AppendLine($"{starRating} stars, {obj["bpm"]} BPM | AR{obj["diff_approach"]}, CS{obj["diff_size"]}, OD{obj["diff_overall"]} | Length: {time}");
|
||||
await Context.Channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ReplyErrorLocalized("something_went_wrong").ConfigureAwait(false);
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Osu5(string user, [Remainder] string mode = null)
|
||||
{
|
||||
var channel = (ITextChannel)Context.Channel;
|
||||
if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
|
||||
{
|
||||
await channel.SendErrorAsync("An osu! API key is required.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(user))
|
||||
{
|
||||
await channel.SendErrorAsync("Please provide a username.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
var m = 0;
|
||||
if (!string.IsNullOrWhiteSpace(mode))
|
||||
{
|
||||
m = ResolveGameMode(mode);
|
||||
}
|
||||
|
||||
var reqString = $"https://osu.ppy.sh/api/get_user_best?k={_creds.OsuApiKey}&u={Uri.EscapeDataString(user)}&type=string&limit=5&m={m}";
|
||||
var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false));
|
||||
var sb = new System.Text.StringBuilder($"`Top 5 plays for {user}:`\n```xl" + Environment.NewLine);
|
||||
foreach (var item in obj)
|
||||
{
|
||||
var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={_creds.OsuApiKey}&b={item["beatmap_id"]}";
|
||||
var map = JArray.Parse(await http.GetStringAsync(mapReqString).ConfigureAwait(false))[0];
|
||||
var pp = Math.Round(double.Parse($"{item["pp"]}", CultureInfo.InvariantCulture), 2);
|
||||
var acc = CalculateAcc(item, m);
|
||||
var mods = ResolveMods(int.Parse($"{item["enabled_mods"]}"));
|
||||
sb.AppendLine(mods != "+"
|
||||
? $"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"] + ")",-40} | **{mods,-10}** | /b/{item["beatmap_id"]}"
|
||||
: $"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"] + ")",-40} | /b/{item["beatmap_id"]}");
|
||||
}
|
||||
sb.Append("```");
|
||||
await channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ReplyErrorLocalized("something_went_wrong").ConfigureAwait(false);
|
||||
_log.Warn(ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//https://osu.ppy.sh/wiki/Accuracy
|
||||
private static double CalculateAcc(JToken play, int mode)
|
||||
{
|
||||
if (mode == 0)
|
||||
{
|
||||
var hitPoints = double.Parse($"{play["count50"]}") * 50 + double.Parse($"{play["count100"]}") * 100 + double.Parse($"{play["count300"]}") * 300;
|
||||
var totalHits = double.Parse($"{play["count50"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["count300"]}") + double.Parse($"{play["countmiss"]}");
|
||||
totalHits *= 300;
|
||||
return Math.Round(hitPoints / totalHits * 100, 2);
|
||||
}
|
||||
else if (mode == 1)
|
||||
{
|
||||
var hitPoints = double.Parse($"{play["countmiss"]}") * 0 + double.Parse($"{play["count100"]}") * 0.5 + double.Parse($"{play["count300"]}") * 1;
|
||||
var totalHits = double.Parse($"{play["countmiss"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["count300"]}");
|
||||
hitPoints *= 300;
|
||||
totalHits *= 300;
|
||||
return Math.Round(hitPoints / totalHits * 100, 2);
|
||||
}
|
||||
else if (mode == 2)
|
||||
{
|
||||
var fruitsCaught = double.Parse($"{play["count50"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["count300"]}");
|
||||
var totalFruits = double.Parse($"{play["countmiss"]}") + double.Parse($"{play["count50"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["count300"]}") + double.Parse($"{play["countkatu"]}");
|
||||
return Math.Round(fruitsCaught / totalFruits * 100, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var hitPoints = double.Parse($"{play["count50"]}") * 50 + double.Parse($"{play["count100"]}") * 100 + double.Parse($"{play["countkatu"]}") * 200 + (double.Parse($"{play["count300"]}") + double.Parse($"{play["countgeki"]}")) * 300;
|
||||
var totalHits = double.Parse($"{play["countmiss"]}") + double.Parse($"{play["count50"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["countkatu"]}") + double.Parse($"{play["count300"]}") + double.Parse($"{play["countgeki"]}");
|
||||
totalHits *= 300;
|
||||
return Math.Round(hitPoints / totalHits * 100, 2);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveMap(string mapLink)
|
||||
{
|
||||
var s = new Regex(@"osu.ppy.sh\/s\/", RegexOptions.IgnoreCase).Match(mapLink);
|
||||
var b = new Regex(@"osu.ppy.sh\/b\/", RegexOptions.IgnoreCase).Match(mapLink);
|
||||
var p = new Regex(@"osu.ppy.sh\/p\/", RegexOptions.IgnoreCase).Match(mapLink);
|
||||
var m = new Regex(@"&m=", RegexOptions.IgnoreCase).Match(mapLink);
|
||||
if (s.Success)
|
||||
{
|
||||
var mapId = mapLink.Substring(mapLink.IndexOf("/s/") + 3);
|
||||
return $"s={mapId}";
|
||||
}
|
||||
else if (b.Success)
|
||||
{
|
||||
if (m.Success)
|
||||
return $"b={mapLink.Substring(mapLink.IndexOf("/b/") + 3, mapLink.IndexOf("&m") - (mapLink.IndexOf("/b/") + 3))}";
|
||||
else
|
||||
return $"b={mapLink.Substring(mapLink.IndexOf("/b/") + 3)}";
|
||||
}
|
||||
else if (p.Success)
|
||||
{
|
||||
if (m.Success)
|
||||
return $"b={mapLink.Substring(mapLink.IndexOf("?b=") + 3, mapLink.IndexOf("&m") - (mapLink.IndexOf("?b=") + 3))}";
|
||||
else
|
||||
return $"b={mapLink.Substring(mapLink.IndexOf("?b=") + 3)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"s={mapLink}"; //just a default incase an ID number was provided by itself (non-url)?
|
||||
}
|
||||
}
|
||||
|
||||
private static int ResolveGameMode(string mode)
|
||||
{
|
||||
switch (mode.ToLower())
|
||||
{
|
||||
case "std":
|
||||
case "standard":
|
||||
return 0;
|
||||
case "taiko":
|
||||
return 1;
|
||||
case "ctb":
|
||||
case "catchthebeat":
|
||||
return 2;
|
||||
case "mania":
|
||||
case "osu!mania":
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//https://github.com/ppy/osu-api/wiki#mods
|
||||
private static string ResolveMods(int mods)
|
||||
{
|
||||
var modString = $"+";
|
||||
|
||||
if (IsBitSet(mods, 0))
|
||||
modString += "NF";
|
||||
if (IsBitSet(mods, 1))
|
||||
modString += "EZ";
|
||||
if (IsBitSet(mods, 8))
|
||||
modString += "HT";
|
||||
|
||||
if (IsBitSet(mods, 3))
|
||||
modString += "HD";
|
||||
if (IsBitSet(mods, 4))
|
||||
modString += "HR";
|
||||
if (IsBitSet(mods, 6) && !IsBitSet(mods, 9))
|
||||
modString += "DT";
|
||||
if (IsBitSet(mods, 9))
|
||||
modString += "NC";
|
||||
if (IsBitSet(mods, 10))
|
||||
modString += "FL";
|
||||
|
||||
if (IsBitSet(mods, 5))
|
||||
modString += "SD";
|
||||
if (IsBitSet(mods, 14))
|
||||
modString += "PF";
|
||||
|
||||
if (IsBitSet(mods, 7))
|
||||
modString += "RX";
|
||||
if (IsBitSet(mods, 11))
|
||||
modString += "AT";
|
||||
if (IsBitSet(mods, 12))
|
||||
modString += "SO";
|
||||
return modString;
|
||||
}
|
||||
|
||||
private static bool IsBitSet(int mods, int pos) =>
|
||||
(mods & (1 << pos)) != 0;
|
||||
}
|
||||
}
|
||||
}
|
111
NadekoBot.Core/Modules/Searches/OverwatchCommands.cs
Normal file
111
NadekoBot.Core/Modules/Searches/OverwatchCommands.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class OverwatchCommands : NadekoSubmodule
|
||||
{
|
||||
public enum Region
|
||||
{
|
||||
Eu,
|
||||
Us,
|
||||
Kr
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Overwatch(Region region, [Remainder] string query = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
var battletag = query.Replace("#", "-");
|
||||
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var model = (await GetProfile(region, battletag))?.Stats;
|
||||
|
||||
if (model != null)
|
||||
{
|
||||
if (model.Competitive == null)
|
||||
{
|
||||
var qp = model.Quickplay;
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eau => eau.WithName(query)
|
||||
.WithUrl($"https://www.overbuff.com/players/pc/{battletag}")
|
||||
.WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png"))
|
||||
.WithThumbnailUrl(qp.OverallStats.avatar)
|
||||
.AddField(fb => fb.WithName(GetText("level")).WithValue((qp.OverallStats.level + (qp.OverallStats.prestige * 100)).ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("quick_wins")).WithValue(qp.OverallStats.wins.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("compet_rank")).WithValue("0").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue($"{qp.GameStats.timePlayed}hrs").WithIsInline(true))
|
||||
.WithOkColor();
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var qp = model.Quickplay;
|
||||
var compet = model.Competitive;
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eau => eau.WithName(query)
|
||||
.WithUrl($"https://www.overbuff.com/players/pc/{battletag}")
|
||||
.WithIconUrl(compet.OverallStats.rank_image))
|
||||
.WithThumbnailUrl(compet.OverallStats.avatar)
|
||||
.AddField(fb => fb.WithName(GetText("level")).WithValue((qp.OverallStats.level + (qp.OverallStats.prestige * 100)).ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("quick_wins")).WithValue(qp.OverallStats.wins.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("compet_wins")).WithValue(compet.OverallStats.wins.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("compet_loses")).WithValue(compet.OverallStats.losses.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("compet_played")).WithValue(compet.OverallStats.games.ToString() ?? "-").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("compet_rank")).WithValue(compet.OverallStats.comprank?.ToString() ?? "-").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("compet_playtime")).WithValue(compet.GameStats.timePlayed + "hrs").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue(qp.GameStats.timePlayed.ToString("F1") + "hrs").WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("ow_user_not_found").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public async Task<OverwatchApiModel.OverwatchPlayer> GetProfile(Region region, string battletag)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var handler = new HttpClientHandler())
|
||||
{
|
||||
handler.ServerCertificateCustomValidationCallback = (x, y, z, e) => true;
|
||||
using (var http = new HttpClient(handler))
|
||||
{
|
||||
http.AddFakeHeaders();
|
||||
var url = $"https://owapi.nadekobot.me/api/v3/u/{battletag}/stats";
|
||||
var res = await http.GetStringAsync($"https://owapi.nadekobot.me/api/v3/u/{battletag}/stats");
|
||||
var model = JsonConvert.DeserializeObject<OverwatchApiModel.OverwatchResponse>(res);
|
||||
switch (region)
|
||||
{
|
||||
case Region.Eu:
|
||||
return model.Eu;
|
||||
case Region.Kr:
|
||||
return model.Kr;
|
||||
default:
|
||||
return model.Us;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
NadekoBot.Core/Modules/Searches/PlaceCommands.cs
Normal file
81
NadekoBot.Core/Modules/Searches/PlaceCommands.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class PlaceCommands : NadekoSubmodule
|
||||
{
|
||||
private static string typesStr { get; } = string.Join(", ", Enum.GetNames(typeof(PlaceType)));
|
||||
|
||||
public enum PlaceType
|
||||
{
|
||||
Cage, //http://www.placecage.com
|
||||
Steven, //http://www.stevensegallery.com
|
||||
Beard, //http://placebeard.it
|
||||
Fill, //http://www.fillmurray.com
|
||||
Bear, //https://www.placebear.com
|
||||
Kitten, //http://placekitten.com
|
||||
Bacon, //http://baconmockup.com
|
||||
Xoart, //http://xoart.link
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Placelist()
|
||||
{
|
||||
await Context.Channel.SendConfirmAsync(GetText("list_of_place_tags", Prefix),
|
||||
typesStr)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Place(PlaceType placeType, uint width = 0, uint height = 0)
|
||||
{
|
||||
var url = "";
|
||||
switch (placeType)
|
||||
{
|
||||
case PlaceType.Cage:
|
||||
url = "http://www.placecage.com";
|
||||
break;
|
||||
case PlaceType.Steven:
|
||||
url = "http://www.stevensegallery.com";
|
||||
break;
|
||||
case PlaceType.Beard:
|
||||
url = "http://placebeard.it";
|
||||
break;
|
||||
case PlaceType.Fill:
|
||||
url = "http://www.fillmurray.com";
|
||||
break;
|
||||
case PlaceType.Bear:
|
||||
url = "https://www.placebear.com";
|
||||
break;
|
||||
case PlaceType.Kitten:
|
||||
url = "http://placekitten.com";
|
||||
break;
|
||||
case PlaceType.Bacon:
|
||||
url = "http://baconmockup.com";
|
||||
break;
|
||||
case PlaceType.Xoart:
|
||||
url = "http://xoart.link";
|
||||
break;
|
||||
}
|
||||
var rng = new NadekoRandom();
|
||||
if (width <= 0 || width > 1000)
|
||||
width = (uint)rng.Next(250, 850);
|
||||
|
||||
if (height <= 0 || height > 1000)
|
||||
height = (uint)rng.Next(250, 850);
|
||||
|
||||
url += $"/{width}/{height}";
|
||||
|
||||
await Context.Channel.SendMessageAsync(url).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
70
NadekoBot.Core/Modules/Searches/PokemonSearchCommands.cs
Normal file
70
NadekoBot.Core/Modules/Searches/PokemonSearchCommands.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class PokemonSearchCommands : NadekoSubmodule<SearchesService>
|
||||
{
|
||||
public Dictionary<string, SearchPokemon> Pokemons => _service.Pokemons;
|
||||
public Dictionary<string, SearchPokemonAbility> PokemonAbilities => _service.PokemonAbilities;
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Pokemon([Remainder] string pokemon = null)
|
||||
{
|
||||
pokemon = pokemon?.Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrWhiteSpace(pokemon))
|
||||
return;
|
||||
|
||||
foreach (var kvp in Pokemons)
|
||||
{
|
||||
if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant())
|
||||
{
|
||||
var p = kvp.Value;
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(kvp.Key.ToTitleCase())
|
||||
.WithDescription(p.BaseStats.ToString())
|
||||
.AddField(efb => efb.WithName(GetText("types")).WithValue(string.Join(",\n", p.Types)).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("height_weight")).WithValue(GetText("height_weight_val", p.HeightM, p.WeightKg)).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("abilities")).WithValue(string.Join(",\n", p.Abilities.Select(a => a.Value))).WithIsInline(true)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
await ReplyErrorLocalized("pokemon_none").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task PokemonAbility([Remainder] string ability = null)
|
||||
{
|
||||
ability = ability?.Trim().ToUpperInvariant().Replace(" ", "");
|
||||
if (string.IsNullOrWhiteSpace(ability))
|
||||
return;
|
||||
foreach (var kvp in PokemonAbilities)
|
||||
{
|
||||
if (kvp.Key.ToUpperInvariant() == ability)
|
||||
{
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(kvp.Value.Name)
|
||||
.WithDescription(string.IsNullOrWhiteSpace(kvp.Value.Desc)
|
||||
? kvp.Value.ShortDesc
|
||||
: kvp.Value.Desc)
|
||||
.AddField(efb => efb.WithName(GetText("rating"))
|
||||
.WithValue(kvp.Value.Rating.ToString(_cultureInfo)).WithIsInline(true))
|
||||
).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await ReplyErrorLocalized("pokemon_ability_none").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
831
NadekoBot.Core/Modules/Searches/Searches.cs
Normal file
831
NadekoBot.Core/Modules/Searches/Searches.cs
Normal file
@ -0,0 +1,831 @@
|
||||
using Discord;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NadekoBot.Core.Services;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using NadekoBot.Extensions;
|
||||
using System.IO;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom.Html;
|
||||
using AngleSharp.Dom;
|
||||
using Configuration = AngleSharp.Configuration;
|
||||
using Discord.Commands;
|
||||
using ImageSharp;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches : NadekoTopLevelModule<SearchesService>
|
||||
{
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IGoogleApiService _google;
|
||||
|
||||
public Searches(IBotCredentials creds, IGoogleApiService google)
|
||||
{
|
||||
_creds = creds;
|
||||
_google = google;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task Say([Remainder]string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(Context.User, Context.Channel, Context.Guild, (DiscordSocketClient)Context.Client)
|
||||
.Build();
|
||||
|
||||
if (CREmbed.TryParse(message, out var embedData))
|
||||
{
|
||||
rep.Replace(embedData);
|
||||
try
|
||||
{
|
||||
await Context.Channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText?.SanitizeMentions() ?? "").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = rep.Replace(message);
|
||||
if (!string.IsNullOrWhiteSpace(msg))
|
||||
{
|
||||
await Context.Channel.SendConfirmAsync(msg).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Weather([Remainder] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
string response;
|
||||
response = await _service.Http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?q={query}&appid=42cd627dd60debf25a5739e50a217d74&units=metric").ConfigureAwait(false);
|
||||
|
||||
var data = JsonConvert.DeserializeObject<WeatherData>(response);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.AddField(fb => fb.WithName("🌍 " + Format.Bold(GetText("location"))).WithValue($"[{data.Name + ", " + data.Sys.Country}](https://openweathermap.org/city/{data.Id})").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("📏 " + Format.Bold(GetText("latlong"))).WithValue($"{data.Coord.Lat}, {data.Coord.Lon}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("☁ " + Format.Bold(GetText("condition"))).WithValue(string.Join(", ", data.Weather.Select(w => w.Main))).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("😓 " + Format.Bold(GetText("humidity"))).WithValue($"{data.Main.Humidity}%").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("💨 " + Format.Bold(GetText("wind_speed"))).WithValue(data.Wind.Speed + " m/s").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌡 " + Format.Bold(GetText("temperature"))).WithValue(data.Main.Temp + "°C").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🔆 " + Format.Bold(GetText("min_max"))).WithValue($"{data.Main.TempMin}°C - {data.Main.TempMax}°C").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌄 " + Format.Bold(GetText("sunrise"))).WithValue($"{data.Sys.Sunrise.ToUnixTimestamp():HH:mm} UTC").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌇 " + Format.Bold(GetText("sunset"))).WithValue($"{data.Sys.Sunset.ToUnixTimestamp():HH:mm} UTC").WithIsInline(true))
|
||||
.WithOkColor()
|
||||
.WithFooter(efb => efb.WithText("Powered by openweathermap.org").WithIconUrl($"http://openweathermap.org/img/w/{data.Weather[0].Icon}.png"));
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Time([Remainder] string arg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(arg) || string.IsNullOrWhiteSpace(_creds.GoogleApiKey))
|
||||
return;
|
||||
|
||||
var res = await _service.Http.GetStringAsync($"https://maps.googleapis.com/maps/api/geocode/json?address={arg}&key={_creds.GoogleApiKey}").ConfigureAwait(false);
|
||||
var obj = JsonConvert.DeserializeObject<GeolocationResult>(res);
|
||||
|
||||
var currentSeconds = DateTime.UtcNow.UnixTimestamp();
|
||||
var timeRes = await _service.Http.GetStringAsync($"https://maps.googleapis.com/maps/api/timezone/json?location={obj.results[0].Geometry.Location.Lat},{obj.results[0].Geometry.Location.Lng}×tamp={currentSeconds}&key={_creds.GoogleApiKey}").ConfigureAwait(false);
|
||||
var timeObj = JsonConvert.DeserializeObject<TimeZoneResult>(timeRes);
|
||||
|
||||
var time = DateTime.UtcNow.AddSeconds(timeObj.DstOffset + timeObj.RawOffset);
|
||||
|
||||
await ReplyConfirmLocalized("time", Format.Bold(obj.results[0].FormattedAddress), Format.Code(time.ToString("HH:mm")), timeObj.TimeZoneName).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Youtube([Remainder] string query = null)
|
||||
{
|
||||
if (!await ValidateQuery(Context.Channel, query).ConfigureAwait(false)) return;
|
||||
var result = (await _google.GetVideoLinksByKeywordAsync(query, 1)).FirstOrDefault();
|
||||
if (string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
await ReplyErrorLocalized("no_results").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await Context.Channel.SendMessageAsync(result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Imdb([Remainder] string query = null)
|
||||
{
|
||||
if (!(await ValidateQuery(Context.Channel, query).ConfigureAwait(false))) return;
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
|
||||
var movie = await OmdbProvider.FindMovie(query, _google);
|
||||
if (movie == null)
|
||||
{
|
||||
await ReplyErrorLocalized("imdb_fail").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await Context.Channel.EmbedAsync(movie.GetEmbed()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task RandomCat()
|
||||
{
|
||||
var res = JObject.Parse(await _service.Http.GetStringAsync("http://www.random.cat/meow").ConfigureAwait(false));
|
||||
await Context.Channel.SendMessageAsync(Uri.EscapeUriString(res["file"].ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task RandomDog()
|
||||
{
|
||||
await Context.Channel.SendMessageAsync("http://random.dog/" + await _service.Http.GetStringAsync("http://random.dog/woof")
|
||||
.ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Image([Remainder] string terms = null)
|
||||
{
|
||||
terms = terms?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(terms))
|
||||
return;
|
||||
|
||||
terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
|
||||
|
||||
try
|
||||
{
|
||||
var res = await _google.GetImageAsync(terms).ConfigureAwait(false);
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
|
||||
.WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch")
|
||||
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
|
||||
.WithDescription(res.Link)
|
||||
.WithImageUrl(res.Link)
|
||||
.WithTitle(Context.User.ToString());
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn("Falling back to Imgur search.");
|
||||
|
||||
var fullQueryLink = $"http://imgur.com/search?q={ terms }";
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
|
||||
var elems = document.QuerySelectorAll("a.image-list-link");
|
||||
|
||||
if (!elems.Any())
|
||||
return;
|
||||
|
||||
var img = (elems.FirstOrDefault()?.Children?.FirstOrDefault() as IHtmlImageElement);
|
||||
|
||||
if (img?.Source == null)
|
||||
return;
|
||||
|
||||
var source = img.Source.Replace("b.", ".");
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
|
||||
.WithDescription(source)
|
||||
.WithImageUrl(source)
|
||||
.WithTitle(Context.User.ToString());
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task RandomImage([Remainder] string terms = null)
|
||||
{
|
||||
terms = terms?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(terms))
|
||||
return;
|
||||
terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
|
||||
try
|
||||
{
|
||||
var res = await _google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false);
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
|
||||
.WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch")
|
||||
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
|
||||
.WithDescription(res.Link)
|
||||
.WithImageUrl(res.Link)
|
||||
.WithTitle(Context.User.ToString());
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn("Falling back to Imgur");
|
||||
terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
|
||||
|
||||
var fullQueryLink = $"http://imgur.com/search?q={ terms }";
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
|
||||
var elems = document.QuerySelectorAll("a.image-list-link").ToList();
|
||||
|
||||
if (!elems.Any())
|
||||
return;
|
||||
|
||||
var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement);
|
||||
|
||||
if (img?.Source == null)
|
||||
return;
|
||||
|
||||
var source = img.Source.Replace("b.", ".");
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
|
||||
.WithDescription(source)
|
||||
.WithImageUrl(source)
|
||||
.WithTitle(Context.User.ToString());
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Lmgtfy([Remainder] string ffs = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ffs))
|
||||
return;
|
||||
|
||||
await Context.Channel.SendConfirmAsync("<" + await _google.ShortenUrl($"http://lmgtfy.com/?q={ Uri.EscapeUriString(ffs) }") + ">")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Shorten([Remainder] string arg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
return;
|
||||
|
||||
var shortened = await _google.ShortenUrl(arg).ConfigureAwait(false);
|
||||
|
||||
if (shortened == arg)
|
||||
{
|
||||
await ReplyErrorLocalized("shorten_fail").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
.AddField(efb => efb.WithName(GetText("original_url"))
|
||||
.WithValue($"<{arg}>"))
|
||||
.AddField(efb => efb.WithName(GetText("short_url"))
|
||||
.WithValue($"<{shortened}>")))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//private readonly Regex googleSearchRegex = new Regex(@"<h3 class=""r""><a href=""(?:\/url?q=)?(?<link>.*?)"".*?>(?<title>.*?)<\/a>.*?class=""st"">(?<text>.*?)<\/span>", RegexOptions.Compiled);
|
||||
//private readonly Regex htmlReplace = new Regex(@"(?:<b>(.*?)<\/b>|<em>(.*?)<\/em>)", RegexOptions.Compiled);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Google([Remainder] string terms = null)
|
||||
{
|
||||
terms = terms?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(terms))
|
||||
return;
|
||||
|
||||
terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
|
||||
|
||||
var fullQueryLink = $"https://www.google.com/search?q={ terms }&gws_rd=cr,ssl";
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
|
||||
var elems = document.QuerySelectorAll("div.g");
|
||||
|
||||
var resultsElem = document.QuerySelectorAll("#resultStats").FirstOrDefault();
|
||||
var totalResults = resultsElem?.TextContent;
|
||||
//var time = resultsElem.Children.FirstOrDefault()?.TextContent
|
||||
//^ this doesn't work for some reason, <nobr> is completely missing in parsed collection
|
||||
if (!elems.Any())
|
||||
return;
|
||||
|
||||
var results = elems.Select<IElement, GoogleSearchResult?>(elem =>
|
||||
{
|
||||
var aTag = (elem.Children.FirstOrDefault()?.Children.FirstOrDefault() as IHtmlAnchorElement); // <h3> -> <a>
|
||||
var href = aTag?.Href;
|
||||
var name = aTag?.TextContent;
|
||||
if (href == null || name == null)
|
||||
return null;
|
||||
|
||||
var txt = elem.QuerySelectorAll(".st").FirstOrDefault()?.TextContent;
|
||||
|
||||
if (txt == null)
|
||||
return null;
|
||||
|
||||
return new GoogleSearchResult(name, href, txt);
|
||||
}).Where(x => x != null).Take(5);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName(GetText("search_for") + " " + terms.TrimTo(50))
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
|
||||
.WithTitle(Context.User.ToString())
|
||||
.WithFooter(efb => efb.WithText(totalResults));
|
||||
|
||||
var desc = await Task.WhenAll(results.Select(async res =>
|
||||
$"[{Format.Bold(res?.Title)}]({(await _google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n"))
|
||||
.ConfigureAwait(false);
|
||||
await Context.Channel.EmbedAsync(embed.WithDescription(string.Concat(desc))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task MagicTheGathering([Remainder] string name)
|
||||
{
|
||||
var arg = name;
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
return;
|
||||
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
var response = await http.GetStringAsync($"https://api.deckbrew.com/mtg/cards?name={Uri.EscapeUriString(arg)}")
|
||||
.ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var items = JArray.Parse(response).ToArray();
|
||||
if (items == null || items.Length == 0)
|
||||
throw new KeyNotFoundException("Cannot find a card by that name");
|
||||
var item = items[new NadekoRandom().Next(0, items.Length)];
|
||||
var storeUrl = await _google.ShortenUrl(item["store_url"].ToString());
|
||||
var cost = item["cost"].ToString();
|
||||
var desc = item["text"].ToString();
|
||||
var types = string.Join(",\n", item["types"].ToObject<string[]>());
|
||||
var img = item["editions"][0]["image_url"].ToString();
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(item["name"].ToString())
|
||||
.WithDescription(desc)
|
||||
.WithImageUrl(img)
|
||||
.AddField(efb => efb.WithName(GetText("store_url")).WithValue(storeUrl).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("cost")).WithValue(cost).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("types")).WithValue(types).WithIsInline(true));
|
||||
//.AddField(efb => efb.WithName("Store Url").WithValue(await _google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true));
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("card_not_found").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Hearthstone([Remainder] string name)
|
||||
{
|
||||
var arg = name;
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
|
||||
{
|
||||
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
|
||||
var response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}")
|
||||
.ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var items = JArray.Parse(response).Shuffle().ToList();
|
||||
var images = new List<Image<Rgba32>>();
|
||||
if (items == null)
|
||||
throw new KeyNotFoundException("Cannot find a card by that name");
|
||||
foreach (var item in items.Where(item => item.HasValues && item["img"] != null).Take(4))
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
using (var sr = await http.GetStreamAsync(item["img"].ToString()))
|
||||
{
|
||||
var imgStream = new MemoryStream();
|
||||
await sr.CopyToAsync(imgStream);
|
||||
imgStream.Position = 0;
|
||||
images.Add(ImageSharp.Image.Load(imgStream));
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
string msg = null;
|
||||
if (items.Count > 4)
|
||||
{
|
||||
msg = GetText("hs_over_x", 4);
|
||||
}
|
||||
var ms = new MemoryStream();
|
||||
await Task.Run(() => images.AsEnumerable().Merge().SaveAsPng(ms));
|
||||
ms.Position = 0;
|
||||
await Context.Channel.SendFileAsync(ms, arg + ".png", msg).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex);
|
||||
await ReplyErrorLocalized("error_occured").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Yodify([Remainder] string query = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
|
||||
{
|
||||
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
|
||||
http.DefaultRequestHeaders.Add("Accept", "text/plain");
|
||||
var res = await http.GetStringAsync($"https://yoda.p.mashape.com/yoda?sentence={Uri.EscapeUriString(query)}").ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithUrl("http://www.yodaspeak.co.uk/")
|
||||
.WithAuthor(au => au.WithName("Yoda").WithIconUrl("http://www.yodaspeak.co.uk/yoda-small1.gif"))
|
||||
.WithDescription(res)
|
||||
.WithOkColor();
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("yodify_error").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task UrbanDict([Remainder] string query = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
|
||||
{
|
||||
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
http.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
var res = await http.GetStringAsync($"http://api.urbandictionary.com/v0/define?term={Uri.EscapeUriString(query)}").ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var items = JObject.Parse(res);
|
||||
var item = items["list"][0];
|
||||
var word = item["word"].ToString();
|
||||
var def = item["definition"].ToString();
|
||||
var link = item["permalink"].ToString();
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithUrl(link)
|
||||
.WithAuthor(eab => eab.WithIconUrl("http://i.imgur.com/nwERwQE.jpg").WithName(word))
|
||||
.WithDescription(def);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("ud_error").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Define([Remainder] string word)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(word))
|
||||
return;
|
||||
|
||||
var res = await _service.Http.GetStringAsync("http://api.pearson.com/v2/dictionaries/entries?headword=" + WebUtility.UrlEncode(word.Trim())).ConfigureAwait(false);
|
||||
|
||||
var data = JsonConvert.DeserializeObject<DefineModel>(res);
|
||||
|
||||
var sense = data.Results.FirstOrDefault(x => x.Senses?[0].Definition != null)?.Senses[0];
|
||||
|
||||
if (sense?.Definition == null)
|
||||
{
|
||||
await ReplyErrorLocalized("define_unknown").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var definition = sense.Definition.ToString();
|
||||
if (!(sense.Definition is string))
|
||||
definition = ((JArray)JToken.Parse(sense.Definition.ToString())).First.ToString();
|
||||
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("define") + " " + word)
|
||||
.WithDescription(definition)
|
||||
.WithFooter(efb => efb.WithText(sense.Gramatical_info?.type));
|
||||
|
||||
if (sense.Examples != null)
|
||||
embed.AddField(efb => efb.WithName(GetText("example")).WithValue(sense.Examples.First().text));
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Hashtag([Remainder] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
|
||||
{
|
||||
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
string res;
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
|
||||
res = await http.GetStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(query)}.json").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var items = JObject.Parse(res);
|
||||
var item = items["defs"]["def"];
|
||||
//var hashtag = item["hashtag"].ToString();
|
||||
var link = item["uri"].ToString();
|
||||
var desc = item["text"].ToString();
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithUrl(link)
|
||||
.WithIconUrl("http://res.cloudinary.com/urbandictionary/image/upload/a_exif,c_fit,h_200,w_200/v1394975045/b8oszuu3tbq7ebyo7vo1.jpg")
|
||||
.WithName(query))
|
||||
.WithDescription(desc))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("hashtag_error").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Catfact()
|
||||
{
|
||||
var response = await _service.Http.GetStringAsync("https://catfact.ninja/fact").ConfigureAwait(false);
|
||||
if (response == null)
|
||||
return;
|
||||
|
||||
var fact = JObject.Parse(response)["fact"].ToString();
|
||||
await Context.Channel.SendConfirmAsync("🐈" + GetText("catfact"), fact).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Revav([Remainder] IGuildUser usr = null)
|
||||
{
|
||||
if (usr == null)
|
||||
usr = (IGuildUser)Context.User;
|
||||
await Context.Channel.SendConfirmAsync($"https://images.google.com/searchbyimage?image_url={usr.RealAvatarUrl()}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Revimg([Remainder] string imageLink = null)
|
||||
{
|
||||
imageLink = imageLink?.Trim() ?? "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(imageLink))
|
||||
return;
|
||||
await Context.Channel.SendConfirmAsync($"https://images.google.com/searchbyimage?image_url={imageLink}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public Task Safebooru([Remainder] string tag = null)
|
||||
=> InternalDapiCommand(Context.Message, tag, DapiSearchType.Safebooru);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Wiki([Remainder] string query = null)
|
||||
{
|
||||
query = query?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var result = await http.GetStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query));
|
||||
var data = JsonConvert.DeserializeObject<WikipediaApiModel>(result);
|
||||
if (data.Query.Pages[0].Missing)
|
||||
await ReplyErrorLocalized("wiki_page_not_found").ConfigureAwait(false);
|
||||
else
|
||||
await Context.Channel.SendMessageAsync(data.Query.Pages[0].FullUrl).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Color([Remainder] string color = null)
|
||||
{
|
||||
color = color?.Trim().Replace("#", "");
|
||||
if (string.IsNullOrWhiteSpace(color))
|
||||
return;
|
||||
Rgba32 clr;
|
||||
try
|
||||
{
|
||||
clr = Rgba32.FromHex(color);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("hex_invalid").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var img = new ImageSharp.Image<Rgba32>(50, 50);
|
||||
|
||||
img.BackgroundColor(clr);
|
||||
|
||||
await Context.Channel.SendFileAsync(img.ToStream(), $"{color}.png").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Videocall(params IGuildUser[] users)
|
||||
{
|
||||
var allUsrs = users.Append(Context.User);
|
||||
var allUsrsArray = allUsrs.ToArray();
|
||||
var str = allUsrsArray.Aggregate("http://appear.in/", (current, usr) => current + Uri.EscapeUriString(usr.Username[0].ToString()));
|
||||
str += new NadekoRandom().Next();
|
||||
foreach (var usr in allUsrsArray)
|
||||
{
|
||||
await (await usr.GetOrCreateDMChannelAsync()).SendConfirmAsync(str).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Avatar([Remainder] IGuildUser usr = null)
|
||||
{
|
||||
if (usr == null)
|
||||
usr = (IGuildUser)Context.User;
|
||||
|
||||
var avatarUrl = usr.RealAvatarUrl();
|
||||
var shortenedAvatarUrl = await _google.ShortenUrl(avatarUrl).ConfigureAwait(false);
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.AddField(efb => efb.WithName("Username").WithValue(usr.ToString()).WithIsInline(false))
|
||||
.AddField(efb => efb.WithName("Avatar Url").WithValue(shortenedAvatarUrl).WithIsInline(false))
|
||||
.WithThumbnailUrl(avatarUrl), Context.User.Mention).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Wikia(string target, [Remainder] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(target) || string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
await ReplyErrorLocalized("wikia_input_error").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
try
|
||||
{
|
||||
var res = await http.GetStringAsync($"http://www.{Uri.EscapeUriString(target)}.wikia.com/api/v1/Search/List?query={Uri.EscapeUriString(query)}&limit=25&minArticleQuality=10&batch=1&namespaces=0%2C14").ConfigureAwait(false);
|
||||
var items = JObject.Parse(res);
|
||||
var found = items["items"][0];
|
||||
var response = $@"`{GetText("title")}` {found["title"]}
|
||||
`{GetText("quality")}` {found["quality"]}
|
||||
`{GetText("url")}:` {await _google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}";
|
||||
await Context.Channel.SendMessageAsync(response).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("wikia_error").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
//public async Task MCPing([Remainder] string query2 = null)
|
||||
//{
|
||||
// var query = query2;
|
||||
// if (string.IsNullOrWhiteSpace(query))
|
||||
// return;
|
||||
// await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
// using (var http = new HttpClient())
|
||||
// {
|
||||
// http.DefaultRequestHeaders.Clear();
|
||||
// var ip = query.Split(':')[0];
|
||||
// var port = query.Split(':')[1];
|
||||
// var res = await http.GetStringAsync($"https://api.minetools.eu/ping/{Uri.EscapeUriString(ip)}/{Uri.EscapeUriString(port)}").ConfigureAwait(false);
|
||||
// try
|
||||
// {
|
||||
// var items = JObject.Parse(res);
|
||||
// var sb = new StringBuilder();
|
||||
// var ping = (int)Math.Ceiling(double.Parse(items["latency"].ToString()));
|
||||
// sb.AppendLine($"`Server:` {query}");
|
||||
// sb.AppendLine($"`Version:` {items["version"]["name"]} / Protocol {items["version"]["protocol"]}");
|
||||
// sb.AppendLine($"`Description:` {items["description"]}");
|
||||
// sb.AppendLine($"`Online Players:` {items["players"]["online"]}/{items["players"]["max"]}");
|
||||
// sb.Append($"`Latency:` {ping}");
|
||||
// await Context.Channel.SendMessageAsync(sb.ToString());
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// await Context.Channel.SendErrorAsync($"Failed finding `{query}`.").ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
//public async Task MCQ([Remainder] string query = null)
|
||||
//{
|
||||
// var arg = query;
|
||||
// if (string.IsNullOrWhiteSpace(arg))
|
||||
// {
|
||||
// await Context.Channel.SendErrorAsync("Please enter `ip:port`.").ConfigureAwait(false);
|
||||
// return;
|
||||
// }
|
||||
// await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
// using (var http = new HttpClient())
|
||||
// {
|
||||
// http.DefaultRequestHeaders.Clear();
|
||||
// try
|
||||
// {
|
||||
// var ip = arg.Split(':')[0];
|
||||
// var port = arg.Split(':')[1];
|
||||
// var res = await http.GetStringAsync($"https://api.minetools.eu/query/{Uri.EscapeUriString(ip)}/{Uri.EscapeUriString(port)}").ConfigureAwait(false);
|
||||
// var items = JObject.Parse(res);
|
||||
// var sb = new StringBuilder();
|
||||
// sb.AppendLine($"`Server:` {arg} 〘Status: {items["status"]}〙");
|
||||
// sb.AppendLine("`Player List (First 5):`");
|
||||
// foreach (var item in items["Playerlist"].Take(5))
|
||||
// {
|
||||
// sb.AppendLine($"〔:rosette: {item}〕");
|
||||
// }
|
||||
// sb.AppendLine($"`Online Players:` {items["Players"]} / {items["MaxPlayers"]}");
|
||||
// sb.AppendLine($"`Plugins:` {items["Plugins"]}");
|
||||
// sb.Append($"`Version:` {items["Version"]}");
|
||||
// await Context.Channel.SendMessageAsync(sb.ToString());
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// await Context.Channel.SendErrorAsync($"Failed finding server `{arg}`.").ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
public async Task InternalDapiCommand(IUserMessage umsg, string tag, DapiSearchType type)
|
||||
{
|
||||
var channel = umsg.Channel;
|
||||
|
||||
tag = tag?.Trim() ?? "";
|
||||
|
||||
var imgObj = await _service.DapiSearch(tag, type, Context.Guild?.Id).ConfigureAwait(false);
|
||||
|
||||
if (imgObj == null)
|
||||
await channel.SendErrorAsync(umsg.Author.Mention + " " + GetText("no_results"));
|
||||
else
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithDescription($"{umsg.Author.Mention} [{tag ?? "url"}]({imgObj.FileUrl})")
|
||||
.WithImageUrl(imgObj.FileUrl)
|
||||
.WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateQuery(IMessageChannel ch, string query)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(query)) return true;
|
||||
await ch.SendErrorAsync(GetText("specify_search_params")).ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
214
NadekoBot.Core/Modules/Searches/Services/FeedsService.cs
Normal file
214
NadekoBot.Core/Modules/Searches/Services/FeedsService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
251
NadekoBot.Core/Modules/Searches/Services/SearchesService.cs
Normal file
251
NadekoBot.Core/Modules/Searches/Services/SearchesService.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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 "??";
|
||||
}
|
||||
}
|
||||
}
|
180
NadekoBot.Core/Modules/Searches/StreamNotificationCommands.cs
Normal file
180
NadekoBot.Core/Modules/Searches/StreamNotificationCommands.cs
Normal file
@ -0,0 +1,180 @@
|
||||
using Discord.Commands;
|
||||
using Discord;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Core.Services;
|
||||
using System.Collections.Generic;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class StreamNotificationCommands : NadekoSubmodule<StreamNotificationService>
|
||||
{
|
||||
private readonly DbService _db;
|
||||
|
||||
public StreamNotificationCommands(DbService db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task Smashcast([Remainder] string username) =>
|
||||
await TrackStream((ITextChannel)Context.Channel, username, FollowedStream.FollowedStreamType.Smashcast)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task Twitch([Remainder] string username) =>
|
||||
await TrackStream((ITextChannel)Context.Channel, username, FollowedStream.FollowedStreamType.Twitch)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task Mixer([Remainder] string username) =>
|
||||
await TrackStream((ITextChannel)Context.Channel, username, FollowedStream.FollowedStreamType.Mixer)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ListStreams()
|
||||
{
|
||||
IEnumerable<FollowedStream> streams;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
streams = uow.GuildConfigs
|
||||
.For(Context.Guild.Id,
|
||||
set => set.Include(gc => gc.FollowedStreams))
|
||||
.FollowedStreams;
|
||||
}
|
||||
|
||||
if (!streams.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("streams_none").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var text = string.Join("\n", await Task.WhenAll(streams.Select(async snc =>
|
||||
{
|
||||
var ch = await Context.Guild.GetTextChannelAsync(snc.ChannelId);
|
||||
return string.Format("{0}'s stream on {1} channel. 【{2}】",
|
||||
Format.Code(snc.Username),
|
||||
Format.Bold(ch?.Name ?? "deleted-channel"),
|
||||
Format.Code(snc.Type.ToString()));
|
||||
})));
|
||||
|
||||
await Context.Channel.SendConfirmAsync(GetText("streams_following", streams.Count()) + "\n\n" + text)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task RemoveStream(FollowedStream.FollowedStreamType type, [Remainder] string username)
|
||||
{
|
||||
username = username.ToLowerInvariant().Trim();
|
||||
|
||||
var fs = new FollowedStream()
|
||||
{
|
||||
ChannelId = Context.Channel.Id,
|
||||
Username = username,
|
||||
Type = type
|
||||
};
|
||||
|
||||
bool removed;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(gc => gc.FollowedStreams));
|
||||
removed = config.FollowedStreams.Remove(fs);
|
||||
if (removed)
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
if (!removed)
|
||||
{
|
||||
await ReplyErrorLocalized("stream_no").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalized("stream_removed",
|
||||
Format.Code(username),
|
||||
type).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task CheckStream(FollowedStream.FollowedStreamType platform, [Remainder] string username)
|
||||
{
|
||||
var stream = username?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(stream))
|
||||
return;
|
||||
try
|
||||
{
|
||||
var streamStatus = (await _service.GetStreamStatus(new FollowedStream
|
||||
{
|
||||
Username = stream,
|
||||
Type = platform,
|
||||
}));
|
||||
if (streamStatus.Live)
|
||||
{
|
||||
await ReplyConfirmLocalized("streamer_online",
|
||||
username,
|
||||
streamStatus.Viewers)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalized("streamer_offline",
|
||||
username).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("no_channel_found").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TrackStream(ITextChannel channel, string username, FollowedStream.FollowedStreamType type)
|
||||
{
|
||||
username = username.ToLowerInvariant().Trim();
|
||||
var fs = new FollowedStream
|
||||
{
|
||||
GuildId = channel.Guild.Id,
|
||||
ChannelId = channel.Id,
|
||||
Username = username,
|
||||
Type = type,
|
||||
};
|
||||
|
||||
IStreamResponse status;
|
||||
try
|
||||
{
|
||||
status = await _service.GetStreamStatus(fs).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("stream_not_exist").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FollowedStreams))
|
||||
.FollowedStreams
|
||||
.Add(fs);
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
await channel.EmbedAsync(_service.GetEmbed(fs, status, Context.Guild.Id), GetText("stream_tracked")).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
137
NadekoBot.Core/Modules/Searches/TranslatorCommands.cs
Normal file
137
NadekoBot.Core/Modules/Searches/TranslatorCommands.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class TranslateCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly SearchesService _searches;
|
||||
private readonly IGoogleApiService _google;
|
||||
|
||||
public TranslateCommands(SearchesService searches, IGoogleApiService google)
|
||||
{
|
||||
_searches = searches;
|
||||
_google = google;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Translate(string langs, [Remainder] string text = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var translation = await _searches.Translate(langs, text);
|
||||
await Context.Channel.SendConfirmAsync(GetText("translation") + " " + langs, translation).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalized("bad_input_format").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
//[OwnerOnly]
|
||||
//public async Task Obfuscate([Remainder] string txt)
|
||||
//{
|
||||
// var lastItem = "en";
|
||||
// foreach (var item in _google.Languages.Except(new[] { "en" }).Where(x => x.Length < 4))
|
||||
// {
|
||||
// var txt2 = await _searches.Translate(lastItem + ">" + item, txt);
|
||||
// await Context.Channel.EmbedAsync(new EmbedBuilder()
|
||||
// .WithOkColor()
|
||||
// .WithTitle(lastItem + ">" + item)
|
||||
// .AddField("Input", txt)
|
||||
// .AddField("Output", txt2));
|
||||
// txt = txt2;
|
||||
// await Task.Delay(500);
|
||||
// lastItem = item;
|
||||
// }
|
||||
// txt = await _searches.Translate(lastItem + ">en", txt);
|
||||
// await Context.Channel.SendConfirmAsync("Final output:\n\n" + txt);
|
||||
//}
|
||||
|
||||
public enum AutoDeleteAutoTranslate
|
||||
{
|
||||
Del,
|
||||
Nodel
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
[OwnerOnly]
|
||||
public async Task AutoTranslate(AutoDeleteAutoTranslate autoDelete = AutoDeleteAutoTranslate.Nodel)
|
||||
{
|
||||
var channel = (ITextChannel)Context.Channel;
|
||||
|
||||
if (autoDelete == AutoDeleteAutoTranslate.Del)
|
||||
{
|
||||
_searches.TranslatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true);
|
||||
await ReplyConfirmLocalized("atl_ad_started").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_searches.TranslatedChannels.TryRemove(channel.Id, out _))
|
||||
{
|
||||
await ReplyConfirmLocalized("atl_stopped").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
if (_searches.TranslatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
|
||||
{
|
||||
await ReplyConfirmLocalized("atl_started").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AutoTransLang([Remainder] string langs = null)
|
||||
{
|
||||
var ucp = new UserChannelPair
|
||||
{
|
||||
UserId = Context.User.Id,
|
||||
ChannelId = Context.Channel.Id,
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(langs))
|
||||
{
|
||||
if (_searches.UserLanguages.TryRemove(ucp, out langs))
|
||||
await ReplyConfirmLocalized("atl_removed").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var langarr = langs.ToLowerInvariant().Split('>');
|
||||
if (langarr.Length != 2)
|
||||
return;
|
||||
var from = langarr[0];
|
||||
var to = langarr[1];
|
||||
|
||||
if (!_google.Languages.Contains(from) || !_google.Languages.Contains(to))
|
||||
{
|
||||
await ReplyErrorLocalized("invalid_lang").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_searches.UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
|
||||
|
||||
await ReplyConfirmLocalized("atl_set", from, to).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Translangs()
|
||||
{
|
||||
await Context.Channel.SendTableAsync(_google.Languages, str => $"{str,-15}", 3);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
85
NadekoBot.Core/Modules/Searches/XkcdCommands.cs
Normal file
85
NadekoBot.Core/Modules/Searches/XkcdCommands.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public class XkcdCommands : NadekoSubmodule
|
||||
{
|
||||
private const string _xkcdUrl = "https://xkcd.com";
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Xkcd(string arg = null)
|
||||
{
|
||||
if (arg?.ToLowerInvariant().Trim() == "latest")
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var res = await http.GetStringAsync($"{_xkcdUrl}/info.0.json").ConfigureAwait(false);
|
||||
var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
|
||||
var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
.WithImageUrl(comic.ImageLink)
|
||||
.WithAuthor(eab => eab.WithName(comic.Title).WithUrl($"{_xkcdUrl}/{comic.Num}").WithIconUrl("http://xkcd.com/s/919f27.ico"))
|
||||
.AddField(efb => efb.WithName(GetText("comic_number")).WithValue(comic.Num.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("date")).WithValue($"{comic.Month}/{comic.Year}").WithIsInline(true));
|
||||
var sent = await Context.Channel.EmbedAsync(embed)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
|
||||
await sent.ModifyAsync(m => m.Embed = embed.AddField(efb => efb.WithName("Alt").WithValue(comic.Alt.ToString()).WithIsInline(false)).Build());
|
||||
}
|
||||
return;
|
||||
}
|
||||
await Xkcd(new NadekoRandom().Next(1, 1750)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Xkcd(int num)
|
||||
{
|
||||
if (num < 1)
|
||||
return;
|
||||
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var res = await http.GetStringAsync($"{_xkcdUrl}/{num}/info.0.json").ConfigureAwait(false);
|
||||
|
||||
var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
|
||||
var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
.WithImageUrl(comic.ImageLink)
|
||||
.WithAuthor(eab => eab.WithName(comic.Title).WithUrl($"{_xkcdUrl}/{num}").WithIconUrl("http://xkcd.com/s/919f27.ico"))
|
||||
.AddField(efb => efb.WithName(GetText("comic_number")).WithValue(comic.Num.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("date")).WithValue($"{comic.Month}/{comic.Year}").WithIsInline(true));
|
||||
var sent = await Context.Channel.EmbedAsync(embed)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
|
||||
await sent.ModifyAsync(m => m.Embed = embed.AddField(efb => efb.WithName("Alt").WithValue(comic.Alt.ToString()).WithIsInline(false)).Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class XkcdComic
|
||||
{
|
||||
public int Num { get; set; }
|
||||
public string Month { get; set; }
|
||||
public string Year { get; set; }
|
||||
[JsonProperty("safe_title")]
|
||||
public string Title { get; set; }
|
||||
[JsonProperty("img")]
|
||||
public string ImageLink { get; set; }
|
||||
public string Alt { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user