diff --git a/AnimeAnnouncer/AnimeAnnouncer.csproj b/AnimeAnnouncer/AnimeAnnouncer.csproj
index e076340..2ef1adf 100644
--- a/AnimeAnnouncer/AnimeAnnouncer.csproj
+++ b/AnimeAnnouncer/AnimeAnnouncer.csproj
@@ -1,5 +1,4 @@
-
-
+
Exe
net8.0
@@ -7,9 +6,9 @@
enable
Linux
-
+
+
-
-
+
\ No newline at end of file
diff --git a/AnimeAnnouncer/Program.cs b/AnimeAnnouncer/Program.cs
index 5e61698..66d2dc3 100644
--- a/AnimeAnnouncer/Program.cs
+++ b/AnimeAnnouncer/Program.cs
@@ -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!");
}
}
}
diff --git a/AnimeAnnouncer/RSS/IndexerBase.cs b/AnimeAnnouncer/RSS/IndexerBase.cs
new file mode 100644
index 0000000..9b36a3f
--- /dev/null
+++ b/AnimeAnnouncer/RSS/IndexerBase.cs
@@ -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
+ 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 NewPost;
+ public event Action RssReadFinished;
+ ///
+ /// Time in seconds to wait before raising the new post event. To avoid site hammering.
+ ///
+ public Int32 PostEventSleepTime { get; set; }
+ ///
+ /// Time in seconds to wait before refreshing RSS again
+ ///
+ public Int32 IterationSleepTime { get; set; }
+
+
+ protected readonly List SeenURLs = new List();
+ private Boolean Running;
+ }
+}
diff --git a/AnimeAnnouncer/RSS/IndexerItem.cs b/AnimeAnnouncer/RSS/IndexerItem.cs
new file mode 100644
index 0000000..a646a71
--- /dev/null
+++ b/AnimeAnnouncer/RSS/IndexerItem.cs
@@ -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; }
+ }
+}
diff --git a/AnimeAnnouncer/RSS/NyaaIndexer.cs b/AnimeAnnouncer/RSS/NyaaIndexer.cs
new file mode 100644
index 0000000..998a833
--- /dev/null
+++ b/AnimeAnnouncer/RSS/NyaaIndexer.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml.Linq;
+
+namespace AnimeAnnouncer.RSS
+{
+ public class NyaaIndexer : IndexerBase
+ {
+
+ 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;
+ }
+ }
+ }
+}
diff --git a/AnimeAnnouncer/RSS/NyaaItem.cs b/AnimeAnnouncer/RSS/NyaaItem.cs
new file mode 100644
index 0000000..97f425f
--- /dev/null
+++ b/AnimeAnnouncer/RSS/NyaaItem.cs
@@ -0,0 +1,10 @@
+namespace AnimeAnnouncer.RSS;
+
+public class NyaaItem : IndexerItem
+{
+ ///
+ /// The size of the release in GB
+ ///
+ public Double Size { get; set; }
+
+}