Basic functionality commit

This commit is contained in:
2024-09-21 20:50:49 -04:00
parent 1e85d0ea42
commit 51f622d6f8
6 changed files with 287 additions and 7 deletions

View File

@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
@@ -7,9 +6,9 @@
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AnitomySharp.NET6" Version="0.5.1" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="TMDbLib" Version="2.2.0" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,10 +1,84 @@
namespace AnimeAnnouncer
using AnimeAnnouncer.RSS;
using TMDbLib.Client;
namespace AnimeAnnouncer
{
internal class Program
{
private static NyaaIndexer nyaaIndexer= new NyaaIndexer();
private const String TMDBApiKey = "";
private static TMDbClient tmdbClient = new TMDbClient(TMDBApiKey);
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
Console.WriteLine("Starting...");
nyaaIndexer.NewPost += OnNewPost;
nyaaIndexer.RssReadFinished += OnRssReadFinished;
nyaaIndexer.Start(true);
Console.WriteLine("Press enter to quit...");
Console.ReadLine();
}
static async void OnNewPost(RSS.NyaaItem item)
{
try
{
ProcessNewItem(item).Wait();
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
static void OnRssReadFinished()
{
}
static async Task ProcessNewItem(NyaaItem item)
{
Console.WriteLine($"Processing {item.Title}");
//Parse release name using Anitomy library
var items = AnitomySharp.AnitomySharp.Parse(item.Title);
var title = items.FirstOrDefault(p => p.Category == AnitomySharp.Element.ElementCategory.ElementAnimeTitle)?.Value;
var episodeTitle = items.FirstOrDefault(p => p.Category == AnitomySharp.Element.ElementCategory.ElementEpisodeTitle)?.Value;
var season = items.FirstOrDefault(p => p.Category == AnitomySharp.Element.ElementCategory.ElementAnimeSeason)?.Value;
var episode = items.FirstOrDefault(p => p.Category == AnitomySharp.Element.ElementCategory.ElementEpisodeNumber)?.Value;
if(title == null || season == null || episode == null)
{
Console.WriteLine("Skipping release due to inability to determine title, season, or episode number.");
return;
}
var searchResults = await tmdbClient.SearchTvShowAsync(title);
var supposedShowId = searchResults.Results.First().Id;
var showResult = await tmdbClient.GetTvShowAsync(supposedShowId);
var latestSeason = showResult.Seasons.OrderByDescending(s => s.SeasonNumber).First();
if(latestSeason.SeasonNumber != int.Parse(season))
{
Console.WriteLine($"Failing release due to TMDB's season number {latestSeason.SeasonNumber} not matching title season {season}");
return;
}
if(latestSeason.EpisodeCount != int.Parse(episode))
{
Console.WriteLine($"Failing release due to TMDB's last episode number of {latestSeason.EpisodeCount} not matching title episode number {episode}");
return;
}
//
//AnnounceFinishedSeason(title);
Console.WriteLine($"Choosing {title} as a finished season!");
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Xml.Linq;
namespace AnimeAnnouncer.RSS
{
public abstract class IndexerBase<T>
where T : IndexerItem
{
public IndexerBase()
{
IterationSleepTime = 300;
PostEventSleepTime = 1;
}
public virtual void Start(Boolean populateSeenURLs = true)
{
Running = true;
//Populate SeenURLs
if(populateSeenURLs)
try
{
fillSeenURLs(GetRSSResponse());
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
ThreadPool.QueueUserWorkItem(p => rssLoop());
}
public virtual void Stop()
{
Running = false;
}
protected virtual String GetRSSResponse()
{
HttpWebRequest req = WebRequest.Create(GetRSSURL()) as HttpWebRequest;
HttpWebResponse resp = req.GetResponse() as HttpWebResponse;
return new StreamReader(resp.GetResponseStream()).ReadToEnd();
}
protected abstract String GetRSSURL();
protected abstract void HandleRSSResponse(String rssResponse);
protected virtual void OnNewPost(T post)
{
NewPost?.Invoke(post);
}
protected virtual void OnRssFinished()
{
RssReadFinished?.Invoke();
}
private void rssLoop()
{
while (Running)
{
Thread.Sleep(IterationSleepTime * 1000);
try
{
String xml = GetRSSResponse();
HandleRSSResponse(xml);
RssReadFinished?.Invoke();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
private void fillSeenURLs(String xml)
{
XDocument doc = XDocument.Parse(xml);
var items = doc.Descendants("channel").Elements("item");
foreach (var item in items)
SeenURLs.Add(item.Element("link").Value);
}
public event Action<T> NewPost;
public event Action RssReadFinished;
/// <summary>
/// Time in seconds to wait before raising the new post event. To avoid site hammering.
/// </summary>
public Int32 PostEventSleepTime { get; set; }
/// <summary>
/// Time in seconds to wait before refreshing RSS again
/// </summary>
public Int32 IterationSleepTime { get; set; }
protected readonly List<String> SeenURLs = new List<string>();
private Boolean Running;
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace AnimeAnnouncer.RSS
{
public class IndexerItem
{
public String Title { get; set; }
public String GUID { get; set; }
public String Link { get; set; }
public String Comments { get; set; }
public DateTime PublicationDate { get; set; }
public String Category { get; set; }
public String Description { get; set; }
}
}

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
namespace AnimeAnnouncer.RSS
{
public class NyaaIndexer : IndexerBase<NyaaItem>
{
public NyaaIndexer()
{
// 30 minutes
IterationSleepTime = (30 * 60);
//Slow down for debugging to avoid API hammer
PostEventSleepTime = 5;
}
protected override string GetRSSURL()
{
return "https://nyaa.si/?page=rss&q=Dual+Audio&c=0_0&f=0";
}
protected override void HandleRSSResponse(string rssResponse)
{
XDocument doc = null;
XNamespace nyaaNS;
try
{
doc = XDocument.Parse(rssResponse);
nyaaNS = doc.Root.GetNamespaceOfPrefix("nyaa");
}
catch (Exception)
{
throw;
}
try
{
var items = from item in doc.Descendants("channel").Elements("item")
select new
{
Title = item.Element("title").Value,
Published = item.Element("pubDate").Value,
Category = item.Element(nyaaNS + "category").Value,
CategoryID = item.Element(nyaaNS + "categoryId").Value,
Size = item.Element(nyaaNS + "size").Value,
Link = item.Element("link").Value,
Description = item.Element("description").Value,
PostID = item.Element("guid").Value,
};
foreach (var item in items)
{
if (SeenURLs.Contains(item.Link))
continue;
NyaaItem post = new NyaaItem()
{
Title = item.Title,
PublicationDate = DateTime.ParseExact(item.Published, "ddd, dd MMM yyyy HH:mm:ss K", new System.Globalization.CultureInfo("en-US")).ToUniversalTime(),
Category = item.Category,
Link = item.Link,
Description = item.Description,
GUID = item.PostID,
Size = Double.Parse(item.Size.Substring(0,item.Size.IndexOf(' ')))
};
SeenURLs.Add(item.Link);
OnNewPost(post);
Thread.Sleep(PostEventSleepTime * 1000);
}
}
catch (Exception)
{
throw;
}
}
}
}

View File

@@ -0,0 +1,10 @@
namespace AnimeAnnouncer.RSS;
public class NyaaItem : IndexerItem
{
/// <summary>
/// The size of the release in GB
/// </summary>
public Double Size { get; set; }
}