mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-14 00:10:43 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
579c35fadc | ||
|
|
72506d6b5f | ||
|
|
f3a6d665cf | ||
|
|
41201653f1 | ||
|
|
39cac1bacb | ||
|
|
55e338f35c | ||
|
|
f7fe5d68e7 | ||
|
|
6fc0915117 | ||
|
|
000618ac5e | ||
|
|
66f39ea9e2 | ||
|
|
ef5c6e4644 | ||
|
|
9fe3863c31 | ||
|
|
7679bbab38 | ||
|
|
365f3de136 | ||
|
|
dbc965c6fe | ||
|
|
75ccf228cd | ||
|
|
e2a6238ab9 | ||
|
|
98e53b38db | ||
|
|
846bf0345a | ||
|
|
95ddc95c79 | ||
|
|
956105c16e | ||
|
|
3b9ee5eb96 |
39
README.md
39
README.md
@ -58,7 +58,7 @@
|
|||||||
- 媒体库整合
|
- 媒体库整合
|
||||||
- [x] 根据任务名搜索 Emby 媒体库
|
- [x] 根据任务名搜索 Emby 媒体库
|
||||||
- [x] 追更或整理后自动刷新 Emby 媒体库
|
- [x] 追更或整理后自动刷新 Emby 媒体库
|
||||||
- [x] 媒体库模块化,用户可很方便地[开发自己的媒体库hook模块](./plugins)
|
- [x] 插件模块化,允许自行开发和挂载[插件](./plugins)
|
||||||
|
|
||||||
- 其它
|
- 其它
|
||||||
- [x] 每日签到领空间 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/使用技巧集锦#每日签到领空间)</sup>
|
- [x] 每日签到领空间 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/使用技巧集锦#每日签到领空间)</sup>
|
||||||
@ -69,7 +69,7 @@
|
|||||||
|
|
||||||
### Docker 部署
|
### Docker 部署
|
||||||
|
|
||||||
Docker 部署提供 WebUI 管理配置,图形化配置已能满足绝大多数需求。部署命令:
|
Docker 部署提供 WebUI 进行管理配置,部署命令:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run -d \
|
docker run -d \
|
||||||
@ -113,6 +113,7 @@ services:
|
|||||||
| `WEBUI_PASSWORD` | `admin123` | 管理密码 |
|
| `WEBUI_PASSWORD` | `admin123` | 管理密码 |
|
||||||
| `PORT` | `5005` | 管理后台端口 |
|
| `PORT` | `5005` | 管理后台端口 |
|
||||||
| `PLUGIN_FLAGS` | | 插件标志,如 `-emby,-aria2` 禁用某些插件 |
|
| `PLUGIN_FLAGS` | | 插件标志,如 `-emby,-aria2` 禁用某些插件 |
|
||||||
|
| `TASK_TIMEOUT` | `1800` | 任务执行超时时间(秒),超时则任务结束 |
|
||||||
|
|
||||||
#### 一键更新
|
#### 一键更新
|
||||||
|
|
||||||
@ -162,6 +163,40 @@ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtow
|
|||||||
|
|
||||||
请参考 Wiki :[使用技巧集锦](https://github.com/Cp0204/quark-auto-save/wiki/使用技巧集锦)
|
请参考 Wiki :[使用技巧集锦](https://github.com/Cp0204/quark-auto-save/wiki/使用技巧集锦)
|
||||||
|
|
||||||
|
## 生态项目
|
||||||
|
|
||||||
|
以下展示 QAS 生态项目,包括官方项目和第三方项目。
|
||||||
|
|
||||||
|
### 官方项目
|
||||||
|
|
||||||
|
* [QAS一键推送助手](https://greasyfork.org/zh-CN/scripts/533201-qas一键推送助手)
|
||||||
|
|
||||||
|
油猴脚本,在夸克网盘分享页面添加推送到 QAS 的按钮
|
||||||
|
|
||||||
|
* [SmartStrm](https://github.com/Cp0204/SmartStrm)
|
||||||
|
|
||||||
|
STRM 文件生成工具,用于转存后处理,媒体免下载入库播放。
|
||||||
|
|
||||||
|
### 第三方开源项目
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
>
|
||||||
|
> 以下第三方开源项目均由社区开发并保持开源,与 QAS 作者无直接关联。在部署到生产环境前,请自行评估相关风险。
|
||||||
|
>
|
||||||
|
> 如果您有新的项目没有在此列出,可以通过 Issues 提交。
|
||||||
|
|
||||||
|
* [nonebot-plugin-quark-autosave](https://github.com/fllesser/nonebot-plugin-quark-autosave)
|
||||||
|
|
||||||
|
QAS Telegram 机器人,快速管理自动转存任务
|
||||||
|
|
||||||
|
* [Astrbot_plugin_quarksave](https://github.com/lm379/astrbot_plugin_quarksave)
|
||||||
|
|
||||||
|
AstrBot 插件,调用 quark_auto_save 实现自动转存资源到夸克网盘
|
||||||
|
|
||||||
|
* [Telegram 媒体资源管理机器人](https://github.com/2beetle/tgbot)
|
||||||
|
|
||||||
|
一个功能丰富的 Telegram 机器人,专注于媒体资源管理、Emby 集成、自动下载和夸克网盘资源管理。
|
||||||
|
|
||||||
## 打赏
|
## 打赏
|
||||||
|
|
||||||
如果这个项目让你受益,你可以无偿赠与我1块钱,让我知道开源有价值。谢谢!
|
如果这个项目让你受益,你可以无偿赠与我1块钱,让我知道开源有价值。谢谢!
|
||||||
|
|||||||
56
app/run.py
56
app/run.py
@ -23,6 +23,7 @@ import subprocess
|
|||||||
import requests
|
import requests
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
import base64
|
import base64
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@ -32,6 +33,19 @@ parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|||||||
sys.path.insert(0, parent_dir)
|
sys.path.insert(0, parent_dir)
|
||||||
from quark_auto_save import Quark, Config, MagicRename
|
from quark_auto_save import Quark, Config, MagicRename
|
||||||
|
|
||||||
|
print(
|
||||||
|
r"""
|
||||||
|
____ ___ _____
|
||||||
|
/ __ \ / | / ___/
|
||||||
|
/ / / / / /| | \__ \
|
||||||
|
/ /_/ / / ___ |___/ /
|
||||||
|
\___\_\/_/ |_/____/
|
||||||
|
|
||||||
|
-- Quark-Auto-Save --
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
def get_app_ver():
|
def get_app_ver():
|
||||||
"""获取应用版本"""
|
"""获取应用版本"""
|
||||||
@ -59,6 +73,7 @@ PLUGIN_FLAGS = os.environ.get("PLUGIN_FLAGS", "")
|
|||||||
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
|
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
|
||||||
HOST = os.environ.get("HOST", "0.0.0.0")
|
HOST = os.environ.get("HOST", "0.0.0.0")
|
||||||
PORT = os.environ.get("PORT", 5005)
|
PORT = os.environ.get("PORT", 5005)
|
||||||
|
TASK_TIMEOUT = int(os.environ.get("TASK_TIMEOUT", 1800))
|
||||||
|
|
||||||
config_data = {}
|
config_data = {}
|
||||||
task_plugins_config_default = {}
|
task_plugins_config_default = {}
|
||||||
@ -82,6 +97,8 @@ logging.basicConfig(
|
|||||||
# 过滤werkzeug日志输出
|
# 过滤werkzeug日志输出
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
||||||
|
logging.getLogger("apscheduler").setLevel(logging.ERROR)
|
||||||
|
sys.modules["flask.cli"].show_server_banner = lambda *x: None
|
||||||
|
|
||||||
|
|
||||||
def gen_md5(string):
|
def gen_md5(string):
|
||||||
@ -470,7 +487,36 @@ def add_task():
|
|||||||
# 定时任务执行的函数
|
# 定时任务执行的函数
|
||||||
def run_python(args):
|
def run_python(args):
|
||||||
logging.info(f">>> 定时运行任务")
|
logging.info(f">>> 定时运行任务")
|
||||||
os.system(f"{PYTHON_PATH} {args}")
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
f"{PYTHON_PATH} {args}",
|
||||||
|
shell=True,
|
||||||
|
timeout=TASK_TIMEOUT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
)
|
||||||
|
# 输出执行日志
|
||||||
|
if result.stdout:
|
||||||
|
for line in result.stdout.strip().split("\n"):
|
||||||
|
if line.strip():
|
||||||
|
logging.info(line)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
logging.info(f">>> 任务执行成功")
|
||||||
|
else:
|
||||||
|
logging.error(f">>> 任务执行失败,返回码: {result.returncode}")
|
||||||
|
if result.stderr:
|
||||||
|
logging.error(f"错误信息: {result.stderr[:500]}")
|
||||||
|
except subprocess.TimeoutExpired as e:
|
||||||
|
logging.error(f">>> 任务执行超时(>{TASK_TIMEOUT}s),强制终止")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f">>> 任务执行异常: {str(e)}")
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
finally:
|
||||||
|
# 确保函数能够正常返回
|
||||||
|
logging.debug(f">>> run_python 函数执行完成")
|
||||||
|
|
||||||
|
|
||||||
# 重新加载任务
|
# 重新加载任务
|
||||||
@ -486,6 +532,10 @@ def reload_tasks():
|
|||||||
trigger=trigger,
|
trigger=trigger,
|
||||||
args=[f"{SCRIPT_PATH} {CONFIG_PATH}"],
|
args=[f"{SCRIPT_PATH} {CONFIG_PATH}"],
|
||||||
id=SCRIPT_PATH,
|
id=SCRIPT_PATH,
|
||||||
|
max_instances=1, # 最多允许1个实例运行
|
||||||
|
coalesce=True, # 合并错过的任务,避免堆积
|
||||||
|
misfire_grace_time=300, # 错过任务的宽限期(秒),超过则跳过
|
||||||
|
replace_existing=True, # 替换已存在的同ID任务
|
||||||
)
|
)
|
||||||
if scheduler.state == 0:
|
if scheduler.state == 0:
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
@ -504,7 +554,7 @@ def reload_tasks():
|
|||||||
|
|
||||||
def init():
|
def init():
|
||||||
global config_data, task_plugins_config_default
|
global config_data, task_plugins_config_default
|
||||||
logging.info(f">>> 初始化配置")
|
logging.info(">>> 初始化配置")
|
||||||
# 检查配置文件是否存在
|
# 检查配置文件是否存在
|
||||||
if not os.path.exists(CONFIG_PATH):
|
if not os.path.exists(CONFIG_PATH):
|
||||||
if not os.path.exists(os.path.dirname(CONFIG_PATH)):
|
if not os.path.exists(os.path.dirname(CONFIG_PATH)):
|
||||||
@ -542,6 +592,8 @@ def init():
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
init()
|
init()
|
||||||
reload_tasks()
|
reload_tasks()
|
||||||
|
logging.info(">>> 启动Web服务")
|
||||||
|
logging.info(f"运行在: http://{HOST}:{PORT}")
|
||||||
app.run(
|
app.run(
|
||||||
debug=DEBUG,
|
debug=DEBUG,
|
||||||
host=HOST,
|
host=HOST,
|
||||||
|
|||||||
@ -45,7 +45,7 @@ body {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.jsoneditor-tree > tbody > tr.jsoneditor-expandable:first-child {
|
table.jsoneditor-tree>tbody>tr.jsoneditor-expandable:first-child {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,3 +196,48 @@ table.jsoneditor-tree > tbody > tr.jsoneditor-expandable:first-child {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toast */
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-width: 0 0 0 5px !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
animation: slideIn 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.success {
|
||||||
|
border-left-color: #28a745 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.error {
|
||||||
|
border-left-color: #dc3545 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.warning {
|
||||||
|
border-left-color: #ffc107 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.info {
|
||||||
|
border-left-color: #17a2b8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
// @name QAS一键推送助手
|
// @name QAS一键推送助手
|
||||||
// @namespace https://github.com/Cp0204/quark-auto-save
|
// @namespace https://github.com/Cp0204/quark-auto-save
|
||||||
// @license AGPL
|
// @license AGPL
|
||||||
// @version 0.5
|
// @version 0.6
|
||||||
// @description 在夸克网盘分享页面添加推送到 QAS 的按钮
|
// @description 在夸克网盘分享页面添加推送到 QAS 的按钮
|
||||||
// @icon https://pan.quark.cn/favicon.ico
|
// @icon https://pan.quark.cn/favicon.ico
|
||||||
// @author Cp0204
|
// @author Cp0204
|
||||||
@ -76,16 +76,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForElement('.DetailLayout--client-download--FpyCkdW.ant-dropdown-trigger', (clientDownloadButton) => {
|
waitForElement('.pc-member-entrance', (PcMemberButton) => {
|
||||||
const qasSettingButton = document.createElement('div');
|
const qasSettingButton = document.createElement('div');
|
||||||
qasSettingButton.className = 'DetailLayout--client-download--FpyCkdW ant-dropdown-trigger';
|
qasSettingButton.className = 'pc-member-entrance';
|
||||||
qasSettingButton.innerHTML = 'QAS设置';
|
qasSettingButton.innerHTML = 'QAS设置';
|
||||||
|
|
||||||
qasSettingButton.addEventListener('click', () => {
|
qasSettingButton.addEventListener('click', () => {
|
||||||
showQASSettingDialog();
|
showQASSettingDialog();
|
||||||
});
|
});
|
||||||
|
|
||||||
clientDownloadButton.parentNode.insertBefore(qasSettingButton, clientDownloadButton.nextSibling);
|
PcMemberButton.parentNode.insertBefore(qasSettingButton, PcMemberButton.nextSibling);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +155,63 @@
|
|||||||
},
|
},
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
onload: function (response) {
|
onload: function (response) {
|
||||||
|
// 检查 HTTP 状态码
|
||||||
|
if (response.status === 401) {
|
||||||
|
Swal.fire({
|
||||||
|
title: '认证失败',
|
||||||
|
text: 'Token 无效或已过期,请重新配置 QAS Token',
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonText: '重新配置',
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
showQASSettingDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 503) {
|
||||||
|
Swal.fire({
|
||||||
|
title: '服务器不可用',
|
||||||
|
html: `服务器暂时无法处理请求 (503)<br><br>
|
||||||
|
<small>可能原因:<br>
|
||||||
|
• QAS 服务未运行<br>
|
||||||
|
• 服务器过载<br>
|
||||||
|
• 网络连接问题</small>`,
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonText: '重新配置',
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
showQASSettingDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查响应内容类型
|
||||||
|
const contentType = response.responseHeaders.match(/content-type:\s*([^;\s]+)/i);
|
||||||
|
if (contentType && !contentType[1].includes('application/json')) {
|
||||||
|
Swal.fire({
|
||||||
|
title: '认证失败',
|
||||||
|
html: `服务器返回了非 JSON 响应,可能是 Token 错误<br><br>
|
||||||
|
<small>响应类型: ${contentType[1]}</small><br>
|
||||||
|
<small>响应状态: ${response.status}</small>`,
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonText: '重新配置',
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
showQASSettingDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const jsonResponse = JSON.parse(response.responseText);
|
const jsonResponse = JSON.parse(response.responseText);
|
||||||
if (jsonResponse.success) {
|
if (jsonResponse.success) {
|
||||||
@ -177,16 +234,34 @@
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: '解析响应失败',
|
title: '解析响应失败',
|
||||||
text: `无法解析 JSON 响应: ${response.responseText}`,
|
html: `<small>
|
||||||
icon: 'error'
|
响应状态: ${response.status}<br>
|
||||||
|
响应内容: ${response.responseText.substring(0, 200)}...<br><br>
|
||||||
|
错误详情: ${e.message}
|
||||||
|
</small>`,
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonText: '重新配置',
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
showQASSettingDialog();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onerror: function (error) {
|
onerror: function (error) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: '任务创建失败',
|
title: '网络请求失败',
|
||||||
text: error,
|
text: '无法连接到 QAS 服务器,请检查网络连接和服务器地址',
|
||||||
icon: 'error'
|
icon: 'error',
|
||||||
|
confirmButtonText: '重新配置',
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
showQASSettingDialog();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -310,9 +310,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-warning" v-if="task.shareurl_ban" :title="task.shareurl_ban" disabled><i class="bi bi-exclamation-triangle-fill"></i></button>
|
<button type="button" class="btn btn-outline-primary btn-sm" @click="copyTaskToClipboard(index)" title="复制任务参数到粘贴板"><i class=" bi bi-clipboard-check-fill"></i></button>
|
||||||
<button type="button" class="btn btn-outline-primary" @click="runScriptNow(index)" title="运行此任务" v-else><i class="bi bi-play-fill"></i></button>
|
<button class="btn btn-warning btn-sm" v-if="task.shareurl_ban" :title="task.shareurl_ban" disabled><i class="bi bi-exclamation-triangle-fill"></i></button>
|
||||||
<button type="button" class="btn btn-outline-danger" @click="removeTask(index)" title="删除此任务"><i class="bi bi-trash3-fill"></i></button>
|
<button type="button" class="btn btn-outline-primary btn-sm" @click="runScriptNow(index)" title="运行此任务" v-else><i class="bi bi-play-fill"></i></button>
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm" @click="removeTask(index)" title="删除此任务"><i class="bi bi-trash3-fill"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse ml-3" :id="'collapse_'+index">
|
<div class="collapse ml-3" :id="'collapse_'+index">
|
||||||
@ -379,7 +380,7 @@
|
|||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=true;fileSelect.switchShare=false;fileSelect.previewRegex=true;fileSelect.sortBy='file_name';fileSelect.sortOrder='asc';showShareSelect(index)" title="预览正则处理效果">正则处理</button>
|
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=true;fileSelect.switchShare=false;fileSelect.previewRegex=true;fileSelect.sortBy='file_name';fileSelect.sortOrder='asc';showShareSelect(index)" title="预览正则处理效果">正则处理</button>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="pattern[]" class="form-control" v-model="task.pattern" placeholder="匹配表达式" list="magicRegex">
|
<input type="text" name="pattern[]" class="form-control" v-model="task.pattern" placeholder="匹配表达式" list="magicRegex" @dblclick="inputRawMagicRegex(task)" title="双击可将魔法匹配释放为填入原始正则表达式">
|
||||||
<input type="text" name="replace[]" class="form-control" v-model="task.replace" placeholder="替换表达式">
|
<input type="text" name="replace[]" class="form-control" v-model="task.replace" placeholder="替换表达式">
|
||||||
<div class="input-group-append" title="保存时只比较文件名的部分,01.mp4 和 01.mkv 视同为同一文件,不重复转存">
|
<div class="input-group-append" title="保存时只比较文件名的部分,01.mp4 和 01.mkv 视同为同一文件,不重复转存">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
@ -446,7 +447,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
<div class="col-sm-12 text-center">
|
<div class="col-sm-12 text-center">
|
||||||
<button type="button" class="btn btn-primary" @click="addTask()"><i class="bi bi-plus"></i> 增加任务</button>
|
<div class="btn-group" role="group" aria-label="任务操作">
|
||||||
|
<button type="button" class="btn btn-primary" @click="addTask()"><i class="bi bi-plus"></i> 增加任务</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click="addTaskForClipboard()" title="从粘贴板导入"><i class="bi bi-clipboard-plus"></i></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -592,6 +596,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast 提示 -->
|
||||||
|
<div class="toast-container">
|
||||||
|
<div v-for="toast in toasts" :key="toast.id" class="toast show shadow-sm" :class="toast.type" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
|
<div class="toast-body d-flex align-items-center">
|
||||||
|
<i class="bi mr-2" :class="getToastIcon(toast.type)"></i>
|
||||||
|
<span>{{ toast.message }}</span>
|
||||||
|
<button type="button" class="ml-auto close" @click="removeToast(toast.id)" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -624,6 +641,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
toasts: [],
|
||||||
newTask: {
|
newTask: {
|
||||||
taskname: "",
|
taskname: "",
|
||||||
shareurl: "",
|
shareurl: "",
|
||||||
@ -799,8 +817,10 @@
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
this.configModified = false;
|
this.configModified = false;
|
||||||
|
this.showToast(response.data.message, 'success');
|
||||||
|
} else {
|
||||||
|
this.showToast(response.data.message, 'error');
|
||||||
}
|
}
|
||||||
alert(response.data.message);
|
|
||||||
console.log('Config saved result:', response.data);
|
console.log('Config saved result:', response.data);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@ -1063,7 +1083,7 @@
|
|||||||
updateMagicRegexKey(oldKey, newKey) {
|
updateMagicRegexKey(oldKey, newKey) {
|
||||||
if (oldKey !== newKey) {
|
if (oldKey !== newKey) {
|
||||||
if (this.formData.magic_regex[newKey]) {
|
if (this.formData.magic_regex[newKey]) {
|
||||||
alert(`魔法名 [${newKey}] 已存在,请使用其他名称`);
|
this.showToast(`魔法名 [${newKey}] 已存在,请使用其他名称`, 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$set(this.formData.magic_regex, newKey, this.formData.magic_regex[oldKey]);
|
this.$set(this.formData.magic_regex, newKey, this.formData.magic_regex[oldKey]);
|
||||||
@ -1083,7 +1103,7 @@
|
|||||||
if (response.data.code == 0) {
|
if (response.data.code == 0) {
|
||||||
this.fileSelect.fileList = this.fileSelect.fileList.filter(item => item.fid != fid);
|
this.fileSelect.fileList = this.fileSelect.fileList.filter(item => item.fid != fid);
|
||||||
} else {
|
} else {
|
||||||
alert('删除失败:' + response.data.message);
|
this.showToast('删除失败:' + response.data.message, 'error');
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('Error /delete_file:', error);
|
console.error('Error /delete_file:', error);
|
||||||
@ -1166,9 +1186,9 @@
|
|||||||
currentIndex = this.smart_param.taskSuggestions.data.indexOf(this.fileSelect.share);
|
currentIndex = this.smart_param.taskSuggestions.data.indexOf(this.fileSelect.share);
|
||||||
nextIndex = currentIndex + index;
|
nextIndex = currentIndex + index;
|
||||||
if (nextIndex < 0) {
|
if (nextIndex < 0) {
|
||||||
alert("没有上一个啦");
|
this.showToast("没有上一个啦", "info");
|
||||||
} else if (nextIndex >= this.smart_param.taskSuggestions.data.length) {
|
} else if (nextIndex >= this.smart_param.taskSuggestions.data.length) {
|
||||||
alert("没有下一个啦");
|
this.showToast("没有下一个啦", "info");
|
||||||
} else {
|
} else {
|
||||||
this.fileSelect.error = "";
|
this.fileSelect.error = "";
|
||||||
this.fileSelect.stoken = "";
|
this.fileSelect.stoken = "";
|
||||||
@ -1245,7 +1265,86 @@
|
|||||||
if (valA > valB) return this.fileSelect.sortOrder === "asc" ? 1 : -1;
|
if (valA > valB) return this.fileSelect.sortOrder === "asc" ? 1 : -1;
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
inputRawMagicRegex(task) {
|
||||||
|
const item = this.formData.magic_regex[task.pattern];
|
||||||
|
if (item) {
|
||||||
|
task.pattern = item.pattern;
|
||||||
|
task.replace = item.replace;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
copyText(text, callback = () => { }) {
|
||||||
|
if (!text) {
|
||||||
|
console.error('No text to copy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
} else {
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.position = 'fixed';
|
||||||
|
textarea.style.top = '0';
|
||||||
|
textarea.style.left = '0';
|
||||||
|
textarea.style.opacity = '0';
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
textarea.setSelectionRange(0, 99999);
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
},
|
||||||
|
copyTaskToClipboard(index) {
|
||||||
|
const task = { ...this.formData.tasklist[index] };
|
||||||
|
delete task.addition;
|
||||||
|
const _this = this;
|
||||||
|
this.copyText(JSON.stringify(task), function () {
|
||||||
|
_this.showToast("任务参数已复制到剪贴板", "success");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async addTaskForClipboard() {
|
||||||
|
text = null
|
||||||
|
try {
|
||||||
|
text = await navigator.clipboard.readText();
|
||||||
|
} catch (error) {
|
||||||
|
text = prompt("当前环境不支持自动读取粘贴板,请手动粘贴任务参数", "");
|
||||||
|
}
|
||||||
|
if (text) {
|
||||||
|
try {
|
||||||
|
let task = JSON.parse(text);
|
||||||
|
task = { ...this.newTask, ...task };
|
||||||
|
this.formData.tasklist.push(task);
|
||||||
|
this.showToast("剪贴板参数已成功导入任务", "success");
|
||||||
|
// 滚到最下
|
||||||
|
setTimeout(() => {
|
||||||
|
$('#collapse_' + (this.formData.tasklist.length - 1)).collapse('show').on('shown.bs.collapse', () => {
|
||||||
|
this.scrollToX();
|
||||||
|
});
|
||||||
|
}, 1);
|
||||||
|
} catch (error) {
|
||||||
|
this.showToast("解析剪贴板内容失败", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showToast(message, type = 'info', duration = 3000) {
|
||||||
|
const id = Date.now();
|
||||||
|
this.toasts.push({ id, message, type });
|
||||||
|
setTimeout(() => {
|
||||||
|
this.removeToast(id);
|
||||||
|
}, duration);
|
||||||
|
},
|
||||||
|
removeToast(id) {
|
||||||
|
this.toasts = this.toasts.filter(t => t.id !== id);
|
||||||
|
},
|
||||||
|
getToastIcon(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'success': return 'bi-check-circle-fill text-success';
|
||||||
|
case 'error': return 'bi-exclamation-circle-fill text-danger';
|
||||||
|
case 'warning': return 'bi-exclamation-triangle-fill text-warning';
|
||||||
|
default: return 'bi-info-circle-fill text-info';
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
[
|
[
|
||||||
"alist",
|
|
||||||
"smartstrm",
|
"smartstrm",
|
||||||
|
"fnv_refresh_v2",
|
||||||
|
"alist",
|
||||||
"alist_strm",
|
"alist_strm",
|
||||||
"alist_strm_gen",
|
"alist_strm_gen",
|
||||||
"alist_sync",
|
"alist_sync",
|
||||||
"aria2",
|
"aria2",
|
||||||
"emby",
|
"emby",
|
||||||
"plex"
|
"plex",
|
||||||
|
"fnv"
|
||||||
]
|
]
|
||||||
@ -25,6 +25,7 @@ class Fnv:
|
|||||||
default_task_config = {
|
default_task_config = {
|
||||||
"auto_refresh": False, # 是否自动刷新媒体库
|
"auto_refresh": False, # 是否自动刷新媒体库
|
||||||
"mdb_name": "", # 飞牛影视目标媒体库名称
|
"mdb_name": "", # 飞牛影视目标媒体库名称
|
||||||
|
"mdb_dir_list": "", # 飞牛影视目标媒体库文件夹路径列表,多个用逗号分隔
|
||||||
}
|
}
|
||||||
|
|
||||||
# 定义一个可选键的集合
|
# 定义一个可选键的集合
|
||||||
@ -84,13 +85,17 @@ class Fnv:
|
|||||||
if not target_library_name:
|
if not target_library_name:
|
||||||
print("飞牛影视: 未指定媒体库名称,跳过处理。")
|
print("飞牛影视: 未指定媒体库名称,跳过处理。")
|
||||||
return
|
return
|
||||||
|
target_library_mdb_dir_list = task_config.get("mdb_dir_list")
|
||||||
|
dir_list = []
|
||||||
|
if target_library_mdb_dir_list:
|
||||||
|
dir_list = [dir_path.strip() for dir_path in target_library_mdb_dir_list.split(",") if dir_path.strip()]
|
||||||
|
|
||||||
# 获取媒体库ID
|
# 获取媒体库ID
|
||||||
library_id = self._get_library_id(target_library_name)
|
library_id = self._get_library_id(target_library_name)
|
||||||
|
|
||||||
if library_id:
|
if library_id:
|
||||||
# 获取ID成功后,刷新该媒体库
|
# 获取ID成功后,刷新该媒体库
|
||||||
self._refresh_library(library_id)
|
self._refresh_library(library_id, dir_list=dir_list)
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# Internal Methods (内部实现方法)
|
# Internal Methods (内部实现方法)
|
||||||
@ -131,7 +136,8 @@ class Fnv:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.session.request(
|
response = self.session.request(
|
||||||
method, url, headers=headers, params=params, json=data if data is not None else {}
|
method, url, headers=headers, params=params,
|
||||||
|
data=self._serialize_data(data if data is not None else {})
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@ -206,7 +212,7 @@ class Fnv:
|
|||||||
print(f"飞牛影视: 未在媒体库列表中找到名为 '{library_name}' 的媒体库 ❌")
|
print(f"飞牛影视: 未在媒体库列表中找到名为 '{library_name}' 的媒体库 ❌")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _refresh_library(self, library_id: str) -> bool:
|
def _refresh_library(self, library_id: str, dir_list: list[str] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
根据给定的媒体库ID触发一次媒体库扫描/刷新。
|
根据给定的媒体库ID触发一次媒体库扫描/刷新。
|
||||||
"""
|
"""
|
||||||
@ -214,9 +220,13 @@ class Fnv:
|
|||||||
print("飞牛影视: 必须先登录才能刷新媒体库。")
|
print("飞牛影视: 必须先登录才能刷新媒体库。")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print(f"飞牛影视: 正在为媒体库 {library_id} 发送刷新指令...")
|
if dir_list:
|
||||||
|
print(f"飞牛影视: 正在为媒体库 {library_id} 发送部分目录{dir_list}刷新指令...")
|
||||||
|
else:
|
||||||
|
print(f"飞牛影视: 正在为媒体库 {library_id} 发送刷新指令...")
|
||||||
rel_url = self.API_MDB_SCAN.format(library_id)
|
rel_url = self.API_MDB_SCAN.format(library_id)
|
||||||
response_json = self._make_request('post', rel_url, data={})
|
request_body = {"dir_list": dir_list} if dir_list else {}
|
||||||
|
response_json = self._make_request('post', rel_url, data=request_body)
|
||||||
|
|
||||||
if not response_json: return False
|
if not response_json: return False
|
||||||
|
|
||||||
@ -263,8 +273,10 @@ class Fnv:
|
|||||||
nonce = str(random.randint(100000, 999999))
|
nonce = str(random.randint(100000, 999999))
|
||||||
timestamp = str(int(time.time() * 1000))
|
timestamp = str(int(time.time() * 1000))
|
||||||
|
|
||||||
if method.lower() == 'get' and params:
|
serialized_str = ""
|
||||||
serialized_str = urlencode(sorted(params.items()))
|
if method.lower() == 'get':
|
||||||
|
if params:
|
||||||
|
serialized_str = urlencode(sorted(params.items()))
|
||||||
else:
|
else:
|
||||||
serialized_str = self._serialize_data(data)
|
serialized_str = self._serialize_data(data)
|
||||||
body_hash = self._md5_hash(serialized_str)
|
body_hash = self._md5_hash(serialized_str)
|
||||||
@ -292,7 +304,7 @@ class Fnv:
|
|||||||
将请求体数据序列化为紧凑的JSON字符串。
|
将请求体数据序列化为紧凑的JSON字符串。
|
||||||
"""
|
"""
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
return json.dumps(data, sort_keys=True, separators=(',', ':'))
|
return json.dumps(data, sort_keys=True, separators=(',', ':'), ensure_ascii=False)
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
return data
|
return data
|
||||||
if not data:
|
if not data:
|
||||||
|
|||||||
BIN
plugins/fnv_refresh_v2.so
Normal file
BIN
plugins/fnv_refresh_v2.so
Normal file
Binary file not shown.
@ -96,20 +96,26 @@ class Config:
|
|||||||
PLUGIN_FLAGS = os.environ.get("PLUGIN_FLAGS", "").split(",")
|
PLUGIN_FLAGS = os.environ.get("PLUGIN_FLAGS", "").split(",")
|
||||||
plugins_available = {}
|
plugins_available = {}
|
||||||
task_plugins_config = {}
|
task_plugins_config = {}
|
||||||
|
# 获取所有模块
|
||||||
|
py_ext = [".py", ".pyd"] if sys.platform == "win32" else [".py", ".so"]
|
||||||
all_modules = [
|
all_modules = [
|
||||||
f.replace(".py", "") for f in os.listdir(plugins_dir) if f.endswith(".py")
|
f.replace(ext, "")
|
||||||
|
for f in os.listdir(plugins_dir)
|
||||||
|
for ext in py_ext
|
||||||
|
if f.endswith(ext)
|
||||||
]
|
]
|
||||||
# 调整模块优先级
|
# 调整模块优先级
|
||||||
priority_path = os.path.join(plugins_dir, "_priority.json")
|
priority_path = os.path.join(plugins_dir, "_priority.json")
|
||||||
try:
|
try:
|
||||||
with open(priority_path, encoding="utf-8") as f:
|
with open(priority_path, encoding="utf-8") as f:
|
||||||
priority_modules = json.load(f)
|
priority_modules = json.load(f)
|
||||||
if priority_modules:
|
|
||||||
all_modules = [
|
|
||||||
module for module in priority_modules if module in all_modules
|
|
||||||
] + [module for module in all_modules if module not in priority_modules]
|
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
priority_modules = []
|
priority_modules = []
|
||||||
|
if priority_modules:
|
||||||
|
all_modules = [
|
||||||
|
module for module in priority_modules if module in all_modules
|
||||||
|
] + [module for module in all_modules if module not in priority_modules]
|
||||||
|
# 加载模块
|
||||||
for module_name in all_modules:
|
for module_name in all_modules:
|
||||||
if f"-{module_name}" in PLUGIN_FLAGS:
|
if f"-{module_name}" in PLUGIN_FLAGS:
|
||||||
continue
|
continue
|
||||||
@ -128,7 +134,6 @@ class Config:
|
|||||||
task_plugins_config[module_name] = plugin.default_task_config
|
task_plugins_config[module_name] = plugin.default_task_config
|
||||||
except (ImportError, AttributeError) as e:
|
except (ImportError, AttributeError) as e:
|
||||||
print(f"载入模块 {module_name} 失败: {e}")
|
print(f"载入模块 {module_name} 失败: {e}")
|
||||||
print()
|
|
||||||
return plugins_available, plugins_config, task_plugins_config
|
return plugins_available, plugins_config, task_plugins_config
|
||||||
|
|
||||||
def breaking_change_update(config_data):
|
def breaking_change_update(config_data):
|
||||||
@ -568,6 +573,8 @@ class Quark:
|
|||||||
"_fetch_sub_dirs": "0",
|
"_fetch_sub_dirs": "0",
|
||||||
"_sort": "file_type:asc,updated_at:desc",
|
"_sort": "file_type:asc,updated_at:desc",
|
||||||
"_fetch_full_path": kwargs.get("fetch_full_path", 0),
|
"_fetch_full_path": kwargs.get("fetch_full_path", 0),
|
||||||
|
"fetch_all_file": 1, # 跟随Web端,作用未知
|
||||||
|
"fetch_risk_file_name": 1, # 如无此参数,违规文件名会被变 ***
|
||||||
}
|
}
|
||||||
response = self._send_request("GET", url, params=querystring).json()
|
response = self._send_request("GET", url, params=querystring).json()
|
||||||
if response["code"] != 0:
|
if response["code"] != 0:
|
||||||
@ -620,6 +627,8 @@ class Quark:
|
|||||||
"__t": datetime.now().timestamp(),
|
"__t": datetime.now().timestamp(),
|
||||||
}
|
}
|
||||||
response = self._send_request("GET", url, params=querystring).json()
|
response = self._send_request("GET", url, params=querystring).json()
|
||||||
|
if response["status"] != 200:
|
||||||
|
return response
|
||||||
if response["data"]["status"] == 2:
|
if response["data"]["status"] == 2:
|
||||||
if retry_index > 0:
|
if retry_index > 0:
|
||||||
print()
|
print()
|
||||||
@ -995,22 +1004,23 @@ class Quark:
|
|||||||
err_msg = save_file_return["message"]
|
err_msg = save_file_return["message"]
|
||||||
if err_msg:
|
if err_msg:
|
||||||
add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n")
|
add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n")
|
||||||
# 建立目录树
|
# 建立目录树
|
||||||
for index, item in enumerate(need_save_list):
|
if len(need_save_list) == len(save_as_top_fids):
|
||||||
icon = self._get_file_icon(item)
|
for index, item in enumerate(need_save_list):
|
||||||
tree.create_node(
|
icon = self._get_file_icon(item)
|
||||||
f"{icon}{item['file_name_re']}",
|
tree.create_node(
|
||||||
item["fid"],
|
f"{icon}{item['file_name_re']}",
|
||||||
parent=pdir_fid,
|
item["fid"],
|
||||||
data={
|
parent=pdir_fid,
|
||||||
"file_name": item["file_name"],
|
data={
|
||||||
"file_name_re": item["file_name_re"],
|
"file_name": item["file_name"],
|
||||||
"fid": f"{save_as_top_fids[index]}",
|
"file_name_re": item["file_name_re"],
|
||||||
"path": f"{savepath}/{item['file_name_re']}",
|
"fid": f"{save_as_top_fids[index]}",
|
||||||
"is_dir": item["dir"],
|
"path": f"{savepath}/{item['file_name_re']}",
|
||||||
"obj_category": item.get("obj_category", ""),
|
"is_dir": item["dir"],
|
||||||
},
|
"obj_category": item.get("obj_category", ""),
|
||||||
)
|
},
|
||||||
|
)
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
def do_rename(self, tree, node_id=None):
|
def do_rename(self, tree, node_id=None):
|
||||||
@ -1105,6 +1115,7 @@ def do_save(account, tasklist=[]):
|
|||||||
plugins, CONFIG_DATA["plugins"], task_plugins_config = Config.load_plugins(
|
plugins, CONFIG_DATA["plugins"], task_plugins_config = Config.load_plugins(
|
||||||
CONFIG_DATA.get("plugins", {})
|
CONFIG_DATA.get("plugins", {})
|
||||||
)
|
)
|
||||||
|
print()
|
||||||
print(f"转存账号: {account.nickname}")
|
print(f"转存账号: {account.nickname}")
|
||||||
# 获取全部保存目录fid
|
# 获取全部保存目录fid
|
||||||
account.update_savepath_fid(tasklist)
|
account.update_savepath_fid(tasklist)
|
||||||
@ -1172,6 +1183,13 @@ def do_save(account, tasklist=[]):
|
|||||||
plugin.run(task, account=account, tree=is_new_tree) or task
|
plugin.run(task, account=account, tree=is_new_tree) or task
|
||||||
)
|
)
|
||||||
print()
|
print()
|
||||||
|
print(f"===============插件收尾===============")
|
||||||
|
for plugin_name, plugin in plugins.items():
|
||||||
|
if plugin.is_active and hasattr(plugin, "task_after"):
|
||||||
|
data = plugin.task_after()
|
||||||
|
if data.get("config"):
|
||||||
|
CONFIG_DATA["plugins"][plugin_name] = data["config"]
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@ -23,10 +23,10 @@
|
|||||||
},
|
},
|
||||||
"$SHOW_MAGIC": {
|
"$SHOW_MAGIC": {
|
||||||
"pattern": "^(?!.*纯享)(?!.*加更)(?!.*抢先)(?!.*预告).*?第\\d+期.*",
|
"pattern": "^(?!.*纯享)(?!.*加更)(?!.*抢先)(?!.*预告).*?第\\d+期.*",
|
||||||
"replace": "{II}.{TASKNAME}.{DATE}.第{E}期{PART}.{EXT}"
|
"replace": "{TASKNAME}.{SXX}E{II}.第{E}期{PART}.{EXT}"
|
||||||
},
|
},
|
||||||
"$TV_MAGIC": {
|
"$TV_MAGIC": {
|
||||||
"pattern": "",
|
"pattern": ".*\\.(mp4|mkv|mov|m4v|avi|mpeg|ts)$",
|
||||||
"replace": "{TASKNAME}.{SXX}E{E}.{EXT}"
|
"replace": "{TASKNAME}.{SXX}E{E}.{EXT}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user