mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-16 17:30:43 +08:00
Compare commits
8 Commits
7666f3a585
...
0e458e934e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e458e934e | ||
|
|
65bacabf49 | ||
|
|
b84679e252 | ||
|
|
79daac6d3d | ||
|
|
eeaf02f5d3 | ||
|
|
6f68c5a290 | ||
|
|
7d4672cb8e | ||
|
|
7b019ab1e0 |
@ -11,6 +11,7 @@
|
|||||||
- **WebUI**:对整个 WebUI 进行了重塑,增加了更多实用功能,如文件选择和预览界面的排序功能、资源搜索的过滤功能、TMDB 和豆瓣搜索功能、页面视图切换功能、账号设置功能等等。
|
- **WebUI**:对整个 WebUI 进行了重塑,增加了更多实用功能,如文件选择和预览界面的排序功能、资源搜索的过滤功能、TMDB 和豆瓣搜索功能、页面视图切换功能、账号设置功能等等。
|
||||||
- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘文件,也不会重复转存。
|
- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘文件,也不会重复转存。
|
||||||
- **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。
|
- **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。
|
||||||
|
- **文件整理**:支持浏览和管理多个夸克账号的网盘文件,支持批量重命名(支持应用完整的命名、过滤规则和撤销重命名等操作)、删除文件等操作。
|
||||||
|
|
||||||
本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG,不一定会被修复。若你要使用本项目,请知晓本人不是程序员,我无法保证本项目的稳定性,如果你在使用过程中发现了 BUG,可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。
|
本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG,不一定会被修复。若你要使用本项目,请知晓本人不是程序员,我无法保证本项目的稳定性,如果你在使用过程中发现了 BUG,可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。
|
||||||
|
|
||||||
@ -39,6 +40,7 @@
|
|||||||
- [x] 转存后文件名整理(正则命名、**顺序命名**、**剧集命名**)
|
- [x] 转存后文件名整理(正则命名、**顺序命名**、**剧集命名**)
|
||||||
- [x] 可选忽略文件后缀
|
- [x] 可选忽略文件后缀
|
||||||
- [x] **数据库记录所有转存历史(支持查看、查询和删除记录)**
|
- [x] **数据库记录所有转存历史(支持查看、查询和删除记录)**
|
||||||
|
- [x] **文件整理(支持浏览和管理多个夸克账号的网盘文件)**
|
||||||
|
|
||||||
- 任务管理
|
- 任务管理
|
||||||
- [x] 支持多组任务
|
- [x] 支持多组任务
|
||||||
@ -54,7 +56,7 @@
|
|||||||
- 其它
|
- 其它
|
||||||
- [x] 每日签到领空间 <sup>[?](https://github.com/x1ao4/quark-auto-save-x/wiki/使用技巧集锦#每日签到领空间)</sup>
|
- [x] 每日签到领空间 <sup>[?](https://github.com/x1ao4/quark-auto-save-x/wiki/使用技巧集锦#每日签到领空间)</sup>
|
||||||
- [x] 支持多个通知推送渠道 <sup>[?](https://github.com/x1ao4/quark-auto-save-x/wiki/通知推送服务配置)</sup>
|
- [x] 支持多个通知推送渠道 <sup>[?](https://github.com/x1ao4/quark-auto-save-x/wiki/通知推送服务配置)</sup>
|
||||||
- [x] 支持多账号(多账号签到,仅首账号转存)
|
- [x] 支持多账号(多账号签到、**文件管理**,仅首账号转存)
|
||||||
- [x] 支持网盘文件下载、strm 文件生成等功能 <sup>[?](https://github.com/x1ao4/quark-auto-save-x/wiki/插件配置)</sup>
|
- [x] 支持网盘文件下载、strm 文件生成等功能 <sup>[?](https://github.com/x1ao4/quark-auto-save-x/wiki/插件配置)</sup>
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|||||||
906
app/run.py
906
app/run.py
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@ class RecordDB:
|
|||||||
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
||||||
|
|
||||||
# 创建数据库连接
|
# 创建数据库连接
|
||||||
self.conn = sqlite3.connect(self.db_path)
|
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
||||||
cursor = self.conn.cursor()
|
cursor = self.conn.cursor()
|
||||||
|
|
||||||
# 创建表,如果不存在
|
# 创建表,如果不存在
|
||||||
@ -49,13 +49,14 @@ class RecordDB:
|
|||||||
self.conn.close()
|
self.conn.close()
|
||||||
|
|
||||||
def add_record(self, task_name, original_name, renamed_to, file_size, modify_date,
|
def add_record(self, task_name, original_name, renamed_to, file_size, modify_date,
|
||||||
duration="", resolution="", file_id="", file_type="", save_path=""):
|
duration="", resolution="", file_id="", file_type="", save_path="", transfer_time=None):
|
||||||
"""添加一条转存记录"""
|
"""添加一条转存记录"""
|
||||||
cursor = self.conn.cursor()
|
cursor = self.conn.cursor()
|
||||||
|
now_ms = int(time.time() * 1000) if transfer_time is None else transfer_time
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT INTO transfer_records (transfer_time, task_name, original_name, renamed_to, file_size, "
|
"INSERT INTO transfer_records (transfer_time, task_name, original_name, renamed_to, file_size, "
|
||||||
"duration, resolution, modify_date, file_id, file_type, save_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
"duration, resolution, modify_date, file_id, file_type, save_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
(int(time.time()), task_name, original_name, renamed_to, file_size,
|
(now_ms, task_name, original_name, renamed_to, file_size,
|
||||||
duration, resolution, modify_date, file_id, file_type, save_path)
|
duration, resolution, modify_date, file_id, file_type, save_path)
|
||||||
)
|
)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@ -123,7 +124,7 @@ class RecordDB:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_records(self, page=1, page_size=20, sort_by="transfer_time", order="desc",
|
def get_records(self, page=1, page_size=20, sort_by="transfer_time", order="desc",
|
||||||
task_name_filter="", keyword_filter=""):
|
task_name_filter="", keyword_filter="", exclude_task_names=None):
|
||||||
"""获取转存记录列表,支持分页、排序和筛选
|
"""获取转存记录列表,支持分页、排序和筛选
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -133,6 +134,7 @@ class RecordDB:
|
|||||||
order: 排序方向(asc/desc)
|
order: 排序方向(asc/desc)
|
||||||
task_name_filter: 任务名称筛选条件(精确匹配)
|
task_name_filter: 任务名称筛选条件(精确匹配)
|
||||||
keyword_filter: 关键字筛选条件(模糊匹配任务名)
|
keyword_filter: 关键字筛选条件(模糊匹配任务名)
|
||||||
|
exclude_task_names: 需要排除的任务名称列表
|
||||||
"""
|
"""
|
||||||
cursor = self.conn.cursor()
|
cursor = self.conn.cursor()
|
||||||
offset = (page - 1) * page_size
|
offset = (page - 1) * page_size
|
||||||
@ -159,6 +161,10 @@ class RecordDB:
|
|||||||
params.append(f"%{keyword_filter}%")
|
params.append(f"%{keyword_filter}%")
|
||||||
params.append(f"%{keyword_filter}%")
|
params.append(f"%{keyword_filter}%")
|
||||||
|
|
||||||
|
if exclude_task_names:
|
||||||
|
where_clauses.append("task_name NOT IN ({})".format(",".join(["?" for _ in exclude_task_names])))
|
||||||
|
params.extend(exclude_task_names)
|
||||||
|
|
||||||
where_clause = " AND ".join(where_clauses)
|
where_clause = " AND ".join(where_clauses)
|
||||||
where_sql = f"WHERE {where_clause}" if where_clause else ""
|
where_sql = f"WHERE {where_clause}" if where_clause else ""
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
155
app/static/js/sort_file_by_name.js
Normal file
155
app/static/js/sort_file_by_name.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// 与后端 quark_auto_save.py 的 sort_file_by_name 完全一致的排序逻辑
|
||||||
|
// 用于前端文件列表排序
|
||||||
|
|
||||||
|
function chineseToArabic(chinese) {
|
||||||
|
// 简单实现,支持一到一万
|
||||||
|
const cnNums = {
|
||||||
|
'零': 0, '一': 1, '二': 2, '两': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
|
||||||
|
'十': 10, '百': 100, '千': 1000, '万': 10000
|
||||||
|
};
|
||||||
|
let result = 0, unit = 1, num = 0;
|
||||||
|
for (let i = chinese.length - 1; i >= 0; i--) {
|
||||||
|
const char = chinese[i];
|
||||||
|
if (cnNums[char] >= 10) {
|
||||||
|
unit = cnNums[char];
|
||||||
|
if (unit === 10 && (i === 0 || cnNums[chinese[i - 1]] === undefined)) {
|
||||||
|
num = 1;
|
||||||
|
}
|
||||||
|
} else if (cnNums[char] !== undefined) {
|
||||||
|
num = cnNums[char];
|
||||||
|
result += num * unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortFileByName(file) {
|
||||||
|
// 兼容 dict 或字符串
|
||||||
|
let filename = typeof file === 'object' ? (file.file_name || '') : file;
|
||||||
|
let update_time = typeof file === 'object' ? (file.updated_at || 0) : 0;
|
||||||
|
let file_name_without_ext = filename.replace(/\.[^/.]+$/, '');
|
||||||
|
let date_value = Infinity, episode_value = Infinity, segment_value = 0;
|
||||||
|
|
||||||
|
// 1. 日期提取
|
||||||
|
let match;
|
||||||
|
// YYYY-MM-DD
|
||||||
|
match = filename.match(/((?:19|20)\d{2})[-./\s](\d{1,2})[-./\s](\d{1,2})/);
|
||||||
|
if (match) {
|
||||||
|
date_value = parseInt(match[1]) * 10000 + parseInt(match[2]) * 100 + parseInt(match[3]);
|
||||||
|
}
|
||||||
|
// YY-MM-DD
|
||||||
|
if (date_value === Infinity) {
|
||||||
|
match = filename.match(/((?:19|20)?\d{2})[-./\s](\d{1,2})[-./\s](\d{1,2})/);
|
||||||
|
if (match && match[1].length === 2) {
|
||||||
|
let year = parseInt('20' + match[1]);
|
||||||
|
date_value = year * 10000 + parseInt(match[2]) * 100 + parseInt(match[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// YYYYMMDD
|
||||||
|
if (date_value === Infinity) {
|
||||||
|
match = filename.match(/((?:19|20)\d{2})(\d{2})(\d{2})/);
|
||||||
|
if (match) {
|
||||||
|
date_value = parseInt(match[1]) * 10000 + parseInt(match[2]) * 100 + parseInt(match[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// YYMMDD
|
||||||
|
if (date_value === Infinity) {
|
||||||
|
match = filename.match(/(?<!\d)(\d{2})(\d{2})(\d{2})(?!\d)/);
|
||||||
|
if (match) {
|
||||||
|
let month = parseInt(match[2]), day = parseInt(match[3]);
|
||||||
|
if (1 <= month && month <= 12 && 1 <= day && day <= 31) {
|
||||||
|
let year = parseInt('20' + match[1]);
|
||||||
|
date_value = year * 10000 + month * 100 + day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// MM/DD/YYYY
|
||||||
|
if (date_value === Infinity) {
|
||||||
|
match = filename.match(/(\d{1,2})[-./\s](\d{1,2})[-./\s]((?:19|20)\d{2})/);
|
||||||
|
if (match) {
|
||||||
|
let month = parseInt(match[1]), day = parseInt(match[2]), year = parseInt(match[3]);
|
||||||
|
if (month > 12) [month, day] = [day, month];
|
||||||
|
date_value = year * 10000 + month * 100 + day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// MM-DD
|
||||||
|
if (date_value === Infinity) {
|
||||||
|
match = filename.match(/(?<!\d)(\d{1,2})[-./\s](\d{1,2})(?!\d)/);
|
||||||
|
if (match) {
|
||||||
|
let month = parseInt(match[1]), day = parseInt(match[2]);
|
||||||
|
if (month > 12) [month, day] = [day, month];
|
||||||
|
date_value = 20000000 + month * 100 + day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 期数/集数
|
||||||
|
// 第X期/集/话
|
||||||
|
match = filename.match(/第(\d+)[期集话]/);
|
||||||
|
if (match) episode_value = parseInt(match[1]);
|
||||||
|
// 第[中文数字]期/集/话
|
||||||
|
if (episode_value === Infinity) {
|
||||||
|
match = filename.match(/第([一二三四五六七八九十百千万零两]+)[期集话]/);
|
||||||
|
if (match) {
|
||||||
|
let arabic = chineseToArabic(match[1]);
|
||||||
|
if (arabic !== null) episode_value = arabic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// X集/期/话
|
||||||
|
if (episode_value === Infinity) {
|
||||||
|
match = filename.match(/(\d+)[期集话]/);
|
||||||
|
if (match) episode_value = parseInt(match[1]);
|
||||||
|
}
|
||||||
|
// [中文数字]集/期/话
|
||||||
|
if (episode_value === Infinity) {
|
||||||
|
match = filename.match(/([一二三四五六七八九十百千万零两]+)[期集话]/);
|
||||||
|
if (match) {
|
||||||
|
let arabic = chineseToArabic(match[1]);
|
||||||
|
if (arabic !== null) episode_value = arabic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// S01E01
|
||||||
|
if (episode_value === Infinity) {
|
||||||
|
match = filename.match(/[Ss](\d+)[Ee](\d+)/);
|
||||||
|
if (match) episode_value = parseInt(match[2]);
|
||||||
|
}
|
||||||
|
// E01/EP01
|
||||||
|
if (episode_value === Infinity) {
|
||||||
|
match = filename.match(/[Ee][Pp]?(\d+)/);
|
||||||
|
if (match) episode_value = parseInt(match[1]);
|
||||||
|
}
|
||||||
|
// 1x01
|
||||||
|
if (episode_value === Infinity) {
|
||||||
|
match = filename.match(/(\d+)[Xx](\d+)/);
|
||||||
|
if (match) episode_value = parseInt(match[2]);
|
||||||
|
}
|
||||||
|
// [数字]或【数字】
|
||||||
|
if (episode_value === Infinity) {
|
||||||
|
match = filename.match(/\[(\d+)\]|【(\d+)】/);
|
||||||
|
if (match) episode_value = parseInt(match[1] || match[2]);
|
||||||
|
}
|
||||||
|
// 纯数字文件名
|
||||||
|
if (episode_value === Infinity) {
|
||||||
|
if (/^\d+$/.test(file_name_without_ext)) {
|
||||||
|
episode_value = parseInt(file_name_without_ext);
|
||||||
|
} else {
|
||||||
|
match = filename.match(/(\d+)/);
|
||||||
|
if (match) episode_value = parseInt(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 上中下
|
||||||
|
if (/[上][集期话部篇]?|[集期话部篇]上/.test(filename)) segment_value = 1;
|
||||||
|
else if (/[中][集期话部篇]?|[集期话部篇]中/.test(filename)) segment_value = 2;
|
||||||
|
else if (/[下][集期话部篇]?|[集期话部篇]下/.test(filename)) segment_value = 3;
|
||||||
|
|
||||||
|
return [date_value, episode_value, segment_value, update_time];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用法:
|
||||||
|
// arr.sort((a, b) => {
|
||||||
|
// const ka = sortFileByName(a), kb = sortFileByName(b);
|
||||||
|
// for (let i = 0; i < ka.length; ++i) {
|
||||||
|
// if (ka[i] !== kb[i]) return ka[i] > kb[i] ? 1 : -1;
|
||||||
|
// }
|
||||||
|
// return 0;
|
||||||
|
// });
|
||||||
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,9 @@ class Alist:
|
|||||||
# 缓存参数
|
# 缓存参数
|
||||||
storage_mount_path = None
|
storage_mount_path = None
|
||||||
quark_root_dir = None
|
quark_root_dir = None
|
||||||
|
# 多账号支持
|
||||||
|
storage_mount_paths = []
|
||||||
|
quark_root_dirs = []
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""初始化AList插件"""
|
"""初始化AList插件"""
|
||||||
@ -28,8 +31,17 @@ class Alist:
|
|||||||
if key in kwargs:
|
if key in kwargs:
|
||||||
setattr(self, key, kwargs[key])
|
setattr(self, key, kwargs[key])
|
||||||
else:
|
else:
|
||||||
print(f"{self.plugin_name} 模块缺少必要参数: {key}")
|
pass # 不显示缺少参数的提示
|
||||||
|
|
||||||
|
# 处理多账号配置:支持数组形式的storage_id
|
||||||
|
if isinstance(self.storage_id, list):
|
||||||
|
self.storage_ids = self.storage_id
|
||||||
|
# 为了向后兼容,使用第一个ID作为默认值
|
||||||
|
self.storage_id = self.storage_ids[0] if self.storage_ids else ""
|
||||||
|
else:
|
||||||
|
# 单一配置转换为数组格式
|
||||||
|
self.storage_ids = [self.storage_id] if self.storage_id else []
|
||||||
|
|
||||||
# 检查基本配置
|
# 检查基本配置
|
||||||
if not self.url or not self.token or not self.storage_id:
|
if not self.url or not self.token or not self.storage_id:
|
||||||
return
|
return
|
||||||
@ -43,27 +55,56 @@ class Alist:
|
|||||||
|
|
||||||
# 验证AList连接
|
# 验证AList连接
|
||||||
if self.get_info():
|
if self.get_info():
|
||||||
# 解析存储ID
|
# 解析所有存储ID
|
||||||
success, result = self.storage_id_to_path(self.storage_id)
|
for i, storage_id in enumerate(self.storage_ids):
|
||||||
if success:
|
success, result = self.storage_id_to_path(storage_id)
|
||||||
self.storage_mount_path, self.quark_root_dir = result
|
if success:
|
||||||
|
mount_path, root_dir = result
|
||||||
# 确保路径格式正确
|
|
||||||
if self.quark_root_dir != "/":
|
# 确保路径格式正确
|
||||||
if not self.quark_root_dir.startswith("/"):
|
if root_dir != "/":
|
||||||
self.quark_root_dir = f"/{self.quark_root_dir}"
|
if not root_dir.startswith("/"):
|
||||||
self.quark_root_dir = self.quark_root_dir.rstrip("/")
|
root_dir = f"/{root_dir}"
|
||||||
|
root_dir = root_dir.rstrip("/")
|
||||||
if not self.storage_mount_path.startswith("/"):
|
|
||||||
self.storage_mount_path = f"/{self.storage_mount_path}"
|
if not mount_path.startswith("/"):
|
||||||
self.storage_mount_path = self.storage_mount_path.rstrip("/")
|
mount_path = f"/{mount_path}"
|
||||||
|
mount_path = mount_path.rstrip("/")
|
||||||
|
|
||||||
|
self.storage_mount_paths.append(mount_path)
|
||||||
|
self.quark_root_dirs.append(root_dir)
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
# 设置默认值(向后兼容)
|
||||||
|
self.storage_mount_path = mount_path
|
||||||
|
self.quark_root_dir = root_dir
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"AList 刷新: 存储ID [{i}] {storage_id} 解析失败")
|
||||||
|
# 添加空值保持索引对应
|
||||||
|
self.storage_mount_paths.append("")
|
||||||
|
self.quark_root_dirs.append("")
|
||||||
|
|
||||||
|
# 只要有一个存储ID解析成功就激活插件
|
||||||
|
if any(self.storage_mount_paths):
|
||||||
self.is_active = True
|
self.is_active = True
|
||||||
else:
|
else:
|
||||||
print(f"AList 刷新: 存储信息解析失败")
|
print(f"AList 刷新: 所有存储ID解析失败")
|
||||||
else:
|
else:
|
||||||
print(f"AList 刷新: 服务器连接失败")
|
print(f"AList 刷新: 服务器连接失败")
|
||||||
|
|
||||||
|
def get_storage_config(self, account_index=0):
|
||||||
|
"""根据账号索引获取对应的存储配置"""
|
||||||
|
if account_index < len(self.storage_mount_paths) and account_index < len(self.quark_root_dirs):
|
||||||
|
return self.storage_mount_paths[account_index], self.quark_root_dirs[account_index]
|
||||||
|
else:
|
||||||
|
# 如果索引超出范围,使用第一个配置作为默认值
|
||||||
|
if self.storage_mount_paths and self.quark_root_dirs:
|
||||||
|
return self.storage_mount_paths[0], self.quark_root_dirs[0]
|
||||||
|
else:
|
||||||
|
return "", ""
|
||||||
|
|
||||||
def run(self, task, **kwargs):
|
def run(self, task, **kwargs):
|
||||||
"""
|
"""
|
||||||
插件主入口,当有新文件保存时触发刷新AList目录
|
插件主入口,当有新文件保存时触发刷新AList目录
|
||||||
|
|||||||
@ -46,12 +46,10 @@ class Alist_strm_gen:
|
|||||||
missing_configs.append(key)
|
missing_configs.append(key)
|
||||||
|
|
||||||
if missing_configs:
|
if missing_configs:
|
||||||
print(f"{self.plugin_name} 模块缺少必要参数: {', '.join(missing_configs)}")
|
return # 不显示缺少参数的提示
|
||||||
return
|
|
||||||
|
|
||||||
if not self.url or not self.token or not self.storage_id:
|
if not self.url or not self.token or not self.storage_id:
|
||||||
print(f"{self.plugin_name} 模块配置不完整,请检查配置")
|
return # 不显示配置不完整的提示
|
||||||
return
|
|
||||||
|
|
||||||
# 检查 strm_save_dir 是否存在
|
# 检查 strm_save_dir 是否存在
|
||||||
if not os.path.exists(self.strm_save_dir):
|
if not os.path.exists(self.strm_save_dir):
|
||||||
|
|||||||
@ -18,11 +18,29 @@ class Plex:
|
|||||||
if key in kwargs:
|
if key in kwargs:
|
||||||
setattr(self, key, kwargs[key])
|
setattr(self, key, kwargs[key])
|
||||||
else:
|
else:
|
||||||
print(f"{self.__class__.__name__} 模块缺少必要参数: {key}")
|
pass # 不显示缺少参数的提示
|
||||||
|
|
||||||
|
# 处理多账号配置:支持数组形式的quark_root_path
|
||||||
|
if isinstance(self.quark_root_path, list):
|
||||||
|
self.quark_root_paths = self.quark_root_path
|
||||||
|
# 为了向后兼容,使用第一个路径作为默认值
|
||||||
|
self.quark_root_path = self.quark_root_paths[0] if self.quark_root_paths else ""
|
||||||
|
else:
|
||||||
|
# 单一配置转换为数组格式
|
||||||
|
self.quark_root_paths = [self.quark_root_path] if self.quark_root_path else []
|
||||||
|
|
||||||
if self.url and self.token and self.quark_root_path:
|
if self.url and self.token and self.quark_root_path:
|
||||||
if self.get_info():
|
if self.get_info():
|
||||||
self.is_active = True
|
self.is_active = True
|
||||||
|
|
||||||
|
def get_quark_root_path(self, account_index=0):
|
||||||
|
"""根据账号索引获取对应的quark_root_path"""
|
||||||
|
if account_index < len(self.quark_root_paths):
|
||||||
|
return self.quark_root_paths[account_index]
|
||||||
|
else:
|
||||||
|
# 如果索引超出范围,使用第一个路径作为默认值
|
||||||
|
return self.quark_root_paths[0] if self.quark_root_paths else ""
|
||||||
|
|
||||||
def run(self, task, **kwargs):
|
def run(self, task, **kwargs):
|
||||||
if task.get("savepath"):
|
if task.get("savepath"):
|
||||||
# 检查是否已缓存库信息
|
# 检查是否已缓存库信息
|
||||||
@ -59,10 +77,8 @@ class Plex:
|
|||||||
try:
|
try:
|
||||||
for library in self._libraries:
|
for library in self._libraries:
|
||||||
for location in library.get("Location", []):
|
for location in library.get("Location", []):
|
||||||
if (
|
location_path = location.get("path", "")
|
||||||
os.path.commonpath([folder_path, location["path"]])
|
if folder_path.startswith(location_path):
|
||||||
== location["path"]
|
|
||||||
):
|
|
||||||
refresh_url = f"{self.url}/library/sections/{library['key']}/refresh?path={folder_path}"
|
refresh_url = f"{self.url}/library/sections/{library['key']}/refresh?path={folder_path}"
|
||||||
refresh_response = requests.get(refresh_url, headers=headers)
|
refresh_response = requests.get(refresh_url, headers=headers)
|
||||||
if refresh_response.status_code == 200:
|
if refresh_response.status_code == 200:
|
||||||
|
|||||||
@ -229,6 +229,9 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
|||||||
Returns:
|
Returns:
|
||||||
int: 提取到的剧集号,如果无法提取则返回None
|
int: 提取到的剧集号,如果无法提取则返回None
|
||||||
"""
|
"""
|
||||||
|
# 首先去除文件扩展名
|
||||||
|
file_name_without_ext = os.path.splitext(filename)[0]
|
||||||
|
|
||||||
# 预处理:排除文件名中可能是日期的部分,避免误识别
|
# 预处理:排除文件名中可能是日期的部分,避免误识别
|
||||||
date_patterns = [
|
date_patterns = [
|
||||||
# YYYY-MM-DD 或 YYYY.MM.DD 或 YYYY/MM/DD 或 YYYY MM DD格式(四位年份)
|
# YYYY-MM-DD 或 YYYY.MM.DD 或 YYYY/MM/DD 或 YYYY MM DD格式(四位年份)
|
||||||
@ -245,10 +248,10 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
|||||||
r'(?<!\d)(\d{1,2})[-./\s](\d{1,2})(?!\d)',
|
r'(?<!\d)(\d{1,2})[-./\s](\d{1,2})(?!\d)',
|
||||||
]
|
]
|
||||||
|
|
||||||
# 从文件名中移除日期部分,创建一个不含日期的文件名副本用于提取剧集号
|
# 从不含扩展名的文件名中移除日期部分
|
||||||
filename_without_dates = filename
|
filename_without_dates = file_name_without_ext
|
||||||
for pattern in date_patterns:
|
for pattern in date_patterns:
|
||||||
matches = re.finditer(pattern, filename)
|
matches = re.finditer(pattern, filename_without_dates)
|
||||||
for match in matches:
|
for match in matches:
|
||||||
# 检查匹配的内容是否确实是日期
|
# 检查匹配的内容是否确实是日期
|
||||||
date_str = match.group(0)
|
date_str = match.group(0)
|
||||||
@ -369,13 +372,9 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
|||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 如果从不含日期的文件名中没有找到剧集号,尝试从原始文件名中提取
|
|
||||||
# 这是为了兼容某些特殊情况,但要检查提取的数字不是日期
|
|
||||||
file_name_without_ext = os.path.splitext(filename)[0]
|
|
||||||
|
|
||||||
# 如果文件名是纯数字,且不是日期格式,则可能是剧集号
|
# 如果文件名是纯数字,且不是日期格式,则可能是剧集号
|
||||||
if file_name_without_ext.isdigit() and not is_date_format(file_name_without_ext):
|
if filename_without_dates.isdigit() and not is_date_format(filename_without_dates):
|
||||||
return int(file_name_without_ext)
|
return int(filename_without_dates)
|
||||||
|
|
||||||
# 最后尝试提取任何数字,但要排除日期可能性
|
# 最后尝试提取任何数字,但要排除日期可能性
|
||||||
num_match = re.search(r'(\d+)', filename_without_dates)
|
num_match = re.search(r'(\d+)', filename_without_dates)
|
||||||
@ -933,6 +932,9 @@ class Quark:
|
|||||||
def ls_dir(self, pdir_fid, **kwargs):
|
def ls_dir(self, pdir_fid, **kwargs):
|
||||||
file_list = []
|
file_list = []
|
||||||
page = 1
|
page = 1
|
||||||
|
# 优化:增加每页大小,减少API调用次数
|
||||||
|
page_size = kwargs.get("page_size", 200) # 从50增加到200
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
url = f"{self.BASE_URL}/1/clouddrive/file/sort"
|
url = f"{self.BASE_URL}/1/clouddrive/file/sort"
|
||||||
querystring = {
|
querystring = {
|
||||||
@ -941,7 +943,7 @@ class Quark:
|
|||||||
"uc_param_str": "",
|
"uc_param_str": "",
|
||||||
"pdir_fid": pdir_fid,
|
"pdir_fid": pdir_fid,
|
||||||
"_page": page,
|
"_page": page,
|
||||||
"_size": "50",
|
"_size": str(page_size),
|
||||||
"_fetch_total": "1",
|
"_fetch_total": "1",
|
||||||
"_fetch_sub_dirs": "0",
|
"_fetch_sub_dirs": "0",
|
||||||
"_sort": "file_type:asc,updated_at:desc",
|
"_sort": "file_type:asc,updated_at:desc",
|
||||||
@ -959,6 +961,48 @@ class Quark:
|
|||||||
break
|
break
|
||||||
return file_list
|
return file_list
|
||||||
|
|
||||||
|
def get_paths(self, folder_id):
|
||||||
|
"""
|
||||||
|
获取指定文件夹ID的完整路径信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
folder_id: 文件夹ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 路径信息列表,每个元素包含fid和name
|
||||||
|
"""
|
||||||
|
if folder_id == "0" or folder_id == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
url = f"{self.BASE_URL}/1/clouddrive/file/sort"
|
||||||
|
querystring = {
|
||||||
|
"pr": "ucpro",
|
||||||
|
"fr": "pc",
|
||||||
|
"uc_param_str": "",
|
||||||
|
"pdir_fid": folder_id,
|
||||||
|
"_page": 1,
|
||||||
|
"_size": "50",
|
||||||
|
"_fetch_total": "1",
|
||||||
|
"_fetch_sub_dirs": "0",
|
||||||
|
"_sort": "file_type:asc,updated_at:desc",
|
||||||
|
"_fetch_full_path": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self._send_request("GET", url, params=querystring).json()
|
||||||
|
if response["code"] == 0 and "full_path" in response["data"]:
|
||||||
|
paths = []
|
||||||
|
for item in response["data"]["full_path"]:
|
||||||
|
paths.append({
|
||||||
|
"fid": item["fid"],
|
||||||
|
"name": item["file_name"]
|
||||||
|
})
|
||||||
|
return paths
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取文件夹路径出错: {str(e)}")
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
def save_file(self, fid_list, fid_token_list, to_pdir_fid, pwd_id, stoken):
|
def save_file(self, fid_list, fid_token_list, to_pdir_fid, pwd_id, stoken):
|
||||||
url = f"{self.BASE_URL}/1/clouddrive/share/sharepage/save"
|
url = f"{self.BASE_URL}/1/clouddrive/share/sharepage/save"
|
||||||
querystring = {
|
querystring = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user