diff --git a/BangumiRenamer.csproj b/BangumiRenamer.csproj index 56073f6..eb83632 100644 --- a/BangumiRenamer.csproj +++ b/BangumiRenamer.csproj @@ -18,6 +18,7 @@ + diff --git a/Src/Config/Attribute/ConfigItemAttribute.cs b/Src/Config/Attribute/ConfigItemAttribute.cs new file mode 100644 index 0000000..4d25ff9 --- /dev/null +++ b/Src/Config/Attribute/ConfigItemAttribute.cs @@ -0,0 +1,11 @@ +namespace BangumiRenamer.Config; + +public class ConfigItemAttribute : System.Attribute +{ + public string Name; + + public ConfigItemAttribute(string name) + { + Name = name; + } +} \ No newline at end of file diff --git a/Src/ConfigManager/ConfigManager.cs b/Src/Config/Config.cs similarity index 51% rename from Src/ConfigManager/ConfigManager.cs rename to Src/Config/Config.cs index 886cbde..498063e 100644 --- a/Src/ConfigManager/ConfigManager.cs +++ b/Src/Config/Config.cs @@ -1,79 +1,115 @@ -using System.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace BangumiRenamer.ConfigManager; - -public class ConfigManager(string configPath) -{ - private static readonly Dictionary Cache = new(); - private readonly Dictionary _configObjects = new(); - private readonly Dictionary _configs = new(); - - private static string GetConfigName(Type type) - { - if (Cache.TryGetValue(type, out var name)) return name; - name = type.Name; - if(type.GetCustomAttribute(typeof(ConfigItemAttribute)) is ConfigItemAttribute info) - { - name = string.IsNullOrEmpty(info.Name) ? type.Name : info.Name; - } - Cache[type] = name; - return name; - } - - public T Get() where T : class, new() - { - var name = GetConfigName(typeof(T)); - if (_configObjects.TryGetValue(name, out var value)) - { - return (T) value; - } - T result; - if (_configs.TryGetValue(name, out var jObject)) - { - result = jObject.ToObject(); - } - else - { - result = new T(); - } - _configObjects[name] = result; - return result; - } - - public void Reset() - { - var name = GetConfigName(typeof(T)); - _configs.Remove(name); - _configObjects.Remove(name); - } - - public void Clear() - { - _configs.Clear(); - _configObjects.Clear(); - } - - public void Load() - { - _configs.Clear(); - _configObjects.Clear(); - if (!File.Exists(configPath)) return; - var configJson = File.ReadAllText(configPath); - var config = JObject.Parse(configJson); - foreach (var kv in config) - { - _configs[kv.Key] = kv.Value.ToObject(); - } - } - - public void Save() - { - foreach (var config in _configObjects) - { - _configs[config.Key] = JObject.FromObject(config.Value); - } - File.WriteAllText(configPath, JsonConvert.SerializeObject(_configs, Formatting.Indented)); - } +namespace BangumiRenamer.Config; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +public class Config(string configPath) +{ + private static readonly Lazy _lazy = new (() => + { + var config = new Config("config.json"); + config.ReLoad(); + return config; + }); + public static Config Default => _lazy.Value; + + + private static readonly Dictionary Cache = new(); + private readonly Dictionary _configObjects = new(); + private readonly Dictionary _configs = new(); + + private static string GetConfigItemName(Type type) + { + if (Cache.TryGetValue(type, out var name)) return name; + name = type.Name; + if(type.GetCustomAttribute(typeof(ConfigItemAttribute)) is ConfigItemAttribute info) + { + name = string.IsNullOrEmpty(info.Name) ? type.Name : info.Name; + } + Cache[type] = name; + return name; + } + + public T Get() where T : class, IConfigItem, new() + { + var name = GetConfigItemName(typeof(T)); + if (_configObjects.TryGetValue(name, out var value)) + { + return (T) value; + } + T result; + if (_configs.TryGetValue(name, out var jObject)) + { + result = jObject.ToObject(); + } + else + { + result = new T(); + } + _configObjects[name] = result; + return result; + } + + public void Reset() where T : class, IConfigItem, new() + { + var name = GetConfigItemName(typeof(T)); + _configs.Remove(name); + _configObjects.Remove(name); + } + + public void Clear() + { + _configs.Clear(); + _configObjects.Clear(); + } + + public void ReLoad() + { + _configs.Clear(); + _configObjects.Clear(); + if (!File.Exists(configPath)) return; + var configJson = File.ReadAllText(configPath); + var config = JObject.Parse(configJson); + foreach (var kv in config) + { + _configs[kv.Key] = kv.Value.ToObject(); + } + } + + public void Save() + { + foreach (var config in _configObjects) + { + _configs[config.Key] = JObject.FromObject(config.Value); + } + File.WriteAllText(configPath, JsonConvert.SerializeObject(_configs, Formatting.Indented)); + } + + public static void CreateEmptyConfig() + { + var configItemTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException) + { + return Array.Empty(); + } + }) + .Where(type => type.IsClass && + !type.IsAbstract && + typeof(IConfigItem).IsAssignableFrom(type)) + .ToList(); + + var configs = new Dictionary(); + foreach (var type in configItemTypes) + { + var name = GetConfigItemName(type); + configs[name] = JObject.FromObject(Activator.CreateInstance(type)); + } + File.WriteAllText("config_default.json", JsonConvert.SerializeObject(configs, Formatting.Indented)); + } } \ No newline at end of file diff --git a/Src/Config/ConfigSchema/ProxyConfig.cs b/Src/Config/ConfigSchema/ProxyConfig.cs new file mode 100644 index 0000000..0e22646 --- /dev/null +++ b/Src/Config/ConfigSchema/ProxyConfig.cs @@ -0,0 +1,7 @@ +namespace BangumiRenamer.Config; + +[ConfigItem("Proxy")] +public class ProxyConfig : IConfigItem +{ + public string HttpProxy = ""; +} \ No newline at end of file diff --git a/Src/Config/ConfigSchema/TMDBConfig.cs b/Src/Config/ConfigSchema/TMDBConfig.cs new file mode 100644 index 0000000..1943e7d --- /dev/null +++ b/Src/Config/ConfigSchema/TMDBConfig.cs @@ -0,0 +1,7 @@ +namespace BangumiRenamer.Config; + +[ConfigItem("TMDB")] +public class TMDBConfig : IConfigItem +{ + public string ApiKey = ""; +} \ No newline at end of file diff --git a/Src/Config/IConfigItem.cs b/Src/Config/IConfigItem.cs new file mode 100644 index 0000000..b0c4aa5 --- /dev/null +++ b/Src/Config/IConfigItem.cs @@ -0,0 +1,3 @@ +namespace BangumiRenamer.Config; + +public interface IConfigItem; \ No newline at end of file diff --git a/Src/ConfigManager/Attribute/ConfigItemAttribute.cs b/Src/ConfigManager/Attribute/ConfigItemAttribute.cs deleted file mode 100644 index 082e142..0000000 --- a/Src/ConfigManager/Attribute/ConfigItemAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace BangumiRenamer.ConfigManager; - -public class ConfigItemAttribute : Attribute -{ - public string Name; - - public ConfigItemAttribute() - { - } - - public ConfigItemAttribute(string name) - { - Name = name; - } -} \ No newline at end of file diff --git a/Src/Entity/Show.cs b/Src/Entity/Show.cs new file mode 100644 index 0000000..efdbbee --- /dev/null +++ b/Src/Entity/Show.cs @@ -0,0 +1,13 @@ +namespace BangumiRenamer.Data; + +public class Show +{ + public string title; + public List sessions; +} + +public class Session +{ + public int id; + public List episodes; +} diff --git a/Src/Tools/ShowCompletionChecker.cs b/Src/Tools/ShowCompletionChecker.cs new file mode 100644 index 0000000..e42ab41 --- /dev/null +++ b/Src/Tools/ShowCompletionChecker.cs @@ -0,0 +1,95 @@ +namespace BangumiRenamer.Tools; + +using Config; +using TMDbLib.Client; +using Data; +using NativeFileDialogSharp; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; + +public static class ShowCompletionChecker +{ + static List FindShows(string checkPath) + { + var shows = new List(); + var showPaths = Directory.GetDirectories(checkPath); + foreach (var showPath in showPaths) + { + var sessions = new List(); + var sessionPaths = Directory.GetDirectories(showPath); + foreach (var sessionPath in sessionPaths) + { + var episodePaths = Directory.GetFiles(sessionPath); + HashSet episodes = new HashSet(); + foreach (var episodePath in episodePaths) + { + var matches = Regex.Matches(episodePath, @".*S\d+E(\d+).*"); + if (matches.Count == 0) continue; + var episode = int.Parse(matches[0].Groups[1].Value); + episodes.Add(episode); + } + var session = new Session + { + id = int.Parse(Path.GetFileName(sessionPath).Replace("Season ", "")), + episodes = episodes.ToList() + }; + sessions.Add(session); + } + shows.Add(new Show + { + title = Path.GetFileName(showPath), + sessions = sessions + }); + } + return shows; + } + + + public static void Run() + { + var dir = Dialog.FolderPicker(); + if (!dir.IsOk) return; + var checkPath = dir.Path; + + var shows = FindShows(checkPath); + Console.WriteLine($"Total Shows: {shows.Count}"); + + var client = new TMDbClient( + apiKey: Config.Default.Get().ApiKey , + proxy: new WebProxy(Config.Default.Get().HttpProxy)); + + var output = new StringBuilder(); + foreach (var t in shows) + { + var match = Regex.Match(t.title, @"(.*) \((\d{4})\)"); + var title = match.Groups[1].Value; + var year = int.Parse(match.Groups[2].Value); + var result = client.SearchTvShowAsync(title, firstAirDateYear: year).Result; + var info = result.Results.FirstOrDefault(); + if (info == null) + { + Console.WriteLine($"找不到对应的TV:{t.title}"); + continue; + } + foreach (var session in t.sessions) + { + var sessionInfo = client.GetTvSeasonAsync(info.Id, session.id).Result; + if (sessionInfo == null) + { + Console.WriteLine($"季度对不上,可能找错了:{t.title} -> {info.OriginalName}"); + break; + } + foreach (var episode in sessionInfo.Episodes) + { + if (DateTime.Now.AddDays(-7) < episode.AirDate) continue; + if (!session.episodes.Contains(episode.EpisodeNumber)) + { + output.AppendLine($"{title} 的第 {session.id} 季少了第 {episode.EpisodeNumber} 集"); + } + } + } + } + Console.Write(output); + } +} \ No newline at end of file