Compare commits

..

2 Commits

Author SHA1 Message Date
4f021d5a3e 完成show完整性检查 2025-11-01 17:20:03 +08:00
e33e985031 调整结构 2025-11-01 16:16:46 +08:00
12 changed files with 268 additions and 106 deletions

View File

@ -18,6 +18,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" /> <PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="TMDbLib" Version="2.3.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,11 @@
namespace BangumiRenamer.Config;
public class ConfigItemAttribute : System.Attribute
{
public string Name;
public ConfigItemAttribute(string name)
{
Name = name;
}
}

View File

@ -1,79 +1,115 @@
using System.Reflection; namespace BangumiRenamer.Config;
using Newtonsoft.Json; using System.Reflection;
using Newtonsoft.Json.Linq; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BangumiRenamer.ConfigManager;
public class Config(string configPath)
public class ConfigManager(string configPath) {
{ private static readonly Lazy<Config> _lazy = new (() =>
private static readonly Dictionary<Type, string> Cache = new(); {
private readonly Dictionary<string, object> _configObjects = new(); var config = new Config("config.json");
private readonly Dictionary<string, JObject> _configs = new(); config.ReLoad();
return config;
private static string GetConfigName(Type type) });
{ public static Config Default => _lazy.Value;
if (Cache.TryGetValue(type, out var name)) return name;
name = type.Name;
if(type.GetCustomAttribute(typeof(ConfigItemAttribute)) is ConfigItemAttribute info) private static readonly Dictionary<Type, string> Cache = new();
{ private readonly Dictionary<string, object> _configObjects = new();
name = string.IsNullOrEmpty(info.Name) ? type.Name : info.Name; private readonly Dictionary<string, JObject> _configs = new();
}
Cache[type] = name; private static string GetConfigItemName(Type type)
return name; {
} if (Cache.TryGetValue(type, out var name)) return name;
name = type.Name;
public T Get<T>() where T : class, new() if(type.GetCustomAttribute(typeof(ConfigItemAttribute)) is ConfigItemAttribute info)
{ {
var name = GetConfigName(typeof(T)); name = string.IsNullOrEmpty(info.Name) ? type.Name : info.Name;
if (_configObjects.TryGetValue(name, out var value)) }
{ Cache[type] = name;
return (T) value; return name;
} }
T result;
if (_configs.TryGetValue(name, out var jObject)) public T Get<T>() where T : class, IConfigItem, new()
{ {
result = jObject.ToObject<T>(); var name = GetConfigItemName(typeof(T));
} if (_configObjects.TryGetValue(name, out var value))
else {
{ return (T) value;
result = new T(); }
} T result;
_configObjects[name] = result; if (_configs.TryGetValue(name, out var jObject))
return result; {
} result = jObject.ToObject<T>();
}
public void Reset<T>() else
{ {
var name = GetConfigName(typeof(T)); result = new T();
_configs.Remove(name); }
_configObjects.Remove(name); _configObjects[name] = result;
} return result;
}
public void Clear()
{ public void Reset<T>() where T : class, IConfigItem, new()
_configs.Clear(); {
_configObjects.Clear(); var name = GetConfigItemName(typeof(T));
} _configs.Remove(name);
_configObjects.Remove(name);
public void Load() }
{
_configs.Clear(); public void Clear()
_configObjects.Clear(); {
if (!File.Exists(configPath)) return; _configs.Clear();
var configJson = File.ReadAllText(configPath); _configObjects.Clear();
var config = JObject.Parse(configJson); }
foreach (var kv in config)
{ public void ReLoad()
_configs[kv.Key] = kv.Value.ToObject<JObject>(); {
} _configs.Clear();
} _configObjects.Clear();
if (!File.Exists(configPath)) return;
public void Save() var configJson = File.ReadAllText(configPath);
{ var config = JObject.Parse(configJson);
foreach (var config in _configObjects) foreach (var kv in config)
{ {
_configs[config.Key] = JObject.FromObject(config.Value); _configs[kv.Key] = kv.Value.ToObject<JObject>();
} }
File.WriteAllText(configPath, JsonConvert.SerializeObject(_configs, Formatting.Indented)); }
}
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<Type>();
}
})
.Where(type => type.IsClass &&
!type.IsAbstract &&
typeof(IConfigItem).IsAssignableFrom(type))
.ToList();
var configs = new Dictionary<string, JObject>();
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));
}
} }

View File

@ -0,0 +1,7 @@
namespace BangumiRenamer.Config;
[ConfigItem("Proxy")]
public class ProxyConfig : IConfigItem
{
public string HttpProxy = "";
}

View File

@ -0,0 +1,7 @@
namespace BangumiRenamer.Config;
[ConfigItem("TMDB")]
public class TMDBConfig : IConfigItem
{
public string ApiKey = "";
}

View File

@ -0,0 +1,3 @@
namespace BangumiRenamer.Config;
public interface IConfigItem;

View File

@ -1,15 +0,0 @@
namespace BangumiRenamer.ConfigManager;
public class ConfigItemAttribute : Attribute
{
public string Name;
public ConfigItemAttribute()
{
}
public ConfigItemAttribute(string name)
{
Name = name;
}
}

13
Src/Entity/Show.cs Normal file
View File

@ -0,0 +1,13 @@
namespace BangumiRenamer.Data;
public class Show
{
public string title;
public List<Session> sessions;
}
public class Session
{
public int id;
public List<int> episodes;
}

View File

@ -1,16 +1,5 @@
using BangumiRenamer.Utils; class Program
class Program
{ {
public static void PrintResNames()
{
var names = ResourceLoader.GetAllResNames();
foreach (var name in names)
{
Console.WriteLine(name);
}
}
static void Main() static void Main()
{ {

View File

@ -0,0 +1,15 @@
using BangumiRenamer.Utils;
namespace BangumiRenamer.Tools;
public class EmbededResourceViewer
{
public static void PrintResourceNames()
{
var names = ResourceLoader.GetAllResNames();
foreach (var name in names)
{
Console.WriteLine(name);
}
}
}

View File

@ -13,7 +13,7 @@ public static class FolderCloner
result = Dialog.FolderPicker(); result = Dialog.FolderPicker();
if (!result.IsOk) return; if (!result.IsOk) return;
var dest = Path.Combine(result.Path, Path.GetFileNameWithoutExtension(source)); var dest = Path.Combine(result.Path, Path.GetFileNameWithoutExtension(source.Replace("\\\\", "")));
var finalDest = dest; var finalDest = dest;
int suffix = 1; int suffix = 1;

View File

@ -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<Show> FindShows(string checkPath)
{
var shows = new List<Show>();
var showPaths = Directory.GetDirectories(checkPath);
foreach (var showPath in showPaths)
{
var sessions = new List<Session>();
var sessionPaths = Directory.GetDirectories(showPath);
foreach (var sessionPath in sessionPaths)
{
var episodePaths = Directory.GetFiles(sessionPath);
HashSet<int> episodes = new HashSet<int>();
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<TMDBConfig>().ApiKey ,
proxy: new WebProxy(Config.Default.Get<ProxyConfig>().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);
}
}