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