Fixed some crashes in response strings source generator, reorganized more submodules into their folders

This commit is contained in:
Kwoth
2022-01-02 03:49:54 +01:00
parent 9c590668df
commit 4b6af0e4ef
191 changed files with 120 additions and 80 deletions

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

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

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

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