Compare commits

...

49 Commits

Author SHA1 Message Date
孙诚
b304269079 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 25s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
2025-11-12 14:31:57 +08:00
孙诚
e7168a2fe6 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 1m0s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
2025-11-12 14:28:26 +08:00
孙诚
626a8a2950 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 23s
Docker Build & Deploy / Deploy to Production (push) Successful in 10s
2025-10-10 18:39:39 +08:00
孙诚
a650d4197a 更新 docker-compose.yml 中的媒体文件路径,移除 JobTriggerController 和 NotifyController 中的未使用代码
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 48s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
2025-09-30 17:17:43 +08:00
孙诚
f93cd5b405 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 23s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
2025-09-29 18:36:45 +08:00
孙诚
e682814630 添加异常处理以捕获 HandleEpisode 方法中的错误
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 24s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
2025-09-29 11:25:35 +08:00
孙诚
33b17c9937 注释掉 DiskMonitorRegistry 的初始化代码
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 28s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
2025-09-29 11:08:51 +08:00
孙诚
e739638fae 更新 docker-compose.yml 中的媒体文件路径
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 7s
Docker Build & Deploy / Deploy to Production (push) Successful in 13s
2025-09-29 11:06:28 +08:00
孙诚
7e67e26bb2 1
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Successful in 15s
Docker Build & Deploy / Deploy to Production (push) Failing after 2s
2025-05-26 23:03:57 +08:00
46832d78a2 更新 src/WebApi/Program.cs
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 14s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-05-24 16:40:46 +08:00
孙诚
2c728891a5 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 15s
Docker Build & Deploy / Deploy to Production (push) Successful in 3s
2025-04-22 19:27:19 +08:00
孙诚
a60c058a49 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 13s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-22 17:52:47 +08:00
孙诚
b492658dc6 优化 ChineseNfoRegistry 类中的季号验证逻辑,简化条件判断以提高代码可读性。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 13s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-22 17:51:14 +08:00
孙诚
aa1970a36e 优化季节 NFO 文件处理逻辑,修复季号提取中的字符串分割问题,并增加日志记录以便于调试。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 25s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
2025-04-22 17:43:09 +08:00
孙诚
1e4da61d68 更新 docker-compose.yml 文件,将用户配置修改为 "1000:1000" 格式,以确保容器的权限设置更加明确。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 4s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-22 17:36:04 +08:00
孙诚
90ccaba1a3 更新 ChineseNfoRegistry 类,优化季节 NFO 文件处理逻辑,移除不必要的文件创建步骤,增强季号验证和处理的准确性。
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Successful in 12s
Docker Build & Deploy / Deploy to Production (push) Failing after 4s
2025-04-22 17:33:44 +08:00
孙诚
cb41791656 1
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Successful in 25s
Docker Build & Deploy / Deploy to Production (push) Failing after 4s
2025-04-22 17:30:11 +08:00
孙诚
8ebab0d6f8 在 JobTriggerController 类中增加了 10 秒的延迟,以防止文件未能创建的问题,确保后续处理的稳定性。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 13s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-22 16:19:43 +08:00
孙诚
7a18330524 更新 ChineseNfoRegistry 类,简化季号和集号的处理逻辑,增强错误日志记录,确保在处理 NFO 文件时更好地捕获异常情况。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 14s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-22 16:17:09 +08:00
孙诚
84e35fb631 更新 IChineseNfoRegistry 和 ChineseNfoRegistry 类,修改 Job 方法的季号和集号参数类型为 int,增强类型安全性;简化日志记录,减少冗余信息,提高可读性。 2025-04-22 15:57:58 +08:00
孙诚
6c443f2fde 更新 NotifyController 类,修正 JSON 对象转换方式,将 ToObject<string>() 方法改为 ToString(),以提高代码的准确性和可读性。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 14s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-22 15:27:56 +08:00
孙诚
3403881b2a 更新 ChineseNfoRegistry 类,优化 Job 方法的锁定逻辑,改为同步执行以避免潜在的并发问题;更新 JobTriggerController 类,增加日志记录以跟踪路径、季号和集号的处理情况。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 15s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
2025-04-22 15:24:21 +08:00
孙诚
2364e41825 更新 ChineseNfoRegistry 类,简化日志记录逻辑,改进季号和集号的提取方式,增强代码可读性。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 12s
Docker Build & Deploy / Deploy to Production (push) Successful in 3s
2025-04-21 17:43:48 +08:00
孙诚
d379f516c5 更新 ChineseNfoRegistry 类,增强日志记录以反映季号和集号的比较结果,改进路径和参数验证逻辑。
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 9s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
2025-04-21 17:27:19 +08:00
孙诚
694db295a9 更新 ChineseNfoRegistry 类,修正路径检查逻辑以使用最新路径;更新 JobTriggerController 类,移除 tmdbId 参数,改为使用路径参数。
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 20s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
2025-04-21 16:20:03 +08:00
孙诚
8ceb88846c 更新 ChineseNfoRegistry 类,增加季节 NFO 文件创建逻辑;更新 JobTriggerController 类,新增根据路径转换中文 NFO 的方法,支持季号和集号参数。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 14s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-21 13:23:42 +08:00
孙诚
84357b7f31 更新 ChineseNfoRegistry 类中的日志记录,将错误日志改为信息日志,以改善日志的可读性和准确性。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 13s
Docker Build & Deploy / Deploy to Production (push) Successful in 3s
2025-04-17 19:07:49 +08:00
孙诚
6773efb842 更新 IChineseNfoRegistry 和 ChineseNfoRegistry 类中的 Job 方法,将 tmdbId 参数更改为 path,并在 JobExecute 方法中增加路径、季号和集号的验证逻辑,改进日志记录以增强错误处理。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 17s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-17 19:00:56 +08:00
孙诚
25ace6703f 更新 JobTriggerController 中的 ConvertChineseNfo 方法,默认参数改为忽略锁定和已完成的任务。 2025-04-17 18:48:56 +08:00
孙诚
903fd8e256 更新 JobTriggerController 中的 Job 方法,增加参数以忽略锁定和已完成的任务。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 14s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
2025-04-17 18:48:40 +08:00
孙诚
3288df0f25 更新 ChineseNfoRegistry 类中的 JobExecute 方法,增加可选参数以支持 TMDB ID、季号和集号;改进错误处理,使用日志记录替代控制台输出。
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 15s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
2025-04-17 18:46:41 +08:00
孙诚
ec3434da82 Improve error handling and logging in ChineseNfoRegistry job methods; adjust delay duration for TMDB API calls
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 12s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-16 18:54:25 +08:00
孙诚
bf4faca1a7 Enhance ChineseNfoRegistry job method to support optional parameters for TMDB ID, season, and episode; update configuration for HTTP proxy and connection string
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 4s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
2025-04-16 18:26:31 +08:00
孙诚
fae4754df4 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 20s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
2025-04-03 17:03:42 +08:00
孙诚
7b0c5f8ce2 Use ExecuteIdentityAsync for record insertion in ChineseNfoRegistry
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 11s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-03 17:02:10 +08:00
孙诚
4dbdf69c25 Log information and add delay in ChineseNfoRegistry.GetTmdbTv() method
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 17s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
2025-04-03 16:44:47 +08:00
孙诚
9981dfa211 Update TV URL language parameter handling in ChineseNfoRegistry 2025-04-03 16:39:32 +08:00
孙诚
81e04c90a3 Log information when downgrading to Traditional Chinese in ChineseNfoRegistry
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 11s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
2025-04-03 16:29:16 +08:00
孙诚
094793930c Change Json property type from varchar(max) to text in ChineseNfoRegistry
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 11s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
2025-04-03 16:25:06 +08:00
孙诚
acb7330d82 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 27s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
2025-04-03 16:22:31 +08:00
孙诚
d41de63887 Update ChineseNfoRegistry to use HTTP instead of HTTPS for API and image URLs
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 11s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-03 12:58:52 +08:00
孙诚
60705f90e5 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 13s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-03 12:55:32 +08:00
孙诚
cca7b5857e 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 11s
Docker Build & Deploy / Deploy to Production (push) Successful in 3s
2025-04-03 12:03:40 +08:00
孙诚
8e3643faeb 1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 12s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-03 12:02:30 +08:00
孙诚
14e59b48f6 Refactor ChineseNfoRegistry to remove redundant permission setting and improve episode processing logic
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 15s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-04-03 11:28:26 +08:00
孙诚
b008a54fdb Refactor GetTmdbEpisode method to improve parameter readability and logging
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 23s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-03-19 15:27:00 +08:00
孙诚
30813961ad Update file permission setting in ChineseNfoRegistry to allow full access
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 12s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-03-19 15:21:29 +08:00
孙诚
fbea48813e Update docker-compose to run as root and modify permission setting in ChineseNfoRegistry to remove sudo
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 11s
Docker Build & Deploy / Deploy to Production (push) Successful in 16s
2025-03-19 15:05:27 +08:00
孙诚
795937d052 Refactor permission setting in ChineseNfoRegistry to use sudo for improved security
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 11s
Docker Build & Deploy / Deploy to Production (push) Successful in 4s
2025-03-19 15:00:52 +08:00
23 changed files with 1015 additions and 1219 deletions

