Files
NasRobot/src/Service/Jobs/ChineseNfoRegistry.cs

481 lines
15 KiB
C#
Raw Normal View History

using System.Diagnostics;
2025-02-27 16:58:21 +08:00
using System.Net;
using System.Text.Json.Nodes;
using System.Xml;
2025-03-13 17:20:34 +08:00
using Core;
2025-02-27 16:58:21 +08:00
using FluentScheduler;
using Interface.Jobs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Service.Jobs;
public class ChineseNfoRegistry : Registry, IChineseNfoRegistry
{
private readonly IConfiguration _configuration;
private readonly ILogger<ChineseNfoRegistry> _logger;
private readonly HttpClient _client;
public ChineseNfoRegistry(IConfiguration configuration, ILogger<ChineseNfoRegistry> logger)
{
_configuration = configuration;
_logger = logger;
2025-03-06 17:25:10 +08:00
var httpClientHandler = new HttpClientHandler();
2025-02-27 16:58:21 +08:00
var proxyAddress = _configuration["ChineseNfo:HttpProxy"];
if (string.IsNullOrEmpty(proxyAddress) == false)
2025-02-27 16:58:21 +08:00
{
2025-03-06 17:25:10 +08:00
httpClientHandler.Proxy = string.IsNullOrEmpty(proxyAddress) ? null : new WebProxy(proxyAddress, false);
httpClientHandler.UseProxy = !string.IsNullOrEmpty(proxyAddress);
}
2025-02-27 16:58:21 +08:00
_client = new HttpClient(httpClientHandler);
_client.BaseAddress = new Uri("https://api.themoviedb.org");
2025-02-27 16:58:21 +08:00
Schedule(() => Job(true, true)).ToRunEvery(1).Days();
}
public async void Job(bool ignoreLocked, bool ignoreCompleted)
{
try
{
await JobExecute(ignoreLocked, ignoreCompleted);
}
catch (Exception e)
{
_logger.LogError(e, "ChineseNfoRegistry.Job() error");
}
}
private async Task JobExecute(bool ignoreLocked, bool ignoreCompleted)
{
// 读取环境变量 tv folder
var tvFolder = _configuration["ChineseNfo:TvFolder"];
if (string.IsNullOrEmpty(tvFolder))
{
Console.WriteLine("请设置环境变量 tv_folder");
return;
}
2025-03-13 17:20:34 +08:00
var successCount = 0;
var failedCount = 0;
var skippedCount = 0;
2025-02-27 16:58:21 +08:00
// 扫描 tvshow.nfo 文件
var tvShowFiles = Directory.GetFiles(tvFolder, "tvshow.nfo", SearchOption.AllDirectories);
foreach (var tvShowFile in tvShowFiles)
{
var nfoContent = File.ReadAllText(tvShowFile);
// 读取 使用XML解析器 读取 <uniqueid type="tmdb">60059</uniqueid>
var tvXml = new XmlDocument();
tvXml.LoadXml(nfoContent);
var uniqueIdNode = tvXml.SelectSingleNode("//uniqueid[@type='tmdb']");
if (uniqueIdNode == null)
{
Console.WriteLine($"{tvShowFile} & 未找到 tmdb id");
continue;
}
if (!int.TryParse(uniqueIdNode.InnerText, out var tmdbId))
{
Console.WriteLine($"{tvShowFile} & tmdb id 不是数字");
continue;
}
await SetTvInfo(tvShowFile, tvXml, tmdbId);
2025-02-27 16:58:21 +08:00
// 获取 tvShowFile 的文件夹
var folderPath = Path.GetDirectoryName(tvShowFile);
if (folderPath == null)
{
Console.WriteLine($"{tvShowFile} & 未找到文件夹");
continue;
}
// 扫描文件夹下其他 nfo 文件
var seasonFiles = Directory.GetFiles(folderPath, "*.nfo", SearchOption.AllDirectories);
seasonFiles = seasonFiles.Where(x => !x.EndsWith("tvshow.nfo")).ToArray();
foreach (var episodeFile in seasonFiles)
2025-02-27 16:58:21 +08:00
{
await SetEpisodeInfo(episodeFile, tmdbId);
}
}
2025-02-27 16:58:21 +08:00
await WxNotify.SendCommonAsync($"ChineseNfoRegistry.Job() success: {successCount}, failed: {failedCount}, skipped: {skippedCount}");
2025-02-27 16:58:21 +08:00
async Task SetTvInfo(string tvShowFile,XmlDocument tvXml, int tmdbId)
{
// 检查 lockdata
var lockedNode = tvXml.SelectSingleNode("//lockdata");
if (lockedNode != null && lockedNode.InnerText == "true" && ignoreLocked == false)
{
skippedCount++;
Console.WriteLine($"{tvShowFile} & 已锁定");
return;
}
// 获取tv info
var tvJson = await GetTmdbTv(tvShowFile, tmdbId);
if (tvJson == null)
{
failedCount++;
return;
}
var title = tvJson["name"]?.ToString();
var overview = tvJson["overview"]?.ToString();
var poster =tvJson["poster_path"]?.ToString();
var genres = tvJson["genres"]
?.AsArray()
.Select(x => x?["name"]?.ToString())
.Where(x=> !string.IsNullOrEmpty(x))
.ToList();
if (!string.IsNullOrEmpty(title))
{
title = $"<![CDATA[{title}]]>";
var titleNode = tvXml.SelectSingleNode("//title");
if (titleNode != null)
2025-02-27 16:58:21 +08:00
{
if (titleNode.InnerXml != title)
{
titleNode.InnerXml = title;
successCount++;
}
2025-02-27 16:58:21 +08:00
}
var sorttitleNode = tvXml.SelectSingleNode("//sorttitle");
if (sorttitleNode != null)
2025-02-27 16:58:21 +08:00
{
if (sorttitleNode.InnerXml != title)
{
sorttitleNode.InnerXml = title;
successCount++;
}
2025-02-27 16:58:21 +08:00
}
}
if (!string.IsNullOrEmpty(overview))
{
overview = $"<![CDATA[{overview}]]>";
var plotNode = tvXml.SelectSingleNode("//plot");
if (plotNode != null)
2025-02-27 16:58:21 +08:00
{
if (plotNode.InnerXml != overview)
{
plotNode.InnerXml = overview;
successCount++;
}
2025-02-27 16:58:21 +08:00
}
var outlineNode = tvXml.SelectSingleNode("//outline");
if (outlineNode != null)
2025-02-27 16:58:21 +08:00
{
if (outlineNode.InnerXml != overview)
{
outlineNode.InnerXml = overview;
successCount++;
}
2025-02-27 16:58:21 +08:00
}
}
if(!string.IsNullOrEmpty(poster))
{
poster = $"https://image.tmdb.org/t/p/w1280/{poster}";
// 下载并覆盖海报
var posterPath = tvShowFile.Replace("tvshow.nfo", $"poster.{poster.Split('.').Last()}");
if (File.Exists(posterPath))
2025-02-27 16:58:21 +08:00
{
File.Delete(posterPath);
2025-02-27 16:58:21 +08:00
}
using var client = new HttpClient();
var response = await client.GetAsync(poster);
if (response.IsSuccessStatusCode)
2025-02-27 16:58:21 +08:00
{
var bytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync(posterPath, bytes);
successCount++;
2025-02-27 16:58:21 +08:00
}
}
2025-02-27 16:58:21 +08:00
if (genres?.Any() == true)
{
// 删除原有的 <genre> 节点
var genreNodes = tvXml.SelectNodes("//genre");
if (genreNodes != null)
2025-02-27 16:58:21 +08:00
{
foreach (XmlNode genreNode in genreNodes)
2025-02-27 16:58:21 +08:00
{
tvXml.DocumentElement?.RemoveChild(genreNode);
2025-02-27 16:58:21 +08:00
}
}
// 添加新的 <genre> 节点
foreach (var genre in genres)
2025-02-27 16:58:21 +08:00
{
var genreNode = tvXml.CreateElement("genre");
genreNode.InnerText = genre!;
tvXml.DocumentElement?.AppendChild(genreNode);
2025-02-27 16:58:21 +08:00
}
successCount++;
}
if (successCount == 0)
{
skippedCount++;
Console.WriteLine($"{tvShowFile} & 无更新");
return;
}
// 锁定节点
if(lockedNode == null)
{
lockedNode = tvXml.CreateElement("lockdata");
tvXml.DocumentElement?.AppendChild(lockedNode);
lockedNode.InnerText = "true";
}
else
{
lockedNode.InnerText = "true";
}
// 添加一个已完成节点
var completedNode = tvXml.SelectSingleNode("//completed");
if (completedNode == null)
{
completedNode = tvXml.CreateElement("completed");
tvXml.DocumentElement?.AppendChild(completedNode);
}
completedNode.InnerText = "true";
try
{
tvXml.Save(tvShowFile);
}
catch (Exception e)
{
Console.WriteLine(e);
failedCount++;
}
}
2025-02-27 16:58:21 +08:00
async Task SetEpisodeInfo(string episodeFile, int tmdbId)
{
var episodeContent = await File.ReadAllTextAsync(episodeFile);
var episodeXml = new XmlDocument();
episodeXml.LoadXml(episodeContent);
2025-02-27 16:58:21 +08:00
// 判断有无 completed 节点
var completedNode = episodeXml.SelectSingleNode("//completed");
if (completedNode != null && ignoreCompleted == false)
{
skippedCount++;
Console.WriteLine($"{episodeFile} & 已完成");
return;
}
// 判断 locked == true
var lockedNode = episodeXml.SelectSingleNode("//lockdata");
if (lockedNode != null && lockedNode.InnerText == "true" && ignoreLocked == false)
{
skippedCount++;
Console.WriteLine($"{episodeFile} & 已锁定");
return;
}
2025-02-27 16:58:21 +08:00
// 读取 <season>1</season>
var seasonNode = episodeXml.SelectSingleNode("//season");
if (seasonNode == null)
{
failedCount++;
Console.WriteLine($"{episodeFile} & 未找到 season");
return;
}
// 读取 <episode>1</episode>
var episodeNode = episodeXml.SelectSingleNode("//episode");
if (episodeNode == null)
{
failedCount++;
Console.WriteLine($"{episodeFile} & 未找到 episode");
return;
}
if (!int.TryParse(seasonNode.InnerText, out var season))
{
failedCount++;
Console.WriteLine($"{episodeFile} & season 不是数字");
return;
}
if (!int.TryParse(episodeNode.InnerText, out var episode))
{
failedCount++;
Console.WriteLine($"{episodeFile} & episode 不是数字");
return;
}
// 设置 title
var titleNode = episodeXml.SelectSingleNode("//title");
if (titleNode == null)
{
failedCount++;
Console.WriteLine($"{episodeFile} & 未找到 title");
return;
}
var json = await GetTmdbEpisode(
episodeFile,
tmdbId,
season,
episode);
if (json == null)
{
failedCount++;
return;
}
2025-02-27 16:58:21 +08:00
var title = json["name"]?.ToString();
var overview = json["overview"]?.ToString();
var isUpdate = false;
if (!string.IsNullOrEmpty(title))
{
title = $"<![CDATA[{title}]]>";
if (titleNode.InnerXml != title)
2025-03-13 17:14:35 +08:00
{
isUpdate = true;
// 写入且不转义
titleNode.InnerXml = title;
2025-03-13 17:14:35 +08:00
}
}
var plotNode = episodeXml.SelectSingleNode("//plot");
if (!string.IsNullOrEmpty(overview) && plotNode != null)
{
overview = $"<![CDATA[{overview}]]>";
if (plotNode.InnerXml != overview)
2025-03-13 17:14:35 +08:00
{
isUpdate = true;
plotNode.InnerXml = overview;
2025-03-13 17:14:35 +08:00
}
}
2025-03-13 17:14:35 +08:00
if (!isUpdate)
{
skippedCount++;
Console.WriteLine($"{episodeFile} & 无更新");
return;
}
2025-03-13 17:14:35 +08:00
successCount++;
// 添加一个已完成节点
if (completedNode == null)
{
episodeXml.DocumentElement?.AppendChild(episodeXml.CreateElement("completed"));
2025-02-27 16:58:21 +08:00
}
// 锁定
if (lockedNode == null)
{
lockedNode = episodeXml.CreateElement("lockdata");
episodeXml.DocumentElement?.AppendChild(lockedNode);
lockedNode.InnerText = "true";
}
else
{
lockedNode.InnerText = "true";
}
try
{
episodeXml.Save(episodeFile);
}
catch (Exception e)
{
Console.WriteLine(e);
return;
}
Console.WriteLine($"{episodeFile} & {title} & {overview}");
}
2025-02-27 16:58:21 +08:00
}
private async Task<JsonObject?> GetTmdbTv(
string path,
int tmdbId)
{
const string tvUrl = "/3/tv/{0}?api_key=e28e1bc408db7adefc8bacce225c5085&language=zh-CN";
var requestUrl = string.Format(tvUrl, tmdbId);
try
{
var response = await _client.GetAsync(requestUrl);
2025-04-03 12:02:30 +08:00
await Task.Delay(10000);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"{requestUrl} & {path} & {response.StatusCode}");
return null;
}
var content = await response.Content.ReadAsStringAsync();
var json = JsonNode.Parse(content);
return json as JsonObject;
}
catch (Exception e)
{
Console.WriteLine($"{requestUrl} & {path} \r {e}");
return null;
}
}
private async Task<JsonObject?> GetTmdbEpisode(
string path,
int tmdbId,
int season,
int episode)
2025-02-27 16:58:21 +08:00
{
const string episodeUrl = "/3/tv/{0}/season/{1}/episode/{2}?api_key=e28e1bc408db7adefc8bacce225c5085&language=zh-CN";
var requestUrl = string.Format(episodeUrl, tmdbId, season, episode);
2025-02-27 16:58:21 +08:00
try
{
var response = await _client.GetAsync(requestUrl);
2025-04-03 12:02:30 +08:00
await Task.Delay(10000);
2025-02-27 16:58:21 +08:00
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"{requestUrl} & {path} & {response.StatusCode}");
2025-02-27 16:58:21 +08:00
return null;
}
var content = await response.Content.ReadAsStringAsync();
var json = JsonNode.Parse(content);
return json as JsonObject;
}
catch (Exception e)
{
Console.WriteLine($"{requestUrl} & {path} \r {e}");
2025-02-27 16:58:21 +08:00
return null;
}
}
}