Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f418281b39 |
@@ -1,63 +0,0 @@
|
|||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace AnimeAnnouncer.Torrents
|
|
||||||
{
|
|
||||||
public record NyaaRssItem(
|
|
||||||
String Title,
|
|
||||||
String Published,
|
|
||||||
String Category,
|
|
||||||
String CategoryID,
|
|
||||||
String Size,
|
|
||||||
String Link,
|
|
||||||
String Description,
|
|
||||||
String PostID);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads Nyaa RSS feeds and downloads the .torrent files they link to.
|
|
||||||
/// </summary>
|
|
||||||
public class NyaaRssReader
|
|
||||||
{
|
|
||||||
private readonly HttpClient httpClient;
|
|
||||||
|
|
||||||
public NyaaRssReader(HttpClient? httpClient = null)
|
|
||||||
{
|
|
||||||
this.httpClient = httpClient ?? new HttpClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a Nyaa search URL into its RSS equivalent, if it isn't one already.
|
|
||||||
/// </summary>
|
|
||||||
public static String ToRssUrl(String url)
|
|
||||||
{
|
|
||||||
return url.Contains("rss") ? url : url + "&page=rss";
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<NyaaRssItem>> ReadAsync(String url)
|
|
||||||
{
|
|
||||||
var response = await httpClient.GetStringAsync(ToRssUrl(url));
|
|
||||||
return Parse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<NyaaRssItem> Parse(String rssXml)
|
|
||||||
{
|
|
||||||
XDocument doc = XDocument.Parse(rssXml);
|
|
||||||
XNamespace nyaaNS = doc.Root!.GetNamespaceOfPrefix("nyaa") ?? XNamespace.None;
|
|
||||||
return (from item in doc.Descendants("channel").Elements("item")
|
|
||||||
select new NyaaRssItem(
|
|
||||||
Title: item.Element("title")?.Value ?? String.Empty,
|
|
||||||
Published: item.Element("pubDate")?.Value ?? String.Empty,
|
|
||||||
Category: item.Element(nyaaNS + "category")?.Value ?? String.Empty,
|
|
||||||
CategoryID: item.Element(nyaaNS + "categoryId")?.Value ?? String.Empty,
|
|
||||||
Size: item.Element(nyaaNS + "size")?.Value ?? String.Empty,
|
|
||||||
Link: item.Element("link")?.Value ?? String.Empty,
|
|
||||||
Description: item.Element("description")?.Value ?? String.Empty,
|
|
||||||
PostID: item.Element("guid")?.Value ?? String.Empty
|
|
||||||
)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<byte[]> DownloadTorrentAsync(NyaaRssItem item)
|
|
||||||
{
|
|
||||||
return httpClient.GetByteArrayAsync(item.Link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace AnimeAnnouncer.Torrents
|
|
||||||
{
|
|
||||||
public static class TorrentDirectoryNamer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Turns a release title into a dotted directory name, stripping the episode number
|
|
||||||
/// (keeping the resolution) and any trailing parenthesised suffix.
|
|
||||||
/// e.g. "Show Name S01E05 1080p WEB (Group)" -> "Show.Name.S01.1080p.WEB"
|
|
||||||
/// </summary>
|
|
||||||
public static String FromTitle(String title)
|
|
||||||
{
|
|
||||||
String name = title.Trim();
|
|
||||||
name = Regex.Replace(name, @"E\d{2,3}.*?(\d{3,4}p)", " $1");
|
|
||||||
name = Regex.Replace(name, @"\s\(.*?\)$", "");
|
|
||||||
return name.Replace(" ", ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String PrepareForURI(String title)
|
|
||||||
{
|
|
||||||
var titlePart = Regex.Replace(title, @"[^\w\s]+", String.Empty);
|
|
||||||
titlePart = Regex.Replace(titlePart, @"\s+", "+");
|
|
||||||
return titlePart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Builds the full save path for a release title under <paramref name="destinationFolder"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static String FromTitle(String title, String destinationFolder)
|
|
||||||
{
|
|
||||||
return destinationFolder + (destinationFolder.EndsWith("/") ? String.Empty : "/") + FromTitle(title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
using Transmission.API.RPC;
|
|
||||||
using Transmission.API.RPC.Arguments;
|
|
||||||
using Transmission.API.RPC.Entity;
|
|
||||||
|
|
||||||
namespace AnimeAnnouncer.Torrents
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Thin wrapper around the Transmission RPC client for adding torrents to the server.
|
|
||||||
/// </summary>
|
|
||||||
public class TransmissionService
|
|
||||||
{
|
|
||||||
public Client Client { get; }
|
|
||||||
|
|
||||||
private TransmissionService(Client client)
|
|
||||||
{
|
|
||||||
Client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a client and verifies the server is reachable by requesting session information.
|
|
||||||
/// Throws <see cref="InvalidOperationException"/> if the server doesn't respond with a valid session.
|
|
||||||
/// </summary>
|
|
||||||
public static async Task<TransmissionService> ConnectAsync(String host, String? username, String? password)
|
|
||||||
{
|
|
||||||
var client = new Client(host, null, username, password);
|
|
||||||
var sessionInformation = await client.GetSessionInformationAsync();
|
|
||||||
if (sessionInformation == null || String.IsNullOrEmpty(sessionInformation.Version))
|
|
||||||
throw new InvalidOperationException($"An error occurred while receiving session information from the rpc server at {host}");
|
|
||||||
|
|
||||||
return new TransmissionService(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a .torrent file to the server, saving to <paramref name="saveLocation"/>, and applies a seed ratio limit.
|
|
||||||
/// </summary>
|
|
||||||
public async Task<NewTorrentInfo> AddNewTorrentFileToServerAsync(byte[] torrentFileData, String saveLocation, bool paused = true, double seedRatioLimit = 1.0)
|
|
||||||
{
|
|
||||||
NewTorrent newTorrent = new()
|
|
||||||
{
|
|
||||||
DownloadDirectory = saveLocation,
|
|
||||||
Paused = paused,
|
|
||||||
Metainfo = Convert.ToBase64String(torrentFileData)
|
|
||||||
};
|
|
||||||
var torrentInfo = await Client.TorrentAddAsync(newTorrent);
|
|
||||||
var seedSettings = new TorrentSettings()
|
|
||||||
{
|
|
||||||
SeedRatioLimit = seedRatioLimit,
|
|
||||||
SeedRatioMode = 1,
|
|
||||||
IDs = [torrentInfo.ID]
|
|
||||||
};
|
|
||||||
|
|
||||||
// TorrentSetAsync returns void in Transmission.API.RPC 2.1.2, so it can't be awaited
|
|
||||||
Client.TorrentSet(seedSettings);
|
|
||||||
return torrentInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<div class="airing-list">
|
<div class="airing-list">
|
||||||
@foreach (var show in Model.AiringShows)
|
@foreach (var show in Model.AiringShows)
|
||||||
{
|
{
|
||||||
<article id="show-@show.ShowID" class="airing-row @(show.IsFuture ? "is-upcoming" : "is-aired")">
|
<article class="airing-row @(show.IsFuture ? "is-upcoming" : "is-aired")">
|
||||||
@if (!string.IsNullOrWhiteSpace(show.HeroImageUrl))
|
@if (!string.IsNullOrWhiteSpace(show.HeroImageUrl))
|
||||||
{
|
{
|
||||||
<div class="airing-backdrop" style="background-image: url('@show.HeroImageUrl');" aria-hidden="true"></div>
|
<div class="airing-backdrop" style="background-image: url('@show.HeroImageUrl');" aria-hidden="true"></div>
|
||||||
@@ -169,17 +169,6 @@
|
|||||||
{
|
{
|
||||||
<a href="@show.AnilistUrl" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-outline-info">AniList</a>
|
<a href="@show.AnilistUrl" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-outline-info">AniList</a>
|
||||||
}
|
}
|
||||||
@if (show.Download)
|
|
||||||
{
|
|
||||||
<span class="btn btn-sm btn-success disabled" aria-disabled="true">✓ Marked for download</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<form method="post" asp-page-handler="Download" class="d-inline">
|
|
||||||
<input type="hidden" name="showId" value="@show.ShowID" />
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-success">⬇ Download</button>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (show.SecondaryImages.Count > 0)
|
@if (show.SecondaryImages.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using AnimeAnnouncer.Cache;
|
using AnimeAnnouncer.Cache;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
namespace AnimeAnnouncer.Web.Pages;
|
namespace AnimeAnnouncer.Web.Pages;
|
||||||
@@ -65,10 +64,6 @@ public class IndexModel : PageModel
|
|||||||
_logger.LogWarning(ex, "Failed to retrieve Anilist cache item for title: {Title}", airingItem.Title);
|
_logger.LogWarning(ex, "Failed to retrieve Anilist cache item for title: {Title}", airingItem.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// With no details from either source there is nothing worth displaying.
|
|
||||||
if (cacheItem == null && anilistItem == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
viewModels.Add(new AiringShowViewModel
|
viewModels.Add(new AiringShowViewModel
|
||||||
{
|
{
|
||||||
AiringSoon = airingItem,
|
AiringSoon = airingItem,
|
||||||
@@ -100,25 +95,6 @@ public class IndexModel : PageModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> 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 class ShowImage
|
||||||
{
|
{
|
||||||
public required string Url { get; set; }
|
public required string Url { get; set; }
|
||||||
@@ -136,7 +112,6 @@ public class IndexModel : PageModel
|
|||||||
|
|
||||||
public string Title => AiringSoon.Title;
|
public string Title => AiringSoon.Title;
|
||||||
public int ShowID => AiringSoon.ShowID;
|
public int ShowID => AiringSoon.ShowID;
|
||||||
public bool Download => AiringSoon.Download;
|
|
||||||
public DateTime? EffectiveLastAirDate => AiringSoon.LastAirDate ?? CachedDetails?.LastAirDate;
|
public DateTime? EffectiveLastAirDate => AiringSoon.LastAirDate ?? CachedDetails?.LastAirDate;
|
||||||
public int LatestEpisodeNumber => AiringSoon.LatestEpisodeNumber > 0
|
public int LatestEpisodeNumber => AiringSoon.LatestEpisodeNumber > 0
|
||||||
? AiringSoon.LatestEpisodeNumber
|
? AiringSoon.LatestEpisodeNumber
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>@ViewData["Title"] - AnimeAnnouncer.Web</title>
|
<title>@ViewData["Title"] - AnimeAnnouncer.Web</title>
|
||||||
|
<link rel="icon" href="~/favicon.ico" sizes="any" asp-append-version="true" />
|
||||||
|
<link rel="icon" type="image/png" href="~/favicon.png" sizes="192x192" asp-append-version="true" />
|
||||||
|
<link rel="apple-touch-icon" href="~/favicon.png" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="~/AnimeAnnouncer.Web.styles.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/AnimeAnnouncer.Web.styles.css" asp-append-version="true" />
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 15 KiB |
@@ -9,8 +9,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassTorrentAdd", "MassTorre
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnimeAnnouncer.Web", "AnimeAnnouncer.Web\AnimeAnnouncer.Web.csproj", "{DD4F134D-B455-4A8C-8DDE-AA0ED735DA42}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnimeAnnouncer.Web", "AnimeAnnouncer.Web\AnimeAnnouncer.Web.csproj", "{DD4F134D-B455-4A8C-8DDE-AA0ED735DA42}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnimeAnnouncer.Torrents", "AnimeAnnouncer.Torrents\AnimeAnnouncer.Torrents.csproj", "{901424DF-F8CD-449B-9517-73CA42D61B54}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -57,18 +55,6 @@ Global
|
|||||||
{DD4F134D-B455-4A8C-8DDE-AA0ED735DA42}.Release|x64.Build.0 = Release|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{DD4F134D-B455-4A8C-8DDE-AA0ED735DA42}.Release|x86.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -27,7 +27,4 @@
|
|||||||
<HintPath>..\Libraries\TMDbLib.dll</HintPath>
|
<HintPath>..\Libraries\TMDbLib.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\AnimeAnnouncer.Torrents\AnimeAnnouncer.Torrents.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -7,6 +7,5 @@ namespace AnimeAnnouncer.Cache
|
|||||||
public int ShowID { get; set; }
|
public int ShowID { get; set; }
|
||||||
public DateTime? LastAirDate { get; set; }
|
public DateTime? LastAirDate { get; set; }
|
||||||
public int LatestEpisodeNumber { get; set; }
|
public int LatestEpisodeNumber { get; set; }
|
||||||
public bool Download { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,19 +60,6 @@ public class TMDBCache
|
|||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
/// <summary>Sets the Download flag on one airing list entry. Returns false when the show is not in the list.</summary>
|
|
||||||
public async Task<bool> 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<bool> KeyExists(String key)
|
public async Task<bool> KeyExists(String key)
|
||||||
{
|
{
|
||||||
var db = redis.GetDatabase();
|
var db = redis.GetDatabase();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using System.Security.Cryptography;
|
|||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using AnimeAnnouncer.Cache;
|
using AnimeAnnouncer.Cache;
|
||||||
using AnimeAnnouncer.RSS;
|
using AnimeAnnouncer.RSS;
|
||||||
using AnimeAnnouncer.Torrents;
|
|
||||||
using Mastonet;
|
using Mastonet;
|
||||||
using Mastonet.Entities;
|
using Mastonet.Entities;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@@ -25,9 +24,6 @@ namespace AnimeAnnouncer
|
|||||||
private static Anilist? anilistClient;
|
private static Anilist? anilistClient;
|
||||||
|
|
||||||
private static List<AiringSoonItem> airingSoonList;
|
private static List<AiringSoonItem> airingSoonList;
|
||||||
|
|
||||||
private static TransmissionService transmissionService;
|
|
||||||
private static String destinationFolder;
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Starting...");
|
Console.WriteLine("Starting...");
|
||||||
@@ -54,7 +50,7 @@ namespace AnimeAnnouncer
|
|||||||
Console.WriteLine("Using cache");
|
Console.WriteLine("Using cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
tmdbCache.CacheExpiration = TimeSpan.FromDays(8);
|
tmdbCache.CacheExpiration = TimeSpan.FromDays(7);
|
||||||
|
|
||||||
String mastodonInstance = config["MastodonInstance"];
|
String mastodonInstance = config["MastodonInstance"];
|
||||||
|
|
||||||
@@ -68,19 +64,6 @@ namespace AnimeAnnouncer
|
|||||||
|
|
||||||
if (!String.IsNullOrEmpty(anilistToken))
|
if (!String.IsNullOrEmpty(anilistToken))
|
||||||
anilistClient = new Anilist(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;
|
airingSoonList = tmdbCache.GetAiringList().Result;
|
||||||
|
|
||||||
@@ -332,7 +315,7 @@ namespace AnimeAnnouncer
|
|||||||
{
|
{
|
||||||
var titleSeason = int.Parse(season);
|
var titleSeason = int.Parse(season);
|
||||||
bool seasonOverride = false;
|
bool seasonOverride = false;
|
||||||
if(latestSeasonNumber < titleSeason && titleSeason > 1 && lastEpisodeNumber > 3)
|
if(latestSeasonNumber ==1 && titleSeason > 1 && lastEpisodeNumber > 3)
|
||||||
{ //Check episode groups to see if this is a single-season default show
|
{ //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 showResult = await tmdbClient.GetTvShowAsync(supposedShowId, TMDbLib.Objects.TvShows.TvShowMethods.EpisodeGroups);
|
||||||
var seasonEpisodeGroup = showResult.EpisodeGroups.Results.FirstOrDefault(eg => eg.Name == "Seasons");
|
var seasonEpisodeGroup = showResult.EpisodeGroups.Results.FirstOrDefault(eg => eg.Name == "Seasons");
|
||||||
@@ -418,13 +401,6 @@ namespace AnimeAnnouncer
|
|||||||
{
|
{
|
||||||
_ = tmdbCache.SetPair($"ShowAnnounced-{supposedShowId}", latestSeasonNumber.ToString());
|
_ = tmdbCache.SetPair($"ShowAnnounced-{supposedShowId}", latestSeasonNumber.ToString());
|
||||||
_ = PostStatus(cachedShow);
|
_ = PostStatus(cachedShow);
|
||||||
var existingAiringItem = airingSoonList.FirstOrDefault(s => s.ShowID == cachedShow.ShowID);
|
|
||||||
if (existingAiringItem is { Download: true })
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Attempting to queue {existingAiringItem.Title} in the torrent client");
|
|
||||||
await AddToTorrentClient(cachedShow);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
@@ -435,29 +411,6 @@ namespace AnimeAnnouncer
|
|||||||
Console.WriteLine($"Choosing {title} as a finished season!");
|
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);
|
|
||||||
Console.WriteLine($"Adding {folderName} to transmission");
|
|
||||||
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
var torrentData = await reader.DownloadTorrentAsync(item);
|
|
||||||
await transmissionService.AddNewTorrentFileToServerAsync(torrentData, folderName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static async Task CreateAiringMonthPost()
|
private static async Task CreateAiringMonthPost()
|
||||||
{
|
{
|
||||||
@@ -577,22 +530,9 @@ namespace AnimeAnnouncer
|
|||||||
Console.WriteLine($"Not updating {cachedShow.Title} airdate due to episode number not incrementing");
|
Console.WriteLine($"Not updating {cachedShow.Title} airdate due to episode number not incrementing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
airingSoonItem.Download = existingAiringItem.Download;
|
|
||||||
airingSoonList.RemoveAll(s => s.ShowID == cachedShow.ShowID);
|
airingSoonList.RemoveAll(s => s.ShowID == cachedShow.ShowID);
|
||||||
airingSoonList.Add(airingSoonItem);
|
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));
|
airingSoonList.Sort((showOne, showTwo) => showOne.LastAirDate.Value.CompareTo(showTwo.LastAirDate.Value));
|
||||||
_ = tmdbCache.SetAiringSoon(airingSoonList);
|
_ = tmdbCache.SetAiringSoon(airingSoonList);
|
||||||
Console.WriteLine($"Updated airing soon list, setting {cachedShow.Title} Season {cachedShow.LatestSeasonNumber} to end on {cachedShow.LastAirDate ?? DateTime.MaxValue}");
|
Console.WriteLine($"Updated airing soon list, setting {cachedShow.Title} Season {cachedShow.LatestSeasonNumber} to end on {cachedShow.LastAirDate ?? DateTime.MaxValue}");
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
||||||
|
<PackageReference Include="Transmission.API.RPC" Version="2.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -24,8 +25,4 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\AnimeAnnouncer.Torrents\AnimeAnnouncer.Torrents.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+65
-34
@@ -1,11 +1,15 @@
|
|||||||
using AnimeAnnouncer.Torrents;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Configuration;
|
using System.Xml.Linq;
|
||||||
|
using Transmission.API.RPC;
|
||||||
|
|
||||||
namespace MassTorrentAdd
|
namespace MassTorrentAdd
|
||||||
{
|
{
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
static async Task Main(string[] args)
|
static String watchFolder, transmissionHost, transmissionUsername, transmissionPassword, transmissionDownloadFolder, destinationFolder;
|
||||||
|
static Transmission.API.RPC.Client transmissionClient;
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
if(args.Length == 0)
|
if(args.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -13,53 +17,60 @@ namespace MassTorrentAdd
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String url = args[0];
|
String url = args[0];
|
||||||
|
if (!url.Contains("rss"))
|
||||||
|
url = url + "&page=rss";
|
||||||
|
|
||||||
var builder = new ConfigurationBuilder()
|
var builder = new ConfigurationBuilder()
|
||||||
.SetBasePath(Directory.GetCurrentDirectory())
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
.AddJsonFile("appSettings.json", optional: false, reloadOnChange: true);
|
.AddJsonFile("appSettings.json", optional: false, reloadOnChange: true);
|
||||||
|
|
||||||
IConfigurationRoot configuration = builder.Build();
|
IConfigurationRoot configuration = builder.Build();
|
||||||
String transmissionHost = configuration.GetValue<String>("transmissionURI")!;
|
transmissionHost = configuration.GetValue<String>("transmissionURI");
|
||||||
String? transmissionUsername = configuration.GetValue<String>("transmissionUsername");
|
transmissionUsername = configuration.GetValue<String>("transmissionUsername");
|
||||||
String? transmissionPassword = configuration.GetValue<String>("transmissionPassword");
|
transmissionPassword = configuration.GetValue<String>("transmissionPassword");
|
||||||
String destinationFolder = configuration.GetValue<String>("destinationFolder")!;
|
destinationFolder = configuration.GetValue<String>("destinationFolder");
|
||||||
|
|
||||||
Console.WriteLine($"Using transmission server {transmissionHost}");
|
Console.WriteLine($"Using transmission server {transmissionHost}");
|
||||||
TransmissionService transmission;
|
transmissionClient = new Transmission.API.RPC.Client(transmissionHost, null, transmissionUsername, transmissionPassword);
|
||||||
try
|
var sessionInformation = transmissionClient.GetSessionInformation();
|
||||||
|
if (sessionInformation == null || String.IsNullOrEmpty(sessionInformation.Version))
|
||||||
{
|
{
|
||||||
transmission = await TransmissionService.ConnectAsync(transmissionHost, transmissionUsername, transmissionPassword);
|
Console.WriteLine("An error occurred while receiving session information from the rpc server");
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex.Message);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nyaa = new NyaaRssReader();
|
|
||||||
List<NyaaRssItem> items;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
items = await nyaa.ReadAsync(url);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"An error occurred while contacting Nyaa for a response: {ex.Message}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (items.Count == 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine("No items found in the feed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
HttpClient httpClient = new HttpClient();
|
||||||
|
var response = httpClient.GetStringAsync(url).Result;
|
||||||
|
if(response == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("An error occurred while contacting Nyaa for a response");
|
||||||
|
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,
|
||||||
|
};
|
||||||
Console.WriteLine("Downloading ..");
|
Console.WriteLine("Downloading ..");
|
||||||
foreach ( var item in items )
|
foreach ( var item in items )
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{item.Title}");
|
Console.WriteLine($"{item.Title}");
|
||||||
}
|
}
|
||||||
String transmissionDirectoryName = TorrentDirectoryNamer.FromTitle(items.First().Title, destinationFolder);
|
String transmissionDirectoryName = items.First().Title.Trim();
|
||||||
Console.WriteLine($"{items.Count} item(s) to {transmissionDirectoryName}");
|
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}");
|
||||||
|
|
||||||
Console.WriteLine("Continue?");
|
Console.WriteLine("Continue?");
|
||||||
if(Console.ReadKey().KeyChar != 'y')
|
if(Console.ReadKey().KeyChar != 'y')
|
||||||
@@ -69,10 +80,30 @@ namespace MassTorrentAdd
|
|||||||
}
|
}
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
var torrentData = await nyaa.DownloadTorrentAsync(item);
|
var torrentData = httpClient.GetByteArrayAsync(item.Link).Result;
|
||||||
await transmission.AddNewTorrentFileToServerAsync(torrentData, transmissionDirectoryName);
|
AddNewTorrentFileToServer(torrentData, transmissionDirectoryName);
|
||||||
}
|
}
|
||||||
Console.WriteLine("Completed");
|
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 });
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user