35
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
// 使用 IntelliSense 找出 C# 调试存在哪些属性
// 将悬停用于现有属性的说明
// 有关详细信息,请访问 https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md。
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// 如果已更改目标框架,请确保更新程序路径。
"program": "${workspaceFolder}/src/WebApi/bin/Debug/net8.0/WebApi.dll",
"args": [],
"cwd": "${workspaceFolder}/src/WebApi",
"stopAtEntry": false,
// 启用在启动 ASP.NET Core 时启动 Web 浏览器。有关详细信息: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/NasRobot.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/NasRobot.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/NasRobot.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -5,15 +5,19 @@
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: nas_robot container_name: nas_robot
restart: always restart: always
privileged: true
user: "root:root"
networks: networks:
- all_in - all_in
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
volumes: volumes:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /wd/volc/media/tv:/data/media/tv:rw - /wd/media/tv:/data/media/tv:rw
- /wd/volc/media/anime:/data/media/anime:rw - /wd/media/anime:/data/media/anime:rw
- /wd/volc/media/anime-other:/data/media/anime-other:rw - /wd/media/music:/data/media/music:rw
- /wd/media/others/anime:/data/media/anime-other:rw
- /wd/apps/vols/nas_robot:/app/data:rw
- /wd:/host/wd:ro - /wd:/host/wd:ro
- /proc/mounts:/host/proc/mounts:ro - /proc/mounts:/host/proc/mounts:ro

View File

@@ -1,23 +0,0 @@
using System.Net;
using System.Net.Http.Json;
namespace Core;
public static class Command
{
public static async Task<(HttpStatusCode responseCode, string responseStr)> ExecAsync(params string[] commands)
{
var execUrl = "http://192.168.31.14:45321/exec";
var client = new HttpClient();
var content = JsonContent.Create(new
{
Token = "4CeVaIQGbB4Qln9%V@Bh8bMYSpHIUV66",
Script = commands
});
var response = await client.PostAsync(execUrl, content);
return (response.StatusCode, await response.Content.ReadAsStringAsync());
}
}

View File

@@ -1,9 +0,0 @@
namespace Core;
public static class Tools
{
public static long ToUnixTimeMilliseconds(this DateTime dateTime)
{
return new DateTimeOffset(dateTime).ToUnixTimeMilliseconds();
}
}

View File

