using System.Net; using System.Text.RegularExpressions; using System.Xml; using FluentScheduler; using FreeSql; using FreeSql.DataAnnotations; using Interface.Jobs; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace Service.Jobs; public class ChineseNfoRegistry : Registry, IChineseNfoRegistry { private readonly HttpClient _apiClient; private readonly HttpClient _imageClient; private readonly IConfiguration _configuration; private readonly ILogger _logger; private readonly IFreeSql _freeSql; 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); } _apiClient = new HttpClient(httpClientHandler); _apiClient.BaseAddress = new Uri("http://api.themoviedb.org"); _imageClient = new HttpClient(httpClientHandler); _imageClient.BaseAddress = new Uri("http://image.tmdb.org"); _freeSql = new FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, _configuration["ChineseNfo:ConnectionString"]) .UseAutoSyncStructure(true) .Build(); Schedule(() => Job(ignoreLocked: true, ignoreCompleted: true)).ToRunEvery(1).Days(); } public async void Job( string? path = null, string? seasonNumber = null, string? episodeNumber = null, bool ignoreLocked = false, bool ignoreCompleted = false ) { try { await JobExecute(path, seasonNumber, episodeNumber, ignoreLocked, ignoreCompleted); } catch (Exception e) { _logger.LogError(e, "ChineseNfoRegistry.Job() error"); } } private async Task JobExecute( string? requestPath = null, string? requestSeasonNumber = null, string? requestEpisodeNumber = null, bool ignoreLocked = false, bool ignoreCompleted = false) { var tvFolder = _configuration["ChineseNfo:TvFolder"]; if (string.IsNullOrEmpty(tvFolder)) { _logger.LogError("ChineseNfoRegistry.Job() tvFolder is null or empty"); return; } /* * 2. 接口数据保留 * 3. 支持语言降级到繁体 * 4. 支持演员名称翻译 */ var tvNfos = Directory.GetFiles(tvFolder, "tvshow.nfo", SearchOption.AllDirectories); if (tvNfos.Length == 0) { _logger.LogError("ChineseNfoRegistry.Job() tvNfo is null or empty"); return; } foreach (var tv in tvNfos) { if (!string.IsNullOrEmpty(requestPath)) { var latestPath = Path.GetFileName(requestPath) ?? string.Empty; if (!tv.Contains(latestPath)) { continue; } _logger.LogInformation("ChineseNfoRegistry.Job() tv is contains path"); } try { await HandleTv(tv); } catch (Exception e) { _logger.LogError(e, "ChineseNfoRegistry.Job() HandleTv() error"); } var seasonNfos = Directory.GetFiles(Path.GetDirectoryName(tv) ?? string.Empty, "season.nfo", SearchOption.AllDirectories); // 如果seasonNfos为空,则创建 if (seasonNfos.Length == 0) { var sseasons = Directory.GetDirectories(Path.GetDirectoryName(tv) ?? string.Empty, "Season *", SearchOption.AllDirectories); var sseasonsNumbers = sseasons.Select(x => x.Split("Season ", StringSplitOptions.None)[1]).Select(int.Parse).ToList(); foreach (var seasonNumber in sseasonsNumbers) { var seasonNfo = Path.Combine(Path.GetDirectoryName(tv) ?? string.Empty,$"Season {seasonNumber}", "season.nfo"); await File.WriteAllTextAsync(seasonNfo, string.Format( """ {0} """ , seasonNumber) ); seasonNfos = seasonNfos.Append(seasonNfo).ToArray(); } } foreach (var season in seasonNfos) { var seasonNumber = season .Split("Season ")[1] .Split(Path.DirectorySeparatorChar)[0]; try { if (!string.IsNullOrEmpty(requestSeasonNumber)) { if (seasonNumber != requestSeasonNumber) { continue; } else { _logger.LogInformation("ChineseNfoRegistry.Job() seasonNumber is equal"); } } await HandleSeason(tv, season); } catch (Exception e) { _logger.LogError(e, "ChineseNfoRegistry.Job() HandleSeason() error"); } var episodeNfos = Directory .GetFiles(Path.GetDirectoryName(season) ?? string.Empty, "*.nfo", SearchOption.AllDirectories) .Where(x => !x.EndsWith("season.nfo")) .ToList(); foreach (var episode in episodeNfos) { try { var split = $"S{(seasonNumber.Length == 1 ? $"0{seasonNumber}" : seasonNumber)}E"; var episodeNumber = Path.GetFileName(episode) .Split(split, StringSplitOptions.None)[1] .Split(" - ", StringSplitOptions.None)[0]; if (!string.IsNullOrEmpty(requestEpisodeNumber)) { if (int.Parse(episodeNumber).ToString() != requestEpisodeNumber) { continue; } else { _logger.LogInformation("ChineseNfoRegistry.Job() episodeNumber is equal"); } } await HandleEpisode(tv, season, episode); } catch (Exception e) { _logger.LogError(e, "ChineseNfoRegistry.Job() episodeNfo is null or empty"); } } } } async Task HandleTv(string tvNfo) { var nfoContent = File.ReadAllText(tvNfo); var tvXml = new XmlDocument(); tvXml.LoadXml(nfoContent); var isLockedNode = tvXml.SelectSingleNode("//locked"); if (isLockedNode != null && isLockedNode.InnerText == "true" && ignoreLocked) { _logger.LogInformation("ChineseNfoRegistry.Job() tvNfo is locked"); return; } var isCompletedNode = tvXml.SelectSingleNode("//completed"); if (isCompletedNode != null && isCompletedNode.InnerText == "true" && ignoreCompleted) { _logger.LogInformation("ChineseNfoRegistry.Job() tvNfo is completed"); return; } var uniqueIdNode = tvXml.SelectSingleNode("//uniqueid[@type='tmdb']"); if (uniqueIdNode == null) { _logger.LogError("ChineseNfoRegistry.Job() uniqueIdNode is null"); return; } if (!int.TryParse(uniqueIdNode.InnerText, out var tmdbId)) { _logger.LogError("ChineseNfoRegistry.Job() tmdbId is null"); return; } var tvInfo = await GetTmdbTv(tvNfo, tmdbId); if (tvInfo == null) { _logger.LogError("ChineseNfoRegistry.Job() tvInfo is null"); return; } if (tvInfo["name"] != null) { var titleNode = tvXml.SelectSingleNode("//title"); if (titleNode != null) { titleNode.InnerXml = $""; } var sorttitleNode = tvXml.SelectSingleNode("//sorttitle"); if (sorttitleNode != null) { sorttitleNode.InnerXml = $""; } _logger.LogInformation("ChineseNfoRegistry.Job() tvInfo: {tvInfo}", tvInfo["name"]); } if (tvInfo["overview"] != null) { var plotNode = tvXml.SelectSingleNode("//plot"); if (plotNode != null) { plotNode.InnerXml = $""; } var outlineNode = tvXml.SelectSingleNode("//outline"); if (outlineNode != null) { outlineNode.InnerXml = $""; } _logger.LogInformation("ChineseNfoRegistry.Job() tvInfo: {tvInfo}", tvInfo["overview"]); } if (tvInfo["poster_path"] != null) { var posterPath = tvInfo["poster_path"]!.ToString(); var image = await GetTmdbImage(tvNfo, posterPath); if (image != null) { var imagePath = Path.Combine(Path.GetDirectoryName(tvNfo) ?? string.Empty, "poster" + Path.GetExtension(posterPath)); await File.WriteAllBytesAsync(imagePath, image); } } var actors = tvXml.SelectNodes("//actor"); if (actors != null) { foreach (XmlNode actor in actors) { await HandleActor(actor); } } _logger.LogInformation("ChineseNfoRegistry.Job() tmdbId: {tmdbId}, name: {name}", tmdbId, tvInfo["name"]?.ToString()); if (isLockedNode != null) { isLockedNode.InnerText = "true"; } else { isLockedNode = tvXml.CreateElement("locked"); tvXml.DocumentElement?.AppendChild(isLockedNode); isLockedNode.InnerText = "true"; } if (isCompletedNode != null) { isCompletedNode.InnerText = "true"; } else { isCompletedNode = tvXml.CreateElement("completed"); tvXml.DocumentElement?.AppendChild(isCompletedNode); isCompletedNode.InnerText = "true"; } // 保存 tvXml.Save(tvNfo); } async Task HandleSeason(string tvNfo, string seasonNfo) { var tvNfoContent = File.ReadAllText(tvNfo); var tvXml = new XmlDocument(); tvXml.LoadXml(tvNfoContent); var uniqueIdNode = tvXml.SelectSingleNode("//uniqueid[@type='tmdb']"); if (uniqueIdNode == null) { _logger.LogError("ChineseNfoRegistry.Job() uniqueIdNode is null"); return; } if (!int.TryParse(uniqueIdNode.InnerText, out var tmdbId)) { _logger.LogError("ChineseNfoRegistry.Job() tmdbId is null"); return; } var nfoContent = File.ReadAllText(seasonNfo); var seasonXml = new XmlDocument(); seasonXml.LoadXml(nfoContent); var isLockedNode = seasonXml.SelectSingleNode("//locked"); if (isLockedNode != null && isLockedNode.InnerText == "true" && ignoreLocked) { _logger.LogInformation("ChineseNfoRegistry.Job() tvNfo is locked"); return; } var isCompletedNode = seasonXml.SelectSingleNode("//completed"); if (isCompletedNode != null && isCompletedNode.InnerText == "true" && ignoreCompleted) { _logger.LogInformation("ChineseNfoRegistry.Job() tvNfo is completed"); return; } var seasonNumberNode = seasonXml.SelectSingleNode("//seasonnumber"); if (seasonNumberNode == null) { _logger.LogError("ChineseNfoRegistry.Job() seasonNumberNode is null"); return; } if (!int.TryParse(seasonNumberNode.InnerText, out var seasonNumber)) { _logger.LogError("ChineseNfoRegistry.Job() seasonNumber is null"); return; } if (!string.IsNullOrEmpty(requestPath)) { if (!string.IsNullOrEmpty(requestSeasonNumber)) { if (seasonNumber != int.Parse(requestSeasonNumber)) { return; } else { _logger.LogInformation("ChineseNfoRegistry.Job() seasonNumber is equal"); return; } } } var seasonInfo = await GetTmdbSeason(seasonNfo, tmdbId, seasonNumber); if (seasonInfo == null) { _logger.LogError("ChineseNfoRegistry.Job() seasonInfo is null"); return; } var titleNode = seasonXml.SelectSingleNode("//sorttitle"); if (titleNode != null && seasonInfo["name"] != null) { _logger.LogInformation("ChineseNfoRegistry.Job() seasonInfo: {seasonInfo}", seasonInfo["name"]); titleNode.InnerXml = $""; } var plotNode = seasonXml.SelectSingleNode("//plot"); if (plotNode != null && seasonInfo["overview"] != null) { _logger.LogInformation("ChineseNfoRegistry.Job() seasonInfo: {seasonInfo}", seasonInfo["overview"]); plotNode.InnerXml = $""; } var outlineNode = seasonXml.SelectSingleNode("//outline"); if (outlineNode != null && seasonInfo["overview"] != null) { _logger.LogInformation("ChineseNfoRegistry.Job() seasonInfo: {seasonInfo}", seasonInfo["overview"]); outlineNode.InnerXml = $""; } var poster_path = seasonInfo["poster_path"]?.ToString(); if (poster_path != null) { var image = await GetTmdbImage(seasonNfo, poster_path); if (image != null) { var imagePath = Path.Combine(Path.GetDirectoryName(tvNfo) ?? string.Empty, "season" + seasonNumber.ToString("D2") + "-poster" + Path.GetExtension(poster_path)); await File.WriteAllBytesAsync(imagePath, image); } } if (isLockedNode != null) { isLockedNode.InnerText = "true"; } else { isLockedNode = seasonXml.CreateElement("locked"); seasonXml.DocumentElement?.AppendChild(isLockedNode); isLockedNode.InnerText = "true"; } if (isCompletedNode != null) { isCompletedNode.InnerText = "true"; } else { isCompletedNode = seasonXml.CreateElement("completed"); seasonXml.DocumentElement?.AppendChild(isCompletedNode); isCompletedNode.InnerText = "true"; } seasonXml.Save(seasonNfo); } async Task HandleEpisode(string tvNfo, string seasonNfo, string episodeNfo) { var tvNfoContent = File.ReadAllText(tvNfo); var tvXml = new XmlDocument(); tvXml.LoadXml(tvNfoContent); var uniqueIdNode = tvXml.SelectSingleNode("//uniqueid[@type='tmdb']"); if (uniqueIdNode == null) { _logger.LogError("ChineseNfoRegistry.Job() uniqueIdNode is null"); return; } if (!int.TryParse(uniqueIdNode.InnerText, out var tmdbId)) { _logger.LogError("ChineseNfoRegistry.Job() tmdbId is null"); return; } var seasonNfoContent = File.ReadAllText(seasonNfo); var seasonXml = new XmlDocument(); seasonXml.LoadXml(seasonNfoContent); var seasonNumberNode = seasonXml.SelectSingleNode("//seasonnumber"); if (seasonNumberNode == null) { _logger.LogError("ChineseNfoRegistry.Job() seasonNumberNode is null"); return; } if (!int.TryParse(seasonNumberNode.InnerText, out var seasonNumber)) { _logger.LogError("ChineseNfoRegistry.Job() seasonNumber is null"); return; } var nfoContent = File.ReadAllText(episodeNfo); var episodeXml = new XmlDocument(); episodeXml.LoadXml(nfoContent); var isLockedNode = episodeXml.SelectSingleNode("//locked"); if (isLockedNode != null && isLockedNode.InnerText == "true" && ignoreLocked) { _logger.LogInformation("ChineseNfoRegistry.Job() tvNfo is locked"); return; } var isCompletedNode = episodeXml.SelectSingleNode("//completed"); if (isCompletedNode != null && isCompletedNode.InnerText == "true" && ignoreCompleted) { _logger.LogInformation("ChineseNfoRegistry.Job() tvNfo is completed"); return; } var episodeNumberNode = episodeXml.SelectSingleNode("//episode"); if (episodeNumberNode == null) { _logger.LogError("ChineseNfoRegistry.Job() episodeNumberNode is null"); return; } if (!int.TryParse(episodeNumberNode.InnerText, out var episodeNumber)) { _logger.LogError("ChineseNfoRegistry.Job() episodeNumber is null"); return; } if (!string.IsNullOrEmpty(requestPath)) { if (!string.IsNullOrEmpty(requestSeasonNumber)) { if (!string.IsNullOrEmpty(requestEpisodeNumber)) { if (seasonNumber != int.Parse(requestSeasonNumber) || episodeNumber != int.Parse(requestEpisodeNumber)) { return; } else { _logger.LogInformation("ChineseNfoRegistry.Job() episodeNumber is equal"); return; } } } } var episodeInfo = await GetTmdbEpisode(episodeNfo, tmdbId, seasonNumber, episodeNumber); if (episodeInfo == null) { _logger.LogError("ChineseNfoRegistry.Job() episodeInfo is null"); return; } var titleNode = episodeXml.SelectSingleNode("//title"); if (titleNode != null && episodeInfo["name"] != null) { _logger.LogInformation("ChineseNfoRegistry.Job() episodeInfo: {episodeInfo}", episodeInfo["name"]); titleNode.InnerXml = $""; } var plotNode = episodeXml.SelectSingleNode("//plot"); if (plotNode != null && episodeInfo["overview"] != null) { _logger.LogInformation("ChineseNfoRegistry.Job() episodeInfo: {episodeInfo}", episodeInfo["overview"]); plotNode.InnerXml = $""; } var outlineNode = episodeXml.SelectSingleNode("//outline"); if (outlineNode != null && episodeInfo["overview"] != null) { _logger.LogInformation("ChineseNfoRegistry.Job() episodeInfo: {episodeInfo}", episodeInfo["overview"]); outlineNode.InnerXml = $""; } var actors = episodeXml.SelectNodes("//actor"); if (actors != null) { foreach (XmlNode actor in actors) { await HandleActor(actor); } } _logger.LogInformation("ChineseNfoRegistry.Job() tmdbId: {tmdbId}, name: {name}", tmdbId, episodeInfo["name"]?.ToString()); if (isLockedNode != null) { isLockedNode.InnerText = "true"; } else { isLockedNode = episodeXml.CreateElement("locked"); episodeXml.DocumentElement?.AppendChild(isLockedNode); isLockedNode.InnerText = "true"; } if (isCompletedNode != null) { isCompletedNode.InnerText = "true"; } else { isCompletedNode = episodeXml.CreateElement("completed"); episodeXml.DocumentElement?.AppendChild(isCompletedNode); isCompletedNode.InnerText = "true"; } // 保存 episodeXml.Save(episodeNfo); } async Task GetTmdbPersonName( string path, int? tmdbId = null, string? name = null ) { if (tmdbId != null) { var person = await GetTmdbPerson(path, tmdbId.Value); return person?["name"]?.ToString() ?? name; } if (string.IsNullOrEmpty(name) == false) { // 如果名称是中文直接返回 if (Regex.IsMatch(name, "^[\u4e00-\u9fa5]+$")) { return name; } var person = await GetTmdbPersonSearch(path, name); return person?["name"]?.ToString() ?? name; } return null; } async Task HandleActor(XmlNode actor) { var nameNode = actor.SelectSingleNode("name"); var tmdbIdNode = actor.SelectSingleNode("tmdbid"); if (nameNode != null || tmdbIdNode != null) { var name = await GetTmdbPersonName( tvFolder, int.TryParse(tmdbIdNode?.InnerText, out var actorTmdbId) ? actorTmdbId : null, nameNode?.InnerText ); if (name == null) { return; } if (nameNode != null) { nameNode.InnerXml = $""; } else { actor.AppendChild(new XmlDocument().CreateElement("name")); nameNode = actor.SelectSingleNode("name"); nameNode!.InnerXml = $""; } _logger.LogInformation("ChineseNfoRegistry.Job() actor: {actor}", name); } } } private async Task GetTmdbTv( string path, int tmdbId) { var record = await _freeSql.Select() .Where(x => x.Type == TheMovieDbType.Tv) .Where(x => x.UniqueId == tmdbId.ToString()) .FirstAsync(); if (record != null) { return JObject.Parse(record.Json); } record = new TheMovieDbRecord { Type = TheMovieDbType.Tv, UniqueId = tmdbId.ToString() }; string tvUrl = "/3/tv/{0}?api_key=e28e1bc408db7adefc8bacce225c5085&language=zh-CN"; var requestUrl = string.Format(tvUrl, tmdbId); try { var response = await _apiClient.GetAsync(requestUrl); if (!response.IsSuccessStatusCode) { _logger.LogError("ChineseNfoRegistry.GetTmdbTv() 接口调用失败 {requestUrl} & {path} & {response.StatusCode}", requestUrl, path, response.StatusCode); return null; } var str = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(str); record.Json = str; await _freeSql.Insert(record).ExecuteIdentityAsync(); _logger.LogInformation("ChineseNfoRegistry.GetTmdbTv() 接口调用, 休眠 1S"); await Task.Delay(1000); return json; } catch (Exception e) { _logger.LogError("ChineseNfoRegistry.GetTmdbTv() 接口调用失败 {requestUrl} & {path} \r {e}", requestUrl, path, e); return null; } } private async Task GetTmdbPerson( string path, int tmdbId) { var record = await _freeSql.Select() .Where(x => x.Type == TheMovieDbType.Person) .Where(x => x.UniqueId == tmdbId.ToString()) .FirstAsync(); if (record != null) { return JObject.Parse(record.Json); } record = new TheMovieDbRecord { Type = TheMovieDbType.Person, UniqueId = tmdbId.ToString(), Language = TheMovieDbLanguage.ZhCn }; var requestUrl = string.Format("/3/person/{0}?api_key=e28e1bc408db7adefc8bacce225c5085&language=zh-CN", tmdbId); try { var response = await _apiClient.GetAsync(requestUrl); if (!response.IsSuccessStatusCode) { _logger.LogError("ChineseNfoRegistry.GetTmdbPerson() 接口调用失败 {requestUrl} & {path} & {response.StatusCode}", requestUrl, path, response.StatusCode); return null; } var str = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(str); record.Json = str; await _freeSql.Insert(record).ExecuteIdentityAsync(); _logger.LogInformation("ChineseNfoRegistry.GetTmdbPerson() 接口调用, 休眠 1S"); await Task.Delay(1000); return json; } catch (Exception e) { _logger.LogError("ChineseNfoRegistry.GetTmdbPerson() 接口调用失败 {requestUrl} & {path} \r {e}", requestUrl, path, e); return null; } } private async Task GetTmdbPersonSearch( string path, string name) { var record = await _freeSql.Select() .Where(x => x.Type == TheMovieDbType.Person) .Where(x => x.UniqueId == name) .FirstAsync(); if (record != null) { return JObject.Parse(record.Json); } record = new TheMovieDbRecord { Type = TheMovieDbType.Person, UniqueId = name }; var requestUrl = string.Format("/3/search/person?api_key=e28e1bc408db7adefc8bacce225c5085&language=zh-CN&query={0}", name); try { var response = await _apiClient.GetAsync(requestUrl); if (!response.IsSuccessStatusCode) { _logger.LogError("ChineseNfoRegistry.GetTmdbPersonSearch() 接口调用失败 {requestUrl} & {path} & {response.StatusCode}", requestUrl, path, response.StatusCode); return null; } var str = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(str); var results = json["results"]; if (results != null) { foreach (var result in results.Cast()) { record.Json = result.ToString(); await _freeSql.Insert(record).ExecuteIdentityAsync(); _logger.LogInformation("ChineseNfoRegistry.GetTmdbPersonSearch() 接口调用, 休眠 1S"); await Task.Delay(1000); return result; } } return null; } catch (Exception e) { _logger.LogError("ChineseNfoRegistry.GetTmdbPersonSearch() 接口调用失败 {requestUrl} & {path} \r {e}", requestUrl, path, e); return null; } } private async Task GetTmdbImage( string path, string posterPath) { var requestUrl = string.Format("/t/p/w1280/{0}", posterPath); try { var response = await _imageClient.GetAsync(requestUrl); if (!response.IsSuccessStatusCode) { _logger.LogError("ChineseNfoRegistry.GetTmdbImage() 接口调用失败 {requestUrl} & {path} & {response.StatusCode}", requestUrl, path, response.StatusCode); return null; } return await response.Content.ReadAsByteArrayAsync(); } catch (Exception e) { _logger.LogError("ChineseNfoRegistry.GetTmdbImage() 接口调用失败 {requestUrl} & {path} \r {e}", requestUrl, path, e); return null; } } private async Task GetTmdbSeason( string path, int tmdbId, int seasonNumber) { var uniqueId = $"{tmdbId}-{seasonNumber}"; var record = await _freeSql.Select() .Where(x => x.Type == TheMovieDbType.Season) .Where(x => x.UniqueId == uniqueId) .FirstAsync(); if (record != null) { return JObject.Parse(record.Json); } record = new TheMovieDbRecord { Type = TheMovieDbType.Season, UniqueId = uniqueId }; var requestUrl = string.Format("/3/tv/{0}/season/{1}?api_key=e28e1bc408db7adefc8bacce225c5085&language=zh-CN", tmdbId, seasonNumber); try { var response = await _apiClient.GetAsync(requestUrl); if (!response.IsSuccessStatusCode) { _logger.LogError("ChineseNfoRegistry.GetTmdbSeason() 接口调用失败 {requestUrl} & {path} & {response.StatusCode}", requestUrl, path, response.StatusCode); return null; } var str = await response.Content.ReadAsStringAsync(); record.Json = str; await _freeSql.Insert(record).ExecuteIdentityAsync(); _logger.LogInformation("ChineseNfoRegistry.GetTmdbSeason() 接口调用, 休眠 1S"); await Task.Delay(1000); return JObject.Parse(str); } catch (Exception e) { _logger.LogError("ChineseNfoRegistry.GetTmdbSeason() 接口调用失败 {requestUrl} & {path} \r {e}", requestUrl, path, e); return null; } } private async Task GetTmdbEpisode( string path, int tmdbId, int seasonNumber, int episodeNumber) { var uniqueId = $"{tmdbId}-{seasonNumber}-{episodeNumber}"; var record = await _freeSql.Select() .Where(x => x.Type == TheMovieDbType.Episode) .Where(x => x.UniqueId == uniqueId) .FirstAsync(); if (record != null) { return JObject.Parse(record.Json); } record = new TheMovieDbRecord { Type = TheMovieDbType.Episode, UniqueId = uniqueId }; var requestUrl = string.Format("/3/tv/{0}/season/{1}/episode/{2}?api_key=e28e1bc408db7adefc8bacce225c5085&language=zh-CN", tmdbId, seasonNumber, episodeNumber); try { var response = await _apiClient.GetAsync(requestUrl); if (!response.IsSuccessStatusCode) { _logger.LogError("ChineseNfoRegistry.GetTmdbEpisode() 接口调用失败 {requestUrl} & {path} & {response.StatusCode}", requestUrl, path, response.StatusCode); return null; } var str = await response.Content.ReadAsStringAsync(); record.Json = str; await _freeSql.Insert(record).ExecuteIdentityAsync(); _logger.LogInformation("ChineseNfoRegistry.GetTmdbEpisode() 接口调用, 休眠 1S"); await Task.Delay(1000); return JObject.Parse(str); } catch (Exception e) { _logger.LogError("ChineseNfoRegistry.GetTmdbEpisode() 接口调用失败 {requestUrl} & {path} \r {e}", requestUrl, path, e); return null; } } } public class TheMovieDbRecord { [Column(IsPrimary = true, IsIdentity = true)] public long Id { get; set; } public TheMovieDbType Type { get; set; } public string UniqueId { get; set; } = string.Empty; public TheMovieDbLanguage Language { get; set; } [Column(DbType = "text")] public string Json { get; set; } = string.Empty; } public enum TheMovieDbType { Tv = 1, Season = 2, Episode = 3, Person = 4 } public enum TheMovieDbLanguage { ZhCn = 1, ZhTw = 2 }