Compare commits

...

9 Commits

Author SHA1 Message Date
Cp0204
4e45e37412 增加配置转换升级处理代码
Some checks are pending
Docker Publish / build-and-push (push) Waiting to run
- 添加打印语句以显示配置更新版本
- 处理媒体服务器和任务列表的结构变化
2024-11-25 01:46:08 +08:00
Cp0204
280f0ef060 📝 更新文档和相关链接 2024-11-25 01:32:00 +08:00
Cp0204
63230d5c2b 增加新建任务时插件默认配置项的支持
- 优化插件加载逻辑以返回可用插件和任务插件配置
- 修改插件配置的 json 编辑器高度为180px
2024-11-25 01:25:40 +08:00
Cp0204
c9c83cb65a 新增 aria2 插件,可创建下载任务
- 提供 RPC 接口配置和任务处理功能
- 支持自动下载和自定义下载目录
2024-11-25 01:20:34 +08:00
Cp0204
c3c4ad6c00 媒体库模块 改称为 插件
- 媒体库模块改称为插件,更好地反映功能
- 更新相关文档和代码中的所有引用
- 修改变量名以反映插件的概念
- 确保代码一致性和可读性
2024-11-25 00:08:18 +08:00
Cp0204
9c5ade608e 🎨 更新插件(媒体库模块)配置栏
- 将“媒体库ID”标签更改为“插件配置”
- 添加 v-jsoneditor 组件以支持插件配置输入
- 引入 v-jsoneditor 的 JavaScript 文件
2024-11-24 22:55:17 +08:00
Cp0204
9da63f12c7 任务运行时传递额外参数给媒体库模块
- 在多个模块的 run() 添加 **kwargs 参数
- 允许在任务中传递附加配置以增强灵活性
- 将打印信息中的“目标目录”修改为“保存路径”以提高可读性
2024-11-24 22:51:25 +08:00
Cp0204
2427a6d26b 优化 Quark.do_save_task() 返回值为 tree
- 调整 tree 生成逻辑
- tree node 记录更多信息,用以扩展处理
2024-11-24 15:12:20 +08:00
Cp0204
a0681e5e44 增加 Quark 类 download() 及 UA 2024-11-24 15:02:46 +08:00
13 changed files with 287 additions and 90 deletions

View File

