mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-12 18:28:27 -04:00
Fixed some crashes in response strings source generator, reorganized more submodules into their folders
This commit is contained in:
37
src/NadekoBot/Modules/Searches/Anime/AnimeResult.cs
Normal file
37
src/NadekoBot/Modules/Searches/Anime/AnimeResult.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#nullable disable
|
||||
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; }
|
||||
|
||||
public string Link
|
||||
=> "http://anilist.co/anime/" + Id;
|
||||
|
||||
public string Synopsis
|
||||
=> Description?[..(Description.Length > 500 ? 500 : Description.Length)] + "...";
|
||||
}
|
202
src/NadekoBot/Modules/Searches/Anime/AnimeSearchCommands.cs
Normal file
202
src/NadekoBot/Modules/Searches/Anime/AnimeSearchCommands.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
#nullable disable
|
||||
using AngleSharp;
|
||||
using AngleSharp.Html.Dom;
|
||||
using NadekoBot.Modules.Searches.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public partial class Searches
|
||||
{
|
||||
[Group]
|
||||
public partial class AnimeSearchCommands : NadekoSubmodule<AnimeSearchService>
|
||||
{
|
||||
// [NadekoCommand, Aliases]
|
||||
// public async Task Novel([Leftover] string query)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(query))
|
||||
// return;
|
||||
//
|
||||
// var novelData = await _service.GetNovelData(query);
|
||||
//
|
||||
// if (novelData is null)
|
||||
// {
|
||||
// await ReplyErrorLocalizedAsync(strs.failed_finding_novel);
|
||||
// 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);
|
||||
// }
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Mal([Leftover] string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
var fullQueryLink = "https://myanimelist.net/profile/" + name;
|
||||
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
var imageElem =
|
||||
document.QuerySelector(
|
||||
"body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-left > div.user-profile > div.user-image > img");
|
||||
var imageUrl = ((IHtmlImageElement)imageElem)?.Source
|
||||
?? "http://icecream.me/uploads/870b03f36b59cc16ebfe314ef2dde781.png";
|
||||
|
||||
var stats = document
|
||||
.QuerySelectorAll(
|
||||
"body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-right > div#statistics > div.user-statistics-stats > div.stats > div.clearfix > ul.stats-status > li > span")
|
||||
.Select(x => x.InnerHtml)
|
||||
.ToList();
|
||||
|
||||
var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
|
||||
|
||||
var favAnime = GetText(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 info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li.clearfix")
|
||||
.Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml))
|
||||
.ToList();
|
||||
|
||||
var daysAndMean = document.QuerySelectorAll("div.anime:nth-child(1) > div:nth-child(2) > div")
|
||||
.Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray())
|
||||
.ToArray();
|
||||
|
||||
var embed = _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($@"
|
||||
** https://myanimelist.net/animelist/{name} **
|
||||
|
||||
**{GetText(strs.top_3_fav_anime)}**
|
||||
{favAnime}")
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithImageUrl(imageUrl);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
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 "❔";
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public partial Task Mal(IGuildUser usr)
|
||||
=> Mal(usr.Username);
|
||||
|
||||
[Cmd]
|
||||
public async partial Task Anime([Leftover] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var animeData = await _service.GetAnimeData(query);
|
||||
|
||||
if (animeData is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.failed_finding_anime);
|
||||
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, 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);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async partial Task Manga([Leftover] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
var mangaData = await _service.GetMangaData(query);
|
||||
|
||||
if (mangaData is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.failed_finding_manga);
|
||||
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, 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);
|
||||
}
|
||||
}
|
||||
}
|
144
src/NadekoBot/Modules/Searches/Anime/AnimeSearchService.cs
Normal file
144
src/NadekoBot/Modules/Searches/Anime/AnimeSearchService.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
#nullable disable
|
||||
using AngleSharp;
|
||||
using AngleSharp.Html.Dom;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public class AnimeSearchService : INService
|
||||
{
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public AnimeSearchService(IDataCache cache, IHttpClientFactory httpFactory)
|
||||
{
|
||||
_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);
|
||||
if (!ok)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
data = await http.GetStringAsync(link);
|
||||
}
|
||||
|
||||
await _cache.SetAnimeDataAsync(link, data);
|
||||
}
|
||||
|
||||
|
||||
return JsonConvert.DeserializeObject<AnimeResult>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
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);
|
||||
if (!ok)
|
||||
{
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using var document = await BrowsingContext.New(config).OpenAsync(link);
|
||||
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 is not null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var authors = document.QuerySelector("div#showauthors")
|
||||
.Children.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x is not 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));
|
||||
|
||||
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);
|
||||
if (!ok)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
data = await http.GetStringAsync(link);
|
||||
}
|
||||
|
||||
await _cache.SetAnimeDataAsync(link, data);
|
||||
}
|
||||
|
||||
|
||||
return JsonConvert.DeserializeObject<MangaResult>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
36
src/NadekoBot/Modules/Searches/Anime/MangaResult.cs
Normal file
36
src/NadekoBot/Modules/Searches/Anime/MangaResult.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
#nullable disable
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common;
|
||||
|
||||
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?[..(Description.Length > 500 ? 500 : Description.Length)] + "...";
|
||||
}
|
Reference in New Issue
Block a user