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

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

View File

@ -0,0 +1,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);
}
}
}
}

View 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) + "...";
}
}

View 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; }
}
}

View File

@ -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.")
{
}
}
}

View 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;
}
}
}

View File

@ -0,0 +1,8 @@
namespace NadekoBot.Modules.Searches.Common
{
public class MagicItem
{
public string Name { get; set; }
public string Description { get; set; }
}
}

View 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) + "...";
}
}

View 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}";
}
}

View 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
{
}
}
}

View 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
}
}

View 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}";
}
}

View 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;
}
}

View 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;
}
}

View 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; }
}
}

View 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; }
}
}
}
}

View 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}**";
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace NadekoBot.Modules.Searches.Exceptions
{
public class TagBlacklistedException : Exception
{
public TagBlacklistedException() : base("Tag you used is blacklisted.")
{
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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}&timestamp={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;
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,251 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using NadekoBot.Modules.Searches.Common;
using NadekoBot.Core.Services.Database.Models;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using AngleSharp;
using System.Threading;
using NadekoBot.Modules.Searches.Exceptions;
namespace NadekoBot.Modules.Searches.Services
{
public class SearchesService : INService, IUnloadableService
{
public HttpClient Http { get; }
private readonly DiscordSocketClient _client;
private readonly IGoogleApiService _google;
private readonly DbService _db;
private readonly Logger _log;
public ConcurrentDictionary<ulong, bool> TranslatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
public ConcurrentDictionary<UserChannelPair, string> UserLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>();
public readonly string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json";
public readonly string PokemonListFile = "data/pokemon/pokemon_list7.json";
public Dictionary<string, SearchPokemon> Pokemons { get; } = new Dictionary<string, SearchPokemon>();
public Dictionary<string, SearchPokemonAbility> PokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
public List<WoWJoke> WowJokes { get; } = new List<WoWJoke>();
public List<MagicItem> MagicItems { get; } = new List<MagicItem>();
private readonly ConcurrentDictionary<ulong, SearchImageCacher> _imageCacher = new ConcurrentDictionary<ulong, SearchImageCacher>();
public ConcurrentDictionary<ulong, Timer> AutoHentaiTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
public ConcurrentDictionary<ulong, Timer> AutoBoobTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
public ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
private readonly ConcurrentDictionary<ulong, HashSet<string>> _blacklistedTags = new ConcurrentDictionary<ulong, HashSet<string>>();
public SearchesService(DiscordSocketClient client, IGoogleApiService google,
DbService db, NadekoBot bot)
{
Http = new HttpClient();
Http.AddFakeHeaders();
_client = client;
_google = google;
_db = db;
_log = LogManager.GetCurrentClassLogger();
_blacklistedTags = new ConcurrentDictionary<ulong, HashSet<string>>(
bot.AllGuildConfigs.ToDictionary(
x => x.GuildId,
x => new HashSet<string>(x.NsfwBlacklistedTags.Select(y => y.Tag))));
//translate commands
_client.MessageReceived += (msg) =>
{
var _ = Task.Run(async () =>
{
try
{
var umsg = msg as SocketUserMessage;
if (umsg == null)
return;
if (!TranslatedChannels.TryGetValue(umsg.Channel.Id, out var autoDelete))
return;
var key = new UserChannelPair()
{
UserId = umsg.Author.Id,
ChannelId = umsg.Channel.Id,
};
if (!UserLanguages.TryGetValue(key, out string langs))
return;
var text = await Translate(langs, umsg.Resolve(TagHandling.Ignore))
.ConfigureAwait(false);
if (autoDelete)
try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
await umsg.Channel.SendConfirmAsync($"{umsg.Author.Mention} `:` " + text.Replace("<@ ", "<@").Replace("<@! ", "<@!")).ConfigureAwait(false);
}
catch { }
});
return Task.CompletedTask;
};
//pokemon commands
if (File.Exists(PokemonListFile))
{
Pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(PokemonListFile));
}
else
_log.Warn(PokemonListFile + " is missing. Pokemon abilities not loaded.");
if (File.Exists(PokemonAbilitiesFile))
PokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(PokemonAbilitiesFile));
else
_log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded.");
//joke commands
if (File.Exists("data/wowjokes.json"))
{
WowJokes = JsonConvert.DeserializeObject<List<WoWJoke>>(File.ReadAllText("data/wowjokes.json"));
}
else
_log.Warn("data/wowjokes.json is missing. WOW Jokes are not loaded.");
if (File.Exists("data/magicitems.json"))
{
MagicItems = JsonConvert.DeserializeObject<List<MagicItem>>(File.ReadAllText("data/magicitems.json"));
}
else
_log.Warn("data/magicitems.json is missing. Magic items are not loaded.");
}
public async Task<string> Translate(string langs, string text = null)
{
var langarr = langs.ToLowerInvariant().Split('>');
if (langarr.Length != 2)
throw new ArgumentException();
var from = langarr[0];
var to = langarr[1];
text = text?.Trim();
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException();
return (await _google.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions();
}
public Task<ImageCacherObject> DapiSearch(string tag, DapiSearchType type, ulong? guild, bool isExplicit = false)
{
if (guild.HasValue)
{
var blacklistedTags = GetBlacklistedTags(guild.Value);
if (blacklistedTags
.Any(x => tag.ToLowerInvariant().Contains(x)))
{
throw new TagBlacklistedException();
}
var cacher = _imageCacher.GetOrAdd(guild.Value, (key) => new SearchImageCacher());
return cacher.GetImage(tag, isExplicit, type, blacklistedTags);
}
else
{
var cacher = _imageCacher.GetOrAdd(guild ?? 0, (key) => new SearchImageCacher());
return cacher.GetImage(tag, isExplicit, type);
}
}
public HashSet<string> GetBlacklistedTags(ulong guildId)
{
if (_blacklistedTags.TryGetValue(guildId, out var tags))
return tags;
return new HashSet<string>();
}
public bool ToggleBlacklistedTag(ulong guildId, string tag)
{
var tagObj = new NsfwBlacklitedTag
{
Tag = tag
};
bool added;
using (var uow = _db.UnitOfWork)
{
var gc = uow.GuildConfigs.For(guildId, set => set.Include(y => y.NsfwBlacklistedTags));
if (gc.NsfwBlacklistedTags.Add(tagObj))
added = true;
else
{
gc.NsfwBlacklistedTags.Remove(tagObj);
added = false;
}
var newTags = new HashSet<string>(gc.NsfwBlacklistedTags.Select(x => x.Tag));
_blacklistedTags.AddOrUpdate(guildId, newTags, delegate { return newTags; });
uow.Complete();
}
return added;
}
public void ClearCache()
{
foreach (var c in _imageCacher)
{
c.Value?.Clear();
}
}
public async Task<string> GetYomamaJoke()
{
var response = await Http.GetStringAsync("http://api.yomomma.info/").ConfigureAwait(false);
return JObject.Parse(response)["joke"].ToString() + " 😆";
}
public async Task<(string Text, string BaseUri)> GetRandomJoke()
{
var config = Configuration.Default.WithDefaultLoader();
var document = await BrowsingContext.New(config).OpenAsync("http://www.goodbadjokes.com/random");
var html = document.QuerySelector(".post > .joke-content");
var part1 = html.QuerySelector("dt").TextContent;
var part2 = html.QuerySelector("dd").TextContent;
return (part1 + "\n\n" + part2, document.BaseUri);
}
public async Task<string> GetChuckNorrisJoke()
{
var response = await Http.GetStringAsync("http://api.icndb.com/jokes/random/").ConfigureAwait(false);
return JObject.Parse(response)["value"]["joke"].ToString() + " 😆";
}
public Task Unload()
{
AutoBoobTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
AutoBoobTimers.Clear();
AutoButtTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
AutoButtTimers.Clear();
AutoHentaiTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
AutoHentaiTimers.Clear();
_imageCacher.Clear();
return Task.CompletedTask;
}
}
public struct UserChannelPair
{
public ulong UserId { get; set; }
public ulong ChannelId { get; set; }
}
}

View File

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

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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; }
}
}
}