@@ -4,34 +4,6 @@ namespace Core;
public static class WxNotify public static class WxNotify
{ {
public static async Task SendDiskInfoAsync(string msg)
{
// 晚上11点到早上8点不通知输出到控制台
if (DateTime.Now.Hour >= 23 || DateTime.Now.Hour < 8)
{
Console.WriteLine("======================Skip WxNotify======================");
Console.WriteLine(msg);
Console.WriteLine("======================Skip WxNotify======================");
return;
}
var wxRebotUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=96f9fa23-a959-4492-ac3a-7415fae19680";
var client = new HttpClient();
var requestBody =
"""
{
"msgtype": "markdown",
"markdown": {
"content": "{Msg}"
}
}
""";
// 转义
requestBody = requestBody.Replace("{Msg}", msg.Replace("\r\n", "\n"));
await client.PostAsync(wxRebotUrl, new StringContent(requestBody, Encoding.UTF8, "application/json"));
}
public static async Task SendCommonAsync(string msg) public static async Task SendCommonAsync(string msg)
{ {
var wxRebotUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=96f9fa23-a959-4492-ac3a-7415fae19680"; var wxRebotUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=96f9fa23-a959-4492-ac3a-7415fae19680";

View File

@@ -2,5 +2,11 @@ namespace Interface.Jobs;
public interface IChineseNfoRegistry public interface IChineseNfoRegistry
{ {
void Job(bool ignoreLocked, bool ignoreCompleted); void Job(
string? path = null,
int? seasonNumber = null,
int? episodeNumber = null,
bool ignoreLocked = false,
bool ignoreCompleted = false
);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,174 +0,0 @@
using Core;
using FluentScheduler;
using Interface.Jobs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Service.Jobs;
public enum DiskNotifyType
{
Action,
Sleep
}
public class DiskActionMonitorRegistry : Registry, IDiskActionMonitorRegistry
{
private readonly IConfiguration _configuration;
private static Dictionary<string, int> _lastReadCount = new();
private static Dictionary<string, int> _lastWriteCount = new();
private readonly ILogger<DiskActionMonitorRegistry> _logger;
private static readonly Dictionary<string, List<(DateTime, DiskNotifyType)>> LatestNotifyTimes = new();
private static readonly Dictionary<string, DateTime> LastChangedTimes = new();
public DiskActionMonitorRegistry(IConfiguration configuration, ILogger<DiskActionMonitorRegistry> logger)
{
_configuration = configuration;
_logger = logger;
Schedule(Job).ToRunEvery(5).Seconds();
}
public async void Job()
{
try
{
await JobExecute();
}
catch (Exception e)
{
_logger.LogError(e, "DiskActionMonitorRegistry.Job() error");
}
}
private async Task JobExecute()
{
var filePath = _configuration["DiskActionMonitor:FilePath"] ?? throw new ArgumentNullException($"DiskActionMonitor:FilePath");
var disks = _configuration
.GetSection("DiskActionMonitor:Disks")
.GetChildren()
.Select(x => x.Value);
foreach (var disk in disks)
{
if (string.IsNullOrEmpty(disk))
{
continue;
}
if (!_lastReadCount.ContainsKey(disk))
{
_lastReadCount.Add(disk, 0);
}
if (!_lastWriteCount.ContainsKey(disk))
{
_lastWriteCount.Add(disk, 0);
}
if (!LatestNotifyTimes.ContainsKey(disk))
{
LatestNotifyTimes.Add(disk, new());
}
if (!LastChangedTimes.ContainsKey(disk))
{
LastChangedTimes.Add(disk, DateTime.Now);
}
var content = await File.ReadAllTextAsync(string.Format(filePath, disk));
var cols = content.Split(" ", StringSplitOptions.RemoveEmptyEntries);
var readCount = TryGetByIndex(cols, 2);
var writeCount = TryGetByIndex(cols, 6);
if (!int.TryParse(readCount, out var read) || !int.TryParse(writeCount, out var write))
{
continue; // 读取失败
}
var readDiff = read - _lastReadCount[disk];
var writeDiff = write - _lastWriteCount[disk];
_lastReadCount[disk] = read;
_lastWriteCount[disk] = write;
if (readDiff > 0 || writeDiff > 0)
{
_logger.LogInformation($"[{disk}] ReadDiff: {readDiff}, WriteDiff: {writeDiff}");
}
if (readDiff > 10 || writeDiff > 10)
{
// 上一次通知的是休眠或者没有通知 发现有变换通知变化
if (LatestNotifyTimes[disk].Any() == false
|| LatestNotifyTimes[disk].Last().Item2 == DiskNotifyType.Sleep)
{
LatestNotifyTimes[disk].Add((DateTime.Now, DiskNotifyType.Action));
await WxNotify.SendDiskInfoAsync(@$"
## 磁盘 <font color='info'> {disk} </font> 刚发生读写操作 🙋
> 当前累计读: <font color='warning'> {GetEasyReadNumber(read)} </font>、新增读: <font color='warning'> {GetEasyReadNumber(readDiff)} </font>
> 当前累计写: <font color='warning'> {GetEasyReadNumber(write)} </font>、新增写: <font color='warning'> {GetEasyReadNumber(writeDiff)} </font>
当前时间: <font color='comment'> {DateTime.Now:yyyy-M-d H:m:s} </font>");
}
else if (DateTime.Now - LatestNotifyTimes[disk].Last().Item1 > TimeSpan.FromHours(1))
{
LatestNotifyTimes[disk].Add((DateTime.Now, DiskNotifyType.Action));
await WxNotify.SendDiskInfoAsync(@$"
## 磁盘 <font color='info'> {disk} </font> 连续运行超一小时 👨‍💻
> 当前累计读: <font color='warning'> {GetEasyReadNumber(read)} </font>
> 当前累计写: <font color='warning'> {GetEasyReadNumber(write)} </font>
当前时间: <font color='comment'> {DateTime.Now:yyyy-M-d H:m:s} </font>");
}
_lastReadCount[disk] = read;
_lastWriteCount[disk] = write;
LastChangedTimes[disk] = DateTime.Now;
continue;
}
// 没有变化 上一次是启动通知且超过10分钟通知休眠
if (LatestNotifyTimes[disk].Any() == false
|| (LatestNotifyTimes[disk].Last().Item2 == DiskNotifyType.Action
&& DateTime.Now - LastChangedTimes[disk] > TimeSpan.FromMinutes(10)))
{
LatestNotifyTimes[disk].Add((DateTime.Now, DiskNotifyType.Sleep));
await WxNotify.SendDiskInfoAsync(@$"
## 磁盘 <font color='info'> {disk} </font> 长时间无读写变化 😪
> 上一次读写时间:{LatestNotifyTimes[disk].Last(x => x.Item2 == DiskNotifyType.Action).Item1:yyyy-M-d H:m:s}
> 当前累计读: <font color='warning'> {GetEasyReadNumber(read)} </font>
> 当前累计写: <font color='warning'> {GetEasyReadNumber(write)} </font>
当前时间: <font color='comment'> {DateTime.Now:yyyy-M-d H:m:s} </font>");
}
}
}
private string TryGetByIndex(string[] arr, int index) => index < arr.Length ? arr[index] : string.Empty;
private string GetEasyReadNumber(int number)
{
if (number < 10000)
{
return number.ToString();
}
if (number < 100000000)
{
return $"{number / 10000M:0.##} 万";
}
return $"{number / 100000000M:0.##} 亿";
}
}

View File

@@ -1,104 +0,0 @@
using System.Diagnostics;
using System.Text;
using FluentScheduler;
using InfluxDB.Client;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Writes;
using Interface.Jobs;
namespace Service.Jobs;
public class DiskMonitorRegistry : Registry, IDiskMonitorRegistry
{
public DiskMonitorRegistry()
{
Schedule(Job).ToRunNow().AndEvery(1).Hours();
}
public void Job()
{
try
{
JobExecute();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private void JobExecute()
{
// 执行 cmd 命令 获取执行结果
var command = "df -h";
var process = new Process
{
StartInfo = new()
{
FileName = "/bin/bash",
Arguments = $"-c \"{command}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
var result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
var format = FormatResult(result);
WriteToInfluxDB(format);
}
private string FormatResult(string result)
{
var lines = result.Split("\n");
var sb = new StringBuilder();
foreach (var line in lines)
{
var cols = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
if (line.Contains("/host/wd/"))
{
sb.AppendLine($"{cols[5].Substring("/host".Length)},{cols[1]},{cols[4].TrimEnd('%')},{cols[3]}");
}
}
return sb.ToString();
}
private void WriteToInfluxDB(string result)
{
var lines = result.Split("\n");
using var client = new InfluxDBClient("http://influxdb:8086", "BD4A71llb9_XbCA5mmKDbc_yTYwadPPLwyk4nAQ0l_yR_WJmw_-dMOWIs0KlS7-pZtHot_HrejY5GcOohKElmA==");
using var writeApi = client.GetWriteApi();
foreach (var line in lines)
{
Console.WriteLine(line);
var cols = line.Split(",", StringSplitOptions.RemoveEmptyEntries);
if(cols.Length != 4) continue;
var path = cols[0];
var totalSize = cols[1];
var usedPercent = cols[2];
var available = cols[3];
var point = PointData
.Measurement("disk_usage")
.Tag("path", path)
.Field("total_size", totalSize)
.Field("used_percent", double.Parse(usedPercent))
.Field("available", available)
.Timestamp(DateTime.UtcNow, WritePrecision.Ns);
writeApi.WritePoint(point, "def-bucket", "def-org");
Console.WriteLine($"[DiskMonitor] Write point: {point.ToLineProtocol()}");
}
}
}

View File

@@ -1,95 +0,0 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using Core;
using FluentScheduler;
using Interface.Jobs;
using Microsoft.Extensions.Configuration;
namespace Service.Jobs;
public class HealthyTaskRegistry : Registry, IHealthyTaskRegistry
{
private readonly IConfiguration _configuration;
public HealthyTaskRegistry(IConfiguration configuration)
{
_configuration = configuration;
Schedule(Job).ToRunNow().AndEvery(2).Minutes();
}
public async void Job()
{
try
{
await JobExecute();
}
catch (Exception e)
{
Console.WriteLine(e);
await WxNotify.SendCommonAsync($"HealthyTaskRegistry.Job() error: {e}");
}
}
private async Task JobExecute()
{
// 获取 配置 是个 KV 集合
var configs = _configuration.GetSection("HealthyTasks");
// 遍历配置
foreach (var item in configs.GetChildren())
{
// 获取配置的 containerName 和 url
var containerName = item["ContainerName"];
var url = item["Url"];
// 执行
await ExecuteItem(containerName!, url!);
}
}
private async Task ExecuteItem(string containerName, string url)
{
try
{
var client = new HttpClient();
// basic auth
string authInfo = Convert.ToBase64String(Encoding.ASCII.GetBytes("suncheng:SCsunch940622"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo);
var response = await client.GetAsync(url);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"StatusCode: {response.StatusCode}");
}
Console.WriteLine($"ContainerName: {containerName}, Url: {url}, StatusCode: {response.StatusCode}");
}
catch (Exception e)
{
Console.WriteLine(e);
await WxNotify.SendCommonAsync($"ExecuteItem {containerName} error: {e.Message}");
await ExecuteReboot(containerName);
}
}
private async Task ExecuteReboot(string containerName)
{
var command = $"docker restart {containerName}";
var (responseCode, message) = await Command.ExecAsync(command);
if (responseCode != HttpStatusCode.OK)
{
await WxNotify.SendCommonAsync($"ExecuteReboot {containerName} error: {responseCode} message: {message}");
}
else
{
await WxNotify.SendCommonAsync($"ContainerName: {containerName}");
}
}
}

View File

@@ -1,106 +0,0 @@
using System.Text;
using Core;
using FluentScheduler;
using Interface.Jobs;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
namespace Service.Jobs;
public class LogTotalNotifyJobRegistry : Registry, ILogTotalNotifyJobRegistry
{
private readonly IConfiguration _configuration;
public LogTotalNotifyJobRegistry(IConfiguration configuration)
{
_configuration = configuration;
Schedule(Job).ToRunEvery(1).Days().At(8, 30);
}
public async void Job()
{
try
{
// await JobExecute();
}
catch (Exception e)
{
Console.WriteLine(e);
await WxNotify.SendCommonAsync($"LogTotalNotifyJobRegistry.Job() error: {e}");
}
}
private async Task JobExecute()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {_configuration["Grafana:Token"]}");
var requestBody =
"""
{
"queries": [
{
"datasource": {
"type": "loki",
"uid": "edf5cwf6n6i2oe"
},
"editorMode": "builder",
"expr": "sum by(container_name) (count_over_time({compose_project=~\"dockers|immich|nasrobot|elasticsearch|webui-docker\"} [1h]))",
"queryType": "range",
"refId": "A",
"datasourceId": 2,
"intervalMs": 3600000
}
],
"from": "1708963200000",
"to": "1709049600000"
}
""";
requestBody = requestBody.Replace("1708963200000", DateTime.Today.AddDays(-2).ToUnixTimeMilliseconds().ToString());
requestBody = requestBody.Replace("1709049600000", DateTime.Today.AddDays(-1).ToUnixTimeMilliseconds().ToString());
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var response = await client.PostAsync(_configuration["Grafana:LokiUrl"], content);
var result = await response.Content.ReadAsStringAsync();
var jObject = JObject.Parse(result);
var frames = jObject["results"]?["A"]?["frames"];
if (frames == null)
{
throw new Exception("frames is null");
}
var msg = new StringBuilder();
msg.AppendLine($"## {DateTime.Today.AddDays(-1):yyyy-MM-dd}日志明细如下:");
var total = 0;
var kv = new Dictionary<string, int>();
foreach (var item in frames)
{
var name = item["schema"]?["fields"]?.LastOrDefault()?["labels"]?["container_name"]?.ToString();
if (string.IsNullOrEmpty(name))
{
continue;
}
var values = item["data"]?["values"]?.LastOrDefault()?.ToObject<int[]>();
var value = values?.Sum() ?? 0;
total += value;
kv[name] = value;
}
foreach (var (key, value) in kv.OrderByDescending(x => x.Value))
{
msg.AppendLine($"<font color='info'>**{key}**</font>: <font color='warning'>{value}</font>");
}
msg.AppendLine("");
msg.AppendLine($"> **总计**: <font color='warning'>{total}</font>");
await WxNotify.SendCommonAsync(msg.ToString());
}
}

View File

@@ -1,69 +0,0 @@
using System.Diagnostics;
using System.Net.Http.Json;
using Core;
using FluentScheduler;
using Interface.Jobs;
using Microsoft.Extensions.Configuration;
namespace Service.Jobs;
public class RSyncTaskRegistry : Registry, IRSyncTaskRegistry
{
private readonly IConfiguration _configuration;
public RSyncTaskRegistry(IConfiguration configuration)
{
_configuration = configuration;
Schedule(Job).ToRunEvery(1).Days().At(10, 0);
}
public async void Job()
{
try
{
// await JobExecute();
}
catch (Exception e)
{
Console.WriteLine(e);
await WxNotify.SendCommonAsync($"RSyncTaskRegistry.Job() error: {e}");
}
}
private async Task JobExecute()
{
var config = _configuration.GetSection("SyncTask");
foreach (var item in config.GetSection("SyncPaths").GetChildren())
{
var source = Path.Combine(config["SourceRoot"]!, item["Source"]!);
var isDeleteAfter = item["DeleteAfter"] == "true";
await ExecuteItem(source, config["TargetRoot"]!, item["Target"]!, isDeleteAfter);
}
}
private async Task ExecuteItem(string source, string remote, string destination, bool isDeleteAfter)
{
var logName = $"rclone_output_{destination.Replace("/", "_")}{DateTime.Now:yyMMddHHmm}.log";
var commands = new[]
{
$"rclone sync " +
$"{source} " +
$"{remote}:{destination} " +
$"--fast-list " +
$"--size-only " +
$"{(isDeleteAfter ? "--delete-after" : "--delete-excluded")} " +
$"> /wd/logs/{logName} 2>&1"
};
await WxNotify.SendCommonAsync($@"RSyncTaskRegistry.ExecuteItem()
`{commands[0]}`
> {DateTime.Now:yyyy-MM-dd HH:mm:ss}
");
_ = Command.ExecAsync(commands);
}
}

View File

@@ -1,61 +0,0 @@
using System.Net;
using Core;
using FluentScheduler;
using Interface.Jobs;
namespace Service.Jobs;
public class ShutdownRegistry : Registry, IShutdownRegistry
{
public ShutdownRegistry()
{
Schedule(Job).ToRunEvery(1).Days().At(23, 30);
}
public async void Job()
{
try
{
await JobExecute();
}
catch (Exception e)
{
Console.WriteLine(e);
await WxNotify.SendCommonAsync($"ShutdownRegistry.Job() error: {e}");
}
}
private async Task JobExecute()
{
var command = "shutdown 9";
var (responseCode, msg) = await Command.ExecAsync(command);
var wxMsg = $@"
# 定时关机指令执行
> 执行结果:{responseCode}
> 执行消息:{msg}
如需取消关机请点击:[取消关机](http://suncheng.online:35642/api/JobTrigger/CancelShutdown)
";
await WxNotify.SendCommonAsync(wxMsg);
if (responseCode == HttpStatusCode.OK)
{
// 每三分钟提醒一次
_ = Task.Delay(3 * 60 * 1000).ContinueWith(async _ => await WxNotify.SendCommonAsync(wxMsg));
}
}
public async Task CancelShutdown()
{
var command = "shutdown -c";
var (responseCode, _) = await Command.ExecAsync(command);
await WxNotify.SendCommonAsync($"取消关机指令执行完成 {responseCode}");
}
}

View File

@@ -1,46 +0,0 @@
using System.Net;
using Core;
using FluentScheduler;
using Interface.Jobs;
namespace Service.Jobs;
public class StartupRegistry : Registry, IStartupRegistry
{
public StartupRegistry()
{
Schedule(Job).ToRunNow();
}
public async void Job()
{
try
{
// await JobExecute();
}
catch (Exception e)
{
Console.WriteLine(e);
await WxNotify.SendCommonAsync($"StartupRegistry.Job() error: {e}");
}
}
private async Task JobExecute()
{
var commands = new[]
{
"chmod 777 /var/run/docker.sock",
};
var (responseCode, _) = await Command.ExecAsync(commands);
if (responseCode != HttpStatusCode.OK)
{
await WxNotify.SendCommonAsync($"StartupRegistry.Job() error: {responseCode}");
}
else
{
await WxNotify.SendCommonAsync("StartupRegistry.Job() success");
}
}
}

View File

@@ -12,6 +12,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FreeSql" Version="3.5.201" />
<PackageReference Include="FreeSql.Provider.Sqlite" Version="3.5.200" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,112 +1,125 @@
using Interface.Jobs; using System.Text.Json.Nodes;
using Core;
using Interface.Jobs;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace WebApi.Controllers; namespace WebApi.Controllers;
public class JobTriggerController : BaseController public class JobTriggerController : BaseController
{ {
private readonly ILogTotalNotifyJobRegistry _logTotalNotifyJobRegistry;
private readonly IDiskActionMonitorRegistry _diskActionMonitorRegistry;
private readonly IHealthyTaskRegistry _healthyTaskRegistry;
private readonly IRSyncTaskRegistry _rSyncTaskRegistry;
private readonly IStartupRegistry _startupRegistry;
private readonly IShutdownRegistry _shutdownRegistry;
private readonly IChineseNfoRegistry _chineseNfoRegistry; private readonly IChineseNfoRegistry _chineseNfoRegistry;
private readonly IDiskMonitorRegistry _diskMonitorRegistry;
private readonly ILogger<JobTriggerController> _logger;
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public JobTriggerController( public JobTriggerController(
ILogTotalNotifyJobRegistry logTotalNotifyJobRegistry,
IDiskActionMonitorRegistry diskActionMonitorRegistry,
IHealthyTaskRegistry healthyTaskRegistry,
IRSyncTaskRegistry rSyncTaskRegistry,
IStartupRegistry startupRegistry,
IShutdownRegistry shutdownRegistry,
IChineseNfoRegistry chineseNfoRegistry, IChineseNfoRegistry chineseNfoRegistry,
IDiskMonitorRegistry diskMonitorRegistry) ILogger<JobTriggerController> logger)
{ {
_logTotalNotifyJobRegistry = logTotalNotifyJobRegistry;
_diskActionMonitorRegistry = diskActionMonitorRegistry;
_healthyTaskRegistry = healthyTaskRegistry;
_rSyncTaskRegistry = rSyncTaskRegistry;
_startupRegistry = startupRegistry;
_shutdownRegistry = shutdownRegistry;
_chineseNfoRegistry = chineseNfoRegistry; _chineseNfoRegistry = chineseNfoRegistry;
_diskMonitorRegistry = diskMonitorRegistry; _logger = logger;
} }
[HttpGet] [HttpGet]
public string LogTotalNotify() public string ConvertChineseNfo(
bool ignoreLocked = true,
bool ignoreCompleted = true)
{ {
_logTotalNotifyJobRegistry.Job(); _chineseNfoRegistry.Job(ignoreLocked: ignoreLocked, ignoreCompleted: ignoreCompleted);
return "OK";
}
[HttpPost]
public async Task<string> SonarrChangedConvertChineseNfo()
{
var body = Request.Body;
using var reader = new StreamReader(body);
var text = await reader.ReadToEndAsync();
var json = JsonConvert.DeserializeObject<JObject>(text);
var eventType = json?["eventType"]?.ToString();
if (eventType != "Download")
{
return "OK";
}
_logger.LogInformation("SonarrChangedConvertChineseNfo() called");
await Task.Delay(10_000);
var seasonNumber = json?["episodes"]?[0]?["seasonNumber"]?.ToObject<int>();
var episodeNumber = json?["episodes"]?[0]?["episodeNumber"]?.ToObject<int>();
var path = json?["series"]?["path"]?.ToString();
await WxNotify.SendCommonAsync($"SonarrChangedConvertChineseNfo() path: {path}, seasonNumber: {seasonNumber}, episodeNumber: {episodeNumber}");
_logger.LogInformation("SonarrChangedConvertChineseNfo() path: {path}, seasonNumber: {seasonNumber}, episodeNumber: {episodeNumber}", path, seasonNumber, episodeNumber);
if (string.IsNullOrEmpty(path) || seasonNumber == null || episodeNumber == null)
{
return "OK";
}
await Task.Delay(10_000); // 休眠10S防止文件还未能创建
_chineseNfoRegistry.Job(path: path, seasonNumber: seasonNumber, episodeNumber: episodeNumber, ignoreLocked: true, ignoreCompleted: true);
return "OK"; return "OK";
} }
[HttpGet] [HttpGet]
public string DiskActionMonitor() public string ConvertChineseNfoByPath(
string path,
int? seasonNumber = null,
int? episodeNumber = null,
bool ignoreLocked = true,
bool ignoreCompleted = true)
{ {
_diskActionMonitorRegistry.Job(); _chineseNfoRegistry.Job(path: path, seasonNumber: seasonNumber, episodeNumber: episodeNumber, ignoreLocked: ignoreLocked, ignoreCompleted: ignoreCompleted);
return "OK"; return "OK";
} }
[HttpGet] [HttpGet]
public string HealthyTask() public string MusicTagAsync()
{ {
_healthyTaskRegistry.Job(); var musicPath = "/data/media/music";
List<string> musicType = ["mp3", "flac", "m4a", "wav", "ape", "ogg", "wma", "alac", "aac", "dsd"];
return "OK"; // 递归获取所有音乐文件
var files = Directory.GetFiles(musicPath, "*.*", SearchOption.AllDirectories)
.Where(f => musicType.Contains(Path.GetExtension(f).TrimStart('.').ToLower()))
.ToList();
foreach (var file in files)
{
_logger.LogInformation("Processing music file: {file}", file);
try
{
var fileTag = TagLib.File.Create(file);
var artist = fileTag.Tag.FirstPerformer ?? "未知艺术家";
var album = fileTag.Tag.Album ?? "未知专辑";
var title = fileTag.Tag.Title ?? Path.GetFileNameWithoutExtension(file);
var year = fileTag.Tag.Year > 0 ? fileTag.Tag.Year.ToString() : "未知年份";
var genre = fileTag.Tag.FirstGenre ?? "未知风格";
_logger.LogInformation("Tag info - Artist: {artist}, Album: {album}, Title: {title}, Year: {year}, Genre: {genre}", artist, album, title, year, genre);
} }
catch (Exception e)
[HttpGet]
public string RSyncTask()
{ {
_rSyncTaskRegistry.Job(); _logger.LogError(e, "Error processing music file: {file}", file);
return "OK";
} }
[HttpGet]
public string Startup()
{
_startupRegistry.Job();
return "OK";
} }
[HttpGet]
public string Shutdown()
{
_shutdownRegistry.Job();
return "OK";
}
[HttpGet]
public string CancelShutdown()
{
_shutdownRegistry.CancelShutdown();
return "OK";
}
[HttpGet]
public string ConvertChineseNfo(bool ignoreLocked = false, bool ignoreCompleted = false)
{
_chineseNfoRegistry.Job(ignoreLocked, ignoreCompleted);
return "OK";
}
[HttpGet]
public string DiskMonitor()
{
_diskMonitorRegistry.Job();
return "OK"; return "OK";
} }
} }

View File

@@ -7,10 +7,6 @@ namespace WebApi.Controllers;
public class NotifyController : BaseController public class NotifyController : BaseController
{ {
public NotifyController()
{
}
[HttpPost] [HttpPost]
public async Task<string> Radarr() public async Task<string> Radarr()
{ {
@@ -21,40 +17,6 @@ public class NotifyController : BaseController
Console.WriteLine(text); Console.WriteLine(text);
/*
{
"movie": {
"id": 1,
"title": "Test Title",
"year": 1970,
"releaseDate": "1970-01-01",
"folderPath": "C:\\testpath",
"tmdbId": 0,
"tags": [
"test-tag"
]
},
"remoteMovie": {
"tmdbId": 1234,
"imdbId": "5678",
"title": "Test title",
"year": 1970
},
"release": {
"quality": "Test Quality",
"qualityVersion": 1,
"releaseGroup": "Test Group",
"releaseTitle": "Test Title",
"indexer": "Test Indexer",
"size": 9999999,
"customFormatScore": 0
},
"eventType": "Test",
"instanceName": "Radarr",
"applicationUrl": ""
}
*/
var jsonObj = JsonConvert.DeserializeObject<JObject>(text); var jsonObj = JsonConvert.DeserializeObject<JObject>(text);
var notify = @$"# Radarr通知 var notify = @$"# Radarr通知
@@ -96,189 +58,6 @@ public class NotifyController : BaseController
Console.WriteLine(text); Console.WriteLine(text);
/*
{
"series": {
"id": 86,
"title": "Demon Slayer: Kimetsu no Yaiba",
"titleSlug": "demon-slayer-kimetsu-no-yaiba",
"path": "/data/anime/Demon Slayer - Kimetsu no Yaiba",
"tvdbId": 348545,
"tvMazeId": 41469,
"tmdbId": 85937,
"imdbId": "tt9335498",
"type": "anime",
"year": 2019,
"genres": [
"Action",
"Adventure",
"Animation",
"Anime",
"Drama",
"Fantasy",
"Thriller"
],
"images": [
{
"coverType": "banner",
"url": "/MediaCover/86/banner.jpg?lastWrite=638509998968281535",
"remoteUrl": "https://artworks.thetvdb.com/banners/graphical/5ccd960cc3aa0.jpg"
},
{
"coverType": "poster",
"url": "/MediaCover/86/poster.jpg?lastWrite=638509998969561521",
"remoteUrl": "https://artworks.thetvdb.com/banners/v4/series/348545/posters/60908d475f49a.jpg"
},
{
"coverType": "fanart",
"url": "/MediaCover/86/fanart.jpg?lastWrite=638509998971801497",
"remoteUrl": "https://artworks.thetvdb.com/banners/fanart/original/5c93cbb2b60b6.jpg"
},
{
"coverType": "clearlogo",
"url": "/MediaCover/86/clearlogo.png?lastWrite=638509998972921485",
"remoteUrl": "https://artworks.thetvdb.com/banners/v4/series/348545/clearlogo/611c7fa8222d6.png"
}
],
"tags": [
"anime"
]
},
"episodes": [
{
"id": 2716,
"episodeNumber": 2,
"seasonNumber": 5,
"title": "Water Hashira Giyu Tomioka's Pain",
"overview": "Kagaya's Kasugai Crow suddenly appears in front of Tamayo and invites her to the Demon Slayer headquarters — even though she is a demon. Meanwhile, Tanjiro, who is recovering at the Butterfly Mansion, receives a letter from Kagaya...",
"airDate": "2024-05-19",
"airDateUtc": "2024-05-19T14:15:00Z",
"seriesId": 86,
"tvdbId": 10445764
}
],
"release": {
"quality": "WEBDL-1080p",
"qualityVersion": 1,
"releaseGroup": "ToonsHub",
"releaseTitle": "[ToonsHub] Demon Slayer Kimetsu no Yaiba S05E02 1080p CR WEB-DL AAC2.0 x264 (Multi-Subs)",
"indexer": "Knaben ",
"size": 1395864320,
"customFormatScore": 0,
"customFormats": []
},
"downloadClient": "aria2",
"downloadClientType": "Aria2",
"downloadId": "915718C3A8A5B15BD3C32A2B05885953D96AFADD",
"customFormatInfo": {
"customFormats": [],
"customFormatScore": 0
},
"eventType": "Grab",
"instanceName": "Sonarr",
"applicationUrl": ""
}
--------------------------
{
"series": {
"id": 86,
"title": "Demon Slayer: Kimetsu no Yaiba",
"titleSlug": "demon-slayer-kimetsu-no-yaiba",
"path": "/data/anime/Demon Slayer - Kimetsu no Yaiba",
"tvdbId": 348545,
"tvMazeId": 41469,
"tmdbId": 85937,
"imdbId": "tt9335498",
"type": "anime",
"year": 2019,
"genres": [
"Action",
"Adventure",
"Animation",
"Anime",
"Drama",
"Fantasy",
"Thriller"
],
"images": [
{
"coverType": "banner",
"url": "/MediaCover/86/banner.jpg?lastWrite=638509998968281535",
"remoteUrl": "https://artworks.thetvdb.com/banners/graphical/5ccd960cc3aa0.jpg"
},
{
"coverType": "poster",
"url": "/MediaCover/86/poster.jpg?lastWrite=638509998969561521",
"remoteUrl": "https://artworks.thetvdb.com/banners/v4/series/348545/posters/60908d475f49a.jpg"
},
{
"coverType": "fanart",
"url": "/MediaCover/86/fanart.jpg?lastWrite=638509998971801497",
"remoteUrl": "https://artworks.thetvdb.com/banners/fanart/original/5c93cbb2b60b6.jpg"
},
{
"coverType": "clearlogo",
"url": "/MediaCover/86/clearlogo.png?lastWrite=638509998972921485",
"remoteUrl": "https://artworks.thetvdb.com/banners/v4/series/348545/clearlogo/611c7fa8222d6.png"
}
],
"tags": [
"anime"
]
},
"episodes": [
{
"id": 2716,
"episodeNumber": 2,
"seasonNumber": 5,
"title": "Water Hashira Giyu Tomioka's Pain",
"overview": "Kagaya's Kasugai Crow suddenly appears in front of Tamayo and invites her to the Demon Slayer headquarters — even though she is a demon. Meanwhile, Tanjiro, who is recovering at the Butterfly Mansion, receives a letter from Kagaya...",
"airDate": "2024-05-19",
"airDateUtc": "2024-05-19T14:15:00Z",
"seriesId": 86,
"tvdbId": 10445764
}
],
"downloadInfo": {
"quality": "WEBDL-1080p",
"qualityVersion": 1,
"title": "Demon.Slayer.Kimetsu.no.Yaiba.S57E02.Water.Hashira.Giyu.Tomiokas.Pain.1080p.CR.WEB-DL.JPN.AAC2.0.H.264.MSubs-ToonsHub.mkv",
"size": 1446462408
},
"downloadClient": "aria2",
"downloadClientType": "Aria2",
"downloadId": "915718C3A8A5B15BD3C32A2B05885953D96AFADD",
"downloadStatus": "Warning",
"downloadStatusMessages": [
{
"title": "One or more episodes expected in this release were not imported or missing from the release",
"messages": []
},
{
"title": "Demon.Slayer.Kimetsu.no.Yaiba.S57E02.Water.Hashira.Giyu.Tomiokas.Pain.1080p.CR.WEB-DL.JPN.AAC2.0.H.264.MSubs-ToonsHub.mkv",
"messages": [
"Invalid season or episode"
]
}
],
"customFormatInfo": {
"customFormats": [],
"customFormatScore": 0
},
"release": {
"releaseTitle": "[ToonsHub] Demon Slayer Kimetsu no Yaiba S05E02 1080p CR WEB-DL AAC2.0 x264 (Multi-Subs)",
"indexer": "Knaben ",
"size": 1395864320
},
"eventType": "ManualInteractionRequired",
"instanceName": "Sonarr",
"applicationUrl": ""
}
*/
var jsonObj = JsonConvert.DeserializeObject<JObject>(text); var jsonObj = JsonConvert.DeserializeObject<JObject>(text);
var notify = @$"# Sonarr通知 var notify = @$"# Sonarr通知
@@ -302,7 +81,12 @@ public class NotifyController : BaseController
if (jsonObj["release"] != null) if (jsonObj["release"] != null)
{ {
var gb = (jsonObj["release"]!.ToObject<decimal>() / 1024M / 1024M / 1024M).ToString("0.##"); var gb = jsonObj["release"]!["size"]!.ToString();
gb = decimal.TryParse(gb, out var size)
? (size / 1024M / 1024M / 1024M).ToString("0.##")
: "0";
notify += @$" notify += @$"
索引器:<font color='info'> {jsonObj["release"]!["indexer"]} </font> 索引器:<font color='info'> {jsonObj["release"]!["indexer"]} </font>
<font color='info'> {jsonObj["release"]!["releaseGroup"]}({jsonObj["release"]!["quality"] ?? jsonObj["downloadInfo"]!["quality"]}) </font> <font color='info'> {jsonObj["release"]!["releaseGroup"]}({jsonObj["release"]!["quality"] ?? jsonObj["downloadInfo"]!["quality"]}) </font>

View File

@@ -1,34 +0,0 @@
using Core;
using Microsoft.AspNetCore.Mvc;
namespace WebApi.Controllers;
public class XiaoController : BaseController
{
private readonly ILogger<XiaoController> _logger;
public XiaoController(ILogger<XiaoController> logger)
{
_logger = logger;
}
[HttpGet]
public async Task<string> GotoHomeGame()
{
await WxNotify.SendCommonAsync("接收到启动游戏机指令");
var commands = new[]
{
"grub-reboot 2",
"reboot"
};
_ = Task.Run(async () =>
{
await Task.Delay(3000);
_ = await Command.ExecAsync(commands);
});
return "OK";
}
}

View File

@@ -36,18 +36,18 @@ app.MapControllers();
// JobManager.Initialize((DiskActionMonitorRegistry)diskActionMonitorRegistry); // JobManager.Initialize((DiskActionMonitorRegistry)diskActionMonitorRegistry);
// var syncTaskRegistry = app.Services.GetRequiredService<IRSyncTaskRegistry>(); // var syncTaskRegistry = app.Services.GetRequiredService<IRSyncTaskRegistry>();
// JobManager.Initialize((RSyncTaskRegistry)syncTaskRegistry); // JobManager.Initialize((RSyncTaskRegistry)syncTaskRegistry);
var healthyTaskRegistry = app.Services.GetRequiredService<IHealthyTaskRegistry>(); // var healthyTaskRegistry = app.Services.GetRequiredService<IHealthyTaskRegistry>();
JobManager.Initialize((HealthyTaskRegistry)healthyTaskRegistry); // JobManager.Initialize((HealthyTaskRegistry)healthyTaskRegistry);
// var rsyncTaskRegistry = app.Services.GetRequiredService<IRSyncTaskRegistry>(); // var rsyncTaskRegistry = app.Services.GetRequiredService<IRSyncTaskRegistry>();
// JobManager.Initialize((RSyncTaskRegistry)rsyncTaskRegistry); // JobManager.Initialize((RSyncTaskRegistry)rsyncTaskRegistry);
// var startupRegistry = app.Services.GetRequiredService<IStartupRegistry>(); // var startupRegistry = app.Services.GetRequiredService<IStartupRegistry>();
// JobManager.Initialize((StartupRegistry)startupRegistry); // JobManager.Initialize((StartupRegistry)startupRegistry);
// var shutdownRegistry = app.Services.GetRequiredService<IShutdownRegistry>(); // var shutdownRegistry = app.Services.GetRequiredService<IShutdownRegistry>();
// JobManager.Initialize((ShutdownRegistry)shutdownRegistry); // JobManager.Initialize((ShutdownRegistry)shutdownRegistry);
var chineseNfoRegistry = app.Services.GetRequiredService<IChineseNfoRegistry>(); // var chineseNfoRegistry = app.Services.GetRequiredService<IChineseNfoRegistry>();
JobManager.Initialize((ChineseNfoRegistry)chineseNfoRegistry); // JobManager.Initialize((ChineseNfoRegistry)chineseNfoRegistry);
var diskMonitorRegistry = app.Services.GetRequiredService<IDiskMonitorRegistry>(); // var diskMonitorRegistry = app.Services.GetRequiredService<IDiskMonitorRegistry>();
JobManager.Initialize((DiskMonitorRegistry)diskMonitorRegistry); // JobManager.Initialize((DiskMonitorRegistry)diskMonitorRegistry);
#endif #endif
app.Run(); app.Run();

View File

@@ -11,6 +11,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="taglib-sharp-netstandard2.0" Version="2.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -31,6 +31,7 @@
], ],
"ChineseNfo": { "ChineseNfo": {
"TvFolder": "D:\\codes\\others\\ConvertChineseNfo", "TvFolder": "D:\\codes\\others\\ConvertChineseNfo",
"HttpProxy": "http://suncheng.online:47890" "HttpProxy": "",
"ConnectionString": "Data Source=D:\\codes\\others\\ConvertChineseNfo\\chinesenfo.db;"
} }
} }

View File

@@ -60,6 +60,7 @@
], ],
"ChineseNfo": { "ChineseNfo": {
"TvFolder": "/data/media", "TvFolder": "/data/media",
"HttpProxy": "" "HttpProxy": "",
"ConnectionString": "Data Source=/app/data/chinesenfo.db;"
} }
} }