@ -40,7 +40,7 @@
- 分享链接
- [x] 支持分享链接的子目录
- [x] 记录失效分享并跳过任务
- [x] 支持需提取码的分享链接 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7%E9%9B%86%E9%94%A6#%E6%94%AF%E6%8C%81%E9%9C%80%E6%8F%90%E5%8F%96%E7%A0%81%E7%9A%84%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5)</sup>
- [x] 支持需提取码的分享链接 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/使用技巧集锦#支持需提取码的分享链接)</sup>
- 文件管理
- [x] 目标目录不存在时自动新建
@ -57,11 +57,11 @@
- 媒体库整合
- [x] 根据任务名搜索 Emby 媒体库
- [x] 追更或整理后自动刷新 Emby 媒体库
- [x] **媒体库模块化,用户可很方便地[开发自己的媒体库hook模块](./media_servers)**
- [x] **媒体库模块化,用户可很方便地[开发自己的媒体库hook模块](./plugins)**
- 其它
- [x] 每日签到领空间 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7%E9%9B%86%E9%94%A6#%E6%AF%8F%E6%97%A5%E7%AD%BE%E5%88%B0%E9%A2%86%E7%A9%BA%E9%97%B4)</sup>
- [x] 支持多个通知推送渠道 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/%E9%80%9A%E7%9F%A5%E6%8E%A8%E9%80%81%E6%9C%8D%E5%8A%A1%E9%85%8D%E7%BD%AE)</sup>
- [x] 每日签到领空间 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/使用技巧集锦#每日签到领空间)</sup>
- [x] 支持多个通知推送渠道 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/通知推送服务配置)</sup>
- [x] 支持多账号(多账号签到,仅首账号转存)
## 部署
@ -155,9 +155,9 @@ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtow
### 刷新媒体库
在有新转存时,可触发完成相应功能,如自动刷新媒体库、生成 .strm 文件等。配置指南:[媒体库模块配置](https://github.com/Cp0204/quark-auto-save/wiki/媒体库模块配置)
在有新转存时,可触发完成相应功能,如自动刷新媒体库、生成 .strm 文件等。配置指南:[插件配置](https://github.com/Cp0204/quark-auto-save/wiki/插件配置)
媒体库模块以模块化方式的集成,如果你有兴趣请参考[媒体库模块开发指南](https://github.com/Cp0204/quark-auto-save/tree/main/media_servers)。
媒体库模块以插件的方式的集成,如果你有兴趣请参考[插件开发指南](https://github.com/Cp0204/quark-auto-save/tree/main/plugins)。
### 更多使用技巧

32
app/static/js/v-jsoneditor.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -82,25 +82,25 @@
<div class="row title">
<div class="col">
<h2 style="display: inline-block;">媒体库</h2>
<h2 style="display: inline-block;">插件</h2>
<span class="badge badge-pill badge-light">
<a href="https://github.com/Cp0204/quark-auto-save/wiki/媒体库模块配置" target="_blank" title="媒体库模块配置">?</a>
<a href="https://github.com/Cp0204/quark-auto-save/wiki/插件配置" target="_blank" title="插件配置">?</a>
</span>
</div>
</div>
<div v-for="(server, serverName) in formData.media_servers" :key="serverName" class="task mb-3">
<div v-for="(plugin, pluginName) in formData.plugins" :key="pluginName" class="task mb-3">
<div class="form-group row" style="display:flex; align-items:center">
<div class="col-9" data-toggle="collapse" :data-target="'#collapse_'+serverName" aria-expanded="true" :aria-controls="'collapse_'+serverName">
<div class="col-9" data-toggle="collapse" :data-target="'#collapse_'+pluginName" aria-expanded="true" :aria-controls="'collapse_'+pluginName">
<div class="btn btn-block text-left">
<i class="bi bi-caret-right-fill"></i> <span v-html="`${serverName}`"></span>
<i class="bi bi-caret-right-fill"></i> <span v-html="`${pluginName}`"></span>
</div>
</div>
</div>
<div class="collapse" :id="'collapse_'+serverName" style="padding-left:2em">
<div v-for="(value, key) in server" :key="key" class="form-group row">
<div class="collapse" :id="'collapse_'+pluginName" style="padding-left:2em">
<div v-for="(value, key) in plugin" :key="key" class="form-group row">
<label class="col-sm-2 col-form-label">{{ key }}</label>
<div class="col-sm-10">
<input type="text" v-model="formData.media_servers[serverName][key]" class="form-control">
<input type="text" v-model="formData.plugins[pluginName][key]" class="form-control">
</div>
</div>
</div>
@ -264,9 +264,10 @@
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">媒体库ID</label>
<label class="col-sm-2 col-form-label">插件配置</label>
<div class="col-sm-10">
<input type="number" name="media_id[]" class="form-control" v-model="task.media_id" placeholder="可选">
<!-- <input type="text" name="addition[]" class="form-control" v-model="task.addition" placeholder="可选"> -->
<v-jsoneditor v-model="task.addition" :options="{mode:'tree'}" :plus="false" height="180px"></v-jsoneditor>
</div>
</div>
</div>
@ -362,6 +363,7 @@
<!-- 引入 Vue.js -->
<script src="./static/js/vue@2.js"></script>
<script src="./static/js/axios.min.js"></script>
<script src="./static/js/v-jsoneditor.min.js"></script>
<script>
var app = new Vue({
@ -384,7 +386,7 @@
pattern: "",
replace: "",
enddate: "",
media_id: "",
addition: {},
ignore_extension: false,
runweek: [1, 2, 3, 4, 5, 6, 7]
},
@ -460,16 +462,13 @@
}
return task;
});
// 添加emby预设
if (!response.data.hasOwnProperty('emby')) {
response.data.emby = { ...this.emby };
}
// 获取所有任务父目录
response.data.tasklist.forEach(item => {
parentDir = this.getParentDirectory(item.savepath)
if (!this.taskDirs.includes(parentDir))
this.taskDirs.push(parentDir);
});
this.newTask.addition = response.data.task_plugins_config;
this.formData = response.data;
})
.catch(error => {

View File

@ -1,21 +1,21 @@
# 媒体库模块开发指南
# 插件开发指南
本指南介绍如何开发自定义媒体库模块,你可以通过添加新的媒体库模块来扩展项目功能。
本指南介绍如何开发自定义插件,你可以通过添加新的插件来扩展项目功能。
## 基本结构
* 模块位于 `media_servers` 目录下.
* 每个模块是一个 `.py` 文件 (例如 `emby.py`, `plex.py`),文件名小写。
* 每个模块文件包含一个与文件名对应的首字母大写命名类(例如 `emby.py` 中的 `Emby` 类)。
* 插件位于 `media_servers` 目录下.
* 每个插件是一个 `.py` 文件 (例如 `emby.py`, `plex.py`),文件名小写。
* 每个插件文件包含一个与文件名对应的首字母大写命名类(例如 `emby.py` 中的 `Emby` 类)。
## 模块要求
## 插件要求
每个模块类必须包含以下内容:
每个插件类必须包含以下内容:
* **`default_config`**:字典,包含模块所需参数及其默认值。例如:
* **`default_config`**:字典,包含插件所需参数及其默认值。例如:
```python
# 该模块必须配置的键,值可留空
# 该插件必须配置的键,值可留空
default_config = {"url": "", "token": ""}
```
@ -25,11 +25,11 @@
1. 检查 `kwargs` 是否包含所有 `default_config` 中的参数,缺少参数则打印警告。
2. 若参数完整,尝试连接服务器并验证配置,成功则设置 `self.is_active = True`
* **`run(self, task)`**:整个模块入口函数,处理模块逻辑。
* **`run(self, task, **kwargs)`**:整个插件入口函数,处理插件逻辑。
* `task` 是一个字典,包含任务信息。如果需要修改任务参数,返回修改后的 `task` 字典;
* 无修改则不返回或返回 `None`
## 模块示例
## 插件示例
参考 [emby.py](emby.py)
@ -43,7 +43,7 @@
### 最佳实践
requests 部分使用 try-except 块,以防模块请求出错中断整个转存任务。
requests 部分使用 try-except 块,以防插件请求出错中断整个转存任务。
```python
try:
@ -56,7 +56,7 @@ except requests.exceptions.RequestException as e:
return False
```
## 使用自定义模块
## 使用自定义插件
放到 `/media_servers` 目录即可识别,如果你使用 docker 运行:
@ -67,11 +67,11 @@ docker run -d \
# ...
```
如果你有写自定义模块的能力,相信你也知道如何挂载自定义模块,算我啰嗦。🙃
如果你有写自定义插件的能力,相信你也知道如何挂载自定义插件,算我啰嗦。🙃
## 配置文件
`quark_config.json``media_servers` 中配置模块参数:
`quark_config.json``media_servers` 中配置插件参数:
```json
{
@ -84,10 +84,11 @@ docker run -d \
}
```
模块代码正确赋值 `default_config` 时,首次运行会自动补充缺失的键。
插件代码正确赋值 `default_config` 时,首次运行会自动补充缺失的键。
## 🤝 贡献者
| 模块 | 说明 | 贡献者 |
| 插件 | 说明 | 贡献者 |
| ------- | -------------------- | --------------------------------------- |
| plex.py | 自动刷新 Plex 媒体库 | [zhazhayu](https://github.com/zhazhayu) |
| plex.py | 自动刷新 Plex 媒体库 | [zhazhayu](https://github.com/zhazhayu) |
| alist_strm_gen.py | 自动生成strm | [xiaoQQya](https://github.com/xiaoQQya) |

View File

@ -2,6 +2,7 @@
"alist",
"alist_strm",
"alist_strm_gen",
"aria2",
"emby",
"plex"
]

View File

@ -30,7 +30,7 @@ class Alist:
self.storage_mount_path, self.quark_root_dir = result
self.is_active = True
def run(self, task):
def run(self, task, **kwargs):
if task.get("savepath") and task.get("savepath").startswith(
self.quark_root_dir
):

View File

@ -12,7 +12,7 @@ class Alist_strm:
default_config = {
"url": "", # alist-strm服务器URL
"cookie": "", # alist-strm的cookieF12抓取关键参数session=ey***
"config_id": "", # 要触发运行的配置ID
"config_id": "", # 要触发运行的配置ID,支持多个,用逗号分隔
}
is_active = False
@ -27,7 +27,7 @@ class Alist_strm:
if self.get_info(self.config_id):
self.is_active = True
def run(self, task):
def run(self, task, **kwargs):
self.run_selected_configs(self.config_id)
def get_info(self, config_id_str):

View File

@ -24,6 +24,9 @@ class Alist_strm_gen:
"strm_save_dir": "/media", # 生成的 strm 文件保存的路径
"strm_replace_host": "", # strm 文件内链接的主机地址 (可选,缺省时=url
}
default_task_config = {
"auto_gen": True, # 是否自动生成 strm 文件
}
is_active = False
# 缓存参数
storage_mount_path = None
@ -31,12 +34,13 @@ class Alist_strm_gen:
strm_server = None
def __init__(self, **kwargs):
self.plugin_name = self.__class__.__name__.lower()
if kwargs:
for key, _ in self.default_config.items():
if key in kwargs:
setattr(self, key, kwargs[key])
else:
print(f"{self.__class__.__name__} 模块缺少必要参数: {key}")
print(f"{self.plugin_name} 模块缺少必要参数: {key}")
if self.url and self.token and self.storage_id:
success, result = self.storage_id_to_path(self.storage_id)
if success:
@ -53,7 +57,10 @@ class Alist_strm_gen:
else:
self.strm_server = f"{self.url.strip()}/d"
def run(self, task):
def run(self, task, **kwargs):
if task_config := task.get("addition", {}).get(self.plugin_name, {}):
if not task_config.get("auto_gen"):
return
if task.get("savepath") and task.get("savepath").startswith(
self.quark_root_dir
):

90
plugins/aria2.py Normal file
View File

@ -0,0 +1,90 @@
import os
import requests
class Aria2:
default_config = {
"host_port": "172.17.0.1:6800", # Aria2 RPC地址
"secret": "", # Aria2 RPC 密钥
"dir": "/Downloads", # 下载目录需要Aria2有权限访问
}
default_task_config = {
"auto_download": False, # 是否开启自动下载
}
is_active = False
rpc_url = None
def __init__(self, **kwargs):
self.plugin_name = self.__class__.__name__.lower()
if kwargs:
for key, _ in self.default_config.items():
if key in kwargs:
setattr(self, key, kwargs[key])
else:
print(f"{self.plugin_name} 模块缺少必要参数: {key}")
if self.host_port and self.secret:
self.rpc_url = f"http://{self.host_port}/jsonrpc"
if self.get_version():
self.is_active = True
def run(self, task, **kwargs):
if task_config := task.get("addition", {}).get(self.plugin_name, {}):
if not task_config.get("auto_download"):
return
if (tree := kwargs.get("tree")) and (account := kwargs.get("account")):
for node in tree.all_nodes_itr():
if not node.data.get("is_dir", True):
quark_path = node.data.get("path")
quark_fid = node.data.get("fid")
save_path = f"{self.dir}{quark_path}"
print(f"📥 Aria2下载: {quark_path}")
download_return, cookie = account.download([quark_fid])
download_url = [
item["download_url"] for item in download_return["data"]
]
aria2_params = [
download_url,
{
"header": [
f"Cookie: {cookie}",
f"User-Agent: {account.common_headers().get('user-agent')}",
],
"out": os.path.basename(save_path),
"dir": os.path.dirname(save_path),
},
]
self.add_uri(aria2_params)
def _make_rpc_request(self, method, params=None):
"""发出 JSON-RPC 请求."""
jsonrpc_data = {
"jsonrpc": "2.0",
"id": "quark-auto-save",
"method": method,
"params": params or [],
}
if self.secret:
jsonrpc_data["params"].insert(0, f"token:{self.secret}")
try:
response = requests.post(self.rpc_url, json=jsonrpc_data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Aria2下载: 错误{e}")
return None
def get_version(self):
"""检查与 Aria2 的连接."""
response = self._make_rpc_request("aria2.getVersion")
if response.get("result"):
print(f"Aria2下载: v{response['result']['version']}")
return True
else:
print(f"Aria2下载: 连接失败{response.get('error')}")
return False
def add_uri(self, params=None):
"""添加 URI 下载任务."""
response = self._make_rpc_request("aria2.addUri", params)
return response.get("result") if response else None

View File

@ -3,30 +3,38 @@ import requests
class Emby:
default_config = {"url": "", "token": ""}
default_config = {
"url": "", # Emby服务器地址
"token": "", # Emby服务器token
}
default_task_config = {
"try_match": True, # 是否尝试匹配
"media_id": "", # 媒体ID当为0时不刷新
}
is_active = False
def __init__(self, **kwargs):
self.plugin_name = self.__class__.__name__.lower()
if kwargs:
for key, value in self.default_config.items():
for key, _ in self.default_config.items():
if key in kwargs:
setattr(self, key, kwargs[key])
else:
print(f"{self.__class__.__name__} 模块缺少必要参数: {key}")
print(f"{self.plugin_name} 模块缺少必要参数: {key}")
if self.url and self.token:
if self.get_info():
self.is_active = True
def run(self, task):
if task.get("media_id"):
if task["media_id"] != "0":
self.refresh(task["media_id"])
else:
match_media_id = self.search(task["taskname"])
if match_media_id:
task["media_id"] = match_media_id
self.refresh(match_media_id)
return task
def run(self, task, **kwargs):
if task_config := task.get("addition", {}).get(self.plugin_name, {}):
if media_id := task_config.get("media_id"):
if media_id != "0":
self.refresh(media_id)
elif task_config.get("try_match"):
if match_media_id := self.search(task["taskname"]):
self.refresh(match_media_id)
task["addition"][self.plugin_name]["media_id"] = match_media_id
return task
def get_info(self):
url = f"{self.url}/emby/System/Info"

View File

@ -23,7 +23,7 @@ class Plex:
if self.get_info():
self.is_active = True
def run(self, task):
def run(self, task, **kwargs):
if task.get("savepath"):
# 检查是否已缓存库信息
if self._libraries is None:

View File

@ -125,6 +125,7 @@ class Quark:
headers = {
"cookie": self.cookie,
"content-type": "application/json",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/3.14.2 Chrome/112.0.5615.165 Electron/24.1.3.8 Safari/537.36 Channel/pckk_other_ch",
}
return headers
@ -329,6 +330,16 @@ class Quark:
).json()
return response
def download(self, fids):
url = "https://drive-h.quark.cn/1/clouddrive/file/download"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
payload = {"fids": fids}
headers = self.common_headers()
response = requests.post(url, json=payload, headers=headers, params=querystring)
set_cookie = response.cookies.get_dict()
cookie_str = "; ".join([f"{key}={value}" for key, value in set_cookie.items()])
return response.json(), cookie_str
def mkdir(self, dir_path):
url = "https://drive-h.quark.cn/1/clouddrive/file"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
@ -487,14 +498,13 @@ class Quark:
updated_tree = self.dir_check_and_save(task, pwd_id, stoken, pdir_fid)
if updated_tree.size(1) > 0:
add_notify(f"✅《{task['taskname']}》添加追更:\n{updated_tree}")
return True
return updated_tree
else:
print(f"任务结束:没有新的转存任务")
return False
def dir_check_and_save(self, task, pwd_id, stoken, pdir_fid="", subdir_path=""):
tree = Tree()
tree.create_node(task["savepath"], pdir_fid)
# 获取分享文件列表
share_file_list = self.get_detail(pwd_id, stoken, pdir_fid)["list"]
# print("share_file_list: ", share_file_list)
@ -526,6 +536,14 @@ class Quark:
dir_file_list = self.ls_dir(to_pdir_fid)
# print("dir_file_list: ", dir_file_list)
tree.create_node(
savepath,
pdir_fid,
data={
"is_dir": True,
},
)
# 需保存的文件清单
need_save_list = []
# 添加符合的
@ -580,6 +598,9 @@ class Quark:
"📁" + share_file["file_name"],
share_file["fid"],
parent=pdir_fid,
data={
"is_dir": share_file["dir"],
},
)
tree.merge(share_file["fid"], subdir_tree, deep=False)
# 指定文件开始订阅/到达指定文件(含)结束历遍
@ -588,7 +609,6 @@ class Quark:
fid_list = [item["fid"] for item in need_save_list]
fid_token_list = [item["share_fid_token"] for item in need_save_list]
save_name_list = [item["save_name"] for item in need_save_list]
if fid_list:
save_file_return = self.save_file(
fid_list, fid_token_list, to_pdir_fid, pwd_id, stoken
@ -598,16 +618,22 @@ class Quark:
task_id = save_file_return["data"]["task_id"]
query_task_return = self.query_task(task_id)
if query_task_return["code"] == 0:
save_name_list.sort()
# 建立目录树
for item in need_save_list:
for index, item in enumerate(need_save_list):
icon = (
"📁"
if item["dir"] == True
else "🎞️" if item["obj_category"] == "video" else ""
)
tree.create_node(
f"{icon}{item['save_name']}", item["fid"], parent=pdir_fid
f"{icon}{item['save_name']}",
item["fid"],
parent=pdir_fid,
data={
"fid": f"{query_task_return['data']['save_as']['save_as_top_fids'][index]}",
"path": f"{savepath}/{item['save_name']}",
"is_dir": item["dir"],
},
)
else:
err_msg = query_task_return["message"]
@ -688,13 +714,14 @@ class Quark:
return is_rename_count > 0
def load_media_servers(media_servers_config, media_servers_dir="media_servers"):
media_servers = {}
def load_plugins(plugins_config, plugins_dir="plugins"):
plugins_available = {}
task_plugins_config = {}
all_modules = [
f.replace(".py", "") for f in os.listdir(media_servers_dir) if f.endswith(".py")
f.replace(".py", "") for f in os.listdir(plugins_dir) if f.endswith(".py")
]
# 调整模块优先级
priority_path = os.path.join(media_servers_dir, "_priority.json")
priority_path = os.path.join(plugins_dir, "_priority.json")
try:
with open(priority_path, encoding="utf-8") as f:
priority_modules = json.load(f)
@ -704,21 +731,25 @@ def load_media_servers(media_servers_config, media_servers_dir="media_servers"):
] + [module for module in all_modules if module not in priority_modules]
except (FileNotFoundError, json.JSONDecodeError):
priority_modules = []
print(f"🧩 载入媒体库模块")
print(f"🧩 载入插件")
for module_name in all_modules:
try:
module = importlib.import_module(f"{media_servers_dir}.{module_name}")
module = importlib.import_module(f"{plugins_dir}.{module_name}")
ServerClass = getattr(module, module_name.capitalize())
# 检查配置中是否存在该模块的配置
if module_name in media_servers_config:
server_config = media_servers_config[module_name]
media_servers[module_name] = ServerClass(**server_config)
if module_name in plugins_config:
plugin = ServerClass(**plugins_config[module_name])
plugins_available[module_name] = plugin
else:
media_servers_config[module_name] = ServerClass().default_config
plugin = ServerClass()
plugins_config[module_name] = plugin.default_config
# 检查插件是否支持单独任务配置
if hasattr(plugin, "default_task_config"):
task_plugins_config[module_name] = plugin.default_task_config
except (ImportError, AttributeError) as e:
print(f"载入模块 {module_name} 失败: {e}")
print()
return media_servers
return plugins_available, task_plugins_config
def verify_account(account):
@ -778,7 +809,9 @@ def do_sign(account):
def do_save(account, tasklist=[]):
media_servers = load_media_servers(CONFIG_DATA.get("media_servers", {}))
plugins, CONFIG_DATA["task_plugins_config"] = load_plugins(
CONFIG_DATA.get("plugins", {})
)
print(f"转存账号: {account.nickname}")
# 获取全部保存目录fid
account.update_savepath_fid(tasklist)
@ -804,33 +837,48 @@ def do_save(account, tasklist=[]):
print(f"#{index+1}------------------")
print(f"任务名称: {task['taskname']}")
print(f"分享链接: {task['shareurl']}")
print(f"目标目录: {task['savepath']}")
print(f"保存路径: {task['savepath']}")
print(f"正则匹配: {task['pattern']}")
print(f"正则替换: {task['replace']}")
if task.get("enddate"):
print(f"任务截止: {task['enddate']}")
if task.get("media_id"):
print(f"刷媒体库: {task['media_id']}")
if task.get("ignore_extension"):
print(f"忽略后缀: {task['ignore_extension']}")
if task.get("update_subdir"):
print(f"更子目录: {task['update_subdir']}")
print()
is_new = account.do_save_task(task)
is_new_tree = account.do_save_task(task)
is_rename = account.do_rename_task(task)
# 调用媒体库模块
if is_new or is_rename:
print(f"🧩 调用媒体库模块")
for server_name, media_server in media_servers.items():
if media_server.is_active:
task = media_server.run(task) or task
# 补充任务的插件配置
def merge_dicts(a, b):
result = a.copy()
for key, value in b.items():
if (
key in result
and isinstance(result[key], dict)
and isinstance(value, dict)
):
result[key] = merge_dicts(result[key], value)
elif key not in result:
result[key] = value
return result
task["addition"] = merge_dicts(
task.get("addition", {}), CONFIG_DATA["task_plugins_config"]
)
# 调用插件
print(f"🧩 调用插件")
for plugin_name, plugin in plugins.items():
if plugin.is_active and (is_new_tree or is_rename):
task = plugin.run(task, account=account, tree=is_new_tree) or task
print()
def reaking_change_update():
def breaking_change_update():
global CONFIG_DATA
# print("Update config v0.3.6.1 to 0.3.7")
if CONFIG_DATA.get("emby"):
print("🔼 Update config v0.3.6.1 to 0.3.7")
CONFIG_DATA.setdefault("media_servers", {})["emby"] = {
"url": CONFIG_DATA["emby"]["url"],
"token": CONFIG_DATA["emby"]["apikey"],
@ -840,6 +888,18 @@ def reaking_change_update():
task["media_id"] = task.get("emby_id", "")
if task.get("emby_id"):
del task["emby_id"]
if CONFIG_DATA.get("media_servers"):
print("🔼 Update config v0.3.8 to 0.3.9")
CONFIG_DATA["plugins"] = CONFIG_DATA.get("media_servers")
del CONFIG_DATA["media_servers"]
for task in CONFIG_DATA.get("tasklist", {}):
task["addition"] = {
"emby": {
"media_id": task.get("media_id", ""),
}
}
if task.get("media_id"):
del task["media_id"]
def main():
@ -869,7 +929,7 @@ def main():
print(f"⚙️ 正从 {config_path} 文件中读取配置")
with open(config_path, "r", encoding="utf-8") as file:
CONFIG_DATA = json.load(file)
reaking_change_update()
breaking_change_update()
cookie_val = CONFIG_DATA.get("cookie")
if not CONFIG_DATA.get("magic_regex"):
CONFIG_DATA["magic_regex"] = MAGIC_REGEX

View File

@ -20,7 +20,6 @@
"pattern": "$TV",
"replace": "",
"enddate": "2099-01-30",
"media_id": "",
"update_subdir": "4k|1080p"
},
{