From e336b2ae08f07cebe90400d36e6fb2fc382f98af Mon Sep 17 00:00:00 2001 From: chrispr Date: Mon, 31 Aug 2026 21:36:21 -0400 Subject: [PATCH] Added Anilist datasource; Handled bug when two-posts were not enough for monthly airing post --- AnimeAnnouncer/Anilist.cs | 48 +++++++++++++++++++++++++++ AnimeAnnouncer/Cache/TMDBCacheItem.cs | 4 +++ AnimeAnnouncer/Program.cs | 42 +++++++++++++++++++++-- build.sh | 2 +- 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 AnimeAnnouncer/Anilist.cs diff --git a/AnimeAnnouncer/Anilist.cs b/AnimeAnnouncer/Anilist.cs new file mode 100644 index 0000000..7c97119 --- /dev/null +++ b/AnimeAnnouncer/Anilist.cs @@ -0,0 +1,48 @@ + +using Mastonet.Entities; + +namespace AnimeAnnouncer; +using Kavita.AniListNet; // Contains base classes for interacting with the API +using Kavita.AniListNet.Parameters; // Contains classes for mutation and filtering +using Kavita.AniListNet.Objects; // Contains model classes for handling data + +public class Anilist +{ + public String Token { get; set; } + private AniClient client; + + + public Anilist(String token) + { + Token = token; + client = new AniClient(); + Task.WaitAll(Authenticate()); + } + + public async Task RetrieveAiringShowInformation(String showName) + { + var results = await client.SearchMediaAsync(new SearchMediaFilter + { + Query = showName, // The term to search for + Type = MediaType.Anime, // Filters search results to anime only + Sort = MediaSort.Popularity, // Sorts them by popularity + Format = new Dictionary + { + { MediaFormat.TV, true }, // Set to only search for TV shows and movies + { MediaFormat.Movie, true }, + { MediaFormat.TVShort, false } // Set to not show TV shorts + } + }); + int currentYear = DateTime.Now.Year; + var result = results.Data.Where(p => p.SeasonYear >= currentYear).OrderByDescending(p => p.StartDate).First(); + return result; + } + + private async Task Authenticate() + { + Boolean success = await client.TryAuthenticateAsync(Token); + if (!success) + throw new Exception("Unable to authenticate with AniList!"); + } +} + diff --git a/AnimeAnnouncer/Cache/TMDBCacheItem.cs b/AnimeAnnouncer/Cache/TMDBCacheItem.cs index c1a5a05..49a3f5d 100644 --- a/AnimeAnnouncer/Cache/TMDBCacheItem.cs +++ b/AnimeAnnouncer/Cache/TMDBCacheItem.cs @@ -19,5 +19,9 @@ namespace AnimeAnnouncer.Cache public DateTime? LastAirDate { get; set; } //This is the latest episode number to air, NOT the last episode in the series public int? LatestEpisodeNumber { get; set; } + + public Boolean? AnilistPopulated { get; set; } + public String? AnilistPosterPath { get; set; } + } } \ No newline at end of file diff --git a/AnimeAnnouncer/Program.cs b/AnimeAnnouncer/Program.cs index 3a7f6e0..37fca19 100644 --- a/AnimeAnnouncer/Program.cs +++ b/AnimeAnnouncer/Program.cs @@ -20,6 +20,7 @@ namespace AnimeAnnouncer private static TMDbClient tmdbClient; private static TMDBCache tmdbCache; private static MastodonClient mastodonClient; + private static Anilist? anilistClient; private static List airingSoonList; static void Main(string[] args) @@ -56,6 +57,11 @@ namespace AnimeAnnouncer mastodonClient = new MastodonClient(mastodonInstance, mastodonToken); } + String anilistToken = config["AnilistToken"]; + + if (!String.IsNullOrEmpty(anilistToken)) + anilistClient = new Anilist(anilistToken); + airingSoonList = tmdbCache.GetAiringList().Result; if(airingSoonList == null) @@ -205,8 +211,25 @@ namespace AnimeAnnouncer lastEpisodeNumber = seasonFinale.EpisodeNumber; } finaleConfirmed = true; + if (!lastAirDate.HasValue) + lastAirDate = seasonFinale.AirDate ?? DateTime.Now; } } + + //potentially override TMDB episode count with more reliable anilist data + string? anilistPosterPath = null; + if (latestEpisodeNumber > 3 && anilistClient != null) + { + Console.WriteLine("Looking up Anilist title " + title); + var anilistMedia = await anilistClient.RetrieveAiringShowInformation(title); + if (anilistMedia.Episodes.HasValue && anilistMedia.Episodes != lastEpisodeNumber) + { + Console.WriteLine($"Overriding episode count of {lastEpisodeNumber} using Anilist's showID {anilistMedia.Id} titled {anilistMedia.Title.EnglishTitle} with {anilistMedia.Episodes} episodes."); + lastEpisodeNumber = anilistMedia.Episodes.Value; + } + + anilistPosterPath = anilistMedia.BannerImageUrl?.ToString(); + } if(showResult.Seasons.Count > 1) { @@ -236,7 +259,9 @@ namespace AnimeAnnouncer LatestSeasonNumber = latestSeasonNumber, LastEpisodeNumber = lastEpisodeNumber, LastAirDate = lastAirDate, - LatestEpisodeNumber = latestEpisodeNumber + LatestEpisodeNumber = latestEpisodeNumber, + AnilistPopulated = anilistPosterPath != null, + AnilistPosterPath = anilistPosterPath }; _ = tmdbCache.SetCacheItem($"ShowCache-{title}", cachedShow); Console.WriteLine($"{title} Added to cache"); @@ -380,7 +405,20 @@ namespace AnimeAnnouncer var statusOne = await mastodonClient.PublishStatus(postOne, Visibility.Public, null); - var statusTwo = await mastodonClient.PublishStatus(postTwo, Visibility.Public, statusOne.Id); + if (postTwo.Length > 500) + { + lineBreakIdx = 500 - new string(postTwo.Take(500).Reverse().ToArray()).IndexOf(Environment.NewLine); + String postThree = postTwo.Substring(lineBreakIdx); + postTwo = postTwo.Substring(0, lineBreakIdx); + var statusTwo = await mastodonClient.PublishStatus(postTwo, Visibility.Public, statusOne.Id); + Thread.Sleep(1000); + var statusThree = await mastodonClient.PublishStatus(postThree, Visibility.Public, statusTwo.Id); + } + else + { + var statusTwo = await mastodonClient.PublishStatus(postTwo, Visibility.Public, statusOne.Id); + } + }else { _ = mastodonClient.PublishStatus(statusText, diff --git a/build.sh b/build.sh index b8b26b8..7346e6a 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,2 @@ -docker build --pull --rm -f "AnimeAnnouncer/Dockerfile" -t dtr.chrispr.org/animeannouncer:latest . +docker build -f "AnimeAnnouncer/Dockerfile" -t dtr.chrispr.org/animeannouncer:latest . docker push dtr.chrispr.org/animeannouncer:latest