diff --git a/AnimeAnnouncer.Web/Pages/Index.cshtml b/AnimeAnnouncer.Web/Pages/Index.cshtml
index 40aebe4..0f79d98 100644
--- a/AnimeAnnouncer.Web/Pages/Index.cshtml
+++ b/AnimeAnnouncer.Web/Pages/Index.cshtml
@@ -35,7 +35,7 @@
@foreach (var show in Model.AiringShows)
{
-
+
@if (!string.IsNullOrWhiteSpace(show.HeroImageUrl))
{
@@ -169,6 +169,17 @@
{
AniList
}
+ @if (show.Download)
+ {
+ ✓ Marked for download
+ }
+ else
+ {
+
+ }
@if (show.SecondaryImages.Count > 0)
{
diff --git a/AnimeAnnouncer.Web/Pages/Index.cshtml.cs b/AnimeAnnouncer.Web/Pages/Index.cshtml.cs
index c83773d..1df3064 100644
--- a/AnimeAnnouncer.Web/Pages/Index.cshtml.cs
+++ b/AnimeAnnouncer.Web/Pages/Index.cshtml.cs
@@ -1,6 +1,7 @@
using System.Net;
using System.Text.RegularExpressions;
using AnimeAnnouncer.Cache;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AnimeAnnouncer.Web.Pages;
@@ -95,6 +96,25 @@ public class IndexModel : PageModel
}
}
+ public async Task OnPostDownloadAsync(int showId)
+ {
+ if (_tmdbCache == null)
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
+
+ try
+ {
+ if (!await _tmdbCache.SetDownload(showId, true))
+ _logger.LogWarning("Download requested for show {ShowID}, which is not in the airing list", showId);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to flag show {ShowID} for download", showId);
+ }
+
+ // Post/redirect/get, landing back on the row that was clicked.
+ return RedirectToPage(null, null, null, $"show-{showId}");
+ }
+
public class ShowImage
{
public required string Url { get; set; }
@@ -112,6 +132,7 @@ public class IndexModel : PageModel
public string Title => AiringSoon.Title;
public int ShowID => AiringSoon.ShowID;
+ public bool Download => AiringSoon.Download;
public DateTime? EffectiveLastAirDate => AiringSoon.LastAirDate ?? CachedDetails?.LastAirDate;
public int LatestEpisodeNumber => AiringSoon.LatestEpisodeNumber > 0
? AiringSoon.LatestEpisodeNumber
diff --git a/AnimeAnnouncer.sln b/AnimeAnnouncer.sln
index 01b2177..3ea7ea4 100644
--- a/AnimeAnnouncer.sln
+++ b/AnimeAnnouncer.sln
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassTorrentAdd", "MassTorre
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnimeAnnouncer.Web", "AnimeAnnouncer.Web\AnimeAnnouncer.Web.csproj", "{DD4F134D-B455-4A8C-8DDE-AA0ED735DA42}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnimeAnnouncer.Torrents", "AnimeAnnouncer.Torrents\AnimeAnnouncer.Torrents.csproj", "{901424DF-F8CD-449B-9517-73CA42D61B54}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -55,6 +57,18 @@ Global
{DD4F134D-B455-4A8C-8DDE-AA0ED735DA42}.Release|x64.Build.0 = Release|Any CPU
{DD4F134D-B455-4A8C-8DDE-AA0ED735DA42}.Release|x86.ActiveCfg = Release|Any CPU
{DD4F134D-B455-4A8C-8DDE-AA0ED735DA42}.Release|x86.Build.0 = Release|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Debug|x64.Build.0 = Debug|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Debug|x86.Build.0 = Debug|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Release|Any CPU.Build.0 = Release|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Release|x64.ActiveCfg = Release|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Release|x64.Build.0 = Release|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Release|x86.ActiveCfg = Release|Any CPU
+ {901424DF-F8CD-449B-9517-73CA42D61B54}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/AnimeAnnouncer/AnimeAnnouncer.csproj b/AnimeAnnouncer/AnimeAnnouncer.csproj
index 5d49f0f..aa2fedc 100644
--- a/AnimeAnnouncer/AnimeAnnouncer.csproj
+++ b/AnimeAnnouncer/AnimeAnnouncer.csproj
@@ -27,4 +27,7 @@
..\Libraries\TMDbLib.dll
+
+
+
\ No newline at end of file
diff --git a/AnimeAnnouncer/Cache/AiringSoonItem.cs b/AnimeAnnouncer/Cache/AiringSoonItem.cs
index 3d2aa2e..43bd397 100644
--- a/AnimeAnnouncer/Cache/AiringSoonItem.cs
+++ b/AnimeAnnouncer/Cache/AiringSoonItem.cs
@@ -7,5 +7,6 @@ namespace AnimeAnnouncer.Cache
public int ShowID { get; set; }
public DateTime? LastAirDate { get; set; }
public int LatestEpisodeNumber { get; set; }
+ public bool Download { get; set; }
}
}
\ No newline at end of file
diff --git a/AnimeAnnouncer/Cache/TMDBCache.cs b/AnimeAnnouncer/Cache/TMDBCache.cs
index 2d9729f..cb86b61 100644
--- a/AnimeAnnouncer/Cache/TMDBCache.cs
+++ b/AnimeAnnouncer/Cache/TMDBCache.cs
@@ -60,6 +60,19 @@ public class TMDBCache
else
return null;
}
+ /// Sets the Download flag on one airing list entry. Returns false when the show is not in the list.
+ public async Task SetDownload(int showID, bool download)
+ {
+ var list = await GetAiringList();
+ var item = list?.FirstOrDefault(s => s.ShowID == showID);
+ if (item == null)
+ return false;
+
+ item.Download = download;
+ await SetAiringSoon(list!);
+ return true;
+ }
+
public async Task KeyExists(String key)
{
var db = redis.GetDatabase();
diff --git a/AnimeAnnouncer/Program.cs b/AnimeAnnouncer/Program.cs
index 8ad0ab5..d23be7d 100644
--- a/AnimeAnnouncer/Program.cs
+++ b/AnimeAnnouncer/Program.cs
@@ -4,6 +4,7 @@ using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using AnimeAnnouncer.Cache;
using AnimeAnnouncer.RSS;
+using AnimeAnnouncer.Torrents;
using Mastonet;
using Mastonet.Entities;
using Microsoft.Extensions.Configuration;
@@ -24,6 +25,9 @@ namespace AnimeAnnouncer
private static Anilist? anilistClient;
private static List airingSoonList;
+
+ private static TransmissionService transmissionService;
+ private static String destinationFolder;
static void Main(string[] args)
{
Console.WriteLine("Starting...");
@@ -64,6 +68,19 @@ namespace AnimeAnnouncer
if (!String.IsNullOrEmpty(anilistToken))
anilistClient = new Anilist(anilistToken);
+
+ String? transmissionHost = config["transmissionURI"];
+ String? transmissionUsername = config["transmissionUsername"];
+ String? transmissionPassword = config["transmissionPassword"];
+ destinationFolder = config["destinationFolder"];
+
+ if (transmissionHost != null && transmissionUsername != null && transmissionPassword != null &&
+ destinationFolder != null)
+ {
+ Console.WriteLine("Connecting to transmission");
+ transmissionService = TransmissionService.ConnectAsync(transmissionHost, transmissionUsername, transmissionPassword).Result;
+
+ }
airingSoonList = tmdbCache.GetAiringList().Result;
@@ -401,6 +418,13 @@ namespace AnimeAnnouncer
{
_ = tmdbCache.SetPair($"ShowAnnounced-{supposedShowId}", latestSeasonNumber.ToString());
_ = PostStatus(cachedShow);
+ var existingAiringItem = airingSoonList.FirstOrDefault(s => s.ShowID == cachedShow.ShowID);
+ if (existingAiringItem != null && existingAiringItem.Download)
+ {
+ Console.WriteLine($"Attempting to queue {existingAiringItem.Title} in the torrent client");
+ _ = AddToTorrentClient(cachedShow);
+ }
+
}
catch(Exception ex)
{
@@ -411,6 +435,28 @@ namespace AnimeAnnouncer
Console.WriteLine($"Choosing {title} as a finished season!");
}
+ private static async Task AddToTorrentClient(TMDBCacheItem cacheItem)
+ {
+ var reader = new NyaaRssReader();
+ var titleParts = TorrentDirectoryNamer.PrepareForURI(cacheItem.Title);
+ var seasonNumber = $"S{cacheItem.LatestSeasonNumber:D2}";
+ var url = $"https://nyaa.si/?f=0&c=0_0&q={titleParts}+{seasonNumber}+CR+VARYG+DUAL";
+ var items = await reader.ReadAsync(url);
+ if (items.Count != cacheItem.LastEpisodeNumber)
+ {
+ Console.WriteLine($"{cacheItem.Title} had {items.Count} results but only {cacheItem.LastEpisodeNumber} episodes. Aborting.");
+ return;
+ }
+
+ var folderName = TorrentDirectoryNamer.FromTitle(items.First().Title, destinationFolder);
+
+ foreach (var item in items)
+ {
+ var torrentData = await reader.DownloadTorrentAsync(item);
+ await transmissionService.AddNewTorrentFileToServerAsync(torrentData, folderName);
+ }
+ }
+
private static async Task CreateAiringMonthPost()
{
@@ -530,9 +576,22 @@ namespace AnimeAnnouncer
Console.WriteLine($"Not updating {cachedShow.Title} airdate due to episode number not incrementing");
return;
}
+ airingSoonItem.Download = existingAiringItem.Download;
airingSoonList.RemoveAll(s => s.ShowID == cachedShow.ShowID);
airingSoonList.Add(airingSoonItem);
}
+ // The web page can flag shows for download directly in the cache, so carry those
+ // flags over rather than overwriting them with this process's in-memory copy.
+ var cachedList = await tmdbCache.GetAiringList();
+ if (cachedList != null)
+ {
+ foreach (var cached in cachedList.Where(c => c.Download))
+ {
+ var local = airingSoonList.FirstOrDefault(s => s.ShowID == cached.ShowID);
+ if (local != null)
+ local.Download = true;
+ }
+ }
airingSoonList.Sort((showOne, showTwo) => showOne.LastAirDate.Value.CompareTo(showTwo.LastAirDate.Value));
_ = tmdbCache.SetAiringSoon(airingSoonList);
Console.WriteLine($"Updated airing soon list, setting {cachedShow.Title} Season {cachedShow.LatestSeasonNumber} to end on {cachedShow.LastAirDate ?? DateTime.MaxValue}");
diff --git a/MassTorrentAdd/MassTorrentAdd.csproj b/MassTorrentAdd/MassTorrentAdd.csproj
index 553a6bb..85fdf37 100644
--- a/MassTorrentAdd/MassTorrentAdd.csproj
+++ b/MassTorrentAdd/MassTorrentAdd.csproj
@@ -16,7 +16,6 @@
-
@@ -25,4 +24,8 @@
+
+
+
+
diff --git a/MassTorrentAdd/Program.cs b/MassTorrentAdd/Program.cs
index a040beb..b2cbbd5 100644
--- a/MassTorrentAdd/Program.cs
+++ b/MassTorrentAdd/Program.cs
@@ -1,15 +1,11 @@
-using Microsoft.Extensions.Configuration;
-using System.Xml.Linq;
-using Transmission.API.RPC;
+using AnimeAnnouncer.Torrents;
+using Microsoft.Extensions.Configuration;
namespace MassTorrentAdd
{
internal class Program
{
- static String watchFolder, transmissionHost, transmissionUsername, transmissionPassword, transmissionDownloadFolder, destinationFolder;
- static Transmission.API.RPC.Client transmissionClient;
-
- static void Main(string[] args)
+ static async Task Main(string[] args)
{
if(args.Length == 0)
{
@@ -17,60 +13,53 @@ namespace MassTorrentAdd
return;
}
String url = args[0];
- if (!url.Contains("rss"))
- url = url + "&page=rss";
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json", optional: false, reloadOnChange: true);
IConfigurationRoot configuration = builder.Build();
- transmissionHost = configuration.GetValue("transmissionURI");
- transmissionUsername = configuration.GetValue("transmissionUsername");
- transmissionPassword = configuration.GetValue("transmissionPassword");
- destinationFolder = configuration.GetValue("destinationFolder");
+ String transmissionHost = configuration.GetValue("transmissionURI")!;
+ String? transmissionUsername = configuration.GetValue("transmissionUsername");
+ String? transmissionPassword = configuration.GetValue("transmissionPassword");
+ String destinationFolder = configuration.GetValue("destinationFolder")!;
Console.WriteLine($"Using transmission server {transmissionHost}");
- transmissionClient = new Transmission.API.RPC.Client(transmissionHost, null, transmissionUsername, transmissionPassword);
- var sessionInformation = transmissionClient.GetSessionInformation();
- if (sessionInformation == null || String.IsNullOrEmpty(sessionInformation.Version))
+ TransmissionService transmission;
+ try
{
- Console.WriteLine("An error occurred while receiving session information from the rpc server");
+ transmission = await TransmissionService.ConnectAsync(transmissionHost, transmissionUsername, transmissionPassword);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
return;
}
-
- HttpClient httpClient = new HttpClient();
- var response = httpClient.GetStringAsync(url).Result;
- if(response == null)
+ var nyaa = new NyaaRssReader();
+ List items;
+ try
{
- Console.WriteLine("An error occurred while contacting Nyaa for a response");
+ items = await nyaa.ReadAsync(url);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"An error occurred while contacting Nyaa for a response: {ex.Message}");
return;
}
- XDocument doc = XDocument.Parse(response);
- XNamespace nyaaNS = doc.Root.GetNamespaceOfPrefix("nyaa");
- 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,
- };
+ if (items.Count == 0)
+ {
+ Console.WriteLine("No items found in the feed");
+ return;
+ }
+
Console.WriteLine("Downloading ..");
foreach ( var item in items )
{
Console.WriteLine($"{item.Title}");
}
- String transmissionDirectoryName = items.First().Title.Trim();
- transmissionDirectoryName = System.Text.RegularExpressions.Regex.Replace(transmissionDirectoryName, @"E\d{2,3}.*?(\d{3,4}p)", " $1");
- transmissionDirectoryName = System.Text.RegularExpressions.Regex.Replace(transmissionDirectoryName, @"\s\(.*?\)$", "");
- transmissionDirectoryName = destinationFolder + (destinationFolder.EndsWith("/") ? String.Empty : "/") + transmissionDirectoryName.Replace(" ", ".");
- Console.WriteLine($"{items.Count()} item(s) to {transmissionDirectoryName}");
+ String transmissionDirectoryName = TorrentDirectoryNamer.FromTitle(items.First().Title, destinationFolder);
+ Console.WriteLine($"{items.Count} item(s) to {transmissionDirectoryName}");
Console.WriteLine("Continue?");
if(Console.ReadKey().KeyChar != 'y')
@@ -80,30 +69,10 @@ namespace MassTorrentAdd
}
foreach (var item in items)
{
- var torrentData = httpClient.GetByteArrayAsync(item.Link).Result;
- AddNewTorrentFileToServer(torrentData, transmissionDirectoryName);
+ var torrentData = await nyaa.DownloadTorrentAsync(item);
+ await transmission.AddNewTorrentFileToServerAsync(torrentData, transmissionDirectoryName);
}
Console.WriteLine("Completed");
}
- private static void AddNewTorrentFileToServer(byte[] torrentFileData, String saveLocation)
- {
- Transmission.API.RPC.Entity.NewTorrent newTorrent = new()
- {
- DownloadDirectory = saveLocation,
- Paused = true,
- Metainfo = Convert.ToBase64String(torrentFileData)
- };
- var torrentInfo = transmissionClient.TorrentAdd(newTorrent);
- var seedSettings = new Transmission.API.RPC.Arguments.TorrentSettings()
- {
- SeedRatioLimit = 1.0,
- SeedRatioMode = 1,
- IDs = [torrentInfo.ID]
- };
-
- transmissionClient.TorrentSet(seedSettings);
- //transmissionClient.TorrentVerify(new object[] { torrentInfo.ID });
-
- }
}
}