mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Global usings and file scoped namespaces
This commit is contained in:
@@ -3,195 +3,192 @@ 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;
|
||||
using AngleSharp.Html.Dom;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class AnimeSearchCommands : NadekoSubmodule<AnimeSearchService>
|
||||
{
|
||||
[Group]
|
||||
public class AnimeSearchCommands : NadekoSubmodule<AnimeSearchService>
|
||||
// [NadekoCommand, Aliases]
|
||||
// public async Task Novel([Leftover] string query)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(query))
|
||||
// return;
|
||||
//
|
||||
// var novelData = await _service.GetNovelData(query).ConfigureAwait(false);
|
||||
//
|
||||
// if (novelData is null)
|
||||
// {
|
||||
// await ReplyErrorLocalizedAsync(strs.failed_finding_novel).ConfigureAwait(false);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var embed = _eb.Create()
|
||||
// .WithOkColor()
|
||||
// .WithDescription(novelData.Description.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
// .WithTitle(novelData.Title)
|
||||
// .WithUrl(novelData.Link)
|
||||
// .WithImageUrl(novelData.ImageUrl)
|
||||
// .AddField(GetText(strs.authors), string.Join("\n", novelData.Authors), true)
|
||||
// .AddField(GetText(strs.status), novelData.Status, true)
|
||||
// .AddField(GetText(strs.genres), string.Join(" ", novelData.Genres.Any() ? novelData.Genres : new[] { "none" }), true)
|
||||
// .WithFooter($"{GetText(strs.score)} {novelData.Score}");
|
||||
//
|
||||
// await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
// }
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Mal([Leftover] string name)
|
||||
{
|
||||
// [NadekoCommand, Aliases]
|
||||
// public async Task Novel([Leftover] string query)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(query))
|
||||
// return;
|
||||
//
|
||||
// var novelData = await _service.GetNovelData(query).ConfigureAwait(false);
|
||||
//
|
||||
// if (novelData is null)
|
||||
// {
|
||||
// await ReplyErrorLocalizedAsync(strs.failed_finding_novel).ConfigureAwait(false);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var embed = _eb.Create()
|
||||
// .WithOkColor()
|
||||
// .WithDescription(novelData.Description.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
// .WithTitle(novelData.Title)
|
||||
// .WithUrl(novelData.Link)
|
||||
// .WithImageUrl(novelData.ImageUrl)
|
||||
// .AddField(GetText(strs.authors), string.Join("\n", novelData.Authors), true)
|
||||
// .AddField(GetText(strs.status), novelData.Status, true)
|
||||
// .AddField(GetText(strs.genres), string.Join(" ", novelData.Genres.Any() ? novelData.Genres : new[] { "none" }), true)
|
||||
// .WithFooter($"{GetText(strs.score)} {novelData.Score}");
|
||||
//
|
||||
// await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
// }
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Mal([Leftover] string name)
|
||||
var fullQueryLink = "https://myanimelist.net/profile/" + name;
|
||||
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using (var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink).ConfigureAwait(false))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
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 fullQueryLink = "https://myanimelist.net/profile/" + name;
|
||||
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 config = Configuration.Default.WithDefaultLoader();
|
||||
using (var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink).ConfigureAwait(false))
|
||||
{
|
||||
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 favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
|
||||
|
||||
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 favAnime = GetText(strs.anime_no_fav);
|
||||
if (favorites.Length > 0 && favorites[0].QuerySelector("p") is 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 favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
|
||||
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 favAnime = GetText(strs.anime_no_fav);
|
||||
if (favorites.Length > 0 && favorites[0].QuerySelector("p") is 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 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 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 embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.mal_profile(name)))
|
||||
.AddField("💚 " + GetText(strs.watching), stats[0], true)
|
||||
.AddField("💙 " + GetText(strs.completed), stats[1], true);
|
||||
if (info.Count < 3)
|
||||
embed.AddField("💛 " + GetText(strs.on_hold), stats[2], true);
|
||||
embed
|
||||
.AddField("💔 " + GetText(strs.dropped), stats[3], true)
|
||||
.AddField("⚪ " + GetText(strs.plan_to_watch), stats[4], true)
|
||||
.AddField("🕐 " + daysAndMean[0][0], daysAndMean[0][1], true)
|
||||
.AddField("📊 " + daysAndMean[1][0], daysAndMean[1][1], true)
|
||||
.AddField(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1, info[0].Item2.TrimTo(20), true)
|
||||
.AddField(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1, info[1].Item2.TrimTo(20), true);
|
||||
if (info.Count > 2)
|
||||
embed.AddField(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1, info[2].Item2.TrimTo(20), true);
|
||||
|
||||
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 = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.mal_profile(name)))
|
||||
.AddField("💚 " + GetText(strs.watching), stats[0], true)
|
||||
.AddField("💙 " + GetText(strs.completed), stats[1], true);
|
||||
if (info.Count < 3)
|
||||
embed.AddField("💛 " + GetText(strs.on_hold), stats[2], true);
|
||||
embed
|
||||
.AddField("💔 " + GetText(strs.dropped), stats[3], true)
|
||||
.AddField("⚪ " + GetText(strs.plan_to_watch), stats[4], true)
|
||||
.AddField("🕐 " + daysAndMean[0][0], daysAndMean[0][1], true)
|
||||
.AddField("📊 " + daysAndMean[1][0], daysAndMean[1][1], true)
|
||||
.AddField(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1, info[0].Item2.TrimTo(20), true)
|
||||
.AddField(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1, info[1].Item2.TrimTo(20), true);
|
||||
if (info.Count > 2)
|
||||
embed.AddField(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1, info[2].Item2.TrimTo(20), true);
|
||||
|
||||
embed
|
||||
.WithDescription($@"
|
||||
embed
|
||||
.WithDescription($@"
|
||||
** https://myanimelist.net/animelist/{ name } **
|
||||
|
||||
**{GetText(strs.top_3_fav_anime)}**
|
||||
{favAnime}"
|
||||
|
||||
)
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithImageUrl(imageUrl);
|
||||
|
||||
await ctx.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, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task Mal(IGuildUser usr) => Mal(usr.Username);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Anime([Leftover] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var animeData = await _service.GetAnimeData(query).ConfigureAwait(false);
|
||||
|
||||
if (animeData is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.failed_finding_anime).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(animeData.Synopsis.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
.WithTitle(animeData.TitleEnglish)
|
||||
.WithUrl(animeData.Link)
|
||||
.WithImageUrl(animeData.ImageUrlLarge)
|
||||
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
||||
.AddField(GetText(strs.status), animeData.AiringStatus.ToString(), true)
|
||||
.AddField(GetText(strs.genres), string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : new[] { "none" }), true)
|
||||
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Manga([Leftover] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var mangaData = await _service.GetMangaData(query).ConfigureAwait(false);
|
||||
|
||||
if (mangaData is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.failed_finding_manga).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(mangaData.Synopsis.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
.WithTitle(mangaData.TitleEnglish)
|
||||
.WithUrl(mangaData.Link)
|
||||
.WithImageUrl(mangaData.ImageUrlLge)
|
||||
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
||||
.AddField(GetText(strs.status), mangaData.PublishingStatus.ToString(), true)
|
||||
.AddField(GetText(strs.genres), string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : new[] { "none" }), true)
|
||||
.WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100");
|
||||
)
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithImageUrl(imageUrl);
|
||||
|
||||
await ctx.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, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task Mal(IGuildUser usr) => Mal(usr.Username);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Anime([Leftover] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var animeData = await _service.GetAnimeData(query).ConfigureAwait(false);
|
||||
|
||||
if (animeData is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.failed_finding_anime).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(animeData.Synopsis.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
.WithTitle(animeData.TitleEnglish)
|
||||
.WithUrl(animeData.Link)
|
||||
.WithImageUrl(animeData.ImageUrlLarge)
|
||||
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
||||
.AddField(GetText(strs.status), animeData.AiringStatus.ToString(), true)
|
||||
.AddField(GetText(strs.genres), string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : new[] { "none" }), true)
|
||||
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Manga([Leftover] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var mangaData = await _service.GetMangaData(query).ConfigureAwait(false);
|
||||
|
||||
if (mangaData is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.failed_finding_manga).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(mangaData.Synopsis.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
||||
.WithTitle(mangaData.TitleEnglish)
|
||||
.WithUrl(mangaData.Link)
|
||||
.WithImageUrl(mangaData.ImageUrlLge)
|
||||
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
||||
.AddField(GetText(strs.status), mangaData.PublishingStatus.ToString(), true)
|
||||
.AddField(GetText(strs.genres), string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : new[] { "none" }), true)
|
||||
.WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100");
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,26 +1,25 @@
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class AnimeResult
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string AiringStatus => AiringStatusParsed.ToTitleCase();
|
||||
[JsonProperty("airing_status")]
|
||||
public string AiringStatusParsed { get; set; }
|
||||
[JsonProperty("title_english")]
|
||||
public string TitleEnglish { get; set; }
|
||||
[JsonProperty("total_episodes")]
|
||||
public int TotalEpisodes { get; set; }
|
||||
public string Description { get; set; }
|
||||
[JsonProperty("image_url_lge")]
|
||||
public string ImageUrlLarge { get; set; }
|
||||
public string[] Genres { get; set; }
|
||||
[JsonProperty("average_score")]
|
||||
public string AverageScore { get; set; }
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public string Link => "http://anilist.co/anime/" + Id;
|
||||
public string Synopsis => Description?.Substring(0, Description.Length > 500 ? 500 : Description.Length) + "...";
|
||||
}
|
||||
public class AnimeResult
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string AiringStatus => AiringStatusParsed.ToTitleCase();
|
||||
[JsonProperty("airing_status")]
|
||||
public string AiringStatusParsed { get; set; }
|
||||
[JsonProperty("title_english")]
|
||||
public string TitleEnglish { get; set; }
|
||||
[JsonProperty("total_episodes")]
|
||||
public int TotalEpisodes { get; set; }
|
||||
public string Description { get; set; }
|
||||
[JsonProperty("image_url_lge")]
|
||||
public string ImageUrlLarge { get; set; }
|
||||
public string[] Genres { get; set; }
|
||||
[JsonProperty("average_score")]
|
||||
public string AverageScore { get; set; }
|
||||
|
||||
public string Link => "http://anilist.co/anime/" + Id;
|
||||
public string Synopsis => Description?.Substring(0, Description.Length > 500 ? 500 : Description.Length) + "...";
|
||||
}
|
@@ -1,19 +1,18 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class BibleVerses
|
||||
{
|
||||
public string Error { get; set; }
|
||||
public BibleVerse[] Verses { get; set; }
|
||||
}
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class BibleVerse
|
||||
{
|
||||
[JsonProperty("book_name")]
|
||||
public string BookName { get; set; }
|
||||
public int Chapter { get; set; }
|
||||
public int Verse { get; set; }
|
||||
public string Text { get; set; }
|
||||
}
|
||||
public class BibleVerses
|
||||
{
|
||||
public string Error { get; set; }
|
||||
public BibleVerse[] Verses { get; set; }
|
||||
}
|
||||
|
||||
public class BibleVerse
|
||||
{
|
||||
[JsonProperty("book_name")]
|
||||
public string BookName { get; set; }
|
||||
public int Chapter { get; set; }
|
||||
public int Verse { get; set; }
|
||||
public string Text { get; set; }
|
||||
}
|
@@ -1,37 +1,35 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class CryptoResponse
|
||||
{
|
||||
public class CryptoResponse
|
||||
{
|
||||
public List<CryptoResponseData> Data { get; set; }
|
||||
}
|
||||
|
||||
public class CryptoResponseData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Symbol { get; set; }
|
||||
public string Slug { get; set; }
|
||||
|
||||
[JsonProperty("cmc_rank")]
|
||||
public int Rank { get; set; }
|
||||
public CurrencyQuotes Quote { get; set; }
|
||||
}
|
||||
|
||||
public class CurrencyQuotes
|
||||
{
|
||||
public Quote Usd { get; set; }
|
||||
}
|
||||
|
||||
public class Quote
|
||||
{
|
||||
public double Price { get; set; }
|
||||
public double Market_Cap { get; set; }
|
||||
public string Percent_Change_1h { get; set; }
|
||||
public string Percent_Change_24h { get; set; }
|
||||
public string Percent_Change_7d { get; set; }
|
||||
public double? Volume_24h { get; set; }
|
||||
}
|
||||
public List<CryptoResponseData> Data { get; set; }
|
||||
}
|
||||
|
||||
public class CryptoResponseData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Symbol { get; set; }
|
||||
public string Slug { get; set; }
|
||||
|
||||
[JsonProperty("cmc_rank")]
|
||||
public int Rank { get; set; }
|
||||
public CurrencyQuotes Quote { get; set; }
|
||||
}
|
||||
|
||||
public class CurrencyQuotes
|
||||
{
|
||||
public Quote Usd { get; set; }
|
||||
}
|
||||
|
||||
public class Quote
|
||||
{
|
||||
public double Price { get; set; }
|
||||
public double Market_Cap { get; set; }
|
||||
public string Percent_Change_1h { get; set; }
|
||||
public string Percent_Change_24h { get; set; }
|
||||
public string Percent_Change_7d { get; set; }
|
||||
public double? Volume_24h { get; set; }
|
||||
}
|
@@ -1,42 +1,40 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class Audio
|
||||
{
|
||||
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; }
|
||||
[JsonProperty("gramatical_info")]
|
||||
public GramaticalInfo GramaticalInfo { get; set; }
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
[JsonProperty("part_of_speech")]
|
||||
public string PartOfSpeech { get; set; }
|
||||
public List<Sens> Senses { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
public class DefineModel
|
||||
{
|
||||
public List<Result> Results { get; set; }
|
||||
}
|
||||
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; }
|
||||
[JsonProperty("gramatical_info")]
|
||||
public GramaticalInfo GramaticalInfo { get; set; }
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
[JsonProperty("part_of_speech")]
|
||||
public string PartOfSpeech { get; set; }
|
||||
public List<Sens> Senses { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
public class DefineModel
|
||||
{
|
||||
public List<Result> Results { get; set; }
|
||||
}
|
@@ -1,24 +1,23 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class E621Object
|
||||
{
|
||||
public class E621Object
|
||||
public class FileData
|
||||
{
|
||||
public class FileData
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
public class TagData
|
||||
{
|
||||
public string[] General { get; set; }
|
||||
}
|
||||
|
||||
public class ScoreData
|
||||
{
|
||||
public string Total { get; set; }
|
||||
}
|
||||
|
||||
public FileData File { get; set; }
|
||||
public TagData Tags { get; set; }
|
||||
public ScoreData Score { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class TagData
|
||||
{
|
||||
public string[] General { get; set; }
|
||||
}
|
||||
|
||||
public class ScoreData
|
||||
{
|
||||
public string Total { get; set; }
|
||||
}
|
||||
|
||||
public FileData File { get; set; }
|
||||
public TagData Tags { get; set; }
|
||||
public ScoreData Score { get; set; }
|
||||
}
|
@@ -1,19 +1,16 @@
|
||||
using System;
|
||||
namespace NadekoBot.Modules.Searches.Common.Exceptions;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common.Exceptions
|
||||
public class StreamNotFoundException : Exception
|
||||
{
|
||||
public class StreamNotFoundException : Exception
|
||||
public StreamNotFoundException()
|
||||
{
|
||||
public StreamNotFoundException()
|
||||
{
|
||||
}
|
||||
|
||||
public StreamNotFoundException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public StreamNotFoundException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public StreamNotFoundException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public StreamNotFoundException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,46 +1,43 @@
|
||||
using System;
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
public sealed class Tag
|
||||
{
|
||||
public sealed class Tag
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
public sealed class Gallery
|
||||
{
|
||||
public string Id { get; }
|
||||
public string Url { get; }
|
||||
public string FullTitle { get; }
|
||||
public string Title { get; }
|
||||
public string Thumbnail { get; }
|
||||
public int PageCount { get; }
|
||||
public int Likes { get; }
|
||||
public DateTime UploadedAt { get; }
|
||||
public Tag[] Tags { get; }
|
||||
|
||||
|
||||
public Gallery(
|
||||
string id,
|
||||
string url,
|
||||
string fullTitle,
|
||||
string title,
|
||||
string thumbnail,
|
||||
int pageCount,
|
||||
int likes,
|
||||
DateTime uploadedAt,
|
||||
Tag[] tags)
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
public sealed class Gallery
|
||||
{
|
||||
public string Id { get; }
|
||||
public string Url { get; }
|
||||
public string FullTitle { get; }
|
||||
public string Title { get; }
|
||||
public string Thumbnail { get; }
|
||||
public int PageCount { get; }
|
||||
public int Likes { get; }
|
||||
public DateTime UploadedAt { get; }
|
||||
public Tag[] Tags { get; }
|
||||
|
||||
|
||||
public Gallery(
|
||||
string id,
|
||||
string url,
|
||||
string fullTitle,
|
||||
string title,
|
||||
string thumbnail,
|
||||
int pageCount,
|
||||
int likes,
|
||||
DateTime uploadedAt,
|
||||
Tag[] tags)
|
||||
{
|
||||
Id = id;
|
||||
Url = url;
|
||||
FullTitle = fullTitle;
|
||||
Title = title;
|
||||
Thumbnail = thumbnail;
|
||||
PageCount = pageCount;
|
||||
Likes = likes;
|
||||
UploadedAt = uploadedAt;
|
||||
Tags = tags;
|
||||
}
|
||||
Id = id;
|
||||
Url = url;
|
||||
FullTitle = fullTitle;
|
||||
Title = title;
|
||||
Thumbnail = thumbnail;
|
||||
PageCount = pageCount;
|
||||
Likes = likes;
|
||||
UploadedAt = uploadedAt;
|
||||
Tags = tags;
|
||||
}
|
||||
}
|
@@ -1,39 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class UserData
|
||||
{
|
||||
public class UserData
|
||||
{
|
||||
[JsonProperty("abbr")] public object Abbr { get; set; }
|
||||
[JsonProperty("abbr")] public object Abbr { get; set; }
|
||||
|
||||
[JsonProperty("clanid")] public object Clanid { get; set; }
|
||||
[JsonProperty("clanid")] public object Clanid { get; set; }
|
||||
|
||||
[JsonProperty("country")] public string Country { get; set; }
|
||||
[JsonProperty("country")] public string Country { get; set; }
|
||||
|
||||
[JsonProperty("favourite_mode")] public int FavouriteMode { get; set; }
|
||||
[JsonProperty("favourite_mode")] public int FavouriteMode { get; set; }
|
||||
|
||||
[JsonProperty("followers_count")] public int FollowersCount { get; set; }
|
||||
[JsonProperty("followers_count")] public int FollowersCount { get; set; }
|
||||
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
|
||||
[JsonProperty("latest_activity")] public int LatestActivity { get; set; }
|
||||
[JsonProperty("latest_activity")] public int LatestActivity { get; set; }
|
||||
|
||||
[JsonProperty("play_style")] public int PlayStyle { get; set; }
|
||||
[JsonProperty("play_style")] public int PlayStyle { get; set; }
|
||||
|
||||
[JsonProperty("privileges")] public int Privileges { get; set; }
|
||||
[JsonProperty("privileges")] public int Privileges { get; set; }
|
||||
|
||||
[JsonProperty("registered_on")] public int RegisteredOn { get; set; }
|
||||
[JsonProperty("registered_on")] public int RegisteredOn { get; set; }
|
||||
|
||||
[JsonProperty("username")] public string Username { get; set; }
|
||||
[JsonProperty("username")] public string Username { get; set; }
|
||||
|
||||
[JsonProperty("username_aka")] public string UsernameAka { get; set; }
|
||||
}
|
||||
[JsonProperty("username_aka")] public string UsernameAka { get; set; }
|
||||
}
|
||||
|
||||
public class GatariUserResponse
|
||||
{
|
||||
[JsonProperty("code")] public int Code { get; set; }
|
||||
public class GatariUserResponse
|
||||
{
|
||||
[JsonProperty("code")] public int Code { get; set; }
|
||||
|
||||
[JsonProperty("users")] public List<UserData> Users { get; set; }
|
||||
}
|
||||
[JsonProperty("users")] public List<UserData> Users { get; set; }
|
||||
}
|
@@ -1,54 +1,53 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class UserStats
|
||||
{
|
||||
public class UserStats
|
||||
{
|
||||
[JsonProperty("a_count")] public int ACount { get; set; }
|
||||
[JsonProperty("a_count")] public int ACount { get; set; }
|
||||
|
||||
[JsonProperty("avg_accuracy")] public double AvgAccuracy { get; set; }
|
||||
[JsonProperty("avg_accuracy")] public double AvgAccuracy { get; set; }
|
||||
|
||||
[JsonProperty("avg_hits_play")] public double AvgHitsPlay { get; set; }
|
||||
[JsonProperty("avg_hits_play")] public double AvgHitsPlay { get; set; }
|
||||
|
||||
[JsonProperty("country_rank")] public int CountryRank { get; set; }
|
||||
[JsonProperty("country_rank")] public int CountryRank { get; set; }
|
||||
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
|
||||
[JsonProperty("level")] public int Level { get; set; }
|
||||
[JsonProperty("level")] public int Level { get; set; }
|
||||
|
||||
[JsonProperty("level_progress")] public int LevelProgress { get; set; }
|
||||
[JsonProperty("level_progress")] public int LevelProgress { get; set; }
|
||||
|
||||
[JsonProperty("max_combo")] public int MaxCombo { get; set; }
|
||||
[JsonProperty("max_combo")] public int MaxCombo { get; set; }
|
||||
|
||||
[JsonProperty("playcount")] public int Playcount { get; set; }
|
||||
[JsonProperty("playcount")] public int Playcount { get; set; }
|
||||
|
||||
[JsonProperty("playtime")] public int Playtime { get; set; }
|
||||
[JsonProperty("playtime")] public int Playtime { get; set; }
|
||||
|
||||
[JsonProperty("pp")] public int Pp { get; set; }
|
||||
[JsonProperty("pp")] public int Pp { get; set; }
|
||||
|
||||
[JsonProperty("rank")] public int Rank { get; set; }
|
||||
[JsonProperty("rank")] public int Rank { get; set; }
|
||||
|
||||
[JsonProperty("ranked_score")] public int RankedScore { get; set; }
|
||||
[JsonProperty("ranked_score")] public int RankedScore { get; set; }
|
||||
|
||||
[JsonProperty("replays_watched")] public int ReplaysWatched { get; set; }
|
||||
[JsonProperty("replays_watched")] public int ReplaysWatched { get; set; }
|
||||
|
||||
[JsonProperty("s_count")] public int SCount { get; set; }
|
||||
[JsonProperty("s_count")] public int SCount { get; set; }
|
||||
|
||||
[JsonProperty("sh_count")] public int ShCount { get; set; }
|
||||
[JsonProperty("sh_count")] public int ShCount { get; set; }
|
||||
|
||||
[JsonProperty("total_hits")] public int TotalHits { get; set; }
|
||||
[JsonProperty("total_hits")] public int TotalHits { get; set; }
|
||||
|
||||
[JsonProperty("total_score")] public long TotalScore { get; set; }
|
||||
[JsonProperty("total_score")] public long TotalScore { get; set; }
|
||||
|
||||
[JsonProperty("x_count")] public int XCount { get; set; }
|
||||
[JsonProperty("x_count")] public int XCount { get; set; }
|
||||
|
||||
[JsonProperty("xh_count")] public int XhCount { get; set; }
|
||||
}
|
||||
[JsonProperty("xh_count")] public int XhCount { get; set; }
|
||||
}
|
||||
|
||||
public class GatariUserStatsResponse
|
||||
{
|
||||
[JsonProperty("code")] public int Code { get; set; }
|
||||
public class GatariUserStatsResponse
|
||||
{
|
||||
[JsonProperty("code")] public int Code { get; set; }
|
||||
|
||||
[JsonProperty("stats")] public UserStats Stats { get; set; }
|
||||
}
|
||||
[JsonProperty("stats")] public UserStats Stats { get; set; }
|
||||
}
|
@@ -1,16 +1,15 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public sealed class GoogleSearchResult
|
||||
{
|
||||
public string Title { get; }
|
||||
public string Link { get; }
|
||||
public string Text { get; }
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public GoogleSearchResult(string title, string link, string text)
|
||||
{
|
||||
this.Title = title;
|
||||
this.Link = link;
|
||||
this.Text = text;
|
||||
}
|
||||
public sealed class 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,13 +1,12 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
public class HearthstoneCardData
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public string Flavor { get; set; }
|
||||
public bool Collectible { get; set; }
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public string Img { get; set; }
|
||||
public string ImgGold { get; set; }
|
||||
public string PlayerClass { get; set; }
|
||||
}
|
||||
}
|
||||
public class HearthstoneCardData
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public string Flavor { get; set; }
|
||||
public bool Collectible { get; set; }
|
||||
|
||||
public string Img { get; set; }
|
||||
public string ImgGold { get; set; }
|
||||
public string PlayerClass { get; set; }
|
||||
}
|
@@ -1,47 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NadekoBot.Modules.Nsfw.Common;
|
||||
using NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class ImageCacherObject : IComparable<ImageCacherObject>
|
||||
{
|
||||
public class ImageCacherObject : IComparable<ImageCacherObject>
|
||||
public Booru SearchType { get; }
|
||||
public string FileUrl { get; }
|
||||
public HashSet<string> Tags { get; }
|
||||
public string Rating { get; }
|
||||
|
||||
public ImageCacherObject(DapiImageObject obj, Booru type)
|
||||
{
|
||||
public Booru SearchType { get; }
|
||||
public string FileUrl { get; }
|
||||
public HashSet<string> Tags { get; }
|
||||
public string Rating { get; }
|
||||
|
||||
public ImageCacherObject(DapiImageObject obj, Booru type)
|
||||
if (type == Booru.Danbooru && !Uri.IsWellFormedUriString(obj.FileUrl, UriKind.Absolute))
|
||||
{
|
||||
if (type == Booru.Danbooru && !Uri.IsWellFormedUriString(obj.FileUrl, UriKind.Absolute))
|
||||
{
|
||||
this.FileUrl = "https://danbooru.donmai.us" + obj.FileUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.FileUrl = obj.FileUrl.StartsWith("http", StringComparison.InvariantCulture) ? obj.FileUrl : "https:" + obj.FileUrl;
|
||||
}
|
||||
this.SearchType = type;
|
||||
this.Rating = obj.Rating;
|
||||
this.Tags = new HashSet<string>((obj.Tags ?? obj.TagString).Split(' '));
|
||||
this.FileUrl = "https://danbooru.donmai.us" + obj.FileUrl;
|
||||
}
|
||||
|
||||
public ImageCacherObject(string url, Booru type, string tags, string rating)
|
||||
else
|
||||
{
|
||||
this.SearchType = type;
|
||||
this.FileUrl = url;
|
||||
this.Tags = new HashSet<string>(tags.Split(' '));
|
||||
this.Rating = rating;
|
||||
this.FileUrl = obj.FileUrl.StartsWith("http", StringComparison.InvariantCulture) ? obj.FileUrl : "https:" + obj.FileUrl;
|
||||
}
|
||||
this.SearchType = type;
|
||||
this.Rating = obj.Rating;
|
||||
this.Tags = new HashSet<string>((obj.Tags ?? obj.TagString).Split(' '));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return FileUrl;
|
||||
}
|
||||
public ImageCacherObject(string url, Booru type, string tags, string rating)
|
||||
{
|
||||
this.SearchType = type;
|
||||
this.FileUrl = url;
|
||||
this.Tags = new HashSet<string>(tags.Split(' '));
|
||||
this.Rating = rating;
|
||||
}
|
||||
|
||||
public int CompareTo(ImageCacherObject other)
|
||||
{
|
||||
return string.Compare(FileUrl, other.FileUrl, StringComparison.InvariantCulture);
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return FileUrl;
|
||||
}
|
||||
|
||||
public int CompareTo(ImageCacherObject other)
|
||||
{
|
||||
return string.Compare(FileUrl, other.FileUrl, StringComparison.InvariantCulture);
|
||||
}
|
||||
}
|
@@ -1,12 +1,11 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace SystemTextJsonSamples
|
||||
namespace SystemTextJsonSamples;
|
||||
|
||||
public class LowerCaseNamingPolicy : JsonNamingPolicy
|
||||
{
|
||||
public class LowerCaseNamingPolicy : JsonNamingPolicy
|
||||
{
|
||||
public static LowerCaseNamingPolicy Default = new LowerCaseNamingPolicy();
|
||||
public static LowerCaseNamingPolicy Default = new LowerCaseNamingPolicy();
|
||||
|
||||
public override string ConvertName(string name) =>
|
||||
name.ToLower();
|
||||
}
|
||||
public override string ConvertName(string name) =>
|
||||
name.ToLower();
|
||||
}
|
@@ -1,8 +1,7 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class MagicItem
|
||||
{
|
||||
public class MagicItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
@@ -1,25 +1,24 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class MangaResult
|
||||
{
|
||||
public class MangaResult
|
||||
{
|
||||
public int Id { get; set; }
|
||||
[JsonProperty("publishing_status")]
|
||||
public string PublishingStatus { get; set; }
|
||||
[JsonProperty("image_url_lge")]
|
||||
public string ImageUrlLge { get; set; }
|
||||
[JsonProperty("title_english")]
|
||||
public string TitleEnglish { get; set; }
|
||||
[JsonProperty("total_chapters")]
|
||||
public int TotalChapters { get; set; }
|
||||
[JsonProperty("total_volumes")]
|
||||
public int TotalVolumes { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Genres { get; set; }
|
||||
[JsonProperty("average_score")]
|
||||
public string AverageScore { get; set; }
|
||||
public string Link => "http://anilist.co/manga/" + Id;
|
||||
public string Synopsis => Description?.Substring(0, Description.Length > 500 ? 500 : Description.Length) + "...";
|
||||
}
|
||||
public int Id { get; set; }
|
||||
[JsonProperty("publishing_status")]
|
||||
public string PublishingStatus { get; set; }
|
||||
[JsonProperty("image_url_lge")]
|
||||
public string ImageUrlLge { get; set; }
|
||||
[JsonProperty("title_english")]
|
||||
public string TitleEnglish { get; set; }
|
||||
[JsonProperty("total_chapters")]
|
||||
public int TotalChapters { get; set; }
|
||||
[JsonProperty("total_volumes")]
|
||||
public int TotalVolumes { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Genres { get; set; }
|
||||
[JsonProperty("average_score")]
|
||||
public string AverageScore { get; set; }
|
||||
public string Link => "http://anilist.co/manga/" + Id;
|
||||
public string Synopsis => Description?.Substring(0, Description.Length > 500 ? 500 : Description.Length) + "...";
|
||||
}
|
@@ -1,28 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
public class MtgData
|
||||
{
|
||||
public class MtgData
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public string StoreUrl { get; set; }
|
||||
public string Types { get; set; }
|
||||
public string ManaCost { get; set; }
|
||||
}
|
||||
|
||||
public class MtgResponse
|
||||
{
|
||||
public class Data
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public string StoreUrl { get; set; }
|
||||
public string Types { get; set; }
|
||||
public string ManaCost { get; set; }
|
||||
public string Text { get; set; }
|
||||
public List<string> Types { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
}
|
||||
|
||||
public class MtgResponse
|
||||
{
|
||||
public class Data
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ManaCost { get; set; }
|
||||
public string Text { get; set; }
|
||||
public List<string> Types { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
}
|
||||
|
||||
public List<Data> Cards { get; set; }
|
||||
}
|
||||
}
|
||||
public List<Data> Cards { get; set; }
|
||||
}
|
@@ -1,92 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public static class NhentaiApiModel
|
||||
{
|
||||
public static class NhentaiApiModel
|
||||
public class Title
|
||||
{
|
||||
public class Title
|
||||
{
|
||||
[JsonProperty("english")] public string English { get; set; }
|
||||
[JsonProperty("english")] public string English { get; set; }
|
||||
|
||||
[JsonProperty("japanese")] public string Japanese { get; set; }
|
||||
[JsonProperty("japanese")] public string Japanese { get; set; }
|
||||
|
||||
[JsonProperty("pretty")] public string Pretty { get; set; }
|
||||
}
|
||||
[JsonProperty("pretty")] public string Pretty { get; set; }
|
||||
}
|
||||
|
||||
public class Page
|
||||
{
|
||||
[JsonProperty("t")] public string T { get; set; }
|
||||
public class Page
|
||||
{
|
||||
[JsonProperty("t")] public string T { get; set; }
|
||||
|
||||
[JsonProperty("w")] public int W { get; set; }
|
||||
[JsonProperty("w")] public int W { get; set; }
|
||||
|
||||
[JsonProperty("h")] public int H { get; set; }
|
||||
}
|
||||
[JsonProperty("h")] public int H { get; set; }
|
||||
}
|
||||
|
||||
public class Cover
|
||||
{
|
||||
[JsonProperty("t")] public string T { get; set; }
|
||||
public class Cover
|
||||
{
|
||||
[JsonProperty("t")] public string T { get; set; }
|
||||
|
||||
[JsonProperty("w")] public int W { get; set; }
|
||||
[JsonProperty("w")] public int W { get; set; }
|
||||
|
||||
[JsonProperty("h")] public int H { get; set; }
|
||||
}
|
||||
[JsonProperty("h")] public int H { get; set; }
|
||||
}
|
||||
|
||||
public class Thumbnail
|
||||
{
|
||||
[JsonProperty("t")] public string T { get; set; }
|
||||
public class Thumbnail
|
||||
{
|
||||
[JsonProperty("t")] public string T { get; set; }
|
||||
|
||||
[JsonProperty("w")] public int W { get; set; }
|
||||
[JsonProperty("w")] public int W { get; set; }
|
||||
|
||||
[JsonProperty("h")] public int H { get; set; }
|
||||
}
|
||||
[JsonProperty("h")] public int H { get; set; }
|
||||
}
|
||||
|
||||
public class Images
|
||||
{
|
||||
[JsonProperty("pages")] public List<Page> Pages { get; set; }
|
||||
public class Images
|
||||
{
|
||||
[JsonProperty("pages")] public List<Page> Pages { get; set; }
|
||||
|
||||
[JsonProperty("cover")] public Cover Cover { get; set; }
|
||||
[JsonProperty("cover")] public Cover Cover { get; set; }
|
||||
|
||||
[JsonProperty("thumbnail")] public Thumbnail Thumbnail { get; set; }
|
||||
}
|
||||
[JsonProperty("thumbnail")] public Thumbnail Thumbnail { get; set; }
|
||||
}
|
||||
|
||||
public class Tag
|
||||
{
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
public class Tag
|
||||
{
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
|
||||
[JsonProperty("type")] public string Type { get; set; }
|
||||
[JsonProperty("type")] public string Type { get; set; }
|
||||
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
|
||||
[JsonProperty("url")] public string Url { get; set; }
|
||||
[JsonProperty("url")] public string Url { get; set; }
|
||||
|
||||
[JsonProperty("count")] public int Count { get; set; }
|
||||
}
|
||||
[JsonProperty("count")] public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class Gallery
|
||||
{
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
public class Gallery
|
||||
{
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
|
||||
[JsonProperty("media_id")] public string MediaId { get; set; }
|
||||
[JsonProperty("media_id")] public string MediaId { get; set; }
|
||||
|
||||
[JsonProperty("title")] public Title Title { get; set; }
|
||||
[JsonProperty("title")] public Title Title { get; set; }
|
||||
|
||||
[JsonProperty("images")] public Images Images { get; set; }
|
||||
[JsonProperty("images")] public Images Images { get; set; }
|
||||
|
||||
[JsonProperty("scanlator")] public string Scanlator { get; set; }
|
||||
[JsonProperty("scanlator")] public string Scanlator { get; set; }
|
||||
|
||||
[JsonProperty("upload_date")] public double UploadDate { get; set; }
|
||||
[JsonProperty("upload_date")] public double UploadDate { get; set; }
|
||||
|
||||
[JsonProperty("tags")] public Tag[] Tags { get; set; }
|
||||
[JsonProperty("tags")] public Tag[] Tags { get; set; }
|
||||
|
||||
[JsonProperty("num_pages")] public int NumPages { get; set; }
|
||||
[JsonProperty("num_pages")] public int NumPages { get; set; }
|
||||
|
||||
[JsonProperty("num_favorites")] public int NumFavorites { get; set; }
|
||||
}
|
||||
[JsonProperty("num_favorites")] public int NumFavorites { get; set; }
|
||||
}
|
||||
|
||||
public class SearchResult
|
||||
{
|
||||
[JsonProperty("result")] public Gallery[] Result { get; set; }
|
||||
}
|
||||
public class SearchResult
|
||||
{
|
||||
[JsonProperty("result")] public Gallery[] Result { get; set; }
|
||||
}
|
||||
}
|
@@ -1,14 +1,13 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class NovelResult
|
||||
{
|
||||
public class NovelResult
|
||||
{
|
||||
public string Description { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Link { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public string[] Authors { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string[] Genres { get; set; }
|
||||
public string Score { get; set; }
|
||||
}
|
||||
}
|
||||
public string Description { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Link { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public string[] Authors { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string[] Genres { get; set; }
|
||||
public string Score { get; set; }
|
||||
}
|
@@ -1,13 +1,12 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class OmdbMovie
|
||||
{
|
||||
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 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; }
|
||||
}
|
@@ -1,49 +1,48 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class OsuUserData
|
||||
{
|
||||
public class OsuUserData
|
||||
{
|
||||
[JsonProperty("user_id")] public string UserId { get; set; }
|
||||
[JsonProperty("user_id")] public string UserId { get; set; }
|
||||
|
||||
[JsonProperty("username")] public string Username { get; set; }
|
||||
[JsonProperty("username")] public string Username { get; set; }
|
||||
|
||||
[JsonProperty("join_date")] public string JoinDate { get; set; }
|
||||
[JsonProperty("join_date")] public string JoinDate { get; set; }
|
||||
|
||||
[JsonProperty("count300")] public string Count300 { get; set; }
|
||||
[JsonProperty("count300")] public string Count300 { get; set; }
|
||||
|
||||
[JsonProperty("count100")] public string Count100 { get; set; }
|
||||
[JsonProperty("count100")] public string Count100 { get; set; }
|
||||
|
||||
[JsonProperty("count50")] public string Count50 { get; set; }
|
||||
[JsonProperty("count50")] public string Count50 { get; set; }
|
||||
|
||||
[JsonProperty("playcount")] public string Playcount { get; set; }
|
||||
[JsonProperty("playcount")] public string Playcount { get; set; }
|
||||
|
||||
[JsonProperty("ranked_score")] public string RankedScore { get; set; }
|
||||
[JsonProperty("ranked_score")] public string RankedScore { get; set; }
|
||||
|
||||
[JsonProperty("total_score")] public string TotalScore { get; set; }
|
||||
[JsonProperty("total_score")] public string TotalScore { get; set; }
|
||||
|
||||
[JsonProperty("pp_rank")] public string PpRank { get; set; }
|
||||
[JsonProperty("pp_rank")] public string PpRank { get; set; }
|
||||
|
||||
[JsonProperty("level")] public double Level { get; set; }
|
||||
[JsonProperty("level")] public double Level { get; set; }
|
||||
|
||||
[JsonProperty("pp_raw")] public double PpRaw { get; set; }
|
||||
[JsonProperty("pp_raw")] public double PpRaw { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")] public double Accuracy { get; set; }
|
||||
[JsonProperty("accuracy")] public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty("count_rank_ss")] public string CountRankSs { get; set; }
|
||||
[JsonProperty("count_rank_ss")] public string CountRankSs { get; set; }
|
||||
|
||||
[JsonProperty("count_rank_ssh")] public string CountRankSsh { get; set; }
|
||||
[JsonProperty("count_rank_ssh")] public string CountRankSsh { get; set; }
|
||||
|
||||
[JsonProperty("count_rank_s")] public string CountRankS { get; set; }
|
||||
[JsonProperty("count_rank_s")] public string CountRankS { get; set; }
|
||||
|
||||
[JsonProperty("count_rank_sh")] public string CountRankSh { get; set; }
|
||||
[JsonProperty("count_rank_sh")] public string CountRankSh { get; set; }
|
||||
|
||||
[JsonProperty("count_rank_a")] public string CountRankA { get; set; }
|
||||
[JsonProperty("count_rank_a")] public string CountRankA { get; set; }
|
||||
|
||||
[JsonProperty("country")] public string Country { get; set; }
|
||||
[JsonProperty("country")] public string Country { get; set; }
|
||||
|
||||
[JsonProperty("total_seconds_played")] public string TotalSecondsPlayed { get; set; }
|
||||
[JsonProperty("total_seconds_played")] public string TotalSecondsPlayed { get; set; }
|
||||
|
||||
[JsonProperty("pp_country_rank")] public string PpCountryRank { get; set; }
|
||||
}
|
||||
[JsonProperty("pp_country_rank")] public string PpCountryRank { get; set; }
|
||||
}
|
@@ -1,42 +1,39 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class Account
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
public class Account
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonProperty("league")]
|
||||
public string League { get; set; }
|
||||
|
||||
[JsonProperty("league")]
|
||||
public string League { get; set; }
|
||||
[JsonProperty("classId")]
|
||||
public int ClassId { get; set; }
|
||||
|
||||
[JsonProperty("classId")]
|
||||
public int ClassId { get; set; }
|
||||
[JsonProperty("ascendancyClass")]
|
||||
public int AscendancyClass { get; set; }
|
||||
|
||||
[JsonProperty("ascendancyClass")]
|
||||
public int AscendancyClass { get; set; }
|
||||
[JsonProperty("class")]
|
||||
public string Class { get; set; }
|
||||
|
||||
[JsonProperty("class")]
|
||||
public string Class { get; set; }
|
||||
|
||||
[JsonProperty("level")]
|
||||
public int Level { get; set; }
|
||||
}
|
||||
|
||||
public class Leagues
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty("startAt")]
|
||||
public DateTime StartAt { get; set; }
|
||||
|
||||
[JsonProperty("endAt")]
|
||||
public object EndAt { get; set; }
|
||||
}
|
||||
[JsonProperty("level")]
|
||||
public int Level { get; set; }
|
||||
}
|
||||
|
||||
public class Leagues
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty("startAt")]
|
||||
public DateTime StartAt { get; set; }
|
||||
|
||||
[JsonProperty("endAt")]
|
||||
public object EndAt { get; set; }
|
||||
}
|
@@ -1,35 +1,34 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public class SteamGameId
|
||||
{
|
||||
public class SteamGameId
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("appid")]
|
||||
public int AppId { get; set; }
|
||||
}
|
||||
[JsonProperty("appid")]
|
||||
public int AppId { get; set; }
|
||||
}
|
||||
|
||||
public class SteamGameData
|
||||
public class SteamGameData
|
||||
{
|
||||
public string ShortDescription { get; set; }
|
||||
|
||||
public class Container
|
||||
{
|
||||
public string ShortDescription { get; set; }
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
public class Container
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("data")]
|
||||
public SteamGameData Data { get; set; }
|
||||
}
|
||||
[JsonProperty("data")]
|
||||
public SteamGameData Data { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public enum TimeErrors
|
||||
{
|
||||
InvalidInput,
|
||||
ApiKeyMissing,
|
||||
NotFound,
|
||||
Unknown
|
||||
}
|
||||
public enum TimeErrors
|
||||
{
|
||||
InvalidInput,
|
||||
ApiKeyMissing,
|
||||
NotFound,
|
||||
Unknown
|
||||
}
|
@@ -1,114 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class PicartoChannelResponse
|
||||
{
|
||||
public class PicartoChannelResponse
|
||||
{
|
||||
[JsonProperty("user_id")] public int UserId { get; set; }
|
||||
[JsonProperty("user_id")] public int UserId { get; set; }
|
||||
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
|
||||
[JsonProperty("avatar")] public string Avatar { get; set; }
|
||||
[JsonProperty("avatar")] public string Avatar { get; set; }
|
||||
|
||||
[JsonProperty("online")] public bool Online { get; set; }
|
||||
[JsonProperty("online")] public bool Online { get; set; }
|
||||
|
||||
[JsonProperty("viewers")] public int Viewers { get; set; }
|
||||
[JsonProperty("viewers")] public int Viewers { get; set; }
|
||||
|
||||
[JsonProperty("viewers_total")] public int ViewersTotal { get; set; }
|
||||
[JsonProperty("viewers_total")] public int ViewersTotal { get; set; }
|
||||
|
||||
[JsonProperty("thumbnails")] public Thumbnails Thumbnails { get; set; }
|
||||
[JsonProperty("thumbnails")] public Thumbnails Thumbnails { get; set; }
|
||||
|
||||
[JsonProperty("followers")] public int Followers { get; set; }
|
||||
[JsonProperty("followers")] public int Followers { get; set; }
|
||||
|
||||
[JsonProperty("subscribers")] public int Subscribers { get; set; }
|
||||
[JsonProperty("subscribers")] public int Subscribers { get; set; }
|
||||
|
||||
[JsonProperty("adult")] public bool Adult { get; set; }
|
||||
[JsonProperty("adult")] public bool Adult { get; set; }
|
||||
|
||||
[JsonProperty("category")] public string Category { get; set; }
|
||||
[JsonProperty("category")] public string Category { get; set; }
|
||||
|
||||
[JsonProperty("account_type")] public string AccountType { get; set; }
|
||||
[JsonProperty("account_type")] public string AccountType { get; set; }
|
||||
|
||||
[JsonProperty("commissions")] public bool Commissions { get; set; }
|
||||
[JsonProperty("commissions")] public bool Commissions { get; set; }
|
||||
|
||||
[JsonProperty("recordings")] public bool Recordings { get; set; }
|
||||
[JsonProperty("recordings")] public bool Recordings { get; set; }
|
||||
|
||||
[JsonProperty("title")] public string Title { get; set; }
|
||||
[JsonProperty("title")] public string Title { get; set; }
|
||||
|
||||
[JsonProperty("description_panels")] public List<DescriptionPanel> DescriptionPanels { get; set; }
|
||||
[JsonProperty("description_panels")] public List<DescriptionPanel> DescriptionPanels { get; set; }
|
||||
|
||||
[JsonProperty("private")] public bool Private { get; set; }
|
||||
[JsonProperty("private")] public bool Private { get; set; }
|
||||
|
||||
[JsonProperty("private_message")] public string PrivateMessage { get; set; }
|
||||
[JsonProperty("private_message")] public string PrivateMessage { get; set; }
|
||||
|
||||
[JsonProperty("gaming")] public bool Gaming { get; set; }
|
||||
[JsonProperty("gaming")] public bool Gaming { get; set; }
|
||||
|
||||
[JsonProperty("chat_settings")] public ChatSettings ChatSettings { get; set; }
|
||||
[JsonProperty("chat_settings")] public ChatSettings ChatSettings { get; set; }
|
||||
|
||||
[JsonProperty("last_live")] public DateTime LastLive { get; set; }
|
||||
[JsonProperty("last_live")] public DateTime LastLive { get; set; }
|
||||
|
||||
[JsonProperty("tags")] public List<string> Tags { get; set; }
|
||||
[JsonProperty("tags")] public List<string> Tags { get; set; }
|
||||
|
||||
[JsonProperty("multistream")] public List<Multistream> Multistream { get; set; }
|
||||
[JsonProperty("multistream")] public List<Multistream> Multistream { get; set; }
|
||||
|
||||
[JsonProperty("languages")] public List<Language> Languages { get; set; }
|
||||
[JsonProperty("languages")] public List<Language> Languages { get; set; }
|
||||
|
||||
[JsonProperty("following")] public bool Following { get; set; }
|
||||
}
|
||||
public class Thumbnails
|
||||
{
|
||||
[JsonProperty("web")] public string Web { get; set; }
|
||||
[JsonProperty("following")] public bool Following { get; set; }
|
||||
}
|
||||
public class Thumbnails
|
||||
{
|
||||
[JsonProperty("web")] public string Web { get; set; }
|
||||
|
||||
[JsonProperty("web_large")] public string WebLarge { get; set; }
|
||||
[JsonProperty("web_large")] public string WebLarge { get; set; }
|
||||
|
||||
[JsonProperty("mobile")] public string Mobile { get; set; }
|
||||
[JsonProperty("mobile")] public string Mobile { get; set; }
|
||||
|
||||
[JsonProperty("tablet")] public string Tablet { get; set; }
|
||||
}
|
||||
[JsonProperty("tablet")] public string Tablet { get; set; }
|
||||
}
|
||||
|
||||
public class DescriptionPanel
|
||||
{
|
||||
[JsonProperty("title")] public string Title { get; set; }
|
||||
public class DescriptionPanel
|
||||
{
|
||||
[JsonProperty("title")] public string Title { get; set; }
|
||||
|
||||
[JsonProperty("body")] public string Body { get; set; }
|
||||
[JsonProperty("body")] public string Body { get; set; }
|
||||
|
||||
[JsonProperty("image")] public string Image { get; set; }
|
||||
[JsonProperty("image")] public string Image { get; set; }
|
||||
|
||||
[JsonProperty("image_link")] public string ImageLink { get; set; }
|
||||
[JsonProperty("image_link")] public string ImageLink { get; set; }
|
||||
|
||||
[JsonProperty("button_text")] public string ButtonText { get; set; }
|
||||
[JsonProperty("button_text")] public string ButtonText { get; set; }
|
||||
|
||||
[JsonProperty("button_link")] public string ButtonLink { get; set; }
|
||||
[JsonProperty("button_link")] public string ButtonLink { get; set; }
|
||||
|
||||
[JsonProperty("position")] public int Position { get; set; }
|
||||
}
|
||||
[JsonProperty("position")] public int Position { get; set; }
|
||||
}
|
||||
|
||||
public class ChatSettings
|
||||
{
|
||||
[JsonProperty("guest_chat")] public bool GuestChat { get; set; }
|
||||
public class ChatSettings
|
||||
{
|
||||
[JsonProperty("guest_chat")] public bool GuestChat { get; set; }
|
||||
|
||||
[JsonProperty("links")] public bool Links { get; set; }
|
||||
[JsonProperty("links")] public bool Links { get; set; }
|
||||
|
||||
[JsonProperty("level")] public int Level { get; set; }
|
||||
}
|
||||
[JsonProperty("level")] public int Level { get; set; }
|
||||
}
|
||||
|
||||
public class Multistream
|
||||
{
|
||||
[JsonProperty("user_id")] public int UserId { get; set; }
|
||||
public class Multistream
|
||||
{
|
||||
[JsonProperty("user_id")] public int UserId { get; set; }
|
||||
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
|
||||
[JsonProperty("online")] public bool Online { get; set; }
|
||||
[JsonProperty("online")] public bool Online { get; set; }
|
||||
|
||||
[JsonProperty("adult")] public bool Adult { get; set; }
|
||||
}
|
||||
[JsonProperty("adult")] public bool Adult { get; set; }
|
||||
}
|
||||
|
||||
public class Language
|
||||
{
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
}
|
||||
public class Language
|
||||
{
|
||||
[JsonProperty("id")] public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
}
|
@@ -1,21 +1,19 @@
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class StreamData
|
||||
{
|
||||
public class StreamData
|
||||
{
|
||||
public FollowedStream.FType StreamType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string UniqueName { get; set; }
|
||||
public int Viewers { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Game { get; set; }
|
||||
public string Preview { get; set; }
|
||||
public bool IsLive { get; set; }
|
||||
public string StreamUrl { get; set; }
|
||||
public string AvatarUrl { get; set; }
|
||||
public FollowedStream.FType StreamType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string UniqueName { get; set; }
|
||||
public int Viewers { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Game { get; set; }
|
||||
public string Preview { get; set; }
|
||||
public bool IsLive { get; set; }
|
||||
public string StreamUrl { get; set; }
|
||||
public string AvatarUrl { get; set; }
|
||||
|
||||
public StreamDataKey CreateKey() => new StreamDataKey(StreamType, UniqueName.ToLower());
|
||||
}
|
||||
public StreamDataKey CreateKey() => new StreamDataKey(StreamType, UniqueName.ToLower());
|
||||
}
|
@@ -1,19 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public readonly struct StreamDataKey
|
||||
{
|
||||
public readonly struct StreamDataKey
|
||||
{
|
||||
public FollowedStream.FType Type { get; }
|
||||
public string Name { get; }
|
||||
public FollowedStream.FType Type { get; }
|
||||
public string Name { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public StreamDataKey(FollowedStream.FType type, string name)
|
||||
{
|
||||
Type = type;
|
||||
Name = name;
|
||||
}
|
||||
[JsonConstructor]
|
||||
public StreamDataKey(FollowedStream.FType type, string name)
|
||||
{
|
||||
Type = type;
|
||||
Name = name;
|
||||
}
|
||||
}
|
@@ -1,22 +1,21 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
{
|
||||
//
|
||||
// public class TwitchResponse
|
||||
// {
|
||||
// public List<StreamApiData> Data { get; set; }
|
||||
//
|
||||
// public class StreamApiData
|
||||
// {
|
||||
// public string Id { get; set; }
|
||||
// public string UserId { get; set; }
|
||||
// public string UserName { get; set; }
|
||||
// public string GameId { get; set; }
|
||||
// public string Type { get; set; }
|
||||
// public string Title { get; set; }
|
||||
// public int ViewerCount { get; set; }
|
||||
// public string Language { get; set; }
|
||||
// public string ThumbnailUrl { get; set; }
|
||||
// public DateTime StartedAt { get; set; }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
//
|
||||
// public class TwitchResponse
|
||||
// {
|
||||
// public List<StreamApiData> Data { get; set; }
|
||||
//
|
||||
// public class StreamApiData
|
||||
// {
|
||||
// public string Id { get; set; }
|
||||
// public string UserId { get; set; }
|
||||
// public string UserName { get; set; }
|
||||
// public string GameId { get; set; }
|
||||
// public string Type { get; set; }
|
||||
// public string Title { get; set; }
|
||||
// public int ViewerCount { get; set; }
|
||||
// public string Language { get; set; }
|
||||
// public string ThumbnailUrl { get; set; }
|
||||
// public DateTime StartedAt { get; set; }
|
||||
// }
|
||||
// }
|
@@ -1,85 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class TwitchResponseV5
|
||||
{
|
||||
public class TwitchResponseV5
|
||||
public List<Stream> Streams { get; set; }
|
||||
|
||||
public class Channel
|
||||
{
|
||||
public List<Stream> Streams { get; set; }
|
||||
[JsonProperty("_id")] public int Id { get; set; }
|
||||
|
||||
public class Channel
|
||||
{
|
||||
[JsonProperty("_id")] public int Id { get; set; }
|
||||
[JsonProperty("broadcaster_language")] public string BroadcasterLanguage { get; set; }
|
||||
|
||||
[JsonProperty("broadcaster_language")] public string BroadcasterLanguage { get; set; }
|
||||
[JsonProperty("created_at")] public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("created_at")] public DateTime CreatedAt { get; set; }
|
||||
[JsonProperty("display_name")] public string DisplayName { get; set; }
|
||||
|
||||
[JsonProperty("display_name")] public string DisplayName { get; set; }
|
||||
[JsonProperty("followers")] public int Followers { get; set; }
|
||||
|
||||
[JsonProperty("followers")] public int Followers { get; set; }
|
||||
[JsonProperty("game")] public string Game { get; set; }
|
||||
|
||||
[JsonProperty("game")] public string Game { get; set; }
|
||||
[JsonProperty("language")] public string Language { get; set; }
|
||||
|
||||
[JsonProperty("language")] public string Language { get; set; }
|
||||
[JsonProperty("logo")] public string Logo { get; set; }
|
||||
|
||||
[JsonProperty("logo")] public string Logo { get; set; }
|
||||
[JsonProperty("mature")] public bool Mature { get; set; }
|
||||
|
||||
[JsonProperty("mature")] public bool Mature { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("partner")] public bool Partner { get; set; }
|
||||
|
||||
[JsonProperty("partner")] public bool Partner { get; set; }
|
||||
[JsonProperty("profile_banner")] public string ProfileBanner { get; set; }
|
||||
|
||||
[JsonProperty("profile_banner")] public string ProfileBanner { get; set; }
|
||||
[JsonProperty("profile_banner_background_color")]
|
||||
public object ProfileBannerBackgroundColor { get; set; }
|
||||
|
||||
[JsonProperty("profile_banner_background_color")]
|
||||
public object ProfileBannerBackgroundColor { get; set; }
|
||||
[JsonProperty("status")] public string Status { get; set; }
|
||||
|
||||
[JsonProperty("status")] public string Status { get; set; }
|
||||
[JsonProperty("updated_at")] public DateTime UpdatedAt { get; set; }
|
||||
|
||||
[JsonProperty("updated_at")] public DateTime UpdatedAt { get; set; }
|
||||
[JsonProperty("url")] public string Url { get; set; }
|
||||
|
||||
[JsonProperty("url")] public string Url { get; set; }
|
||||
[JsonProperty("video_banner")] public string VideoBanner { get; set; }
|
||||
|
||||
[JsonProperty("video_banner")] public string VideoBanner { get; set; }
|
||||
[JsonProperty("views")] public int Views { get; set; }
|
||||
}
|
||||
|
||||
[JsonProperty("views")] public int Views { get; set; }
|
||||
}
|
||||
public class Preview
|
||||
{
|
||||
[JsonProperty("large")] public string Large { get; set; }
|
||||
|
||||
public class Preview
|
||||
{
|
||||
[JsonProperty("large")] public string Large { get; set; }
|
||||
[JsonProperty("medium")] public string Medium { get; set; }
|
||||
|
||||
[JsonProperty("medium")] public string Medium { get; set; }
|
||||
[JsonProperty("small")] public string Small { get; set; }
|
||||
|
||||
[JsonProperty("small")] public string Small { get; set; }
|
||||
[JsonProperty("template")] public string Template { get; set; }
|
||||
}
|
||||
|
||||
[JsonProperty("template")] public string Template { get; set; }
|
||||
}
|
||||
public class Stream
|
||||
{
|
||||
[JsonProperty("_id")] public long Id { get; set; }
|
||||
|
||||
public class Stream
|
||||
{
|
||||
[JsonProperty("_id")] public long Id { get; set; }
|
||||
[JsonProperty("average_fps")] public double AverageFps { get; set; }
|
||||
|
||||
[JsonProperty("average_fps")] public double AverageFps { get; set; }
|
||||
[JsonProperty("channel")] public Channel Channel { get; set; }
|
||||
|
||||
[JsonProperty("channel")] public Channel Channel { get; set; }
|
||||
[JsonProperty("created_at")] public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("created_at")] public DateTime CreatedAt { get; set; }
|
||||
[JsonProperty("delay")] public double Delay { get; set; }
|
||||
|
||||
[JsonProperty("delay")] public double Delay { get; set; }
|
||||
[JsonProperty("game")] public string Game { get; set; }
|
||||
|
||||
[JsonProperty("game")] public string Game { get; set; }
|
||||
[JsonProperty("is_playlist")] public bool IsPlaylist { get; set; }
|
||||
|
||||
[JsonProperty("is_playlist")] public bool IsPlaylist { get; set; }
|
||||
[JsonProperty("preview")] public Preview Preview { get; set; }
|
||||
|
||||
[JsonProperty("preview")] public Preview Preview { get; set; }
|
||||
[JsonProperty("video_height")] public int VideoHeight { get; set; }
|
||||
|
||||
[JsonProperty("video_height")] public int VideoHeight { get; set; }
|
||||
|
||||
[JsonProperty("viewers")] public int Viewers { get; set; }
|
||||
}
|
||||
[JsonProperty("viewers")] public int Viewers { get; set; }
|
||||
}
|
||||
}
|
@@ -1,39 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class TwitchUsersResponseV5
|
||||
{
|
||||
public class TwitchUsersResponseV5
|
||||
[JsonProperty("users")] public List<User> Users { get; set; }
|
||||
|
||||
public class User
|
||||
{
|
||||
[JsonProperty("users")] public List<User> Users { get; set; }
|
||||
|
||||
public class User
|
||||
{
|
||||
[JsonProperty("_id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("_id")]
|
||||
public string Id { get; set; }
|
||||
// [JsonProperty("bio")]
|
||||
// public string Bio { get; set; }
|
||||
//
|
||||
// [JsonProperty("created_at")]
|
||||
// public DateTime CreatedAt { get; set; }
|
||||
//
|
||||
// [JsonProperty("display_name")]
|
||||
// public string DisplayName { get; set; }
|
||||
//
|
||||
// [JsonProperty("logo")]
|
||||
// public string Logo { get; set; }
|
||||
//
|
||||
// [JsonProperty("name")]
|
||||
// public string Name { get; set; }
|
||||
//
|
||||
// [JsonProperty("type")]
|
||||
// public string Type { get; set; }
|
||||
//
|
||||
// [JsonProperty("updated_at")]
|
||||
// public DateTime UpdatedAt { get; set; }
|
||||
|
||||
// [JsonProperty("bio")]
|
||||
// public string Bio { get; set; }
|
||||
//
|
||||
// [JsonProperty("created_at")]
|
||||
// public DateTime CreatedAt { get; set; }
|
||||
//
|
||||
// [JsonProperty("display_name")]
|
||||
// public string DisplayName { get; set; }
|
||||
//
|
||||
// [JsonProperty("logo")]
|
||||
// public string Logo { get; set; }
|
||||
//
|
||||
// [JsonProperty("name")]
|
||||
// public string Name { get; set; }
|
||||
//
|
||||
// [JsonProperty("type")]
|
||||
// public string Type { get; set; }
|
||||
//
|
||||
// [JsonProperty("updated_at")]
|
||||
// public DateTime UpdatedAt { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,250 +1,244 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Modules.Searches.Common.StreamNotifications.Providers;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using StackExchange.Redis;
|
||||
|
||||
#nullable enable
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications;
|
||||
|
||||
public class NotifChecker
|
||||
{
|
||||
public class NotifChecker
|
||||
private readonly ConnectionMultiplexer _multi;
|
||||
private readonly string _key;
|
||||
|
||||
public event Func<List<StreamData>, Task> OnStreamsOffline = _ => Task.CompletedTask;
|
||||
public event Func<List<StreamData>, Task> OnStreamsOnline = _ => Task.CompletedTask;
|
||||
|
||||
private readonly Dictionary<FollowedStream.FType, Provider> _streamProviders;
|
||||
private readonly HashSet<(FollowedStream.FType, string)> _offlineBuffer;
|
||||
|
||||
public NotifChecker(IHttpClientFactory httpClientFactory, ConnectionMultiplexer multi, string uniqueCacheKey,
|
||||
bool isMaster)
|
||||
{
|
||||
private readonly ConnectionMultiplexer _multi;
|
||||
private readonly string _key;
|
||||
|
||||
public event Func<List<StreamData>, Task> OnStreamsOffline = _ => Task.CompletedTask;
|
||||
public event Func<List<StreamData>, Task> OnStreamsOnline = _ => Task.CompletedTask;
|
||||
|
||||
private readonly Dictionary<FollowedStream.FType, Provider> _streamProviders;
|
||||
private readonly HashSet<(FollowedStream.FType, string)> _offlineBuffer;
|
||||
|
||||
public NotifChecker(IHttpClientFactory httpClientFactory, ConnectionMultiplexer multi, string uniqueCacheKey,
|
||||
bool isMaster)
|
||||
_multi = multi;
|
||||
_key = $"{uniqueCacheKey}_followed_streams_data";
|
||||
_streamProviders = new Dictionary<FollowedStream.FType, Provider>()
|
||||
{
|
||||
_multi = multi;
|
||||
_key = $"{uniqueCacheKey}_followed_streams_data";
|
||||
_streamProviders = new Dictionary<FollowedStream.FType, Provider>()
|
||||
{
|
||||
{FollowedStream.FType.Twitch, new TwitchProvider(httpClientFactory)},
|
||||
{FollowedStream.FType.Picarto, new PicartoProvider(httpClientFactory)}
|
||||
};
|
||||
_offlineBuffer = new HashSet<(FollowedStream.FType, string)>();
|
||||
if (isMaster)
|
||||
{
|
||||
CacheClearAllData();
|
||||
}
|
||||
{FollowedStream.FType.Twitch, new TwitchProvider(httpClientFactory)},
|
||||
{FollowedStream.FType.Picarto, new PicartoProvider(httpClientFactory)}
|
||||
};
|
||||
_offlineBuffer = new HashSet<(FollowedStream.FType, string)>();
|
||||
if (isMaster)
|
||||
{
|
||||
CacheClearAllData();
|
||||
}
|
||||
}
|
||||
|
||||
// gets all streams which have been failing for more than the provided timespan
|
||||
public IEnumerable<StreamDataKey> GetFailingStreams(TimeSpan duration, bool remove = false)
|
||||
{
|
||||
var toReturn = _streamProviders.SelectMany(prov => prov.Value
|
||||
// gets all streams which have been failing for more than the provided timespan
|
||||
public IEnumerable<StreamDataKey> GetFailingStreams(TimeSpan duration, bool remove = false)
|
||||
{
|
||||
var toReturn = _streamProviders.SelectMany(prov => prov.Value
|
||||
.FailingStreams
|
||||
.Where(fs => DateTime.UtcNow - fs.ErroringSince > duration)
|
||||
.Select(fs => new StreamDataKey(prov.Value.Platform, fs.Item1)))
|
||||
.ToList();
|
||||
.ToList();
|
||||
|
||||
if (remove)
|
||||
if (remove)
|
||||
{
|
||||
foreach (var toBeRemoved in toReturn)
|
||||
{
|
||||
foreach (var toBeRemoved in toReturn)
|
||||
{
|
||||
_streamProviders[toBeRemoved.Type].ClearErrorsFor(toBeRemoved.Name);
|
||||
}
|
||||
_streamProviders[toBeRemoved.Type].ClearErrorsFor(toBeRemoved.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public Task RunAsync() => Task.Run(async () =>
|
||||
public Task RunAsync() => Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var allStreamData = CacheGetAllData();
|
||||
var allStreamData = CacheGetAllData();
|
||||
|
||||
var oldStreamDataDict = allStreamData
|
||||
// group by type
|
||||
.GroupBy(entry => entry.Key.Type)
|
||||
.ToDictionary(
|
||||
entry => entry.Key,
|
||||
entry => entry.AsEnumerable().ToDictionary(x => x.Key.Name, x => x.Value)
|
||||
);
|
||||
var oldStreamDataDict = allStreamData
|
||||
// group by type
|
||||
.GroupBy(entry => entry.Key.Type)
|
||||
.ToDictionary(
|
||||
entry => entry.Key,
|
||||
entry => entry.AsEnumerable().ToDictionary(x => x.Key.Name, x => x.Value)
|
||||
);
|
||||
|
||||
var newStreamData = await Task.WhenAll(oldStreamDataDict
|
||||
.Select(x =>
|
||||
{
|
||||
// get all stream data for the streams of this type
|
||||
if (_streamProviders.TryGetValue(x.Key, out var provider))
|
||||
{
|
||||
return provider.GetStreamDataAsync(x.Value.Select(entry => entry.Key).ToList());
|
||||
}
|
||||
|
||||
// this means there's no provider for this stream data, (and there was before?)
|
||||
return Task.FromResult(new List<StreamData>());
|
||||
}));
|
||||
|
||||
var newlyOnline = new List<StreamData>();
|
||||
var newlyOffline = new List<StreamData>();
|
||||
// go through all new stream data, compare them with the old ones
|
||||
foreach (var newData in newStreamData.SelectMany(x => x))
|
||||
var newStreamData = await Task.WhenAll(oldStreamDataDict
|
||||
.Select(x =>
|
||||
{
|
||||
// update cached data
|
||||
var key = newData.CreateKey();
|
||||
CacheAddData(key, newData, replace: true);
|
||||
|
||||
// compare old data with new data
|
||||
var oldData = oldStreamDataDict[key.Type][key.Name];
|
||||
|
||||
// this is the first pass
|
||||
if (oldData is null)
|
||||
continue;
|
||||
|
||||
// if the stream is offline, we need to check if it was
|
||||
// marked as offline once previously
|
||||
// if it was, that means this is second time we're getting offline
|
||||
// status for that stream -> notify subscribers
|
||||
// Note: This is done because twitch api will sometimes return an offline status
|
||||
// shortly after the stream is already online, which causes duplicate notifications.
|
||||
// (stream is online -> stream is offline -> stream is online again (and stays online))
|
||||
// This offlineBuffer will make it so that the stream has to be marked as offline TWICE
|
||||
// before it sends an offline notification to the subscribers.
|
||||
var streamId = (key.Type, key.Name);
|
||||
if (!newData.IsLive && _offlineBuffer.Remove(streamId))
|
||||
// get all stream data for the streams of this type
|
||||
if (_streamProviders.TryGetValue(x.Key, out var provider))
|
||||
{
|
||||
newlyOffline.Add(newData);
|
||||
return provider.GetStreamDataAsync(x.Value.Select(entry => entry.Key).ToList());
|
||||
}
|
||||
else if (newData.IsLive != oldData.IsLive)
|
||||
|
||||
// this means there's no provider for this stream data, (and there was before?)
|
||||
return Task.FromResult(new List<StreamData>());
|
||||
}));
|
||||
|
||||
var newlyOnline = new List<StreamData>();
|
||||
var newlyOffline = new List<StreamData>();
|
||||
// go through all new stream data, compare them with the old ones
|
||||
foreach (var newData in newStreamData.SelectMany(x => x))
|
||||
{
|
||||
// update cached data
|
||||
var key = newData.CreateKey();
|
||||
CacheAddData(key, newData, replace: true);
|
||||
|
||||
// compare old data with new data
|
||||
var oldData = oldStreamDataDict[key.Type][key.Name];
|
||||
|
||||
// this is the first pass
|
||||
if (oldData is null)
|
||||
continue;
|
||||
|
||||
// if the stream is offline, we need to check if it was
|
||||
// marked as offline once previously
|
||||
// if it was, that means this is second time we're getting offline
|
||||
// status for that stream -> notify subscribers
|
||||
// Note: This is done because twitch api will sometimes return an offline status
|
||||
// shortly after the stream is already online, which causes duplicate notifications.
|
||||
// (stream is online -> stream is offline -> stream is online again (and stays online))
|
||||
// This offlineBuffer will make it so that the stream has to be marked as offline TWICE
|
||||
// before it sends an offline notification to the subscribers.
|
||||
var streamId = (key.Type, key.Name);
|
||||
if (!newData.IsLive && _offlineBuffer.Remove(streamId))
|
||||
{
|
||||
newlyOffline.Add(newData);
|
||||
}
|
||||
else if (newData.IsLive != oldData.IsLive)
|
||||
{
|
||||
if (newData.IsLive)
|
||||
{
|
||||
if (newData.IsLive)
|
||||
{
|
||||
_offlineBuffer.Remove(streamId);
|
||||
newlyOnline.Add(newData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_offlineBuffer.Add(streamId);
|
||||
// newlyOffline.Add(newData);
|
||||
}
|
||||
_offlineBuffer.Remove(streamId);
|
||||
newlyOnline.Add(newData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_offlineBuffer.Add(streamId);
|
||||
// newlyOffline.Add(newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tasks = new List<Task>
|
||||
{
|
||||
Task.Delay(30_000)
|
||||
};
|
||||
|
||||
if (newlyOnline.Count > 0)
|
||||
{
|
||||
tasks.Add(OnStreamsOnline(newlyOnline));
|
||||
}
|
||||
|
||||
if (newlyOffline.Count > 0)
|
||||
{
|
||||
tasks.Add(OnStreamsOffline(newlyOffline));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
catch (Exception ex)
|
||||
var tasks = new List<Task>
|
||||
{
|
||||
Log.Error(ex, $"Error getting stream notifications: {ex.Message}");
|
||||
Task.Delay(30_000)
|
||||
};
|
||||
|
||||
if (newlyOnline.Count > 0)
|
||||
{
|
||||
tasks.Add(OnStreamsOnline(newlyOnline));
|
||||
}
|
||||
|
||||
if (newlyOffline.Count > 0)
|
||||
{
|
||||
tasks.Add(OnStreamsOffline(newlyOffline));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
});
|
||||
|
||||
public bool CacheAddData(StreamDataKey key, StreamData? data, bool replace)
|
||||
{
|
||||
var db = _multi.GetDatabase();
|
||||
return db.HashSet(
|
||||
_key,
|
||||
JsonConvert.SerializeObject(key),
|
||||
JsonConvert.SerializeObject(data),
|
||||
when: replace ? When.Always : When.NotExists);
|
||||
}
|
||||
|
||||
public void CacheDeleteData(StreamDataKey key)
|
||||
{
|
||||
var db = _multi.GetDatabase();
|
||||
db.HashDelete(_key, JsonConvert.SerializeObject(key));
|
||||
}
|
||||
|
||||
public void CacheClearAllData()
|
||||
{
|
||||
var db = _multi.GetDatabase();
|
||||
db.KeyDelete(_key);
|
||||
}
|
||||
|
||||
public Dictionary<StreamDataKey, StreamData?> CacheGetAllData()
|
||||
{
|
||||
var db = _multi.GetDatabase();
|
||||
if (!db.KeyExists(_key))
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new Dictionary<StreamDataKey, StreamData?>();
|
||||
Log.Error(ex, $"Error getting stream notifications: {ex.Message}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return db.HashGetAll(_key)
|
||||
.ToDictionary(
|
||||
entry => JsonConvert.DeserializeObject<StreamDataKey>(entry.Name),
|
||||
entry => entry.Value.IsNullOrEmpty
|
||||
? default(StreamData)
|
||||
: JsonConvert.DeserializeObject<StreamData>(entry.Value));
|
||||
public bool CacheAddData(StreamDataKey key, StreamData? data, bool replace)
|
||||
{
|
||||
var db = _multi.GetDatabase();
|
||||
return db.HashSet(
|
||||
_key,
|
||||
JsonConvert.SerializeObject(key),
|
||||
JsonConvert.SerializeObject(data),
|
||||
when: replace ? When.Always : When.NotExists);
|
||||
}
|
||||
|
||||
public void CacheDeleteData(StreamDataKey key)
|
||||
{
|
||||
var db = _multi.GetDatabase();
|
||||
db.HashDelete(_key, JsonConvert.SerializeObject(key));
|
||||
}
|
||||
|
||||
public void CacheClearAllData()
|
||||
{
|
||||
var db = _multi.GetDatabase();
|
||||
db.KeyDelete(_key);
|
||||
}
|
||||
|
||||
public Dictionary<StreamDataKey, StreamData?> CacheGetAllData()
|
||||
{
|
||||
var db = _multi.GetDatabase();
|
||||
if (!db.KeyExists(_key))
|
||||
{
|
||||
return new Dictionary<StreamDataKey, StreamData?>();
|
||||
}
|
||||
|
||||
public async Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
{
|
||||
// loop through all providers and see which regex matches
|
||||
foreach (var (_, provider) in _streamProviders)
|
||||
{
|
||||
var isValid = await provider.IsValidUrl(url);
|
||||
if (!isValid)
|
||||
continue;
|
||||
// if it's not a valid url, try another provider
|
||||
var data = await provider.GetStreamDataByUrlAsync(url);
|
||||
return data;
|
||||
}
|
||||
return db.HashGetAll(_key)
|
||||
.ToDictionary(
|
||||
entry => JsonConvert.DeserializeObject<StreamDataKey>(entry.Name),
|
||||
entry => entry.Value.IsNullOrEmpty
|
||||
? default(StreamData)
|
||||
: JsonConvert.DeserializeObject<StreamData>(entry.Value));
|
||||
}
|
||||
|
||||
// if no provider found, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return currently available stream data, get new one if none available, and start tracking the stream.
|
||||
/// </summary>
|
||||
/// <param name="url">Url of the stream</param>
|
||||
/// <returns>Stream data, if any</returns>
|
||||
public async Task<StreamData?> TrackStreamByUrlAsync(string url)
|
||||
public async Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
{
|
||||
// loop through all providers and see which regex matches
|
||||
foreach (var (_, provider) in _streamProviders)
|
||||
{
|
||||
var data = await GetStreamDataByUrlAsync(url);
|
||||
EnsureTracked(data);
|
||||
var isValid = await provider.IsValidUrl(url);
|
||||
if (!isValid)
|
||||
continue;
|
||||
// if it's not a valid url, try another provider
|
||||
var data = await provider.GetStreamDataByUrlAsync(url);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure a stream is tracked using its stream data.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to try to track if not already tracked</param>
|
||||
/// <returns>Whether it's newly added</returns>
|
||||
private bool EnsureTracked(StreamData? data)
|
||||
{
|
||||
// something failed, don't add anything to cache
|
||||
if (data is null)
|
||||
return false;
|
||||
// if no provider found, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
// if stream is found, add it to the cache for tracking only if it doesn't already exist
|
||||
// because stream will be checked and events will fire in a loop. We don't want to override old state
|
||||
return CacheAddData(data.CreateKey(), data, replace: false);
|
||||
}
|
||||
/// <summary>
|
||||
/// Return currently available stream data, get new one if none available, and start tracking the stream.
|
||||
/// </summary>
|
||||
/// <param name="url">Url of the stream</param>
|
||||
/// <returns>Stream data, if any</returns>
|
||||
public async Task<StreamData?> TrackStreamByUrlAsync(string url)
|
||||
{
|
||||
var data = await GetStreamDataByUrlAsync(url);
|
||||
EnsureTracked(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public void UntrackStreamByKey(in StreamDataKey key)
|
||||
{
|
||||
CacheDeleteData(key);
|
||||
}
|
||||
/// <summary>
|
||||
/// Make sure a stream is tracked using its stream data.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to try to track if not already tracked</param>
|
||||
/// <returns>Whether it's newly added</returns>
|
||||
private bool EnsureTracked(StreamData? data)
|
||||
{
|
||||
// something failed, don't add anything to cache
|
||||
if (data is null)
|
||||
return false;
|
||||
|
||||
// if stream is found, add it to the cache for tracking only if it doesn't already exist
|
||||
// because stream will be checked and events will fire in a loop. We don't want to override old state
|
||||
return CacheAddData(data.CreateKey(), data, replace: false);
|
||||
}
|
||||
|
||||
public void UntrackStreamByKey(in StreamDataKey key)
|
||||
{
|
||||
CacheDeleteData(key);
|
||||
}
|
||||
}
|
@@ -1,111 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
#nullable enable
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers;
|
||||
|
||||
public class PicartoProvider : Provider
|
||||
{
|
||||
public class PicartoProvider : Provider
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
private static Regex Regex { get; } = new Regex(@"picarto.tv/(?<name>.+[^/])/?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public override FollowedStream.FType Platform => FollowedStream.FType.Picarto;
|
||||
|
||||
public PicartoProvider(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
private static Regex Regex { get; } = new Regex(@"picarto.tv/(?<name>.+[^/])/?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public override Task<bool> IsValidUrl(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (!match.Success)
|
||||
return Task.FromResult(false);
|
||||
|
||||
public override FollowedStream.FType Platform => FollowedStream.FType.Picarto;
|
||||
// var username = match.Groups["name"].Value;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public PicartoProvider(IHttpClientFactory httpClientFactory)
|
||||
public override Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (match.Success)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
var name = match.Groups["name"].Value;
|
||||
return GetStreamDataAsync(name);
|
||||
}
|
||||
|
||||
public override Task<bool> IsValidUrl(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (!match.Success)
|
||||
return Task.FromResult(false);
|
||||
return Task.FromResult<StreamData?>(null);
|
||||
}
|
||||
|
||||
// var username = match.Groups["name"].Value;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
public override async Task<StreamData?> GetStreamDataAsync(string id)
|
||||
{
|
||||
var data = await GetStreamDataAsync(new List<string> {id});
|
||||
|
||||
public override Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
return data.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async override Task<List<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||
{
|
||||
if (logins.Count == 0)
|
||||
return new List<StreamData>();
|
||||
|
||||
using (var http = _httpClientFactory.CreateClient())
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (match.Success)
|
||||
var toReturn = new List<StreamData>();
|
||||
foreach (var login in logins)
|
||||
{
|
||||
var name = match.Groups["name"].Value;
|
||||
return GetStreamDataAsync(name);
|
||||
}
|
||||
|
||||
return Task.FromResult<StreamData?>(null);
|
||||
}
|
||||
|
||||
public override async Task<StreamData?> GetStreamDataAsync(string id)
|
||||
{
|
||||
var data = await GetStreamDataAsync(new List<string> {id});
|
||||
|
||||
return data.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async override Task<List<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||
{
|
||||
if (logins.Count == 0)
|
||||
return new List<StreamData>();
|
||||
|
||||
using (var http = _httpClientFactory.CreateClient())
|
||||
{
|
||||
var toReturn = new List<StreamData>();
|
||||
foreach (var login in logins)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
// get id based on the username
|
||||
var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
|
||||
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
// get id based on the username
|
||||
var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
|
||||
|
||||
if (!res.IsSuccessStatusCode)
|
||||
continue;
|
||||
if (!res.IsSuccessStatusCode)
|
||||
continue;
|
||||
|
||||
var userData = JsonConvert.DeserializeObject<PicartoChannelResponse>(await res.Content.ReadAsStringAsync())!;
|
||||
var userData = JsonConvert.DeserializeObject<PicartoChannelResponse>(await res.Content.ReadAsStringAsync())!;
|
||||
|
||||
toReturn.Add(ToStreamData(userData));
|
||||
_failingStreams.TryRemove(login, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, $"Something went wrong retreiving {Platform} stream data for {login}: {ex.Message}");
|
||||
_failingStreams.TryAdd(login, DateTime.UtcNow);
|
||||
}
|
||||
toReturn.Add(ToStreamData(userData));
|
||||
_failingStreams.TryRemove(login, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, $"Something went wrong retreiving {Platform} stream data for {login}: {ex.Message}");
|
||||
_failingStreams.TryAdd(login, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
private StreamData ToStreamData(PicartoChannelResponse stream)
|
||||
{
|
||||
return new StreamData()
|
||||
{
|
||||
StreamType = FollowedStream.FType.Picarto,
|
||||
Name = stream.Name,
|
||||
UniqueName = stream.Name,
|
||||
Viewers = stream.Viewers,
|
||||
Title = stream.Title,
|
||||
IsLive = stream.Online,
|
||||
Preview = stream.Thumbnails.Web,
|
||||
Game = stream.Category,
|
||||
StreamUrl = $"https://picarto.tv/{stream.Name}",
|
||||
AvatarUrl = stream.Avatar
|
||||
};
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
private StreamData ToStreamData(PicartoChannelResponse stream)
|
||||
{
|
||||
return new StreamData()
|
||||
{
|
||||
StreamType = FollowedStream.FType.Picarto,
|
||||
Name = stream.Name,
|
||||
UniqueName = stream.Name,
|
||||
Viewers = stream.Viewers,
|
||||
Title = stream.Title,
|
||||
IsLive = stream.Online,
|
||||
Preview = stream.Thumbnails.Web,
|
||||
Game = stream.Category,
|
||||
StreamUrl = $"https://picarto.tv/{stream.Name}",
|
||||
AvatarUrl = stream.Avatar
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,66 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
#nullable enable
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class implemented by providers of all supported platforms
|
||||
/// </summary>
|
||||
public abstract class Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract class implemented by providers of all supported platforms
|
||||
/// Type of the platform.
|
||||
/// </summary>
|
||||
public abstract class Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the platform.
|
||||
/// </summary>
|
||||
public abstract FollowedStream.FType Platform { get; }
|
||||
public abstract FollowedStream.FType Platform { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the specified url is a valid stream url for this platform.
|
||||
/// </summary>
|
||||
/// <param name="url">Url to check</param>
|
||||
/// <returns>True if valid, otherwise false</returns>
|
||||
public abstract Task<bool> IsValidUrl(string url);
|
||||
/// <summary>
|
||||
/// Checks whether the specified url is a valid stream url for this platform.
|
||||
/// </summary>
|
||||
/// <param name="url">Url to check</param>
|
||||
/// <returns>True if valid, otherwise false</returns>
|
||||
public abstract Task<bool> IsValidUrl(string url);
|
||||
|
||||
/// <summary>
|
||||
/// Gets stream data of the stream on the specified url on this <see cref="Platform"/>
|
||||
/// </summary>
|
||||
/// <param name="url">Url of the stream</param>
|
||||
/// <returns><see cref="StreamData"/> of the specified stream. Null if none found</returns>
|
||||
public abstract Task<StreamData?> GetStreamDataByUrlAsync(string url);
|
||||
/// <summary>
|
||||
/// Gets stream data of the stream on the specified url on this <see cref="Platform"/>
|
||||
/// </summary>
|
||||
/// <param name="url">Url of the stream</param>
|
||||
/// <returns><see cref="StreamData"/> of the specified stream. Null if none found</returns>
|
||||
public abstract Task<StreamData?> GetStreamDataByUrlAsync(string url);
|
||||
|
||||
/// <summary>
|
||||
/// Gets stream data of the specified id/username on this <see cref="Platform"/>
|
||||
/// </summary>
|
||||
/// <param name="id">Name (or id where applicable) of the user on the platform</param>
|
||||
/// <returns><see cref="StreamData"/> of the user. Null if none found</returns>
|
||||
public abstract Task<StreamData?> GetStreamDataAsync(string id);
|
||||
/// <summary>
|
||||
/// Gets stream data of the specified id/username on this <see cref="Platform"/>
|
||||
/// </summary>
|
||||
/// <param name="id">Name (or id where applicable) of the user on the platform</param>
|
||||
/// <returns><see cref="StreamData"/> of the user. Null if none found</returns>
|
||||
public abstract Task<StreamData?> GetStreamDataAsync(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets stream data of all specified ids/usernames on this <see cref="Platform"/>
|
||||
/// </summary>
|
||||
/// <param name="usernames">List of ids/usernames</param>
|
||||
/// <returns><see cref="StreamData"/> of all users, in the same order. Null for every id/user not found.</returns>
|
||||
public abstract Task<List<StreamData>> GetStreamDataAsync(List<string> usernames);
|
||||
/// <summary>
|
||||
/// Gets stream data of all specified ids/usernames on this <see cref="Platform"/>
|
||||
/// </summary>
|
||||
/// <param name="usernames">List of ids/usernames</param>
|
||||
/// <returns><see cref="StreamData"/> of all users, in the same order. Null for every id/user not found.</returns>
|
||||
public abstract Task<List<StreamData>> GetStreamDataAsync(List<string> usernames);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream usernames which fail to execute due to an error, and when they started throwing errors.
|
||||
/// This can happen if stream name is invalid, or if the stream doesn't exist anymore.
|
||||
/// </summary>
|
||||
public IEnumerable<(string Login, DateTime ErroringSince)> FailingStreams =>
|
||||
_failingStreams.Select(entry => (entry.Key, entry.Value)).ToList();
|
||||
/// <summary>
|
||||
/// Gets the stream usernames which fail to execute due to an error, and when they started throwing errors.
|
||||
/// This can happen if stream name is invalid, or if the stream doesn't exist anymore.
|
||||
/// </summary>
|
||||
public IEnumerable<(string Login, DateTime ErroringSince)> FailingStreams =>
|
||||
_failingStreams.Select(entry => (entry.Key, entry.Value)).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// When was the first time the stream continually had errors while being retrieved
|
||||
/// </summary>
|
||||
protected readonly ConcurrentDictionary<string, DateTime> _failingStreams =
|
||||
new ConcurrentDictionary<string, DateTime>();
|
||||
/// <summary>
|
||||
/// When was the first time the stream continually had errors while being retrieved
|
||||
/// </summary>
|
||||
protected readonly ConcurrentDictionary<string, DateTime> _failingStreams =
|
||||
new ConcurrentDictionary<string, DateTime>();
|
||||
|
||||
public void ClearErrorsFor(string login)
|
||||
=> _failingStreams.TryRemove(login, out _);
|
||||
}
|
||||
public void ClearErrorsFor(string login)
|
||||
=> _failingStreams.TryRemove(login, out _);
|
||||
}
|
@@ -1,139 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
#nullable enable
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers;
|
||||
|
||||
public class TwitchProvider : Provider
|
||||
{
|
||||
public class TwitchProvider : Provider
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
private static Regex Regex { get; } = new Regex(@"twitch.tv/(?<name>.+[^/])/?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public override FollowedStream.FType Platform => FollowedStream.FType.Twitch;
|
||||
|
||||
public TwitchProvider(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
private static Regex Regex { get; } = new Regex(@"twitch.tv/(?<name>.+[^/])/?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public override Task<bool> IsValidUrl(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (!match.Success)
|
||||
return Task.FromResult(false);
|
||||
|
||||
public override FollowedStream.FType Platform => FollowedStream.FType.Twitch;
|
||||
// var username = match.Groups["name"].Value;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public TwitchProvider(IHttpClientFactory httpClientFactory)
|
||||
public override Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (match.Success)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
var name = match.Groups["name"].Value;
|
||||
return GetStreamDataAsync(name);
|
||||
}
|
||||
|
||||
public override Task<bool> IsValidUrl(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (!match.Success)
|
||||
return Task.FromResult(false);
|
||||
return Task.FromResult<StreamData?>(null);
|
||||
}
|
||||
|
||||
// var username = match.Groups["name"].Value;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
public override async Task<StreamData?> GetStreamDataAsync(string id)
|
||||
{
|
||||
var data = await GetStreamDataAsync(new List<string> {id});
|
||||
|
||||
public override Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
return data.FirstOrDefault();
|
||||
}
|
||||
|
||||
public override async Task<List<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||
{
|
||||
if (logins.Count == 0)
|
||||
return new List<StreamData>();
|
||||
|
||||
using (var http = _httpClientFactory.CreateClient())
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (match.Success)
|
||||
http.DefaultRequestHeaders.Add("Client-Id", "67w6z9i09xv2uoojdm9l0wsyph4hxo6");
|
||||
http.DefaultRequestHeaders.Add("Accept", "application/vnd.twitchtv.v5+json");
|
||||
|
||||
var toReturn = new List<StreamData>();
|
||||
foreach (var login in logins)
|
||||
{
|
||||
var name = match.Groups["name"].Value;
|
||||
return GetStreamDataAsync(name);
|
||||
}
|
||||
|
||||
return Task.FromResult<StreamData?>(null);
|
||||
}
|
||||
|
||||
public override async Task<StreamData?> GetStreamDataAsync(string id)
|
||||
{
|
||||
var data = await GetStreamDataAsync(new List<string> {id});
|
||||
|
||||
return data.FirstOrDefault();
|
||||
}
|
||||
|
||||
public override async Task<List<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||
{
|
||||
if (logins.Count == 0)
|
||||
return new List<StreamData>();
|
||||
|
||||
using (var http = _httpClientFactory.CreateClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Add("Client-Id", "67w6z9i09xv2uoojdm9l0wsyph4hxo6");
|
||||
http.DefaultRequestHeaders.Add("Accept", "application/vnd.twitchtv.v5+json");
|
||||
|
||||
var toReturn = new List<StreamData>();
|
||||
foreach (var login in logins)
|
||||
try
|
||||
{
|
||||
try
|
||||
// get id based on the username
|
||||
var idsStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/users?login={login}");
|
||||
var userData = JsonConvert.DeserializeObject<TwitchUsersResponseV5>(idsStr);
|
||||
var user = userData?.Users.FirstOrDefault();
|
||||
|
||||
// if user can't be found, skip, it means there is no such user
|
||||
if (user is null)
|
||||
continue;
|
||||
|
||||
// get stream data
|
||||
var str = await http.GetStringAsync($"https://api.twitch.tv/kraken/streams/{user.Id}");
|
||||
var resObj =
|
||||
JsonConvert.DeserializeAnonymousType(str, new {Stream = new TwitchResponseV5.Stream()});
|
||||
|
||||
// if stream is null, user is not streaming
|
||||
if (resObj?.Stream is null)
|
||||
{
|
||||
// get id based on the username
|
||||
var idsStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/users?login={login}");
|
||||
var userData = JsonConvert.DeserializeObject<TwitchUsersResponseV5>(idsStr);
|
||||
var user = userData?.Users.FirstOrDefault();
|
||||
|
||||
// if user can't be found, skip, it means there is no such user
|
||||
if (user is null)
|
||||
continue;
|
||||
|
||||
// get stream data
|
||||
var str = await http.GetStringAsync($"https://api.twitch.tv/kraken/streams/{user.Id}");
|
||||
var resObj =
|
||||
JsonConvert.DeserializeAnonymousType(str, new {Stream = new TwitchResponseV5.Stream()});
|
||||
|
||||
// if stream is null, user is not streaming
|
||||
if (resObj?.Stream is null)
|
||||
{
|
||||
// if user is not streaming, get his offline banner
|
||||
var chStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/channels/{user.Id}");
|
||||
var ch = JsonConvert.DeserializeObject<TwitchResponseV5.Channel>(chStr)!;
|
||||
// if user is not streaming, get his offline banner
|
||||
var chStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/channels/{user.Id}");
|
||||
var ch = JsonConvert.DeserializeObject<TwitchResponseV5.Channel>(chStr)!;
|
||||
|
||||
toReturn.Add(new StreamData
|
||||
{
|
||||
StreamType = FollowedStream.FType.Twitch,
|
||||
Name = ch.DisplayName,
|
||||
UniqueName = ch.Name,
|
||||
Title = ch.Status,
|
||||
IsLive = false,
|
||||
AvatarUrl = ch.Logo,
|
||||
StreamUrl = $"https://twitch.tv/{ch.Name}",
|
||||
Preview = ch.VideoBanner // set video banner as the preview,
|
||||
});
|
||||
continue; // move on
|
||||
}
|
||||
toReturn.Add(new StreamData
|
||||
{
|
||||
StreamType = FollowedStream.FType.Twitch,
|
||||
Name = ch.DisplayName,
|
||||
UniqueName = ch.Name,
|
||||
Title = ch.Status,
|
||||
IsLive = false,
|
||||
AvatarUrl = ch.Logo,
|
||||
StreamUrl = $"https://twitch.tv/{ch.Name}",
|
||||
Preview = ch.VideoBanner // set video banner as the preview,
|
||||
});
|
||||
continue; // move on
|
||||
}
|
||||
|
||||
toReturn.Add(ToStreamData(resObj.Stream));
|
||||
_failingStreams.TryRemove(login, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"Something went wrong retreiving {Platform} stream data for {login}: {ex.Message}");
|
||||
_failingStreams.TryAdd(login, DateTime.UtcNow);
|
||||
}
|
||||
toReturn.Add(ToStreamData(resObj.Stream));
|
||||
_failingStreams.TryRemove(login, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"Something went wrong retreiving {Platform} stream data for {login}: {ex.Message}");
|
||||
_failingStreams.TryAdd(login, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
private StreamData ToStreamData(TwitchResponseV5.Stream stream)
|
||||
{
|
||||
return new StreamData()
|
||||
{
|
||||
StreamType = FollowedStream.FType.Twitch,
|
||||
Name = stream.Channel.DisplayName,
|
||||
UniqueName = stream.Channel.Name,
|
||||
Viewers = stream.Viewers,
|
||||
Title = stream.Channel.Status,
|
||||
IsLive = true,
|
||||
Preview = stream.Preview.Large,
|
||||
Game = stream.Channel.Game,
|
||||
StreamUrl = $"https://twitch.tv/{stream.Channel.Name}",
|
||||
AvatarUrl = stream.Channel.Logo
|
||||
};
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
private StreamData ToStreamData(TwitchResponseV5.Stream stream)
|
||||
{
|
||||
return new StreamData()
|
||||
{
|
||||
StreamType = FollowedStream.FType.Twitch,
|
||||
Name = stream.Channel.DisplayName,
|
||||
UniqueName = stream.Channel.Name,
|
||||
Viewers = stream.Viewers,
|
||||
Title = stream.Channel.Status,
|
||||
IsLive = true,
|
||||
Preview = stream.Preview.Large,
|
||||
Game = stream.Channel.Game,
|
||||
StreamUrl = $"https://twitch.tv/{stream.Channel.Name}",
|
||||
AvatarUrl = stream.Channel.Logo
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
public class TimeData
|
||||
{
|
||||
public class TimeData
|
||||
{
|
||||
public string Address { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
public string TimeZoneName { get; set; }
|
||||
}
|
||||
}
|
||||
public string Address { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
public string TimeZoneName { get; set; }
|
||||
}
|
@@ -1,21 +1,20 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class TimeZoneResult
|
||||
{
|
||||
public class TimeZoneResult
|
||||
{
|
||||
[JsonProperty("abbreviation")]
|
||||
public string TimezoneName { get; set; }
|
||||
[JsonProperty("timestamp")]
|
||||
public int Timestamp { get; set; }
|
||||
}
|
||||
|
||||
public class LocationIqResponse
|
||||
{
|
||||
public float Lat { get; set; }
|
||||
public float Lon { get; set; }
|
||||
|
||||
[JsonProperty("display_name")]
|
||||
public string DisplayName { get; set; }
|
||||
}
|
||||
[JsonProperty("abbreviation")]
|
||||
public string TimezoneName { get; set; }
|
||||
[JsonProperty("timestamp")]
|
||||
public int Timestamp { get; set; }
|
||||
}
|
||||
|
||||
public class LocationIqResponse
|
||||
{
|
||||
public float Lat { get; set; }
|
||||
public float Lon { get; set; }
|
||||
|
||||
[JsonProperty("display_name")]
|
||||
public string DisplayName { get; set; }
|
||||
}
|
@@ -1,13 +1,12 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class UrbanResponse
|
||||
{
|
||||
public class UrbanResponse
|
||||
{
|
||||
public UrbanDef[] List { get; set; }
|
||||
}
|
||||
public class UrbanDef
|
||||
{
|
||||
public string Word { get; set; }
|
||||
public string Definition { get; set; }
|
||||
public string Permalink { get; set; }
|
||||
}
|
||||
public UrbanDef[] List { get; set; }
|
||||
}
|
||||
public class UrbanDef
|
||||
{
|
||||
public string Word { get; set; }
|
||||
public string Definition { get; set; }
|
||||
public string Permalink { get; set; }
|
||||
}
|
@@ -1,66 +1,64 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class Coord
|
||||
{
|
||||
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; }
|
||||
}
|
||||
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; }
|
||||
}
|
@@ -1,18 +1,17 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class WikipediaApiModel
|
||||
{
|
||||
public class WikipediaApiModel
|
||||
public WikipediaQuery Query { get; set; }
|
||||
|
||||
public class WikipediaQuery
|
||||
{
|
||||
public WikipediaQuery Query { get; set; }
|
||||
public WikipediaPage[] Pages { get; set; }
|
||||
|
||||
public class WikipediaQuery
|
||||
public class WikipediaPage
|
||||
{
|
||||
public WikipediaPage[] Pages { get; set; }
|
||||
|
||||
public class WikipediaPage
|
||||
{
|
||||
public bool Missing { get; set; } = false;
|
||||
public string FullUrl { get; set; }
|
||||
}
|
||||
public bool Missing { get; set; } = false;
|
||||
public string FullUrl { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,8 @@
|
||||
namespace NadekoBot.Modules.Searches.Common
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
public class WoWJoke
|
||||
{
|
||||
public class WoWJoke
|
||||
{
|
||||
public string Question { get; set; }
|
||||
public string Answer { get; set; }
|
||||
public override string ToString() => $"`{Question}`\n\n**{Answer}**";
|
||||
}
|
||||
}
|
||||
public string Question { get; set; }
|
||||
public string Answer { get; set; }
|
||||
public override string ToString() => $"`{Question}`\n\n**{Answer}**";
|
||||
}
|
@@ -4,59 +4,58 @@ using NadekoBot.Modules.Searches.Services;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
public class CryptoCommands : NadekoSubmodule<CryptoService>
|
||||
{
|
||||
public class CryptoCommands : NadekoSubmodule<CryptoService>
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Crypto(string name)
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Crypto(string name)
|
||||
name = name?.ToUpperInvariant();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
var (crypto, nearest) = await _service.GetCryptoData(name).ConfigureAwait(false);
|
||||
|
||||
if (nearest is not null)
|
||||
{
|
||||
name = name?.ToUpperInvariant();
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.crypto_not_found))
|
||||
.WithDescription(GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})"))));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
var (crypto, nearest) = await _service.GetCryptoData(name).ConfigureAwait(false);
|
||||
|
||||
if (nearest is not null)
|
||||
if (await PromptUserConfirmAsync(embed).ConfigureAwait(false))
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.crypto_not_found))
|
||||
.WithDescription(GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})"))));
|
||||
|
||||
if (await PromptUserConfirmAsync(embed).ConfigureAwait(false))
|
||||
{
|
||||
crypto = nearest;
|
||||
}
|
||||
crypto = nearest;
|
||||
}
|
||||
|
||||
if (crypto is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.crypto_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var sevenDay = decimal.TryParse(crypto.Quote.Usd.Percent_Change_7d, out var sd)
|
||||
? sd.ToString("F2")
|
||||
: crypto.Quote.Usd.Percent_Change_7d;
|
||||
|
||||
var lastDay = decimal.TryParse(crypto.Quote.Usd.Percent_Change_24h, out var ld)
|
||||
? ld.ToString("F2")
|
||||
: crypto.Quote.Usd.Percent_Change_24h;
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"{crypto.Name} ({crypto.Symbol})")
|
||||
.WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/")
|
||||
.WithThumbnailUrl($"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png")
|
||||
.AddField(GetText(strs.market_cap), $"${crypto.Quote.Usd.Market_Cap:n0}", true)
|
||||
.AddField(GetText(strs.price), $"${crypto.Quote.Usd.Price}", true)
|
||||
.AddField(GetText(strs.volume_24h), $"${crypto.Quote.Usd.Volume_24h:n0}", true)
|
||||
.AddField(GetText(strs.change_7d_24h), $"{sevenDay}% / {lastDay}%", true)
|
||||
.WithImageUrl($"https://s3.coinmarketcap.com/generated/sparklines/web/7d/usd/{crypto.Id}.png")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (crypto is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.crypto_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var sevenDay = decimal.TryParse(crypto.Quote.Usd.Percent_Change_7d, out var sd)
|
||||
? sd.ToString("F2")
|
||||
: crypto.Quote.Usd.Percent_Change_7d;
|
||||
|
||||
var lastDay = decimal.TryParse(crypto.Quote.Usd.Percent_Change_24h, out var ld)
|
||||
? ld.ToString("F2")
|
||||
: crypto.Quote.Usd.Percent_Change_24h;
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"{crypto.Name} ({crypto.Symbol})")
|
||||
.WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/")
|
||||
.WithThumbnailUrl($"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png")
|
||||
.AddField(GetText(strs.market_cap), $"${crypto.Quote.Usd.Market_Cap:n0}", true)
|
||||
.AddField(GetText(strs.price), $"${crypto.Quote.Usd.Price}", true)
|
||||
.AddField(GetText(strs.volume_24h), $"${crypto.Quote.Usd.Volume_24h:n0}", true)
|
||||
.AddField(GetText(strs.change_7d_24h), $"{sevenDay}% / {lastDay}%", true)
|
||||
.WithImageUrl($"https://s3.coinmarketcap.com/generated/sparklines/web/7d/usd/{crypto.Id}.png")).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,114 +3,110 @@ using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class FeedCommands : NadekoSubmodule<FeedsService>
|
||||
{
|
||||
[Group]
|
||||
public class FeedCommands : NadekoSubmodule<FeedsService>
|
||||
{
|
||||
private static readonly Regex YtChannelRegex =
|
||||
new Regex(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?<channelid>[a-zA-Z0-9\-]{1,})");
|
||||
private static readonly Regex YtChannelRegex =
|
||||
new Regex(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?<channelid>[a-zA-Z0-9\-]{1,})");
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public Task YtUploadNotif(string url, [Leftover] ITextChannel channel = null)
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public Task YtUploadNotif(string url, [Leftover] ITextChannel channel = null)
|
||||
{
|
||||
var m = YtChannelRegex.Match(url);
|
||||
if (!m.Success)
|
||||
{
|
||||
var m = YtChannelRegex.Match(url);
|
||||
if (!m.Success)
|
||||
{
|
||||
return ReplyErrorLocalizedAsync(strs.invalid_input);
|
||||
}
|
||||
|
||||
var channelId = m.Groups["channelid"].Value;
|
||||
|
||||
return Feed("https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId, channel);
|
||||
return ReplyErrorLocalizedAsync(strs.invalid_input);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task Feed(string url, [Leftover] ITextChannel channel = null)
|
||||
var channelId = m.Groups["channelid"].Value;
|
||||
|
||||
return Feed("https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId, channel);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task Feed(string url, [Leftover] ITextChannel channel = null)
|
||||
{
|
||||
var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
|
||||
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
if (success)
|
||||
{
|
||||
var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
|
||||
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
channel = channel ?? (ITextChannel)ctx.Channel;
|
||||
try
|
||||
{
|
||||
var feeds = await CodeHollow.FeedReader.FeedReader.ReadAsync(url).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information(ex, "Unable to get feeds from that url");
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
success = _service.AddFeed(ctx.Guild.Id, channel.Id, url);
|
||||
if (success)
|
||||
{
|
||||
channel = channel ?? (ITextChannel)ctx.Channel;
|
||||
try
|
||||
{
|
||||
var feeds = await CodeHollow.FeedReader.FeedReader.ReadAsync(url).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information(ex, "Unable to get feeds from that url");
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
success = _service.AddFeed(ctx.Guild.Id, channel.Id, url);
|
||||
if (success)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_added).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_not_valid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task FeedRemove(int index)
|
||||
{
|
||||
if (_service.RemoveFeed(ctx.Guild.Id, --index))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_removed).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.feed_out_of_range).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task FeedList()
|
||||
{
|
||||
var feeds = _service.GetFeeds(ctx.Guild.Id);
|
||||
|
||||
if (!feeds.Any())
|
||||
{
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.feed_no_feed)))
|
||||
.ConfigureAwait(false);
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_added).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(0, (cur) =>
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.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).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_not_valid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task FeedRemove(int index)
|
||||
{
|
||||
if (_service.RemoveFeed(ctx.Guild.Id, --index))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_removed).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.feed_out_of_range).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task FeedList()
|
||||
{
|
||||
var feeds = _service.GetFeeds(ctx.Guild.Id);
|
||||
|
||||
if (!feeds.Any())
|
||||
{
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.feed_no_feed)))
|
||||
.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(0, (cur) =>
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.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).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,62 +1,59 @@
|
||||
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
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class JokeCommands : NadekoSubmodule<SearchesService>
|
||||
{
|
||||
[Group]
|
||||
public class JokeCommands : NadekoSubmodule<SearchesService>
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Yomama()
|
||||
{
|
||||
await SendConfirmAsync(await _service.GetYomamaJoke().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Yomama()
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Randjoke()
|
||||
{
|
||||
var (setup, punchline) = await _service.GetRandomJoke().ConfigureAwait(false);
|
||||
await SendConfirmAsync(setup, punchline).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task ChuckNorris()
|
||||
{
|
||||
await SendConfirmAsync(await _service.GetChuckNorrisJoke().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task WowJoke()
|
||||
{
|
||||
if (!_service.WowJokes.Any())
|
||||
{
|
||||
await SendConfirmAsync(await _service.GetYomamaJoke().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.jokes_not_loaded).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var joke = _service.WowJokes[new NadekoRandom().Next(0, _service.WowJokes.Count)];
|
||||
await SendConfirmAsync(joke.Question, joke.Answer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Randjoke()
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task MagicItem()
|
||||
{
|
||||
if (!_service.WowJokes.Any())
|
||||
{
|
||||
var (setup, punchline) = await _service.GetRandomJoke().ConfigureAwait(false);
|
||||
await SendConfirmAsync(setup, punchline).ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.magicitems_not_loaded).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var item = _service.MagicItems[new NadekoRandom().Next(0, _service.MagicItems.Count)];
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task ChuckNorris()
|
||||
{
|
||||
await SendConfirmAsync(await _service.GetChuckNorrisJoke().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task WowJoke()
|
||||
{
|
||||
if (!_service.WowJokes.Any())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.jokes_not_loaded).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var joke = _service.WowJokes[new NadekoRandom().Next(0, _service.WowJokes.Count)];
|
||||
await SendConfirmAsync(joke.Question, joke.Answer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task MagicItem()
|
||||
{
|
||||
if (!_service.WowJokes.Any())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.magicitems_not_loaded).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var item = _service.MagicItems[new NadekoRandom().Next(0, _service.MagicItems.Count)];
|
||||
|
||||
await SendConfirmAsync("✨" + item.Name, item.Description).ConfigureAwait(false);
|
||||
}
|
||||
await SendConfirmAsync("✨" + item.Name, item.Description).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,109 +1,105 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class MemegenCommands : NadekoSubmodule
|
||||
{
|
||||
[Group]
|
||||
public class MemegenCommands : NadekoSubmodule
|
||||
private class MemegenTemplate
|
||||
{
|
||||
private class MemegenTemplate
|
||||
public string Name { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
private static readonly ImmutableDictionary<char, string> _map = new Dictionary<char, string>()
|
||||
{
|
||||
{'?', "~q"},
|
||||
{'%', "~p"},
|
||||
{'#', "~h"},
|
||||
{'/', "~s"},
|
||||
{' ', "-"},
|
||||
{'-', "--"},
|
||||
{'_', "__"},
|
||||
{'"', "''"}
|
||||
|
||||
}.ToImmutableDictionary();
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public MemegenCommands(IHttpClientFactory factory)
|
||||
{
|
||||
_httpFactory = factory;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Memelist(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
using (var http = _httpFactory.CreateClient("memelist"))
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
private static readonly ImmutableDictionary<char, string> _map = new Dictionary<char, string>()
|
||||
{
|
||||
{'?', "~q"},
|
||||
{'%', "~p"},
|
||||
{'#', "~h"},
|
||||
{'/', "~s"},
|
||||
{' ', "-"},
|
||||
{'-', "--"},
|
||||
{'_', "__"},
|
||||
{'"', "''"}
|
||||
|
||||
}.ToImmutableDictionary();
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public MemegenCommands(IHttpClientFactory factory)
|
||||
{
|
||||
_httpFactory = factory;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Memelist(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
using (var http = _httpFactory.CreateClient("memelist"))
|
||||
{
|
||||
var res = await http.GetAsync("https://api.memegen.link/templates/")
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var rawJson = await res.Content.ReadAsStringAsync();
|
||||
|
||||
var data = JsonConvert.DeserializeObject<List<MemegenTemplate>>(rawJson);
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var templates = "";
|
||||
foreach (var template in data.Skip(curPage * 15).Take(15))
|
||||
{
|
||||
templates += $"**{template.Name}:**\n key: `{template.Id}`\n";
|
||||
}
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(templates);
|
||||
|
||||
return embed;
|
||||
}, data.Count, 15).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Memegen(string meme, [Leftover] string memeText = null)
|
||||
{
|
||||
var memeUrl = $"http://api.memegen.link/{meme}";
|
||||
if (!string.IsNullOrWhiteSpace(memeText))
|
||||
{
|
||||
var memeTextArray = memeText.Split(';');
|
||||
foreach(var text in memeTextArray)
|
||||
{
|
||||
var newText = Replace(text);
|
||||
memeUrl += $"/{newText}";
|
||||
}
|
||||
}
|
||||
memeUrl += ".png";
|
||||
await ctx.Channel.SendMessageAsync(memeUrl)
|
||||
var res = await http.GetAsync("https://api.memegen.link/templates/")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string Replace(string input)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var rawJson = await res.Content.ReadAsStringAsync();
|
||||
|
||||
var data = JsonConvert.DeserializeObject<List<MemegenTemplate>>(rawJson);
|
||||
|
||||
foreach (var c in input)
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
if (_map.TryGetValue(c, out var tmp))
|
||||
sb.Append(tmp);
|
||||
else
|
||||
sb.Append(c);
|
||||
}
|
||||
var templates = "";
|
||||
foreach (var template in data.Skip(curPage * 15).Take(15))
|
||||
{
|
||||
templates += $"**{template.Name}:**\n key: `{template.Id}`\n";
|
||||
}
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(templates);
|
||||
|
||||
return sb.ToString();
|
||||
return embed;
|
||||
}, data.Count, 15).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Memegen(string meme, [Leftover] string memeText = null)
|
||||
{
|
||||
var memeUrl = $"http://api.memegen.link/{meme}";
|
||||
if (!string.IsNullOrWhiteSpace(memeText))
|
||||
{
|
||||
var memeTextArray = memeText.Split(';');
|
||||
foreach(var text in memeTextArray)
|
||||
{
|
||||
var newText = Replace(text);
|
||||
memeUrl += $"/{newText}";
|
||||
}
|
||||
}
|
||||
memeUrl += ".png";
|
||||
await ctx.Channel.SendMessageAsync(memeUrl)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string Replace(string input)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (_map.TryGetValue(c, out var tmp))
|
||||
sb.Append(tmp);
|
||||
else
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,327 +1,321 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class OsuCommands : NadekoSubmodule
|
||||
{
|
||||
[Group]
|
||||
public class OsuCommands : NadekoSubmodule
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public OsuCommands(IBotCredentials creds, IHttpClientFactory factory)
|
||||
{
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
_creds = creds;
|
||||
_httpFactory = factory;
|
||||
}
|
||||
|
||||
public OsuCommands(IBotCredentials creds, IHttpClientFactory factory)
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Osu(string user, [Leftover] string mode = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(user))
|
||||
return;
|
||||
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
_creds = creds;
|
||||
_httpFactory = factory;
|
||||
}
|
||||
var modeNumber = string.IsNullOrWhiteSpace(mode)
|
||||
? 0
|
||||
: ResolveGameMode(mode);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Osu(string user, [Leftover] string mode = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(user))
|
||||
return;
|
||||
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
try
|
||||
{
|
||||
var modeNumber = string.IsNullOrWhiteSpace(mode)
|
||||
? 0
|
||||
: ResolveGameMode(mode);
|
||||
|
||||
try
|
||||
if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.osu_api_key).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var smode = ResolveGameMode(modeNumber);
|
||||
var userReq = $"https://osu.ppy.sh/api/get_user?k={_creds.OsuApiKey}&u={user}&m={modeNumber}";
|
||||
var userResString = await http.GetStringAsync(userReq)
|
||||
.ConfigureAwait(false);
|
||||
var objs = JsonConvert.DeserializeObject<List<OsuUserData>>(userResString);
|
||||
|
||||
if (objs.Count == 0)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = objs[0];
|
||||
var userId = obj.UserId;
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"osu! {smode} profile for {user}")
|
||||
.WithThumbnailUrl($"https://a.ppy.sh/{userId}")
|
||||
.WithDescription($"https://osu.ppy.sh/u/{userId}")
|
||||
.AddField("Official Rank", $"#{obj.PpRank}", true)
|
||||
.AddField("Country Rank", $"#{obj.PpCountryRank} :flag_{obj.Country.ToLower()}:", true)
|
||||
.AddField("Total PP", Math.Round(obj.PpRaw, 2), true)
|
||||
.AddField("Accuracy", Math.Round(obj.Accuracy, 2) + "%", true)
|
||||
.AddField("Playcount", obj.Playcount, true)
|
||||
.AddField("Level", Math.Round(obj.Level), true)
|
||||
);
|
||||
await ReplyErrorLocalizedAsync(strs.osu_api_key).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.osu_failed).ConfigureAwait(false);
|
||||
Log.Warning(ex, "Osu command failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Gatari(string user, [Leftover] string mode = null)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
var modeNumber = string.IsNullOrWhiteSpace(mode)
|
||||
? 0
|
||||
: ResolveGameMode(mode);
|
||||
|
||||
var modeStr = ResolveGameMode(modeNumber);
|
||||
var resString = await http
|
||||
.GetStringAsync($"https://api.gatari.pw/user/stats?u={user}&mode={modeNumber}")
|
||||
var smode = ResolveGameMode(modeNumber);
|
||||
var userReq = $"https://osu.ppy.sh/api/get_user?k={_creds.OsuApiKey}&u={user}&m={modeNumber}";
|
||||
var userResString = await http.GetStringAsync(userReq)
|
||||
.ConfigureAwait(false);
|
||||
var objs = JsonConvert.DeserializeObject<List<OsuUserData>>(userResString);
|
||||
|
||||
var statsResponse = JsonConvert.DeserializeObject<GatariUserStatsResponse>(resString);
|
||||
if (statsResponse.Code != 200 || statsResponse.Stats.Id == 0)
|
||||
if (objs.Count == 0)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var usrResString = await http.GetStringAsync($"https://api.gatari.pw/users/get?u={user}")
|
||||
.ConfigureAwait(false);
|
||||
var obj = objs[0];
|
||||
var userId = obj.UserId;
|
||||
|
||||
var userData = JsonConvert.DeserializeObject<GatariUserResponse>(usrResString).Users[0];
|
||||
var userStats = statsResponse.Stats;
|
||||
|
||||
var embed = _eb.Create()
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"osu!Gatari {modeStr} profile for {user}")
|
||||
.WithThumbnailUrl($"https://a.gatari.pw/{userStats.Id}")
|
||||
.WithDescription($"https://osu.gatari.pw/u/{userStats.Id}")
|
||||
.AddField("Official Rank", $"#{userStats.Rank}", true)
|
||||
.AddField("Country Rank", $"#{userStats.CountryRank} :flag_{userData.Country.ToLower()}:", true)
|
||||
.AddField("Total PP", userStats.Pp, true)
|
||||
.AddField("Accuracy", $"{Math.Round(userStats.AvgAccuracy, 2)}%", true)
|
||||
.AddField("Playcount", userStats.Playcount, true)
|
||||
.AddField("Level", userStats.Level, true);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
.WithTitle($"osu! {smode} profile for {user}")
|
||||
.WithThumbnailUrl($"https://a.ppy.sh/{userId}")
|
||||
.WithDescription($"https://osu.ppy.sh/u/{userId}")
|
||||
.AddField("Official Rank", $"#{obj.PpRank}", true)
|
||||
.AddField("Country Rank", $"#{obj.PpCountryRank} :flag_{obj.Country.ToLower()}:", true)
|
||||
.AddField("Total PP", Math.Round(obj.PpRaw, 2), true)
|
||||
.AddField("Accuracy", Math.Round(obj.Accuracy, 2) + "%", true)
|
||||
.AddField("Playcount", obj.Playcount, true)
|
||||
.AddField("Level", Math.Round(obj.Level), true)
|
||||
);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.osu_failed).ConfigureAwait(false);
|
||||
Log.Warning(ex, "Osu command failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Osu5(string user, [Leftover] string mode = null)
|
||||
{;
|
||||
if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Gatari(string user, [Leftover] string mode = null)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
var modeNumber = string.IsNullOrWhiteSpace(mode)
|
||||
? 0
|
||||
: ResolveGameMode(mode);
|
||||
|
||||
var modeStr = ResolveGameMode(modeNumber);
|
||||
var resString = await http
|
||||
.GetStringAsync($"https://api.gatari.pw/user/stats?u={user}&mode={modeNumber}")
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var statsResponse = JsonConvert.DeserializeObject<GatariUserStatsResponse>(resString);
|
||||
if (statsResponse.Code != 200 || statsResponse.Stats.Id == 0)
|
||||
{
|
||||
await SendErrorAsync("An osu! API key is required.").ConfigureAwait(false);
|
||||
await ReplyErrorLocalizedAsync(strs.osu_user_not_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(user))
|
||||
var usrResString = await http.GetStringAsync($"https://api.gatari.pw/users/get?u={user}")
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var userData = JsonConvert.DeserializeObject<GatariUserResponse>(usrResString).Users[0];
|
||||
var userStats = statsResponse.Stats;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"osu!Gatari {modeStr} profile for {user}")
|
||||
.WithThumbnailUrl($"https://a.gatari.pw/{userStats.Id}")
|
||||
.WithDescription($"https://osu.gatari.pw/u/{userStats.Id}")
|
||||
.AddField("Official Rank", $"#{userStats.Rank}", true)
|
||||
.AddField("Country Rank", $"#{userStats.CountryRank} :flag_{userData.Country.ToLower()}:", true)
|
||||
.AddField("Total PP", userStats.Pp, true)
|
||||
.AddField("Accuracy", $"{Math.Round(userStats.AvgAccuracy, 2)}%", true)
|
||||
.AddField("Playcount", userStats.Playcount, true)
|
||||
.AddField("Level", userStats.Level, true);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Osu5(string user, [Leftover] string mode = null)
|
||||
{;
|
||||
if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
|
||||
{
|
||||
await SendErrorAsync("An osu! API key is required.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(user))
|
||||
{
|
||||
await SendErrorAsync("Please provide a username.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
var m = 0;
|
||||
if (!string.IsNullOrWhiteSpace(mode))
|
||||
{
|
||||
await SendErrorAsync("Please provide a username.").ConfigureAwait(false);
|
||||
return;
|
||||
m = ResolveGameMode(mode);
|
||||
}
|
||||
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
var reqString = $"https://osu.ppy.sh/api/get_user_best" +
|
||||
$"?k={_creds.OsuApiKey}" +
|
||||
$"&u={Uri.EscapeDataString(user)}" +
|
||||
$"&type=string" +
|
||||
$"&limit=5" +
|
||||
$"&m={m}";
|
||||
|
||||
var resString = await http.GetStringAsync(reqString).ConfigureAwait(false);
|
||||
var obj = JsonConvert.DeserializeObject<List<OsuUserBests>>(resString);
|
||||
|
||||
var mapTasks = obj.Select(async item =>
|
||||
{
|
||||
var m = 0;
|
||||
if (!string.IsNullOrWhiteSpace(mode))
|
||||
{
|
||||
m = ResolveGameMode(mode);
|
||||
}
|
||||
var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps" +
|
||||
$"?k={_creds.OsuApiKey}" +
|
||||
$"&b={item.BeatmapId}";
|
||||
|
||||
var reqString = $"https://osu.ppy.sh/api/get_user_best" +
|
||||
$"?k={_creds.OsuApiKey}" +
|
||||
$"&u={Uri.EscapeDataString(user)}" +
|
||||
$"&type=string" +
|
||||
$"&limit=5" +
|
||||
$"&m={m}";
|
||||
var mapResString = await http.GetStringAsync(mapReqString).ConfigureAwait(false);
|
||||
var map = JsonConvert.DeserializeObject<List<OsuMapData>>(mapResString).FirstOrDefault();
|
||||
if (map is null)
|
||||
return default;
|
||||
var pp = Math.Round(item.Pp, 2);
|
||||
var acc = CalculateAcc(item, m);
|
||||
var mods = ResolveMods(item.EnabledMods);
|
||||
|
||||
var resString = await http.GetStringAsync(reqString).ConfigureAwait(false);
|
||||
var obj = JsonConvert.DeserializeObject<List<OsuUserBests>>(resString);
|
||||
|
||||
var mapTasks = obj.Select(async item =>
|
||||
{
|
||||
var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps" +
|
||||
$"?k={_creds.OsuApiKey}" +
|
||||
$"&b={item.BeatmapId}";
|
||||
|
||||
var mapResString = await http.GetStringAsync(mapReqString).ConfigureAwait(false);
|
||||
var map = JsonConvert.DeserializeObject<List<OsuMapData>>(mapResString).FirstOrDefault();
|
||||
if (map is null)
|
||||
return default;
|
||||
var pp = Math.Round(item.Pp, 2);
|
||||
var acc = CalculateAcc(item, m);
|
||||
var mods = ResolveMods(item.EnabledMods);
|
||||
|
||||
var title = $"{map.Artist}-{map.Title} ({map.Version})";
|
||||
var desc = $@"[/b/{item.BeatmapId}](https://osu.ppy.sh/b/{item.BeatmapId})
|
||||
var title = $"{map.Artist}-{map.Title} ({map.Version})";
|
||||
var desc = $@"[/b/{item.BeatmapId}](https://osu.ppy.sh/b/{item.BeatmapId})
|
||||
{pp + "pp",-7} | {acc + "%",-7}
|
||||
";
|
||||
if (mods != "+")
|
||||
{
|
||||
desc += Format.Bold(mods);
|
||||
}
|
||||
|
||||
return (title, desc);
|
||||
});
|
||||
|
||||
var eb = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"Top 5 plays for {user}");
|
||||
|
||||
var mapData = await Task.WhenAll(mapTasks);
|
||||
foreach (var (title, desc) in mapData.Where(x => x != default))
|
||||
if (mods != "+")
|
||||
{
|
||||
eb.AddField(title, desc, false);
|
||||
desc += Format.Bold(mods);
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb).ConfigureAwait(false);
|
||||
return (title, desc);
|
||||
});
|
||||
|
||||
var eb = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"Top 5 plays for {user}");
|
||||
|
||||
var mapData = await Task.WhenAll(mapTasks);
|
||||
foreach (var (title, desc) in mapData.Where(x => x != default))
|
||||
{
|
||||
eb.AddField(title, desc, false);
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//https://osu.ppy.sh/wiki/Accuracy
|
||||
private static double CalculateAcc(OsuUserBests play, int mode)
|
||||
{
|
||||
double hitPoints;
|
||||
double totalHits;
|
||||
if (mode == 0)
|
||||
{
|
||||
hitPoints = play.Count50 * 50 +
|
||||
play.Count100 * 100 +
|
||||
play.Count300 * 300;
|
||||
totalHits = play.Count50 + play.Count100 +
|
||||
play.Count300 + play.Countmiss;
|
||||
totalHits *= 300;
|
||||
}
|
||||
else if (mode == 1)
|
||||
{
|
||||
hitPoints = play.Countmiss * 0 + play.Count100 * 0.5 + play.Count300;
|
||||
totalHits = (play.Countmiss + play.Count100 + play.Count300) * 300;
|
||||
hitPoints *= 300;
|
||||
}
|
||||
else if (mode == 2)
|
||||
{
|
||||
hitPoints = play.Count50 + play.Count100 + play.Count300;
|
||||
totalHits = play.Countmiss + play.Count50 + play.Count100 + play.Count300 +
|
||||
play.Countkatu;
|
||||
}
|
||||
else
|
||||
{
|
||||
hitPoints = play.Count50 * 50 +
|
||||
play.Count100 * 100 +
|
||||
play.Countkatu * 200 +
|
||||
(play.Count300 + play.Countgeki) * 300;
|
||||
|
||||
totalHits = (play.Countmiss + play.Count50 + play.Count100 +
|
||||
play.Countkatu + play.Count300 + play.Countgeki) * 300;
|
||||
}
|
||||
|
||||
|
||||
return Math.Round(hitPoints / totalHits * 100, 2);
|
||||
}
|
||||
|
||||
private static int ResolveGameMode(string mode)
|
||||
{
|
||||
switch (mode.ToUpperInvariant())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveGameMode(int mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case 0:
|
||||
return "Standard";
|
||||
case 1:
|
||||
return "Taiko";
|
||||
case 2:
|
||||
return "Catch";
|
||||
case 3:
|
||||
return "Mania";
|
||||
default:
|
||||
return "Standard";
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//https://osu.ppy.sh/wiki/Accuracy
|
||||
private static double CalculateAcc(OsuUserBests play, int mode)
|
||||
{
|
||||
double hitPoints;
|
||||
double totalHits;
|
||||
if (mode == 0)
|
||||
{
|
||||
hitPoints = play.Count50 * 50 +
|
||||
play.Count100 * 100 +
|
||||
play.Count300 * 300;
|
||||
totalHits = play.Count50 + play.Count100 +
|
||||
play.Count300 + play.Countmiss;
|
||||
totalHits *= 300;
|
||||
}
|
||||
else if (mode == 1)
|
||||
{
|
||||
hitPoints = play.Countmiss * 0 + play.Count100 * 0.5 + play.Count300;
|
||||
totalHits = (play.Countmiss + play.Count100 + play.Count300) * 300;
|
||||
hitPoints *= 300;
|
||||
}
|
||||
else if (mode == 2)
|
||||
{
|
||||
hitPoints = play.Count50 + play.Count100 + play.Count300;
|
||||
totalHits = play.Countmiss + play.Count50 + play.Count100 + play.Count300 +
|
||||
play.Countkatu;
|
||||
}
|
||||
else
|
||||
{
|
||||
hitPoints = play.Count50 * 50 +
|
||||
play.Count100 * 100 +
|
||||
play.Countkatu * 200 +
|
||||
(play.Count300 + play.Countgeki) * 300;
|
||||
|
||||
totalHits = (play.Countmiss + play.Count50 + play.Count100 +
|
||||
play.Countkatu + play.Count300 + play.Countgeki) * 300;
|
||||
}
|
||||
|
||||
|
||||
return Math.Round(hitPoints / totalHits * 100, 2);
|
||||
}
|
||||
|
||||
private static int ResolveGameMode(string mode)
|
||||
{
|
||||
switch (mode.ToUpperInvariant())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveGameMode(int mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case 0:
|
||||
return "Standard";
|
||||
case 1:
|
||||
return "Taiko";
|
||||
case 2:
|
||||
return "Catch";
|
||||
case 3:
|
||||
return "Mania";
|
||||
default:
|
||||
return "Standard";
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
@@ -1,328 +1,322 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class PathOfExileCommands : NadekoSubmodule<SearchesService>
|
||||
{
|
||||
[Group]
|
||||
public class PathOfExileCommands : NadekoSubmodule<SearchesService>
|
||||
private const string _poeURL = "https://www.pathofexile.com/character-window/get-characters?accountName=";
|
||||
private const string _ponURL = "http://poe.ninja/api/Data/GetCurrencyOverview?league=";
|
||||
private const string _pogsURL = "http://pathofexile.gamepedia.com/api.php?action=opensearch&search=";
|
||||
private const string _pogURL = "https://pathofexile.gamepedia.com/api.php?action=browsebysubject&format=json&subject=";
|
||||
private const string _pogiURL = "https://pathofexile.gamepedia.com/api.php?action=query&prop=imageinfo&iiprop=url&format=json&titles=File:";
|
||||
private const string _profileURL = "https://www.pathofexile.com/account/view-profile/";
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public PathOfExileCommands(IHttpClientFactory httpFactory)
|
||||
{
|
||||
private const string _poeURL = "https://www.pathofexile.com/character-window/get-characters?accountName=";
|
||||
private const string _ponURL = "http://poe.ninja/api/Data/GetCurrencyOverview?league=";
|
||||
private const string _pogsURL = "http://pathofexile.gamepedia.com/api.php?action=opensearch&search=";
|
||||
private const string _pogURL = "https://pathofexile.gamepedia.com/api.php?action=browsebysubject&format=json&subject=";
|
||||
private const string _pogiURL = "https://pathofexile.gamepedia.com/api.php?action=query&prop=imageinfo&iiprop=url&format=json&titles=File:";
|
||||
private const string _profileURL = "https://www.pathofexile.com/account/view-profile/";
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task PathOfExile(string usr, string league = "", int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
public PathOfExileCommands(IHttpClientFactory httpFactory)
|
||||
if (string.IsNullOrWhiteSpace(usr))
|
||||
{
|
||||
_httpFactory = httpFactory;
|
||||
await SendErrorAsync("Please provide an account name.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task PathOfExile(string usr, string league = "", int page = 1)
|
||||
var characters = new List<Account>();
|
||||
|
||||
try
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(usr))
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
await SendErrorAsync("Please provide an account name.").ConfigureAwait(false);
|
||||
return;
|
||||
var res = await http.GetStringAsync($"{_poeURL}{usr}").ConfigureAwait(false);
|
||||
characters = JsonConvert.DeserializeObject<List<Account>>(res);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithDescription(GetText(strs.account_not_found))
|
||||
.WithErrorColor();
|
||||
|
||||
var characters = new List<Account>();
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
if (!string.IsNullOrWhiteSpace(league))
|
||||
{
|
||||
characters.RemoveAll(c => c.League != league);
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, (curPage) =>
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor($"Characters on {usr}'s account",
|
||||
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||
$"{_profileURL}{usr}")
|
||||
.WithOkColor();
|
||||
|
||||
var tempList = characters.Skip(curPage * 9).Take(9).ToList();
|
||||
|
||||
if (characters.Count == 0)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
return embed.WithDescription("This account has no characters.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine($"```{"#",-5}{"Character Name",-23}{"League",-10}{"Class",-13}{"Level",-3}");
|
||||
for (int i = 0; i < tempList.Count; i++)
|
||||
{
|
||||
var res = await http.GetStringAsync($"{_poeURL}{usr}").ConfigureAwait(false);
|
||||
characters = JsonConvert.DeserializeObject<List<Account>>(res);
|
||||
var character = tempList[i];
|
||||
|
||||
sb.AppendLine($"#{i + 1 + (curPage * 9),-4}{character.Name,-23}{ShortLeagueName(character.League),-10}{character.Class,-13}{character.Level,-3}");
|
||||
}
|
||||
|
||||
sb.AppendLine("```");
|
||||
embed.WithDescription(sb.ToString());
|
||||
|
||||
return embed;
|
||||
}
|
||||
catch
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithDescription(GetText(strs.account_not_found))
|
||||
.WithErrorColor();
|
||||
}, characters.Count, 9, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
return;
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task PathOfExileLeagues()
|
||||
{
|
||||
var leagues = new List<Leagues>();
|
||||
|
||||
try
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
var res = await http.GetStringAsync("http://api.pathofexile.com/leagues?type=main&compact=1").ConfigureAwait(false);
|
||||
leagues = JsonConvert.DeserializeObject<List<Leagues>>(res);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
var eembed = _eb.Create()
|
||||
.WithDescription(GetText(strs.leagues_not_found))
|
||||
.WithErrorColor();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(league))
|
||||
await ctx.Channel.EmbedAsync(eembed).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor($"Path of Exile Leagues",
|
||||
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||
"https://www.pathofexile.com")
|
||||
.WithOkColor();
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine($"```{"#",-5}{"League Name",-23}");
|
||||
for (int i = 0; i < leagues.Count; i++)
|
||||
{
|
||||
var league = leagues[i];
|
||||
|
||||
sb.AppendLine($"#{i + 1,-4}{league.Id,-23}");
|
||||
}
|
||||
sb.AppendLine("```");
|
||||
|
||||
embed.WithDescription(sb.ToString());
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task PathOfExileCurrency(string leagueName, string currencyName, string convertName = "Chaos Orb")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(leagueName))
|
||||
{
|
||||
await SendErrorAsync("Please provide league name.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(currencyName))
|
||||
{
|
||||
await SendErrorAsync("Please provide currency name.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var cleanCurrency = ShortCurrencyName(currencyName);
|
||||
var cleanConvert = ShortCurrencyName(convertName);
|
||||
|
||||
try
|
||||
{
|
||||
var res = $"{_ponURL}{leagueName}";
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
characters.RemoveAll(c => c.League != league);
|
||||
}
|
||||
var obj = JObject.Parse(await http.GetStringAsync(res).ConfigureAwait(false));
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, (curPage) =>
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor($"Characters on {usr}'s account",
|
||||
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||
$"{_profileURL}{usr}")
|
||||
.WithOkColor();
|
||||
float chaosEquivalent = 0.0F;
|
||||
float conversionEquivalent = 0.0F;
|
||||
|
||||
var tempList = characters.Skip(curPage * 9).Take(9).ToList();
|
||||
|
||||
if (characters.Count == 0)
|
||||
// poe.ninja API does not include a "chaosEquivalent" property for Chaos Orbs.
|
||||
if (cleanCurrency == "Chaos Orb")
|
||||
{
|
||||
return embed.WithDescription("This account has no characters.");
|
||||
chaosEquivalent = 1.0F;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine($"```{"#",-5}{"Character Name",-23}{"League",-10}{"Class",-13}{"Level",-3}");
|
||||
for (int i = 0; i < tempList.Count; i++)
|
||||
{
|
||||
var character = tempList[i];
|
||||
|
||||
sb.AppendLine($"#{i + 1 + (curPage * 9),-4}{character.Name,-23}{ShortLeagueName(character.League),-10}{character.Class,-13}{character.Level,-3}");
|
||||
}
|
||||
|
||||
sb.AppendLine("```");
|
||||
embed.WithDescription(sb.ToString());
|
||||
|
||||
return embed;
|
||||
var currencyInput = obj["lines"].Values<JObject>()
|
||||
.Where(i => i["currencyTypeName"].Value<string>() == cleanCurrency)
|
||||
.FirstOrDefault();
|
||||
chaosEquivalent = float.Parse(currencyInput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
}, characters.Count, 9, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task PathOfExileLeagues()
|
||||
{
|
||||
var leagues = new List<Leagues>();
|
||||
|
||||
try
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
if (cleanConvert == "Chaos Orb")
|
||||
{
|
||||
var res = await http.GetStringAsync("http://api.pathofexile.com/leagues?type=main&compact=1").ConfigureAwait(false);
|
||||
leagues = JsonConvert.DeserializeObject<List<Leagues>>(res);
|
||||
conversionEquivalent = 1.0F;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
var eembed = _eb.Create()
|
||||
.WithDescription(GetText(strs.leagues_not_found))
|
||||
.WithErrorColor();
|
||||
|
||||
await ctx.Channel.EmbedAsync(eembed).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor($"Path of Exile Leagues",
|
||||
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||
"https://www.pathofexile.com")
|
||||
.WithOkColor();
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine($"```{"#",-5}{"League Name",-23}");
|
||||
for (int i = 0; i < leagues.Count; i++)
|
||||
{
|
||||
var league = leagues[i];
|
||||
|
||||
sb.AppendLine($"#{i + 1,-4}{league.Id,-23}");
|
||||
}
|
||||
sb.AppendLine("```");
|
||||
|
||||
embed.WithDescription(sb.ToString());
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task PathOfExileCurrency(string leagueName, string currencyName, string convertName = "Chaos Orb")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(leagueName))
|
||||
{
|
||||
await SendErrorAsync("Please provide league name.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(currencyName))
|
||||
{
|
||||
await SendErrorAsync("Please provide currency name.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var cleanCurrency = ShortCurrencyName(currencyName);
|
||||
var cleanConvert = ShortCurrencyName(convertName);
|
||||
|
||||
try
|
||||
{
|
||||
var res = $"{_ponURL}{leagueName}";
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
else
|
||||
{
|
||||
var obj = JObject.Parse(await http.GetStringAsync(res).ConfigureAwait(false));
|
||||
|
||||
float chaosEquivalent = 0.0F;
|
||||
float conversionEquivalent = 0.0F;
|
||||
|
||||
// poe.ninja API does not include a "chaosEquivalent" property for Chaos Orbs.
|
||||
if (cleanCurrency == "Chaos Orb")
|
||||
{
|
||||
chaosEquivalent = 1.0F;
|
||||
}
|
||||
else
|
||||
{
|
||||
var currencyInput = obj["lines"].Values<JObject>()
|
||||
.Where(i => i["currencyTypeName"].Value<string>() == cleanCurrency)
|
||||
.FirstOrDefault();
|
||||
chaosEquivalent = float.Parse(currencyInput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (cleanConvert == "Chaos Orb")
|
||||
{
|
||||
conversionEquivalent = 1.0F;
|
||||
}
|
||||
else
|
||||
{
|
||||
var currencyOutput = obj["lines"].Values<JObject>()
|
||||
.Where(i => i["currencyTypeName"].Value<string>() == cleanConvert)
|
||||
.FirstOrDefault();
|
||||
conversionEquivalent = float.Parse(currencyOutput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor($"{leagueName} Currency Exchange",
|
||||
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||
"http://poe.ninja")
|
||||
.AddField("Currency Type", cleanCurrency, true)
|
||||
.AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true)
|
||||
.WithOkColor();
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
var currencyOutput = obj["lines"].Values<JObject>()
|
||||
.Where(i => i["currencyTypeName"].Value<string>() == cleanConvert)
|
||||
.FirstOrDefault();
|
||||
conversionEquivalent = float.Parse(currencyOutput["chaosEquivalent"].ToString(), System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithDescription(GetText(strs.ninja_not_found))
|
||||
.WithErrorColor();
|
||||
.WithAuthor($"{leagueName} Currency Exchange",
|
||||
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||
"http://poe.ninja")
|
||||
.AddField("Currency Type", cleanCurrency, true)
|
||||
.AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true)
|
||||
.WithOkColor();
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, string> currencyDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
catch
|
||||
{
|
||||
{"Chaos Orb", "Chaos Orb" },
|
||||
{"Orb of Alchemy", "Orb of Alchemy" },
|
||||
{"Jeweller's Orb", "Jeweller's Orb" },
|
||||
{"Exalted Orb", "Exalted Orb" },
|
||||
{"Mirror of Kalandra", "Mirror of Kalandra" },
|
||||
{"Vaal Orb", "Vaal Orb" },
|
||||
{"Orb of Alteration", "Orb of Alteration" },
|
||||
{"Orb of Scouring", "Orb of Scouring" },
|
||||
{"Divine Orb", "Divine Orb" },
|
||||
{"Orb of Annulment", "Orb of Annulment" },
|
||||
{"Master Cartographer's Sextant", "Master Cartographer's Sextant" },
|
||||
{"Journeyman Cartographer's Sextant", "Journeyman Cartographer's Sextant" },
|
||||
{"Apprentice Cartographer's Sextant", "Apprentice Cartographer's Sextant" },
|
||||
{"Blessed Orb", "Blessed Orb" },
|
||||
{"Orb of Regret", "Orb of Regret" },
|
||||
{"Gemcutter's Prism", "Gemcutter's Prism" },
|
||||
{"Glassblower's Bauble", "Glassblower's Bauble" },
|
||||
{"Orb of Fusing", "Orb of Fusing" },
|
||||
{"Cartographer's Chisel", "Cartographer's Chisel" },
|
||||
{"Chromatic Orb", "Chromatic Orb" },
|
||||
{"Orb of Augmentation", "Orb of Augmentation" },
|
||||
{"Blacksmith's Whetstone", "Blacksmith's Whetstone" },
|
||||
{"Orb of Transmutation", "Orb of Transmutation" },
|
||||
{"Armourer's Scrap", "Armourer's Scrap" },
|
||||
{"Scroll of Wisdom", "Scroll of Wisdom" },
|
||||
{"Regal Orb", "Regal Orb" },
|
||||
{"Chaos", "Chaos Orb" },
|
||||
{"Alch", "Orb of Alchemy" },
|
||||
{"Alchs", "Orb of Alchemy" },
|
||||
{"Jews", "Jeweller's Orb" },
|
||||
{"Jeweller", "Jeweller's Orb" },
|
||||
{"Jewellers", "Jeweller's Orb" },
|
||||
{"Jeweller's", "Jeweller's Orb" },
|
||||
{"X", "Exalted Orb" },
|
||||
{"Ex", "Exalted Orb" },
|
||||
{"Exalt", "Exalted Orb" },
|
||||
{"Exalts", "Exalted Orb" },
|
||||
{"Mirror", "Mirror of Kalandra" },
|
||||
{"Mirrors", "Mirror of Kalandra" },
|
||||
{"Vaal", "Vaal Orb" },
|
||||
{"Alt", "Orb of Alteration" },
|
||||
{"Alts", "Orb of Alteration" },
|
||||
{"Scour", "Orb of Scouring" },
|
||||
{"Scours", "Orb of Scouring" },
|
||||
{"Divine", "Divine Orb" },
|
||||
{"Annul", "Orb of Annulment" },
|
||||
{"Annulment", "Orb of Annulment" },
|
||||
{"Master Sextant", "Master Cartographer's Sextant" },
|
||||
{"Journeyman Sextant", "Journeyman Cartographer's Sextant" },
|
||||
{"Apprentice Sextant", "Apprentice Cartographer's Sextant" },
|
||||
{"Blessed", "Blessed Orb" },
|
||||
{"Regret", "Orb of Regret" },
|
||||
{"Regrets", "Orb of Regret" },
|
||||
{"Gcp", "Gemcutter's Prism" },
|
||||
{"Glassblowers", "Glassblower's Bauble" },
|
||||
{"Glassblower's", "Glassblower's Bauble" },
|
||||
{"Fusing", "Orb of Fusing" },
|
||||
{"Fuses", "Orb of Fusing" },
|
||||
{"Fuse", "Orb of Fusing" },
|
||||
{"Chisel", "Cartographer's Chisel" },
|
||||
{"Chisels", "Cartographer's Chisel" },
|
||||
{"Chance", "Orb of Chance" },
|
||||
{"Chances", "Orb of Chance" },
|
||||
{"Chrome", "Chromatic Orb" },
|
||||
{"Chromes", "Chromatic Orb" },
|
||||
{"Aug", "Orb of Augmentation" },
|
||||
{"Augmentation", "Orb of Augmentation" },
|
||||
{"Augment", "Orb of Augmentation" },
|
||||
{"Augments", "Orb of Augmentation" },
|
||||
{"Whetstone", "Blacksmith's Whetstone" },
|
||||
{"Whetstones", "Blacksmith's Whetstone" },
|
||||
{"Transmute", "Orb of Transmutation" },
|
||||
{"Transmutes", "Orb of Transmutation" },
|
||||
{"Armourers", "Armourer's Scrap" },
|
||||
{"Armourer's", "Armourer's Scrap" },
|
||||
{"Wisdom Scroll", "Scroll of Wisdom" },
|
||||
{"Wisdom Scrolls", "Scroll of Wisdom" },
|
||||
{"Regal", "Regal Orb" },
|
||||
{"Regals", "Regal Orb" }
|
||||
};
|
||||
var embed = _eb.Create()
|
||||
.WithDescription(GetText(strs.ninja_not_found))
|
||||
.WithErrorColor();
|
||||
|
||||
private string ShortCurrencyName(string str)
|
||||
{
|
||||
if (currencyDictionary.ContainsValue(str))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
var currency = currencyDictionary[str];
|
||||
|
||||
return currency;
|
||||
}
|
||||
|
||||
private static string ShortLeagueName(string str)
|
||||
{
|
||||
var league = Regex.Replace(str, "Hardcore", "HC", RegexOptions.IgnoreCase);
|
||||
|
||||
return league;
|
||||
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, string> currencyDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{"Chaos Orb", "Chaos Orb" },
|
||||
{"Orb of Alchemy", "Orb of Alchemy" },
|
||||
{"Jeweller's Orb", "Jeweller's Orb" },
|
||||
{"Exalted Orb", "Exalted Orb" },
|
||||
{"Mirror of Kalandra", "Mirror of Kalandra" },
|
||||
{"Vaal Orb", "Vaal Orb" },
|
||||
{"Orb of Alteration", "Orb of Alteration" },
|
||||
{"Orb of Scouring", "Orb of Scouring" },
|
||||
{"Divine Orb", "Divine Orb" },
|
||||
{"Orb of Annulment", "Orb of Annulment" },
|
||||
{"Master Cartographer's Sextant", "Master Cartographer's Sextant" },
|
||||
{"Journeyman Cartographer's Sextant", "Journeyman Cartographer's Sextant" },
|
||||
{"Apprentice Cartographer's Sextant", "Apprentice Cartographer's Sextant" },
|
||||
{"Blessed Orb", "Blessed Orb" },
|
||||
{"Orb of Regret", "Orb of Regret" },
|
||||
{"Gemcutter's Prism", "Gemcutter's Prism" },
|
||||
{"Glassblower's Bauble", "Glassblower's Bauble" },
|
||||
{"Orb of Fusing", "Orb of Fusing" },
|
||||
{"Cartographer's Chisel", "Cartographer's Chisel" },
|
||||
{"Chromatic Orb", "Chromatic Orb" },
|
||||
{"Orb of Augmentation", "Orb of Augmentation" },
|
||||
{"Blacksmith's Whetstone", "Blacksmith's Whetstone" },
|
||||
{"Orb of Transmutation", "Orb of Transmutation" },
|
||||
{"Armourer's Scrap", "Armourer's Scrap" },
|
||||
{"Scroll of Wisdom", "Scroll of Wisdom" },
|
||||
{"Regal Orb", "Regal Orb" },
|
||||
{"Chaos", "Chaos Orb" },
|
||||
{"Alch", "Orb of Alchemy" },
|
||||
{"Alchs", "Orb of Alchemy" },
|
||||
{"Jews", "Jeweller's Orb" },
|
||||
{"Jeweller", "Jeweller's Orb" },
|
||||
{"Jewellers", "Jeweller's Orb" },
|
||||
{"Jeweller's", "Jeweller's Orb" },
|
||||
{"X", "Exalted Orb" },
|
||||
{"Ex", "Exalted Orb" },
|
||||
{"Exalt", "Exalted Orb" },
|
||||
{"Exalts", "Exalted Orb" },
|
||||
{"Mirror", "Mirror of Kalandra" },
|
||||
{"Mirrors", "Mirror of Kalandra" },
|
||||
{"Vaal", "Vaal Orb" },
|
||||
{"Alt", "Orb of Alteration" },
|
||||
{"Alts", "Orb of Alteration" },
|
||||
{"Scour", "Orb of Scouring" },
|
||||
{"Scours", "Orb of Scouring" },
|
||||
{"Divine", "Divine Orb" },
|
||||
{"Annul", "Orb of Annulment" },
|
||||
{"Annulment", "Orb of Annulment" },
|
||||
{"Master Sextant", "Master Cartographer's Sextant" },
|
||||
{"Journeyman Sextant", "Journeyman Cartographer's Sextant" },
|
||||
{"Apprentice Sextant", "Apprentice Cartographer's Sextant" },
|
||||
{"Blessed", "Blessed Orb" },
|
||||
{"Regret", "Orb of Regret" },
|
||||
{"Regrets", "Orb of Regret" },
|
||||
{"Gcp", "Gemcutter's Prism" },
|
||||
{"Glassblowers", "Glassblower's Bauble" },
|
||||
{"Glassblower's", "Glassblower's Bauble" },
|
||||
{"Fusing", "Orb of Fusing" },
|
||||
{"Fuses", "Orb of Fusing" },
|
||||
{"Fuse", "Orb of Fusing" },
|
||||
{"Chisel", "Cartographer's Chisel" },
|
||||
{"Chisels", "Cartographer's Chisel" },
|
||||
{"Chance", "Orb of Chance" },
|
||||
{"Chances", "Orb of Chance" },
|
||||
{"Chrome", "Chromatic Orb" },
|
||||
{"Chromes", "Chromatic Orb" },
|
||||
{"Aug", "Orb of Augmentation" },
|
||||
{"Augmentation", "Orb of Augmentation" },
|
||||
{"Augment", "Orb of Augmentation" },
|
||||
{"Augments", "Orb of Augmentation" },
|
||||
{"Whetstone", "Blacksmith's Whetstone" },
|
||||
{"Whetstones", "Blacksmith's Whetstone" },
|
||||
{"Transmute", "Orb of Transmutation" },
|
||||
{"Transmutes", "Orb of Transmutation" },
|
||||
{"Armourers", "Armourer's Scrap" },
|
||||
{"Armourer's", "Armourer's Scrap" },
|
||||
{"Wisdom Scroll", "Scroll of Wisdom" },
|
||||
{"Wisdom Scrolls", "Scroll of Wisdom" },
|
||||
{"Regal", "Regal Orb" },
|
||||
{"Regals", "Regal Orb" }
|
||||
};
|
||||
|
||||
private string ShortCurrencyName(string str)
|
||||
{
|
||||
if (currencyDictionary.ContainsValue(str))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
var currency = currencyDictionary[str];
|
||||
|
||||
return currency;
|
||||
}
|
||||
|
||||
private static string ShortLeagueName(string str)
|
||||
{
|
||||
var league = Regex.Replace(str, "Hardcore", "HC", RegexOptions.IgnoreCase);
|
||||
|
||||
return league;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,82 +1,79 @@
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class PlaceCommands : NadekoSubmodule
|
||||
{
|
||||
[Group]
|
||||
public class PlaceCommands : NadekoSubmodule
|
||||
private static readonly string _typesStr =
|
||||
string.Join(", ", Enum.GetNames(typeof(PlaceType)));
|
||||
|
||||
public enum PlaceType
|
||||
{
|
||||
private static readonly string _typesStr =
|
||||
string.Join(", ", Enum.GetNames(typeof(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
|
||||
}
|
||||
|
||||
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, Aliases]
|
||||
public async Task Placelist()
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.list_of_place_tags(Prefix)),
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Placelist()
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.list_of_place_tags(Prefix)),
|
||||
_typesStr)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Place(PlaceType placeType, uint width = 0, uint height = 0)
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Place(PlaceType placeType, uint width = 0, uint height = 0)
|
||||
{
|
||||
var url = "";
|
||||
switch (placeType)
|
||||
{
|
||||
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 ctx.Channel.SendMessageAsync(url).ConfigureAwait(false);
|
||||
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 ctx.Channel.SendMessageAsync(url).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,78 +1,73 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
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.Common.Pokemon;
|
||||
using NadekoBot.Services;
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class PokemonSearchCommands : NadekoSubmodule<SearchesService>
|
||||
{
|
||||
[Group]
|
||||
public class PokemonSearchCommands : NadekoSubmodule<SearchesService>
|
||||
private readonly IDataCache _cache;
|
||||
|
||||
public IReadOnlyDictionary<string, SearchPokemon> Pokemons => _cache.LocalData.Pokemons;
|
||||
public IReadOnlyDictionary<string, SearchPokemonAbility> PokemonAbilities => _cache.LocalData.PokemonAbilities;
|
||||
|
||||
public PokemonSearchCommands(IDataCache cache)
|
||||
{
|
||||
private readonly IDataCache _cache;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, SearchPokemon> Pokemons => _cache.LocalData.Pokemons;
|
||||
public IReadOnlyDictionary<string, SearchPokemonAbility> PokemonAbilities => _cache.LocalData.PokemonAbilities;
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Pokemon([Leftover] string pokemon = null)
|
||||
{
|
||||
pokemon = pokemon?.Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrWhiteSpace(pokemon))
|
||||
return;
|
||||
|
||||
public PokemonSearchCommands(IDataCache cache)
|
||||
foreach (var kvp in Pokemons)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Pokemon([Leftover] string pokemon = null)
|
||||
{
|
||||
pokemon = pokemon?.Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrWhiteSpace(pokemon))
|
||||
return;
|
||||
|
||||
foreach (var kvp in Pokemons)
|
||||
if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant())
|
||||
{
|
||||
if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant())
|
||||
{
|
||||
var p = kvp.Value;
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(kvp.Key.ToTitleCase())
|
||||
.WithDescription(p.BaseStats.ToString())
|
||||
.WithThumbnailUrl($"https://assets.pokemon.com/assets/cms2/img/pokedex/detail/{p.Id.ToString("000")}.png")
|
||||
.AddField(GetText(strs.types), string.Join("\n", p.Types), true)
|
||||
.AddField(GetText(strs.height_weight), GetText(strs.height_weight_val(p.HeightM, p.WeightKg)), true)
|
||||
.AddField(GetText(strs.abilities), string.Join("\n", p.Abilities.Select(a => a.Value)), true)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.pokemon_none).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task PokemonAbility([Leftover] string ability = null)
|
||||
{
|
||||
ability = ability?.Trim().ToUpperInvariant().Replace(" ", "", StringComparison.InvariantCulture);
|
||||
if (string.IsNullOrWhiteSpace(ability))
|
||||
var p = kvp.Value;
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(kvp.Key.ToTitleCase())
|
||||
.WithDescription(p.BaseStats.ToString())
|
||||
.WithThumbnailUrl($"https://assets.pokemon.com/assets/cms2/img/pokedex/detail/{p.Id.ToString("000")}.png")
|
||||
.AddField(GetText(strs.types), string.Join("\n", p.Types), true)
|
||||
.AddField(GetText(strs.height_weight), GetText(strs.height_weight_val(p.HeightM, p.WeightKg)), true)
|
||||
.AddField(GetText(strs.abilities), string.Join("\n", p.Abilities.Select(a => a.Value)), true)).ConfigureAwait(false);
|
||||
return;
|
||||
foreach (var kvp in PokemonAbilities)
|
||||
{
|
||||
if (kvp.Key.ToUpperInvariant() == ability)
|
||||
{
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(kvp.Value.Name)
|
||||
.WithDescription(string.IsNullOrWhiteSpace(kvp.Value.Desc)
|
||||
? kvp.Value.ShortDesc
|
||||
: kvp.Value.Desc)
|
||||
.AddField(GetText(strs.rating), kvp.Value.Rating.ToString(_cultureInfo), true));
|
||||
return;
|
||||
}
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.pokemon_ability_none).ConfigureAwait(false);
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.pokemon_none).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task PokemonAbility([Leftover] string ability = null)
|
||||
{
|
||||
ability = ability?.Trim().ToUpperInvariant().Replace(" ", "", StringComparison.InvariantCulture);
|
||||
if (string.IsNullOrWhiteSpace(ability))
|
||||
return;
|
||||
foreach (var kvp in PokemonAbilities)
|
||||
{
|
||||
if (kvp.Key.ToUpperInvariant() == ability)
|
||||
{
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(kvp.Value.Name)
|
||||
.WithDescription(string.IsNullOrWhiteSpace(kvp.Value.Desc)
|
||||
? kvp.Value.ShortDesc
|
||||
: kvp.Value.Desc)
|
||||
.AddField(GetText(strs.rating), kvp.Value.Rating.ToString(_cultureInfo), true));
|
||||
return;
|
||||
}
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.pokemon_ability_none).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -3,159 +3,155 @@ using AngleSharp.Html.Dom;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public class AnimeSearchService : INService
|
||||
{
|
||||
public class AnimeSearchService : INService
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public AnimeSearchService(IDataCache cache, IHttpClientFactory httpFactory)
|
||||
{
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
_cache = cache;
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
public AnimeSearchService(IDataCache cache, IHttpClientFactory httpFactory)
|
||||
public async Task<AnimeResult> GetAnimeData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
try
|
||||
{
|
||||
_cache = cache;
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
public async Task<AnimeResult> GetAnimeData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
try
|
||||
var link = "https://aniapi.nadeko.bot/anime/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
|
||||
var link = "https://aniapi.nadeko.bot/anime/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
data = await http.GetStringAsync(link).ConfigureAwait(false);
|
||||
}
|
||||
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
|
||||
data = await http.GetStringAsync(link).ConfigureAwait(false);
|
||||
}
|
||||
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
return JsonConvert.DeserializeObject<AnimeResult>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonConvert.DeserializeObject<AnimeResult>(data);
|
||||
}
|
||||
|
||||
public async Task<NovelResult> GetNovelData(string query)
|
||||
catch
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
|
||||
query = query.Replace(" ", "-", StringComparison.InvariantCulture);
|
||||
try
|
||||
{
|
||||
var link = "https://www.novelupdates.com/series/" + Uri.EscapeDataString(query
|
||||
.Replace(" ", "-")
|
||||
.Replace("/", " ")
|
||||
);
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetNovelDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using (var document = await BrowsingContext.New(config).OpenAsync(link).ConfigureAwait(false))
|
||||
{
|
||||
var imageElem = document.QuerySelector("div.seriesimg > img");
|
||||
if (imageElem is null)
|
||||
return null;
|
||||
var imageUrl = ((IHtmlImageElement)imageElem).Source;
|
||||
|
||||
var descElem = document.QuerySelector("div#editdescription > p");
|
||||
var desc = descElem.InnerHtml;
|
||||
|
||||
var genres = document.QuerySelector("div#seriesgenre").Children
|
||||
.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var authors = document
|
||||
.QuerySelector("div#showauthors")
|
||||
.Children
|
||||
.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var score = ((IHtmlSpanElement)document
|
||||
.QuerySelector("h5.seriesother > span.uvotes"))
|
||||
.InnerHtml;
|
||||
|
||||
var status = document
|
||||
.QuerySelector("div#editstatus")
|
||||
.InnerHtml;
|
||||
var title = document
|
||||
.QuerySelector("div.w-blog-content > div.seriestitlenu")
|
||||
.InnerHtml;
|
||||
|
||||
var obj = new NovelResult()
|
||||
{
|
||||
Description = desc,
|
||||
Authors = authors,
|
||||
Genres = genres,
|
||||
ImageUrl = imageUrl,
|
||||
Link = link,
|
||||
Score = score,
|
||||
Status = status,
|
||||
Title = title,
|
||||
};
|
||||
|
||||
await _cache.SetNovelDataAsync(link,
|
||||
JsonConvert.SerializeObject(obj)).ConfigureAwait(false);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<NovelResult>(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting novel data");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MangaResult> GetMangaData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
try
|
||||
{
|
||||
|
||||
var link = "https://aniapi.nadeko.bot/manga/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
data = await http.GetStringAsync(link).ConfigureAwait(false);
|
||||
}
|
||||
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
return JsonConvert.DeserializeObject<MangaResult>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<NovelResult> GetNovelData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
|
||||
query = query.Replace(" ", "-", StringComparison.InvariantCulture);
|
||||
try
|
||||
{
|
||||
var link = "https://www.novelupdates.com/series/" + Uri.EscapeDataString(query
|
||||
.Replace(" ", "-")
|
||||
.Replace("/", " ")
|
||||
);
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetNovelDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using (var document = await BrowsingContext.New(config).OpenAsync(link).ConfigureAwait(false))
|
||||
{
|
||||
var imageElem = document.QuerySelector("div.seriesimg > img");
|
||||
if (imageElem is null)
|
||||
return null;
|
||||
var imageUrl = ((IHtmlImageElement)imageElem).Source;
|
||||
|
||||
var descElem = document.QuerySelector("div#editdescription > p");
|
||||
var desc = descElem.InnerHtml;
|
||||
|
||||
var genres = document.QuerySelector("div#seriesgenre").Children
|
||||
.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var authors = document
|
||||
.QuerySelector("div#showauthors")
|
||||
.Children
|
||||
.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var score = ((IHtmlSpanElement)document
|
||||
.QuerySelector("h5.seriesother > span.uvotes"))
|
||||
.InnerHtml;
|
||||
|
||||
var status = document
|
||||
.QuerySelector("div#editstatus")
|
||||
.InnerHtml;
|
||||
var title = document
|
||||
.QuerySelector("div.w-blog-content > div.seriestitlenu")
|
||||
.InnerHtml;
|
||||
|
||||
var obj = new NovelResult()
|
||||
{
|
||||
Description = desc,
|
||||
Authors = authors,
|
||||
Genres = genres,
|
||||
ImageUrl = imageUrl,
|
||||
Link = link,
|
||||
Score = score,
|
||||
Status = status,
|
||||
Title = title,
|
||||
};
|
||||
|
||||
await _cache.SetNovelDataAsync(link,
|
||||
JsonConvert.SerializeObject(obj)).ConfigureAwait(false);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<NovelResult>(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting novel data");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MangaResult> GetMangaData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
try
|
||||
{
|
||||
|
||||
var link = "https://aniapi.nadeko.bot/manga/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
data = await http.GetStringAsync(link).ConfigureAwait(false);
|
||||
}
|
||||
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
return JsonConvert.DeserializeObject<MangaResult>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +1,14 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public static class AtlExtensions
|
||||
{
|
||||
public static class AtlExtensions
|
||||
{
|
||||
public static Task<AutoTranslateChannel> GetByChannelId(this IQueryable<AutoTranslateChannel> set, ulong channelId)
|
||||
=> set
|
||||
.Include(x => x.Users)
|
||||
.FirstOrDefaultAsyncEF(x => x.ChannelId == channelId);
|
||||
}
|
||||
public static Task<AutoTranslateChannel> GetByChannelId(this IQueryable<AutoTranslateChannel> set, ulong channelId)
|
||||
=> set
|
||||
.Include(x => x.Users)
|
||||
.FirstOrDefaultAsyncEF(x => x.ChannelId == channelId);
|
||||
}
|
@@ -2,107 +2,102 @@
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public class CryptoService : INService
|
||||
{
|
||||
public class CryptoService : INService
|
||||
{
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IBotCredentials _creds;
|
||||
|
||||
public CryptoService(IDataCache cache, IHttpClientFactory httpFactory, IBotCredentials creds)
|
||||
public CryptoService(IDataCache cache, IHttpClientFactory httpFactory, IBotCredentials creds)
|
||||
{
|
||||
_cache = cache;
|
||||
_httpFactory = httpFactory;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public async Task<(CryptoResponseData Data, CryptoResponseData Nearest)> GetCryptoData(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
_cache = cache;
|
||||
_httpFactory = httpFactory;
|
||||
_creds = creds;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
public async Task<(CryptoResponseData Data, CryptoResponseData Nearest)> GetCryptoData(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
name = name.ToUpperInvariant();
|
||||
var cryptos = await CryptoData().ConfigureAwait(false);
|
||||
|
||||
name = name.ToUpperInvariant();
|
||||
var cryptos = await CryptoData().ConfigureAwait(false);
|
||||
|
||||
if (cryptos is null)
|
||||
return (null, null);
|
||||
if (cryptos is null)
|
||||
return (null, null);
|
||||
|
||||
var crypto = cryptos
|
||||
?.FirstOrDefault(x => x.Id.ToUpperInvariant() == name || x.Name.ToUpperInvariant() == name
|
||||
|| x.Symbol.ToUpperInvariant() == name);
|
||||
var crypto = cryptos
|
||||
?.FirstOrDefault(x => x.Id.ToUpperInvariant() == name || x.Name.ToUpperInvariant() == name
|
||||
|| x.Symbol.ToUpperInvariant() == name);
|
||||
|
||||
(CryptoResponseData Elem, int Distance)? nearest = null;
|
||||
if (crypto is null)
|
||||
{
|
||||
nearest = cryptos
|
||||
.Select(x => (x, Distance: x.Name.ToUpperInvariant().LevenshteinDistance(name)))
|
||||
.OrderBy(x => x.Distance)
|
||||
.Where(x => x.Distance <= 2)
|
||||
.FirstOrDefault();
|
||||
(CryptoResponseData Elem, int Distance)? nearest = null;
|
||||
if (crypto is null)
|
||||
{
|
||||
nearest = cryptos
|
||||
.Select(x => (x, Distance: x.Name.ToUpperInvariant().LevenshteinDistance(name)))
|
||||
.OrderBy(x => x.Distance)
|
||||
.Where(x => x.Distance <= 2)
|
||||
.FirstOrDefault();
|
||||
|
||||
crypto = nearest?.Elem;
|
||||
}
|
||||
|
||||
if (nearest != null)
|
||||
{
|
||||
return (null, crypto);
|
||||
}
|
||||
|
||||
return (crypto, null);
|
||||
crypto = nearest?.Elem;
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim getCryptoLock = new SemaphoreSlim(1, 1);
|
||||
public async Task<List<CryptoResponseData>> CryptoData()
|
||||
if (nearest != null)
|
||||
{
|
||||
await getCryptoLock.WaitAsync();
|
||||
try
|
||||
return (null, crypto);
|
||||
}
|
||||
|
||||
return (crypto, null);
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim getCryptoLock = new SemaphoreSlim(1, 1);
|
||||
public async Task<List<CryptoResponseData>> CryptoData()
|
||||
{
|
||||
await getCryptoLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var fullStrData = await _cache.GetOrAddCachedDataAsync("nadeko:crypto_data", async _ =>
|
||||
{
|
||||
var fullStrData = await _cache.GetOrAddCachedDataAsync("nadeko:crypto_data", async _ =>
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
using var _http = _httpFactory.CreateClient();
|
||||
var strData = await _http.GetStringAsync(
|
||||
$"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" +
|
||||
$"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}" +
|
||||
$"&start=1" +
|
||||
$"&limit=5000" +
|
||||
$"&convert=USD");
|
||||
using var _http = _httpFactory.CreateClient();
|
||||
var strData = await _http.GetStringAsync(
|
||||
$"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" +
|
||||
$"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}" +
|
||||
$"&start=1" +
|
||||
$"&limit=5000" +
|
||||
$"&convert=USD");
|
||||
|
||||
JsonConvert.DeserializeObject<CryptoResponse>(strData); // just to see if its' valid
|
||||
JsonConvert.DeserializeObject<CryptoResponse>(strData); // just to see if its' valid
|
||||
|
||||
return strData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting crypto data: {Message}", ex.Message);
|
||||
return default;
|
||||
}
|
||||
return strData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting crypto data: {Message}", ex.Message);
|
||||
return default;
|
||||
}
|
||||
|
||||
}, "", TimeSpan.FromHours(1));
|
||||
}, "", TimeSpan.FromHours(1));
|
||||
|
||||
return JsonConvert.DeserializeObject<CryptoResponse>(fullStrData).Data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error retreiving crypto data: {Message}", ex.Message);
|
||||
return default;
|
||||
}
|
||||
finally
|
||||
{
|
||||
getCryptoLock.Release();
|
||||
}
|
||||
return JsonConvert.DeserializeObject<CryptoResponse>(fullStrData).Data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error retreiving crypto data: {Message}", ex.Message);
|
||||
return default;
|
||||
}
|
||||
finally
|
||||
{
|
||||
getCryptoLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,246 +5,241 @@ using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public class FeedsService : INService
|
||||
{
|
||||
public class FeedsService : INService
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<string, HashSet<FeedSub>> _subs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
private readonly ConcurrentDictionary<string, DateTime> _lastPosts =
|
||||
new ConcurrentDictionary<string, DateTime>();
|
||||
|
||||
public FeedsService(Bot bot, DbService db, DiscordSocketClient client, IEmbedBuilderService eb)
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<string, HashSet<FeedSub>> _subs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
_db = db;
|
||||
|
||||
private readonly ConcurrentDictionary<string, DateTime> _lastPosts =
|
||||
new ConcurrentDictionary<string, DateTime>();
|
||||
|
||||
public FeedsService(Bot bot, DbService db, DiscordSocketClient client, IEmbedBuilderService eb)
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
_db = db;
|
||||
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var guildConfigIds = bot.AllGuildConfigs.Select(x => x.Id).ToList();
|
||||
_subs = uow.GuildConfigs
|
||||
.AsQueryable()
|
||||
.Where(x => guildConfigIds.Contains(x.Id))
|
||||
.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig)
|
||||
.ToList()
|
||||
.SelectMany(x => x.FeedSubs)
|
||||
.GroupBy(x => x.Url.ToLower())
|
||||
.ToDictionary(x => x.Key, x => x.ToHashSet())
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
_client = client;
|
||||
_eb = eb;
|
||||
|
||||
var _ = Task.Run(TrackFeeds);
|
||||
var guildConfigIds = bot.AllGuildConfigs.Select(x => x.Id).ToList();
|
||||
_subs = uow.GuildConfigs
|
||||
.AsQueryable()
|
||||
.Where(x => guildConfigIds.Contains(x.Id))
|
||||
.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig)
|
||||
.ToList()
|
||||
.SelectMany(x => x.FeedSubs)
|
||||
.GroupBy(x => x.Url.ToLower())
|
||||
.ToDictionary(x => x.Key, x => x.ToHashSet())
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
public async Task<EmbedBuilder> TrackFeeds()
|
||||
_client = client;
|
||||
_eb = eb;
|
||||
|
||||
var _ = Task.Run(TrackFeeds);
|
||||
}
|
||||
|
||||
public async Task<EmbedBuilder> TrackFeeds()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (true)
|
||||
var allSendTasks = new List<Task>(_subs.Count);
|
||||
foreach (var kvp in _subs)
|
||||
{
|
||||
var allSendTasks = new List<Task>(_subs.Count);
|
||||
foreach (var kvp in _subs)
|
||||
if (kvp.Value.Count == 0)
|
||||
continue;
|
||||
|
||||
var rssUrl = kvp.Key;
|
||||
try
|
||||
{
|
||||
if (kvp.Value.Count == 0)
|
||||
continue;
|
||||
var feed = await CodeHollow.FeedReader.FeedReader.ReadAsync(rssUrl).ConfigureAwait(false);
|
||||
|
||||
var rssUrl = kvp.Key;
|
||||
try
|
||||
var items = feed
|
||||
.Items
|
||||
.Select(item => (Item: item, LastUpdate: item.PublishingDate?.ToUniversalTime()
|
||||
?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate
|
||||
?.ToUniversalTime()))
|
||||
.Where(data => !(data.LastUpdate is null))
|
||||
.Select(data => (data.Item, LastUpdate: (DateTime) data.LastUpdate))
|
||||
.OrderByDescending(data => data.LastUpdate)
|
||||
.Reverse() // start from the oldest
|
||||
.ToList();
|
||||
|
||||
if (!_lastPosts.TryGetValue(kvp.Key, out DateTime lastFeedUpdate))
|
||||
{
|
||||
var feed = await CodeHollow.FeedReader.FeedReader.ReadAsync(rssUrl).ConfigureAwait(false);
|
||||
lastFeedUpdate = _lastPosts[kvp.Key] =
|
||||
items.Any() ? items[items.Count - 1].LastUpdate : DateTime.UtcNow;
|
||||
}
|
||||
|
||||
var items = feed
|
||||
.Items
|
||||
.Select(item => (Item: item, LastUpdate: item.PublishingDate?.ToUniversalTime()
|
||||
?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate
|
||||
?.ToUniversalTime()))
|
||||
.Where(data => !(data.LastUpdate is null))
|
||||
.Select(data => (data.Item, LastUpdate: (DateTime) data.LastUpdate))
|
||||
.OrderByDescending(data => data.LastUpdate)
|
||||
.Reverse() // start from the oldest
|
||||
.ToList();
|
||||
|
||||
if (!_lastPosts.TryGetValue(kvp.Key, out DateTime lastFeedUpdate))
|
||||
foreach (var (feedItem, itemUpdateDate) in items)
|
||||
{
|
||||
if (itemUpdateDate <= lastFeedUpdate)
|
||||
{
|
||||
lastFeedUpdate = _lastPosts[kvp.Key] =
|
||||
items.Any() ? items[items.Count - 1].LastUpdate : DateTime.UtcNow;
|
||||
continue;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithFooter(rssUrl);
|
||||
|
||||
_lastPosts[kvp.Key] = itemUpdateDate;
|
||||
|
||||
var link = feedItem.SpecificItem.Link;
|
||||
if (!string.IsNullOrWhiteSpace(link) && Uri.IsWellFormedUriString(link, UriKind.Absolute))
|
||||
embed.WithUrl(link);
|
||||
|
||||
var title = string.IsNullOrWhiteSpace(feedItem.Title)
|
||||
? "-"
|
||||
: feedItem.Title;
|
||||
|
||||
var gotImage = false;
|
||||
if (feedItem.SpecificItem is MediaRssFeedItem mrfi &&
|
||||
(mrfi.Enclosure?.MediaType?.StartsWith("image/") ?? false))
|
||||
{
|
||||
var imgUrl = mrfi.Enclosure.Url;
|
||||
if (!string.IsNullOrWhiteSpace(imgUrl) &&
|
||||
Uri.IsWellFormedUriString(imgUrl, UriKind.Absolute))
|
||||
{
|
||||
embed.WithImageUrl(imgUrl);
|
||||
gotImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (feedItem, itemUpdateDate) in items)
|
||||
|
||||
if (!gotImage && feedItem.SpecificItem is AtomFeedItem afi)
|
||||
{
|
||||
if (itemUpdateDate <= lastFeedUpdate)
|
||||
var previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "preview");
|
||||
|
||||
if (previewElement is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithFooter(rssUrl);
|
||||
|
||||
_lastPosts[kvp.Key] = itemUpdateDate;
|
||||
|
||||
var link = feedItem.SpecificItem.Link;
|
||||
if (!string.IsNullOrWhiteSpace(link) && Uri.IsWellFormedUriString(link, UriKind.Absolute))
|
||||
embed.WithUrl(link);
|
||||
|
||||
var title = string.IsNullOrWhiteSpace(feedItem.Title)
|
||||
? "-"
|
||||
: feedItem.Title;
|
||||
|
||||
var gotImage = false;
|
||||
if (feedItem.SpecificItem is MediaRssFeedItem mrfi &&
|
||||
(mrfi.Enclosure?.MediaType?.StartsWith("image/") ?? false))
|
||||
previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "thumbnail");
|
||||
}
|
||||
|
||||
if (previewElement != null)
|
||||
{
|
||||
var imgUrl = mrfi.Enclosure.Url;
|
||||
if (!string.IsNullOrWhiteSpace(imgUrl) &&
|
||||
Uri.IsWellFormedUriString(imgUrl, UriKind.Absolute))
|
||||
var urlAttribute = previewElement.Attribute("url");
|
||||
if (urlAttribute != null && !string.IsNullOrWhiteSpace(urlAttribute.Value)
|
||||
&& Uri.IsWellFormedUriString(urlAttribute.Value,
|
||||
UriKind.Absolute))
|
||||
{
|
||||
embed.WithImageUrl(imgUrl);
|
||||
embed.WithImageUrl(urlAttribute.Value);
|
||||
gotImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotImage && feedItem.SpecificItem is AtomFeedItem afi)
|
||||
{
|
||||
var previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "preview");
|
||||
|
||||
if (previewElement is null)
|
||||
{
|
||||
previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "thumbnail");
|
||||
}
|
||||
|
||||
if (previewElement != null)
|
||||
{
|
||||
var urlAttribute = previewElement.Attribute("url");
|
||||
if (urlAttribute != null && !string.IsNullOrWhiteSpace(urlAttribute.Value)
|
||||
&& Uri.IsWellFormedUriString(urlAttribute.Value,
|
||||
UriKind.Absolute))
|
||||
{
|
||||
embed.WithImageUrl(urlAttribute.Value);
|
||||
gotImage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
embed.WithTitle(title.TrimTo(256));
|
||||
|
||||
var desc = feedItem.Description?.StripHTML();
|
||||
if (!string.IsNullOrWhiteSpace(feedItem.Description))
|
||||
embed.WithDescription(desc.TrimTo(2048));
|
||||
|
||||
//send the created embed to all subscribed channels
|
||||
var feedSendTasks = 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));
|
||||
|
||||
allSendTasks.Add(Task.WhenAll(feedSendTasks));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
|
||||
embed.WithTitle(title.TrimTo(256));
|
||||
|
||||
var desc = feedItem.Description?.StripHTML();
|
||||
if (!string.IsNullOrWhiteSpace(feedItem.Description))
|
||||
embed.WithDescription(desc.TrimTo(2048));
|
||||
|
||||
//send the created embed to all subscribed channels
|
||||
var feedSendTasks = 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));
|
||||
|
||||
allSendTasks.Add(Task.WhenAll(feedSendTasks));
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(Task.WhenAll(allSendTasks), Task.Delay(10000)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FeedSub> GetFeeds(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig))
|
||||
.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(),
|
||||
};
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig));
|
||||
|
||||
if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (gc.FeedSubs.Count >= 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gc.FeedSubs.Add(fs);
|
||||
uow.SaveChanges();
|
||||
//adding all, in case bot wasn't on this guild when it started
|
||||
foreach (var feed in gc.FeedSubs)
|
||||
{
|
||||
_subs.AddOrUpdate(feed.Url.ToLower(), new HashSet<FeedSub>() {feed}, (k, old) =>
|
||||
{
|
||||
old.Add(feed);
|
||||
return old;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveFeed(ulong guildId, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var items = uow.GuildConfigsForId(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.ToLower(), new HashSet<FeedSub>(), (key, old) =>
|
||||
{
|
||||
old.Remove(toRemove);
|
||||
return old;
|
||||
});
|
||||
uow.Remove(toRemove);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
await Task.WhenAll(Task.WhenAll(allSendTasks), Task.Delay(10000)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FeedSub> GetFeeds(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig))
|
||||
.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(),
|
||||
};
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig));
|
||||
|
||||
if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (gc.FeedSubs.Count >= 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gc.FeedSubs.Add(fs);
|
||||
uow.SaveChanges();
|
||||
//adding all, in case bot wasn't on this guild when it started
|
||||
foreach (var feed in gc.FeedSubs)
|
||||
{
|
||||
_subs.AddOrUpdate(feed.Url.ToLower(), new HashSet<FeedSub>() {feed}, (k, old) =>
|
||||
{
|
||||
old.Add(feed);
|
||||
return old;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveFeed(ulong guildId, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var items = uow.GuildConfigsForId(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.ToLower(), new HashSet<FeedSub>(), (key, old) =>
|
||||
{
|
||||
old.Remove(toRemove);
|
||||
return old;
|
||||
});
|
||||
uow.Remove(toRemove);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,14 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public interface ITranslateService
|
||||
{
|
||||
public interface ITranslateService
|
||||
{
|
||||
public Task<string> Translate(string source, string target, string text = null);
|
||||
Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete);
|
||||
IEnumerable<string> GetLanguages();
|
||||
Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string @from, string to);
|
||||
Task<bool> UnregisterUser(ulong channelId, ulong userId);
|
||||
}
|
||||
public Task<string> Translate(string source, string target, string text = null);
|
||||
Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete);
|
||||
IEnumerable<string> GetLanguages();
|
||||
Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string @from, string to);
|
||||
Task<bool> UnregisterUser(ulong channelId, ulong userId);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
@@ -13,223 +10,222 @@ using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyExecutor, INService
|
||||
{
|
||||
public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyExecutor, INService
|
||||
private readonly IGoogleApiService _google;
|
||||
private readonly DbService _db;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly Bot _bot;
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, bool> _atcs = new();
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, (string From, string To)>> _users = new();
|
||||
|
||||
public TranslateService(IGoogleApiService google,
|
||||
DbService db,
|
||||
IEmbedBuilderService eb,
|
||||
Bot bot)
|
||||
{
|
||||
private readonly IGoogleApiService _google;
|
||||
private readonly DbService _db;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly Bot _bot;
|
||||
_google = google;
|
||||
_db = db;
|
||||
_eb = eb;
|
||||
_bot = bot;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, bool> _atcs = new();
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, (string From, string To)>> _users = new();
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
|
||||
public TranslateService(IGoogleApiService google,
|
||||
DbService db,
|
||||
IEmbedBuilderService eb,
|
||||
Bot bot)
|
||||
var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList();
|
||||
var cs = await ctx.AutoTranslateChannels
|
||||
.Include(x => x.Users)
|
||||
.Where(x => guilds.Contains(x.GuildId))
|
||||
.ToListAsyncEF();
|
||||
|
||||
foreach (var c in cs)
|
||||
{
|
||||
_google = google;
|
||||
_db = db;
|
||||
_eb = eb;
|
||||
_bot = bot;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
|
||||
var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList();
|
||||
var cs = await ctx.AutoTranslateChannels
|
||||
.Include(x => x.Users)
|
||||
.Where(x => guilds.Contains(x.GuildId))
|
||||
.ToListAsyncEF();
|
||||
|
||||
foreach (var c in cs)
|
||||
{
|
||||
_atcs[c.ChannelId] = c.AutoDelete;
|
||||
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||
}
|
||||
_atcs[c.ChannelId] = c.AutoDelete;
|
||||
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task LateExecute(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg.Content))
|
||||
return;
|
||||
public async Task LateExecute(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg.Content))
|
||||
return;
|
||||
|
||||
if (msg is IUserMessage { Channel: ITextChannel tch } um)
|
||||
{
|
||||
if (!_atcs.TryGetValue(tch.Id, out var autoDelete))
|
||||
return;
|
||||
if (msg is IUserMessage { Channel: ITextChannel tch } um)
|
||||
{
|
||||
if (!_atcs.TryGetValue(tch.Id, out var autoDelete))
|
||||
return;
|
||||
|
||||
if (!_users.TryGetValue(tch.Id, out var users)
|
||||
|| !users.TryGetValue(um.Author.Id, out var langs))
|
||||
return;
|
||||
if (!_users.TryGetValue(tch.Id, out var users)
|
||||
|| !users.TryGetValue(um.Author.Id, out var langs))
|
||||
return;
|
||||
|
||||
var output = await _google.Translate(msg.Content, langs.From, langs.To);
|
||||
var output = await _google.Translate(msg.Content, langs.From, langs.To);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(output)
|
||||
|| msg.Content.Equals(output, StringComparison.InvariantCultureIgnoreCase))
|
||||
return;
|
||||
if (string.IsNullOrWhiteSpace(output)
|
||||
|| msg.Content.Equals(output, StringComparison.InvariantCultureIgnoreCase))
|
||||
return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor();
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor();
|
||||
|
||||
if (autoDelete)
|
||||
{
|
||||
embed
|
||||
.WithAuthor(um.Author.ToString(), um.Author.GetAvatarUrl())
|
||||
.AddField(langs.From, um.Content)
|
||||
.AddField(langs.To, output);
|
||||
if (autoDelete)
|
||||
{
|
||||
embed
|
||||
.WithAuthor(um.Author.ToString(), um.Author.GetAvatarUrl())
|
||||
.AddField(langs.From, um.Content)
|
||||
.AddField(langs.To, output);
|
||||
|
||||
await tch.EmbedAsync(embed);
|
||||
await tch.EmbedAsync(embed);
|
||||
|
||||
try
|
||||
{
|
||||
await um.DeleteAsync();
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_atcs.TryUpdate(tch.Id, false, true);
|
||||
}
|
||||
|
||||
return;
|
||||
try
|
||||
{
|
||||
await um.DeleteAsync();
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_atcs.TryUpdate(tch.Id, false, true);
|
||||
}
|
||||
|
||||
await um.ReplyAsync(embed: embed
|
||||
.AddField(langs.To, output)
|
||||
.Build(),
|
||||
allowedMentions: AllowedMentions.None);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> Translate(string source, string target, string text = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
throw new ArgumentException("Text is empty or null", nameof(text));
|
||||
|
||||
var res = await _google.Translate(text, source, target).ConfigureAwait(false);
|
||||
return res.SanitizeMentions(true);
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete)
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
|
||||
var old = await ctx.AutoTranslateChannels
|
||||
.ToLinqToDBTable()
|
||||
.FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId);
|
||||
|
||||
if (old is null)
|
||||
{
|
||||
ctx.AutoTranslateChannels
|
||||
.Add(new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
AutoDelete = autoDelete,
|
||||
});
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
_atcs[channelId] = autoDelete;
|
||||
_users[channelId] = new();
|
||||
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// if autodelete value is different, update the autodelete value
|
||||
// instead of disabling
|
||||
if (old.AutoDelete != autoDelete)
|
||||
{
|
||||
old.AutoDelete = autoDelete;
|
||||
await ctx.SaveChangesAsync();
|
||||
_atcs[channelId] = autoDelete;
|
||||
return true;
|
||||
}
|
||||
|
||||
await ctx.AutoTranslateChannels
|
||||
.ToLinqToDBTable()
|
||||
.DeleteAsync(x => x.ChannelId == channelId);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
_atcs.TryRemove(channelId, out _);
|
||||
_users.TryRemove(channelId, out _);
|
||||
|
||||
return false;
|
||||
await um.ReplyAsync(embed: embed
|
||||
.AddField(langs.To, output)
|
||||
.Build(),
|
||||
allowedMentions: AllowedMentions.None);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> Translate(string source, string target, string text = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
throw new ArgumentException("Text is empty or null", nameof(text));
|
||||
|
||||
private void UpdateUser(ulong channelId, ulong userId, string from, string to)
|
||||
{
|
||||
var dict = _users.GetOrAdd(channelId, new ConcurrentDictionary<ulong, (string, string)>());
|
||||
dict[userId] = (from, to);
|
||||
}
|
||||
|
||||
public async Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string from, string to)
|
||||
{
|
||||
if (!_google.Languages.ContainsKey(from) || !_google.Languages.ContainsKey(to))
|
||||
return null;
|
||||
var res = await _google.Translate(text, source, target).ConfigureAwait(false);
|
||||
return res.SanitizeMentions(true);
|
||||
}
|
||||
|
||||
var ctx = _db.GetDbContext();
|
||||
var ch = await ctx.AutoTranslateChannels
|
||||
.GetByChannelId(channelId);
|
||||
public async Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete)
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
|
||||
var old = await ctx.AutoTranslateChannels
|
||||
.ToLinqToDBTable()
|
||||
.FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId);
|
||||
|
||||
if (ch is null)
|
||||
return null;
|
||||
|
||||
var user = ch.Users
|
||||
.FirstOrDefault(x => x.UserId == userId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
ch.Users.Add(user = new()
|
||||
if (old is null)
|
||||
{
|
||||
ctx.AutoTranslateChannels
|
||||
.Add(new()
|
||||
{
|
||||
Source = from,
|
||||
Target = to,
|
||||
UserId = userId,
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
AutoDelete = autoDelete,
|
||||
});
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
UpdateUser(channelId, userId, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it's different from old settings, update
|
||||
if (user.Source != from || user.Target != to)
|
||||
{
|
||||
user.Source = from;
|
||||
user.Target = to;
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
UpdateUser(channelId, userId, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return await UnregisterUser(channelId, userId);
|
||||
}
|
||||
|
||||
public async Task<bool> UnregisterUser(ulong channelId, ulong userId)
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
var rows = await ctx.AutoTranslateUsers
|
||||
.ToLinqToDBTable()
|
||||
.DeleteAsync(x => x.UserId == userId &&
|
||||
x.Channel.ChannelId == channelId);
|
||||
|
||||
if (_users.TryGetValue(channelId, out var inner))
|
||||
inner.TryRemove(userId, out _);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
return rows > 0;
|
||||
|
||||
_atcs[channelId] = autoDelete;
|
||||
_users[channelId] = new();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if autodelete value is different, update the autodelete value
|
||||
// instead of disabling
|
||||
if (old.AutoDelete != autoDelete)
|
||||
{
|
||||
old.AutoDelete = autoDelete;
|
||||
await ctx.SaveChangesAsync();
|
||||
_atcs[channelId] = autoDelete;
|
||||
return true;
|
||||
}
|
||||
|
||||
await ctx.AutoTranslateChannels
|
||||
.ToLinqToDBTable()
|
||||
.DeleteAsync(x => x.ChannelId == channelId);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
_atcs.TryRemove(channelId, out _);
|
||||
_users.TryRemove(channelId, out _);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private void UpdateUser(ulong channelId, ulong userId, string from, string to)
|
||||
{
|
||||
var dict = _users.GetOrAdd(channelId, new ConcurrentDictionary<ulong, (string, string)>());
|
||||
dict[userId] = (from, to);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetLanguages() => _google.Languages.Select(x => x.Key);
|
||||
}
|
||||
public async Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string from, string to)
|
||||
{
|
||||
if (!_google.Languages.ContainsKey(from) || !_google.Languages.ContainsKey(to))
|
||||
return null;
|
||||
|
||||
var ctx = _db.GetDbContext();
|
||||
var ch = await ctx.AutoTranslateChannels
|
||||
.GetByChannelId(channelId);
|
||||
|
||||
if (ch is null)
|
||||
return null;
|
||||
|
||||
var user = ch.Users
|
||||
.FirstOrDefault(x => x.UserId == userId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
ch.Users.Add(user = new()
|
||||
{
|
||||
Source = from,
|
||||
Target = to,
|
||||
UserId = userId,
|
||||
});
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
UpdateUser(channelId, userId, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it's different from old settings, update
|
||||
if (user.Source != from || user.Target != to)
|
||||
{
|
||||
user.Source = from;
|
||||
user.Target = to;
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
UpdateUser(channelId, userId, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return await UnregisterUser(channelId, userId);
|
||||
}
|
||||
|
||||
public async Task<bool> UnregisterUser(ulong channelId, ulong userId)
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
var rows = await ctx.AutoTranslateUsers
|
||||
.ToLinqToDBTable()
|
||||
.DeleteAsync(x => x.UserId == userId &&
|
||||
x.Channel.ChannelId == channelId);
|
||||
|
||||
if (_users.TryGetValue(channelId, out var inner))
|
||||
inner.TryRemove(userId, out _);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetLanguages() => _google.Languages.Select(x => x.Key);
|
||||
}
|
@@ -1,137 +1,134 @@
|
||||
using NadekoBot.Services;
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services
|
||||
{
|
||||
// public class YtTrackService : INService
|
||||
// {
|
||||
// private readonly IGoogleApiService _google;
|
||||
// private readonly IHttpClientFactory httpClientFactory;
|
||||
// private readonly DiscordSocketClient _client;
|
||||
// private readonly DbService _db;
|
||||
// private readonly ConcurrentDictionary<string, ConcurrentDictionary<ulong, List<YtFollowedChannel>>> followedChannels;
|
||||
// private readonly ConcurrentDictionary<string, DateTime> _latestPublishes = new ConcurrentDictionary<string, DateTime>();
|
||||
//
|
||||
// public YtTrackService(IGoogleApiService google, IHttpClientFactory httpClientFactory, DiscordSocketClient client,
|
||||
// DbService db)
|
||||
// {
|
||||
// this._google = google;
|
||||
// this.httpClientFactory = httpClientFactory;
|
||||
// this._client = client;
|
||||
// this._db = db;
|
||||
//
|
||||
// if (_client.ShardId == 0)
|
||||
// {
|
||||
// _ = CheckLoop();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public async Task CheckLoop()
|
||||
// {
|
||||
// while (true)
|
||||
// {
|
||||
// await Task.Delay(10000);
|
||||
// using (var http = httpClientFactory.CreateClient())
|
||||
// {
|
||||
// await Task.WhenAll(followedChannels.Select(kvp => CheckChannel(kvp.Key, kvp.Value.SelectMany(x => x.Value).ToList())));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Checks the specified youtube channel, and sends a message to all provided
|
||||
// /// </summary>
|
||||
// /// <param name="youtubeChannelId">Id of the youtube channel</param>
|
||||
// /// <param name="followedChannels">Where to post updates if there is a new update</param>
|
||||
// private async Task CheckChannel(string youtubeChannelId, List<YtFollowedChannel> followedChannels)
|
||||
// {
|
||||
// var latestVid = (await _google.GetLatestChannelVideosAsync(youtubeChannelId, 1))
|
||||
// .FirstOrDefault();
|
||||
// if (latestVid is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (_latestPublishes.TryGetValue(youtubeChannelId, out var latestPub) && latestPub >= latestVid.PublishedAt)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// _latestPublishes[youtubeChannelId] = latestVid.PublishedAt;
|
||||
//
|
||||
// foreach (var chObj in followedChannels)
|
||||
// {
|
||||
// var gCh = _client.GetChannel(chObj.ChannelId);
|
||||
// if (gCh is ITextChannel ch)
|
||||
// {
|
||||
// var msg = latestVid.GetVideoUrl();
|
||||
// if (!string.IsNullOrWhiteSpace(chObj.UploadMessage))
|
||||
// msg = chObj.UploadMessage + Environment.NewLine + msg;
|
||||
//
|
||||
// await ch.SendMessageAsync(msg);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Starts posting updates on the specified discord channel when a new video is posted on the specified YouTube channel.
|
||||
// /// </summary>
|
||||
// /// <param name="guildId">Id of the discord guild</param>
|
||||
// /// <param name="channelId">Id of the discord channel</param>
|
||||
// /// <param name="ytChannelId">Id of the youtube channel</param>
|
||||
// /// <param name="uploadMessage">Message to post when a new video is uploaded, along with video URL</param>
|
||||
// /// <returns>Whether adding was successful</returns>
|
||||
// public async Task<bool> ToggleChannelFollowAsync(ulong guildId, ulong channelId, string ytChannelId, string uploadMessage)
|
||||
// {
|
||||
// // to to see if we can get a video from that channel
|
||||
// var vids = await _google.GetLatestChannelVideosAsync(ytChannelId, 1);
|
||||
// if (vids.Count == 0)
|
||||
// return false;
|
||||
//
|
||||
// using(var uow = _db.GetDbContext())
|
||||
// {
|
||||
// var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.YtFollowedChannels));
|
||||
//
|
||||
// // see if this yt channel was already followed on this discord channel
|
||||
// var oldObj = gc.YtFollowedChannels
|
||||
// .FirstOrDefault(x => x.ChannelId == channelId && x.YtChannelId == ytChannelId);
|
||||
//
|
||||
// if(oldObj is not null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // can only add up to 10 tracked channels per server
|
||||
// if (gc.YtFollowedChannels.Count >= 10)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// var obj = new YtFollowedChannel
|
||||
// {
|
||||
// ChannelId = channelId,
|
||||
// YtChannelId = ytChannelId,
|
||||
// UploadMessage = uploadMessage
|
||||
// };
|
||||
//
|
||||
// // add to database
|
||||
// gc.YtFollowedChannels.Add(obj);
|
||||
//
|
||||
// // add to the local cache:
|
||||
//
|
||||
// // get follows on all guilds
|
||||
// var allGuildFollows = followedChannels.GetOrAdd(ytChannelId, new ConcurrentDictionary<ulong, List<YtFollowedChannel>>());
|
||||
// // add to this guild's follows
|
||||
// allGuildFollows.AddOrUpdate(guildId,
|
||||
// new List<YtFollowedChannel>(),
|
||||
// (key, old) =>
|
||||
// {
|
||||
// old.Add(obj);
|
||||
// return old;
|
||||
// });
|
||||
//
|
||||
// await uow.SaveChangesAsync();
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
// public class YtTrackService : INService
|
||||
// {
|
||||
// private readonly IGoogleApiService _google;
|
||||
// private readonly IHttpClientFactory httpClientFactory;
|
||||
// private readonly DiscordSocketClient _client;
|
||||
// private readonly DbService _db;
|
||||
// private readonly ConcurrentDictionary<string, ConcurrentDictionary<ulong, List<YtFollowedChannel>>> followedChannels;
|
||||
// private readonly ConcurrentDictionary<string, DateTime> _latestPublishes = new ConcurrentDictionary<string, DateTime>();
|
||||
//
|
||||
// public YtTrackService(IGoogleApiService google, IHttpClientFactory httpClientFactory, DiscordSocketClient client,
|
||||
// DbService db)
|
||||
// {
|
||||
// this._google = google;
|
||||
// this.httpClientFactory = httpClientFactory;
|
||||
// this._client = client;
|
||||
// this._db = db;
|
||||
//
|
||||
// if (_client.ShardId == 0)
|
||||
// {
|
||||
// _ = CheckLoop();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public async Task CheckLoop()
|
||||
// {
|
||||
// while (true)
|
||||
// {
|
||||
// await Task.Delay(10000);
|
||||
// using (var http = httpClientFactory.CreateClient())
|
||||
// {
|
||||
// await Task.WhenAll(followedChannels.Select(kvp => CheckChannel(kvp.Key, kvp.Value.SelectMany(x => x.Value).ToList())));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Checks the specified youtube channel, and sends a message to all provided
|
||||
// /// </summary>
|
||||
// /// <param name="youtubeChannelId">Id of the youtube channel</param>
|
||||
// /// <param name="followedChannels">Where to post updates if there is a new update</param>
|
||||
// private async Task CheckChannel(string youtubeChannelId, List<YtFollowedChannel> followedChannels)
|
||||
// {
|
||||
// var latestVid = (await _google.GetLatestChannelVideosAsync(youtubeChannelId, 1))
|
||||
// .FirstOrDefault();
|
||||
// if (latestVid is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (_latestPublishes.TryGetValue(youtubeChannelId, out var latestPub) && latestPub >= latestVid.PublishedAt)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// _latestPublishes[youtubeChannelId] = latestVid.PublishedAt;
|
||||
//
|
||||
// foreach (var chObj in followedChannels)
|
||||
// {
|
||||
// var gCh = _client.GetChannel(chObj.ChannelId);
|
||||
// if (gCh is ITextChannel ch)
|
||||
// {
|
||||
// var msg = latestVid.GetVideoUrl();
|
||||
// if (!string.IsNullOrWhiteSpace(chObj.UploadMessage))
|
||||
// msg = chObj.UploadMessage + Environment.NewLine + msg;
|
||||
//
|
||||
// await ch.SendMessageAsync(msg);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Starts posting updates on the specified discord channel when a new video is posted on the specified YouTube channel.
|
||||
// /// </summary>
|
||||
// /// <param name="guildId">Id of the discord guild</param>
|
||||
// /// <param name="channelId">Id of the discord channel</param>
|
||||
// /// <param name="ytChannelId">Id of the youtube channel</param>
|
||||
// /// <param name="uploadMessage">Message to post when a new video is uploaded, along with video URL</param>
|
||||
// /// <returns>Whether adding was successful</returns>
|
||||
// public async Task<bool> ToggleChannelFollowAsync(ulong guildId, ulong channelId, string ytChannelId, string uploadMessage)
|
||||
// {
|
||||
// // to to see if we can get a video from that channel
|
||||
// var vids = await _google.GetLatestChannelVideosAsync(ytChannelId, 1);
|
||||
// if (vids.Count == 0)
|
||||
// return false;
|
||||
//
|
||||
// using(var uow = _db.GetDbContext())
|
||||
// {
|
||||
// var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.YtFollowedChannels));
|
||||
//
|
||||
// // see if this yt channel was already followed on this discord channel
|
||||
// var oldObj = gc.YtFollowedChannels
|
||||
// .FirstOrDefault(x => x.ChannelId == channelId && x.YtChannelId == ytChannelId);
|
||||
//
|
||||
// if(oldObj is not null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // can only add up to 10 tracked channels per server
|
||||
// if (gc.YtFollowedChannels.Count >= 10)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// var obj = new YtFollowedChannel
|
||||
// {
|
||||
// ChannelId = channelId,
|
||||
// YtChannelId = ytChannelId,
|
||||
// UploadMessage = uploadMessage
|
||||
// };
|
||||
//
|
||||
// // add to database
|
||||
// gc.YtFollowedChannels.Add(obj);
|
||||
//
|
||||
// // add to the local cache:
|
||||
//
|
||||
// // get follows on all guilds
|
||||
// var allGuildFollows = followedChannels.GetOrAdd(ytChannelId, new ConcurrentDictionary<ulong, List<YtFollowedChannel>>());
|
||||
// // add to this guild's follows
|
||||
// allGuildFollows.AddOrUpdate(guildId,
|
||||
// new List<YtFollowedChannel>(),
|
||||
// (key, old) =>
|
||||
// {
|
||||
// old.Add(obj);
|
||||
// return old;
|
||||
// });
|
||||
//
|
||||
// await uow.SaveChangesAsync();
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// }
|
@@ -1,227 +1,222 @@
|
||||
using Discord.Commands;
|
||||
using Discord;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Services;
|
||||
using System.Collections.Generic;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class StreamNotificationCommands : NadekoSubmodule<StreamNotificationService>
|
||||
{
|
||||
[Group]
|
||||
public class StreamNotificationCommands : NadekoSubmodule<StreamNotificationService>
|
||||
private readonly DbService _db;
|
||||
|
||||
public StreamNotificationCommands(DbService db)
|
||||
{
|
||||
private readonly DbService _db;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public StreamNotificationCommands(DbService db)
|
||||
// private static readonly Regex picartoRegex = new Regex(@"picarto.tv/(?<name>.+[^/])/?",
|
||||
// RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task StreamAdd(string link)
|
||||
{
|
||||
var data = await _service.FollowStream(ctx.Guild.Id, ctx.Channel.Id, link);
|
||||
if (data is null)
|
||||
{
|
||||
_db = db;
|
||||
await ReplyErrorLocalizedAsync(strs.stream_not_added).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// private static readonly Regex picartoRegex = new Regex(@"picarto.tv/(?<name>.+[^/])/?",
|
||||
// RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
var embed = _service.GetEmbed(ctx.Guild.Id, data);
|
||||
await ctx.Channel.EmbedAsync(embed, GetText(strs.stream_tracked)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task StreamAdd(string link)
|
||||
{
|
||||
var data = await _service.FollowStream(ctx.Guild.Id, ctx.Channel.Id, link);
|
||||
if (data is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.stream_not_added).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _service.GetEmbed(ctx.Guild.Id, data);
|
||||
await ctx.Channel.EmbedAsync(embed, GetText(strs.stream_tracked)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(1)]
|
||||
public async Task StreamRemove(int index)
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(1)]
|
||||
public async Task StreamRemove(int index)
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
var fs = await _service.UnfollowStreamAsync(ctx.Guild.Id, index);
|
||||
if (fs is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.stream_no).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(
|
||||
strs.stream_removed(
|
||||
Format.Bold(fs.Username),
|
||||
fs.Type));
|
||||
var fs = await _service.UnfollowStreamAsync(ctx.Guild.Id, index);
|
||||
if (fs is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.stream_no).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task StreamsClear()
|
||||
await ReplyConfirmLocalizedAsync(
|
||||
strs.stream_removed(
|
||||
Format.Bold(fs.Username),
|
||||
fs.Type));
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task StreamsClear()
|
||||
{
|
||||
var count = _service.ClearAllStreams(ctx.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.streams_cleared);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamList(int page = 1)
|
||||
{
|
||||
if (page-- < 1)
|
||||
{
|
||||
var count = _service.ClearAllStreams(ctx.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.streams_cleared);
|
||||
return;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamList(int page = 1)
|
||||
List<FollowedStream> streams = new List<FollowedStream>();
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
if (page-- < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var all = uow
|
||||
.GuildConfigsForId(ctx.Guild.Id, set => set.Include(gc => gc.FollowedStreams))
|
||||
.FollowedStreams
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
|
||||
List<FollowedStream> streams = new List<FollowedStream>();
|
||||
using (var uow = _db.GetDbContext())
|
||||
for (var index = all.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var all = uow
|
||||
.GuildConfigsForId(ctx.Guild.Id, set => set.Include(gc => gc.FollowedStreams))
|
||||
.FollowedStreams
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
|
||||
for (var index = all.Count - 1; index >= 0; index--)
|
||||
var fs = all[index];
|
||||
if (((SocketGuild) ctx.Guild).GetTextChannel(fs.ChannelId) is null)
|
||||
{
|
||||
var fs = all[index];
|
||||
if (((SocketGuild) ctx.Guild).GetTextChannel(fs.ChannelId) is null)
|
||||
{
|
||||
await _service.UnfollowStreamAsync(fs.GuildId, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
streams.Insert(0, fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, (cur) =>
|
||||
{
|
||||
var elements = streams.Skip(cur * 12).Take(12)
|
||||
.ToList();
|
||||
|
||||
if (elements.Count == 0)
|
||||
{
|
||||
return _eb.Create()
|
||||
.WithDescription(GetText(strs.streams_none))
|
||||
.WithErrorColor();
|
||||
}
|
||||
|
||||
var eb = _eb.Create()
|
||||
.WithTitle(GetText(strs.streams_follow_title))
|
||||
.WithOkColor();
|
||||
for (var index = 0; index < elements.Count; index++)
|
||||
{
|
||||
var elem = elements[index];
|
||||
eb.AddField(
|
||||
$"**#{(index + 1) + (12 * cur)}** {elem.Username.ToLower()}",
|
||||
$"【{elem.Type}】\n<#{elem.ChannelId}>\n{elem.Message?.TrimTo(50)}",
|
||||
true);
|
||||
}
|
||||
|
||||
return eb;
|
||||
}, streams.Count, 12).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task StreamOffline()
|
||||
{
|
||||
var newValue = _service.ToggleStreamOffline(ctx.Guild.Id);
|
||||
if (newValue)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_off_enabled).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_off_disabled).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task StreamMessage(int index, [Leftover] string message)
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
if (!_service.SetStreamMessage(ctx.Guild.Id, index, message, out var fs))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_not_following).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_message_reset(Format.Bold(fs.Username)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_message_set(Format.Bold(fs.Username)));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task StreamMessageAll([Leftover] string message)
|
||||
{
|
||||
var count = _service.SetStreamMessageForAll(ctx.Guild.Id, message);
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_not_following_any);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_message_set_all(count));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamCheck(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await _service.GetStreamDataAsync(url).ConfigureAwait(false);
|
||||
if (data is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_channel_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.IsLive)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.streamer_online(
|
||||
Format.Bold(data.Name),
|
||||
Format.Bold(data.Viewers.ToString())));
|
||||
await _service.UnfollowStreamAsync(fs.GuildId, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.streamer_offline(data.Name));
|
||||
streams.Insert(0, fs);
|
||||
}
|
||||
}
|
||||
catch
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, (cur) =>
|
||||
{
|
||||
var elements = streams.Skip(cur * 12).Take(12)
|
||||
.ToList();
|
||||
|
||||
if (elements.Count == 0)
|
||||
{
|
||||
return _eb.Create()
|
||||
.WithDescription(GetText(strs.streams_none))
|
||||
.WithErrorColor();
|
||||
}
|
||||
|
||||
var eb = _eb.Create()
|
||||
.WithTitle(GetText(strs.streams_follow_title))
|
||||
.WithOkColor();
|
||||
for (var index = 0; index < elements.Count; index++)
|
||||
{
|
||||
var elem = elements[index];
|
||||
eb.AddField(
|
||||
$"**#{(index + 1) + (12 * cur)}** {elem.Username.ToLower()}",
|
||||
$"【{elem.Type}】\n<#{elem.ChannelId}>\n{elem.Message?.TrimTo(50)}",
|
||||
true);
|
||||
}
|
||||
|
||||
return eb;
|
||||
}, streams.Count, 12).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task StreamOffline()
|
||||
{
|
||||
var newValue = _service.ToggleStreamOffline(ctx.Guild.Id);
|
||||
if (newValue)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_off_enabled).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_off_disabled).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task StreamMessage(int index, [Leftover] string message)
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
if (!_service.SetStreamMessage(ctx.Guild.Id, index, message, out var fs))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_not_following).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_message_reset(Format.Bold(fs.Username)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_message_set(Format.Bold(fs.Username)));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task StreamMessageAll([Leftover] string message)
|
||||
{
|
||||
var count = _service.SetStreamMessageForAll(ctx.Guild.Id, message);
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_not_following_any);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_message_set_all(count));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamCheck(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await _service.GetStreamDataAsync(url).ConfigureAwait(false);
|
||||
if (data is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_channel_found).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.IsLive)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.streamer_online(
|
||||
Format.Bold(data.Name),
|
||||
Format.Bold(data.Viewers.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.streamer_offline(data.Name));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_channel_found).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,99 +4,98 @@ using NadekoBot.Extensions;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class TranslateCommands : NadekoSubmodule<ITranslateService>
|
||||
{
|
||||
[Group]
|
||||
public class TranslateCommands : NadekoSubmodule<ITranslateService>
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Translate(string from, string to, [Leftover] string text = null)
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Translate(string from, string to, [Leftover] string text = null)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var translation = await _service.Translate(from, to, text).ConfigureAwait(false);
|
||||
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var translation = await _service.Translate(from, to, text).ConfigureAwait(false);
|
||||
|
||||
var embed = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.AddField(from, text, false)
|
||||
.AddField(to, translation, false);
|
||||
var embed = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.AddField(from, text, false)
|
||||
.AddField(to, translation, false);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.bad_input_format).ConfigureAwait(false);
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
public enum AutoDeleteAutoTranslate
|
||||
catch
|
||||
{
|
||||
Del,
|
||||
Nodel
|
||||
await ReplyErrorLocalizedAsync(strs.bad_input_format).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
[OwnerOnly]
|
||||
public async Task AutoTranslate(AutoDeleteAutoTranslate autoDelete = AutoDeleteAutoTranslate.Nodel)
|
||||
{
|
||||
var toggle = await _service.ToggleAtl(ctx.Guild.Id, ctx.Channel.Id, autoDelete == AutoDeleteAutoTranslate.Del);
|
||||
if (toggle)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_started).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_stopped).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AutoTransLang()
|
||||
{
|
||||
if (await _service.UnregisterUser(ctx.Channel.Id, ctx.User.Id))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_removed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AutoTransLang(string from, string to)
|
||||
{
|
||||
var succ = await _service.RegisterUserAsync(ctx.User.Id,
|
||||
ctx.Channel.Id,
|
||||
from.ToLower(),
|
||||
to.ToLower());
|
||||
|
||||
if (succ is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.atl_not_enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (succ is false)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.invalid_lang).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_set(from, to));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Translangs()
|
||||
{
|
||||
await ctx.Channel.SendTableAsync(_service.GetLanguages(), str => $"{str,-15}", 3).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum AutoDeleteAutoTranslate
|
||||
{
|
||||
Del,
|
||||
Nodel
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
[OwnerOnly]
|
||||
public async Task AutoTranslate(AutoDeleteAutoTranslate autoDelete = AutoDeleteAutoTranslate.Nodel)
|
||||
{
|
||||
var toggle = await _service.ToggleAtl(ctx.Guild.Id, ctx.Channel.Id, autoDelete == AutoDeleteAutoTranslate.Del);
|
||||
if (toggle)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_started).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_stopped).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AutoTransLang()
|
||||
{
|
||||
if (await _service.UnregisterUser(ctx.Channel.Id, ctx.User.Id))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_removed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AutoTransLang(string from, string to)
|
||||
{
|
||||
var succ = await _service.RegisterUserAsync(ctx.User.Id,
|
||||
ctx.Channel.Id,
|
||||
from.ToLower(),
|
||||
to.ToLower());
|
||||
|
||||
if (succ is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.atl_not_enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (succ is false)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.invalid_lang).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.atl_set(from, to));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Translangs()
|
||||
{
|
||||
await ctx.Channel.SendTableAsync(_service.GetLanguages(), str => $"{str,-15}", 3).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.Http;
|
||||
@@ -7,77 +6,40 @@ using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
[Group]
|
||||
public class XkcdCommands : NadekoSubmodule
|
||||
{
|
||||
[Group]
|
||||
public class XkcdCommands : NadekoSubmodule
|
||||
private const string _xkcdUrl = "https://xkcd.com";
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public XkcdCommands(IHttpClientFactory factory)
|
||||
{
|
||||
private const string _xkcdUrl = "https://xkcd.com";
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
_httpFactory = factory;
|
||||
}
|
||||
|
||||
public XkcdCommands(IHttpClientFactory factory)
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Xkcd(string arg = null)
|
||||
{
|
||||
if (arg?.ToLowerInvariant().Trim() == "latest")
|
||||
{
|
||||
_httpFactory = factory;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Xkcd(string arg = null)
|
||||
{
|
||||
if (arg?.ToLowerInvariant().Trim() == "latest")
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
var res = await http.GetStringAsync($"{_xkcdUrl}/info.0.json").ConfigureAwait(false);
|
||||
var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithImageUrl(comic.ImageLink)
|
||||
.WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{comic.Num}")
|
||||
.AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
|
||||
.AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
|
||||
var sent = await ctx.Channel.EmbedAsync(embed)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
|
||||
await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt.ToString(), false).Build()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.comic_not_found).ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
await Xkcd(new NadekoRandom().Next(1, 1750)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Xkcd(int num)
|
||||
{
|
||||
if (num < 1)
|
||||
return;
|
||||
try
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
var res = await http.GetStringAsync($"{_xkcdUrl}/{num}/info.0.json").ConfigureAwait(false);
|
||||
|
||||
var res = await http.GetStringAsync($"{_xkcdUrl}/info.0.json").ConfigureAwait(false);
|
||||
var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithImageUrl(comic.ImageLink)
|
||||
.WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{num}")
|
||||
.WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{comic.Num}")
|
||||
.AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
|
||||
.AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
|
||||
|
||||
var sent = await ctx.Channel.EmbedAsync(embed)
|
||||
.ConfigureAwait(false);
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
|
||||
@@ -88,19 +50,55 @@ namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.comic_not_found).ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
await Xkcd(new NadekoRandom().Next(1, 1750)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public class XkcdComic
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Xkcd(int num)
|
||||
{
|
||||
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; }
|
||||
if (num < 1)
|
||||
return;
|
||||
try
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
var res = await http.GetStringAsync($"{_xkcdUrl}/{num}/info.0.json").ConfigureAwait(false);
|
||||
|
||||
var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithImageUrl(comic.ImageLink)
|
||||
.WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{num}")
|
||||
.AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
|
||||
.AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
|
||||
|
||||
var sent = await ctx.Channel.EmbedAsync(embed)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
|
||||
await sent.ModifyAsync(m => m.Embed = embed.AddField("Alt", comic.Alt.ToString(), false).Build()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.comic_not_found).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
@@ -1,58 +1,53 @@
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
using NadekoBot.Modules;
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
public partial class Searches
|
||||
{
|
||||
public partial class Searches
|
||||
{
|
||||
// [Group]
|
||||
// public class YtTrackCommands : NadekoSubmodule<YtTrackService>
|
||||
// {
|
||||
// ;
|
||||
// [RequireContext(ContextType.Guild)]
|
||||
// public async Task YtFollow(string ytChannelId, [Leftover] string uploadMessage = null)
|
||||
// {
|
||||
// var succ = await _service.ToggleChannelFollowAsync(ctx.Guild.Id, ctx.Channel.Id, ytChannelId, uploadMessage);
|
||||
// if(succ)
|
||||
// {
|
||||
// await ReplyConfirmLocalizedAsync(strs.yt_follow_added).ConfigureAwait(false);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// await ReplyConfirmLocalizedAsync(strs.yt_follow_fail).ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [NadekoCommand, Usage, Description, Aliases]
|
||||
// [RequireContext(ContextType.Guild)]
|
||||
// public async Task YtTrackRm(int index)
|
||||
// {
|
||||
// //var succ = await _service.ToggleChannelTrackingAsync(ctx.Guild.Id, ctx.Channel.Id, ytChannelId, uploadMessage);
|
||||
// //if (succ)
|
||||
// //{
|
||||
// // await ReplyConfirmLocalizedAsync(strs.yt_track_added).ConfigureAwait(false);
|
||||
// //}
|
||||
// //else
|
||||
// //{
|
||||
// // await ReplyConfirmLocalizedAsync(strs.yt_track_fail).ConfigureAwait(false);
|
||||
// //}
|
||||
// }
|
||||
//
|
||||
// [NadekoCommand, Usage, Description, Aliases]
|
||||
// [RequireContext(ContextType.Guild)]
|
||||
// public async Task YtTrackList()
|
||||
// {
|
||||
// //var succ = await _service.ToggleChannelTrackingAsync(ctx.Guild.Id, ctx.Channel.Id, ytChannelId, uploadMessage);
|
||||
// //if (succ)
|
||||
// //{
|
||||
// // await ReplyConfirmLocalizedAsync(strs.yt_track_added).ConfigureAwait(false);
|
||||
// //}
|
||||
// //else
|
||||
// //{
|
||||
// // await ReplyConfirmLocalizedAsync(strs.yt_track_fail).ConfigureAwait(false);
|
||||
// //}
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
// [Group]
|
||||
// public class YtTrackCommands : NadekoSubmodule<YtTrackService>
|
||||
// {
|
||||
// ;
|
||||
// [RequireContext(ContextType.Guild)]
|
||||
// public async Task YtFollow(string ytChannelId, [Leftover] string uploadMessage = null)
|
||||
// {
|
||||
// var succ = await _service.ToggleChannelFollowAsync(ctx.Guild.Id, ctx.Channel.Id, ytChannelId, uploadMessage);
|
||||
// if(succ)
|
||||
// {
|
||||
// await ReplyConfirmLocalizedAsync(strs.yt_follow_added).ConfigureAwait(false);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// await ReplyConfirmLocalizedAsync(strs.yt_follow_fail).ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [NadekoCommand, Usage, Description, Aliases]
|
||||
// [RequireContext(ContextType.Guild)]
|
||||
// public async Task YtTrackRm(int index)
|
||||
// {
|
||||
// //var succ = await _service.ToggleChannelTrackingAsync(ctx.Guild.Id, ctx.Channel.Id, ytChannelId, uploadMessage);
|
||||
// //if (succ)
|
||||
// //{
|
||||
// // await ReplyConfirmLocalizedAsync(strs.yt_track_added).ConfigureAwait(false);
|
||||
// //}
|
||||
// //else
|
||||
// //{
|
||||
// // await ReplyConfirmLocalizedAsync(strs.yt_track_fail).ConfigureAwait(false);
|
||||
// //}
|
||||
// }
|
||||
//
|
||||
// [NadekoCommand, Usage, Description, Aliases]
|
||||
// [RequireContext(ContextType.Guild)]
|
||||
// public async Task YtTrackList()
|
||||
// {
|
||||
// //var succ = await _service.ToggleChannelTrackingAsync(ctx.Guild.Id, ctx.Channel.Id, ytChannelId, uploadMessage);
|
||||
// //if (succ)
|
||||
// //{
|
||||
// // await ReplyConfirmLocalizedAsync(strs.yt_track_added).ConfigureAwait(false);
|
||||
// //}
|
||||
// //else
|
||||
// //{
|
||||
// // await ReplyConfirmLocalizedAsync(strs.yt_track_fail).ConfigureAwait(false);
|
||||
// //}
|
||||
// }
|
||||
// }
|
||||
}
|
Reference in New Issue
Block a user