using System.Diagnostics; using System.Net; using System.Text.Json.Nodes; using System.Xml; using Core; 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 _logger; private readonly HttpClient _client; public ChineseNfoRegistry(IConfiguration configuration, ILogger logger) { _configuration = configuration; _logger = logger; var httpClientHandler = new HttpClientHandler(); var proxyAddress = _configuration["ChineseNfo:HttpProxy"]; if (string.IsNullOrEmpty(proxyAddress) == false) { httpClientHandler.Proxy = string.IsNullOrEmpty(proxyAddress) ? null : new WebProxy(proxyAddress, false); httpClientHandler.UseProxy = !string.IsNullOrEmpty(proxyAddress); } _client = new HttpClient(httpClientHandler); _client.BaseAddress = new Uri("https://api.themoviedb.org"); 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; } var successCount = 0; var failedCount = 0; var skippedCount = 0; // 使用 root 角色执行 chmod -R 666 设置全部文件的读写权限 var permission = new ProcessStartInfo("sudo", "chmod -R 666 " + tvFolder) { CreateNoWindow = true, UseShellExecute = false }; Process.Start(permission); // 扫描 tvshow.nfo 文件 var tvShowFiles = Directory.GetFiles(tvFolder, "tvshow.nfo", SearchOption.AllDirectories); foreach (var tvShowFile in tvShowFiles) { var nfoContent = File.ReadAllText(tvShowFile); // 读取 使用XML解析器 读取 60059 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; } // 获取 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) { var episodeContent = await File.ReadAllTextAsync(episodeFile); var episodeXml = new XmlDocument(); episodeXml.LoadXml(episodeContent); // 判断有无 completed 节点 var completedNode = episodeXml.SelectSingleNode("//completed"); if (completedNode != null && ignoreCompleted == false) { skippedCount++; Console.WriteLine($"{episodeFile} & 已完成"); continue; } // 判断 locked == true var lockedNode = episodeXml.SelectSingleNode("//lockdata"); if (lockedNode != null && lockedNode.InnerText == "true" && ignoreLocked == false) { skippedCount++; Console.WriteLine($"{episodeFile} & 已锁定"); continue; } // 读取 1 var seasonNode = episodeXml.SelectSingleNode("//season"); if (seasonNode == null) { failedCount++; Console.WriteLine($"{episodeFile} & 未找到 season"); continue; } // 读取 1 var episodeNode = episodeXml.SelectSingleNode("//episode"); if (episodeNode == null) { failedCount++; Console.WriteLine($"{episodeFile} & 未找到 episode"); continue; } if (!int.TryParse(seasonNode.InnerText, out var season)) { failedCount++; Console.WriteLine($"{episodeFile} & season 不是数字"); continue; } if (!int.TryParse(episodeNode.InnerText, out var episode)) { failedCount++; Console.WriteLine($"{episodeFile} & episode 不是数字"); continue; } // 设置 title var titleNode = episodeXml.SelectSingleNode("//title"); if (titleNode == null) { failedCount++; Console.WriteLine($"{episodeFile} & 未找到 title"); continue; } var json = await GetTmdbEpisode(tmdbId, season, episode); if (json == null) { failedCount++; continue; } var title = json["name"]?.ToString(); var overview = json["overview"]?.ToString(); var isUpdate = false; if (!string.IsNullOrEmpty(title)) { title = $""; if (titleNode.InnerXml != title) { isUpdate = true; // 写入且不转义 titleNode.InnerXml = title; } } var plotNode = episodeXml.SelectSingleNode("//plot"); if (!string.IsNullOrEmpty(overview) && plotNode != null) { overview = $""; if (plotNode.InnerXml != overview) { isUpdate = true; plotNode.InnerXml = overview; } } if (!isUpdate) { skippedCount++; Console.WriteLine($"{episodeFile} & 无更新"); continue; } successCount++; // 添加一个已完成节点 if (completedNode == null) { episodeXml.DocumentElement?.AppendChild(episodeXml.CreateElement("completed")); } // 锁定 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); continue; } Console.WriteLine($"{episodeFile} & {title} & {overview}"); await Task.Delay(10000); } } await WxNotify.SendCommonAsync($"ChineseNfoRegistry.Job() success: {successCount}, failed: {failedCount}, skipped: {skippedCount}"); } private async Task GetTmdbEpisode(int tmdbId, int season, int episode) { const string episodeUrl = "/3/tv/{0}/season/{1}/episode/{2}?api_key=e28e1bc408db7adefc8bacce225c5085&language=zh-CN"; var requestUrl = string.Format(episodeUrl, tmdbId, season, episode); try { var response = await _client.GetAsync(requestUrl); if (!response.IsSuccessStatusCode) { Console.WriteLine($"{requestUrl} & {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} \r {e}"); return null; } } }