@@ -1,4 +1,5 @@
using System.ComponentModel ;
using System.Runtime.ExceptionServices ;
using System.Security.Cryptography ;
using System.Security.Cryptography.X509Certificates ;
using AnimeAnnouncer.Cache ;
@@ -18,6 +19,8 @@ namespace AnimeAnnouncer
private static TMDbClient tmdbClient ;
private static TMDBCache tmdbCache ;
private static MastodonClient mastodonClient ;
private static List < AiringSoonItem > airingSoonList ;
static void Main ( string [ ] args )
{
Console . WriteLine ( "Starting..." ) ;
@@ -52,6 +55,11 @@ namespace AnimeAnnouncer
mastodonClient = new MastodonClient ( mastodonInstance , mastodonToken ) ;
}
airingSoonList = tmdbCache . GetAiringList ( ) . Result ;
if ( airingSoonList = = null )
airingSoonList = new List < AiringSoonItem > ( ) ;
nyaaIndexer . NewPost + = OnNewPost ;
nyaaIndexer . RssReadFinished + = OnRssReadFinished ;
nyaaIndexer . Start ( true ) ;
@@ -74,6 +82,14 @@ namespace AnimeAnnouncer
static void OnRssReadFinished ( )
{
if ( DateTime . Today . Day = = 1 )
{
if ( ! tmdbCache . KeyExists ( $"UpcomingAnnounced-{DateTime.Today.Year}-{DateTime.Today.Month}" ) . Result )
{
CreateAiringMonthPost ( ) . Wait ( ) ;
tmdbCache . SetPair ( $"UpcomingAnnounced-{DateTime.Today.Year}-{DateTime.Today.Month}" , "1" ) . Wait ( ) ;
}
}
}
@@ -93,10 +109,14 @@ namespace AnimeAnnouncer
return ;
}
int latestSeasonNumber = 0 , latestEpisodeNumber = 0 , supposedShowId = 0 ;
int latestSeasonNumber = 0 , lastEpisodeNumber = 0 , latestEpisodeNumber = 0 , supposedShowId = 0 ;
int? latestOrdinalEpisodeNumber = null ;
bool finaleConfirmed = false ;
DateTime ? lastAirDate = null ;
TMDBCacheItem ? cachedShow = null ;
try
@@ -106,8 +126,9 @@ namespace AnimeAnnouncer
{
Console . WriteLine ( "Cache hit" ) ;
latestSeasonNumber = cachedShow . LatestSeasonNumber ;
late stEpisodeNumber = cachedShow . LastEpisodeNumber ;
lastEpisodeNumber = cachedShow . LastEpisodeNumber ;
latestOrdinalEpisodeNumber = cachedShow . LatestOrdinalEpisodeNumber ;
lastAirDate = cachedShow . LastAirDate ;
supposedShowId = cachedShow . ShowID ;
}
}
@@ -129,7 +150,8 @@ namespace AnimeAnnouncer
return ;
}
supposedShowId = searchResults. Results. First ( ) . Id ;
// supposedShowId = searchResults. Results.First().Id ;
supposedShowId = searchResults . Results . OrderBy ( r = > ComputeLevenshteinDistance ( title , r . Name ) ) . First ( ) . Id ;
var showResult = await tmdbClient . GetTvShowAsync ( supposedShowId ) ;
@@ -143,20 +165,27 @@ namespace AnimeAnnouncer
latestSeasonNumber = latestSeason . SeasonNumber ;
late stEpisodeNumber = latestSeason . EpisodeCount ;
lastEpisodeNumber = latestSeason . EpisodeCount ;
latestEpisodeNumber = int . Parse ( episode ) ;
//if nearing the end of a season, confirm it's marked with season finale
if ( int . Parse ( episode ) > = ( late stEpisodeNumber - 2 ) )
if ( latestEpisodeNumber > = ( lastEpisodeNumber - 2 ) | | ! HasAiringEndDate ( supposedShowId ) )
{
Console . WriteLine ( "Querying episode information" ) ;
var latestSeasonDetail = await tmdbClient . GetTvSeasonAsync ( supposedShowId , latestSeasonNumber ) ;
var seasonFinale = latestSeasonDetail . Episodes . LastOrDefault ( e = > e . EpisodeType . Equals ( "finale" , StringComparison . InvariantCultureIgnoreCase ) ) ;
if ( seasonFinale ! = null & & seasonFinale . EpisodeNumber ! = latestEpisodeNumber )
if ( seasonFinale ! = null )
{
Console . WriteLine ( $"Overriding previous finale choice of {latestEpisodeNumber} due to season detail response where it's {seasonFinale. EpisodeNumber}" ) ;
latestEpisodeNumber = seasonFinale . EpisodeNumber ;
if ( seasonFinale . EpisodeNumber ! = last EpisodeNumber)
{
Console . WriteLine ( $"Overriding previous finale choice of {lastEpisodeNumber} due to season detail response where it's {seasonFinale.EpisodeNumber}" ) ;
lastEpisodeNumber = seasonFinale . EpisodeNumber ;
}
finaleConfirmed = true ;
}
}
@@ -164,6 +193,11 @@ namespace AnimeAnnouncer
{
latestOrdinalEpisodeNumber = showResult . Seasons . Where ( s = > s . SeasonNumber ! = 0 ) . Sum ( s = > s . EpisodeCount ) ;
}
//try to figure out Last Air Date based on this new airing episode
if ( lastEpisodeNumber > latestEpisodeNumber )
{
lastAirDate = DateTime . Today . AddDays ( 7 * ( lastEpisodeNumber - latestEpisodeNumber ) ) ;
}
if ( tmdbCache ! = null & & searchResults ! = null )
{
@@ -181,10 +215,13 @@ namespace AnimeAnnouncer
VoteAverage = showResult . VoteAverage ,
ShowID = supposedShowId ,
LatestSeasonNumber = latestSeasonNumber ,
LastEpisodeNumber = late stEpisodeNumber
LastEpisodeNumber = lastEpisodeNumber ,
LastAirDate = lastAirDate ,
LatestEpisodeNumber = latestEpisodeNumber
} ;
_ = tmdbCache . SetCacheItem ( $"ShowCache-{title}" , cachedShow ) ;
Console . WriteLine ( $"{title} Added to cache" ) ;
UpdateAiringShowList ( cachedShow ) ;
}
catch ( Exception ex )
{
@@ -199,7 +236,7 @@ namespace AnimeAnnouncer
{
var titleSeason = int . Parse ( season ) ;
bool seasonOverride = false ;
if ( latestSeasonNumber = = 1 & & titleSeason > 1 & & late stEpisodeNumber > 3 )
if ( latestSeasonNumber = = 1 & & titleSeason > 1 & & lastEpisodeNumber > 3 )
{ //Check episode groups to see if this is a single-season default show
var showResult = await tmdbClient . GetTvShowAsync ( supposedShowId , TMDbLib . Objects . TvShows . TvShowMethods . EpisodeGroups ) ;
var seasonEpisodeGroup = showResult . EpisodeGroups . Results . FirstOrDefault ( eg = > eg . Name = = "Seasons" ) ;
@@ -215,11 +252,21 @@ namespace AnimeAnnouncer
//Confirm it's aired within the last week
if ( targetFinale ! = null & & targetEpisodeGroup . Groups . OrderByDescending ( g = > g . Order ) . FirstOrDefault ( ) = = targetSeason )
{ //confirm the episode is marked as finale and that this season is the latest one in the episode group
Console . WriteLine ( $"!!Choosing S{titleSeason}E{targetFinale.Order + 1} for finale using episode groups" ) ;
Console . WriteLine ( $"!!Choosing S{titleSeason}E{targetFinale.Order + 1} airing on {targetFinale.AirDate} for finale using episode groups" ) ;
cachedShow . LatestSeasonNumber = latestSeasonNumber = titleSeason ;
cachedShow . LastEpisodeNumber = late stEpisodeNumber = targetFinale . Order + 1 ;
seasonOverride = true ;
cachedShow . LastEpisodeNumber = lastEpisodeNumber = targetFinale . Order + 1 ;
if ( cachedShow . LastEpisodeNumber > cachedShow . LatestEpisodeNumber )
{
cachedShow . LastAirDate = DateTime . Today . AddDays ( 7 * ( lastEpisodeNumber - latestEpisodeNumber ) ) ;
}
else
{
cachedShow . LastAirDate = lastAirDate = targetFinale . AirDate ;
}
seasonOverride = finaleConfirmed = true ;
_ = tmdbCache . SetCacheItem ( $"ShowCache-{title}" , cachedShow ) ;
UpdateAiringShowList ( cachedShow ) ;
}
else
Console . WriteLine ( "Could not find a finale episode." ) ;
@@ -235,16 +282,16 @@ namespace AnimeAnnouncer
}
}
if ( late stEpisodeNumber ! = int . Parse ( episode ) )
if ( lastEpisodeNumber ! = int . Parse ( episode ) )
{
if ( ! latestOrdinalEpisodeNumber . HasValue | | ( latestOrdinalEpisodeNumber . HasValue & & latestOrdinalEpisodeNumber . Value ! = int . Parse ( episode ) ) )
{
Console . WriteLine ( $"Failing release due to TMDB's last episode number of {late stEpisodeNumber}|{latestOrdinalEpisodeNumber ?? 0} not matching title episode number {episode}" ) ;
Console . WriteLine ( $"Failing release due to TMDB's last episode number of {lastEpisodeNumber}|{latestOrdinalEpisodeNumber ?? 0} not matching title episode number {episode}" ) ;
return ;
}
}
//Sometimes TMDB metadata hasn't been filled in yet
if ( late stEpisodeNumber < 3 )
if ( lastEpisodeNumber < 3 )
{
Console . WriteLine ( "Skipping announcement due to a low episode number" ) ;
return ;
@@ -256,16 +303,19 @@ namespace AnimeAnnouncer
Console . WriteLine ( $"{title} has been previously announced, so avoiding announcing it again." ) ;
return ;
}
else if ( ! finaleConfirmed )
{
Console . WriteLine ( $"Would have announced finale for {title}, on S{cachedShow.LatestSeasonNumber}E{cachedShow.LastEpisodeNumber} but the finale episode type was not found." ) ;
}
else
{
_ = tmdbCache . SetPair ( $"ShowAnnounced-{supposedShowId}" , "1" ) ;
_ = tmdbCache . SetPair ( $"ShowAnnounced-{supposedShowId}" , latestSeasonNumber . ToString ( ) ) ;
}
if ( mastodonClient ! = null & & cachedShow ! = null )
{
try
{
PostStatus ( cachedShow ) ;
}
catch ( Exception ex )
{
@@ -276,6 +326,46 @@ namespace AnimeAnnouncer
Console . WriteLine ( $"Choosing {title} as a finished season!" ) ;
}
private static async Task CreateAiringMonthPost ( )
{
var airingSoon = await tmdbCache . GetAiringList ( ) ;
DateTime beginningOfMonth = DateTime . Today ;
DateTime endOfMonth = DateTime . Today . AddMonths ( 1 ) ;
var airingThisMonth = airingSoon . Where ( p = > p . LastAirDate > = beginningOfMonth & & p . LastAirDate < = endOfMonth ) . ToList ( ) ;
String statusText = $"DUBBED anime airing this month (probably){Environment.NewLine + Environment.NewLine}" ;
int cnt = 1 ;
for ( int i = 0 ; i < airingThisMonth . Count ; i + + , cnt + + )
{
var airing = airingThisMonth [ i ] ;
if ( ! await tmdbCache . KeyExists ( $"ShowAnnounced-{airing.ShowID}" ) )
{ //TODO: fix for seasons
statusText + = $"{cnt}. {airing.Title} on {airing.LastAirDate:MM/dd/yyyy}{Environment.NewLine}" ;
}
}
int neededPostCount = ( int ) Math . Ceiling ( ( decimal ) statusText . Length / 500 ) ;
if ( neededPostCount > = 2 )
{
statusText = statusText [ . . 497 ] + "..." ;
}
_ = mastodonClient . PublishStatus ( statusText ,
Visibility . Public , null ) ;
/*
else
{
for(cnt = 1; cnt < neededPostCount; cnt++)
{
//go back and find a line break
String postPart = "";
//_ = mastodonClient.PublishStatus(${statusText},
// Visibility.Public, null);
System.Threading.Thread.Sleep(1000);
}
}
*/
}
private static async void PostStatus ( TMDBCacheItem cachedShow )
{
//get poster
@@ -308,5 +398,71 @@ namespace AnimeAnnouncer
Visibility . Public , mediaIds : attachment ! = null ? new String [ ] { attachment . Id } : null ) ;
}
private static async void UpdateAiringShowList ( TMDBCacheItem cachedShow )
{
var airingSoonItem = new AiringSoonItem ( )
{
Title = cachedShow . Title ,
ShowID = cachedShow . ShowID ,
LastAirDate = cachedShow . LastAirDate ? ? DateTime . MaxValue
} ;
if ( ! airingSoonList . Any ( s = > s . ShowID = = cachedShow . ShowID ) )
{
airingSoonList . Add ( airingSoonItem ) ;
}
else
{
airingSoonList . RemoveAll ( s = > s . ShowID = = cachedShow . ShowID ) ;
airingSoonList . Add ( airingSoonItem ) ;
}
airingSoonList . Sort ( ( showOne , showTwo ) = > showOne . LastAirDate . Value . CompareTo ( showTwo . LastAirDate . Value ) ) ;
_ = tmdbCache . SetAiringSoon ( airingSoonList ) ;
Console . WriteLine ( "Updated airing soon list" ) ;
}
private static bool HasAiringEndDate ( int showID )
{
return airingSoonList . Any ( s = > s . ShowID = = showID & & s . LastAirDate . HasValue ) ;
}
private static int ComputeLevenshteinDistance ( string s , string t )
{
int n = s . Length ;
int m = t . Length ;
int [ , ] d = new int [ n + 1 , m + 1 ] ;
// Verify arguments.
if ( n = = 0 )
{
return m ;
}
if ( m = = 0 )
{
return n ;
}
// Initialize arrays.
for ( int i = 0 ; i < = n ; d [ i , 0 ] = i + + )
{
}
for ( int j = 0 ; j < = m ; d [ 0 , j ] = j + + )
{
}
// Begin looping.
for ( int i = 1 ; i < = n ; i + + )
{
for ( int j = 1 ; j < = m ; j + + )
{
// Compute cost.
int cost = ( t [ j - 1 ] = = s [ i - 1 ] ) ? 0 : 1 ;
d [ i , j ] = Math . Min (
Math . Min ( d [ i - 1 , j ] + 1 , d [ i , j - 1 ] + 1 ) ,
d [ i - 1 , j - 1 ] + cost ) ;
}
}
// Return cost.
return d [ n , m ] ;
}
}
}