diff --git a/README.md b/README.md
index a2ca6ac..29d76d5 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,8 @@
- **数据库**:引入 SQLite 数据库,记录和管理所有转存历史,便于查询和追踪。
- **转存记录**:支持通过 WebUI 的转存记录页面查看、查询历史转存记录的相关信息,支持删除转存记录。
- **WebUI**:对整个 WebUI 进行了重塑,增加了更多实用功能,如文件选择和预览界面的排序功能、资源搜索的过滤功能、TMDB 和豆瓣搜索功能、页面视图切换功能、账号设置功能等等。
+- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘文件,也不会重复转存。
+- **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。
本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG,不一定会被修复。若你要使用本项目,请知晓本人不是程序员,我无法保证本项目的稳定性,如果你在使用过程中发现了 BUG,可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。
@@ -32,7 +34,7 @@
- 文件管理
- [x] 目标目录不存在时自动新建
- - [x] 跳过已转存过的文件
+ - [x] 跳过已转存过的文件(**即使删除网盘文件,也不会重复转存**)
- [x] **过滤不需要转存的文件或文件夹**
- [x] 转存后文件名整理(正则命名、**顺序命名**、**剧集命名**)
- [x] 可选忽略文件后缀
@@ -53,6 +55,7 @@
- [x] 每日签到领空间 [?](https://github.com/x1ao4/quark-auto-save-x/wiki/使用技巧集锦#每日签到领空间)
- [x] 支持多个通知推送渠道 [?](https://github.com/x1ao4/quark-auto-save-x/wiki/通知推送服务配置)
- [x] 支持多账号(多账号签到,仅首账号转存)
+ - [x] 支持网盘文件下载、strm 文件生成等功能 [?](https://github.com/x1ao4/quark-auto-save-x/wiki/插件配置)
## 部署
### Docker 部署
diff --git a/app/run.py b/app/run.py
index 596bbd0..851f311 100644
--- a/app/run.py
+++ b/app/run.py
@@ -187,6 +187,62 @@ def get_data():
return jsonify({"success": True, "data": data})
+def sync_task_plugins_config():
+ """同步更新所有任务的插件配置
+
+ 1. 检查每个任务的插件配置
+ 2. 如果插件配置不存在,使用默认配置
+ 3. 如果插件配置存在但缺少新的配置项,添加默认值
+ 4. 保留原有的自定义配置
+ 5. 只处理已启用的插件(通过PLUGIN_FLAGS检查)
+ 6. 清理被禁用插件的配置
+ """
+ global config_data, task_plugins_config_default
+
+ # 如果没有任务列表,直接返回
+ if not config_data.get("tasklist"):
+ return
+
+ # 获取禁用的插件列表
+ disabled_plugins = set()
+ if PLUGIN_FLAGS:
+ disabled_plugins = {name.lstrip('-') for name in PLUGIN_FLAGS.split(',')}
+
+ # 遍历所有任务
+ for task in config_data["tasklist"]:
+ # 确保任务有addition字段
+ if "addition" not in task:
+ task["addition"] = {}
+
+ # 清理被禁用插件的配置
+ for plugin_name in list(task["addition"].keys()):
+ if plugin_name in disabled_plugins:
+ del task["addition"][plugin_name]
+
+ # 遍历所有插件的默认配置
+ for plugin_name, default_config in task_plugins_config_default.items():
+ # 跳过被禁用的插件
+ if plugin_name in disabled_plugins:
+ continue
+
+ # 如果任务中没有该插件的配置,添加默认配置
+ if plugin_name not in task["addition"]:
+ task["addition"][plugin_name] = default_config.copy()
+ else:
+ # 如果任务中有该插件的配置,检查是否有新的配置项
+ current_config = task["addition"][plugin_name]
+ # 确保current_config是字典类型
+ if not isinstance(current_config, dict):
+ # 如果不是字典类型,使用默认配置
+ task["addition"][plugin_name] = default_config.copy()
+ continue
+
+ # 遍历默认配置的每个键值对
+ for key, default_value in default_config.items():
+ if key not in current_config:
+ current_config[key] = default_value
+
+
# 更新数据
@app.route("/update", methods=["POST"])
def update():
@@ -202,6 +258,10 @@ def update():
config_data["webui"]["password"] = value.get("password", config_data["webui"]["password"])
else:
config_data.update({key: value})
+
+ # 同步更新任务的插件配置
+ sync_task_plugins_config()
+
Config.write_json(CONFIG_PATH, config_data)
# 更新session token,确保当前会话在用户名密码更改后仍然有效
session["token"] = get_login_token()
@@ -650,6 +710,55 @@ def delete_file():
account = Quark(config_data["cookie"][0], 0)
if fid := request.json.get("fid"):
response = account.delete([fid])
+
+ # 处理delete_records参数
+ if request.json.get("delete_records") and response.get("code") == 0:
+ try:
+ # 初始化数据库
+ db = RecordDB()
+
+ # 获取save_path参数
+ save_path = request.json.get("save_path", "")
+
+ # 如果没有提供save_path,则不删除任何记录
+ if not save_path:
+ response["deleted_records"] = 0
+ # logging.info(f">>> 删除文件 {fid} 但未提供save_path,不删除任何记录")
+ return jsonify(response)
+
+ # 查询与该文件ID和save_path相关的所有记录
+ cursor = db.conn.cursor()
+
+ # 使用file_id和save_path进行精确匹配
+ cursor.execute("SELECT id FROM transfer_records WHERE file_id = ? AND save_path = ?", (fid, save_path))
+ record_ids = [row[0] for row in cursor.fetchall()]
+
+ # 如果没有找到匹配的file_id记录,尝试通过文件名查找
+ if not record_ids:
+ # 获取文件名(如果有的话)
+ file_name = request.json.get("file_name", "")
+ if file_name:
+ # 使用文件名和save_path进行精确匹配
+ cursor.execute("""
+ SELECT id FROM transfer_records
+ WHERE (original_name = ? OR renamed_to = ?)
+ AND save_path = ?
+ """, (file_name, file_name, save_path))
+
+ record_ids = [row[0] for row in cursor.fetchall()]
+
+ # 删除找到的所有记录
+ deleted_count = 0
+ for record_id in record_ids:
+ deleted_count += db.delete_record(record_id)
+
+ # 添加删除记录的信息到响应中
+ response["deleted_records"] = deleted_count
+ # logging.info(f">>> 删除文件 {fid} 同时删除了 {deleted_count} 条相关记录")
+
+ except Exception as e:
+ logging.error(f">>> 删除记录时出错: {str(e)}")
+ # 不影响主流程,即使删除记录失败也返回文件删除成功
else:
response = {"success": False, "message": "缺失必要字段: fid"}
return jsonify(response)
@@ -766,6 +875,22 @@ def init():
_, plugins_config_default, task_plugins_config_default = Config.load_plugins()
plugins_config_default.update(config_data.get("plugins", {}))
config_data["plugins"] = plugins_config_default
+
+ # 获取禁用的插件列表
+ disabled_plugins = set()
+ if PLUGIN_FLAGS:
+ disabled_plugins = {name.lstrip('-') for name in PLUGIN_FLAGS.split(',')}
+
+ # 清理所有任务中被禁用插件的配置
+ if config_data.get("tasklist"):
+ for task in config_data["tasklist"]:
+ if "addition" in task:
+ for plugin_name in list(task["addition"].keys()):
+ if plugin_name in disabled_plugins:
+ del task["addition"][plugin_name]
+
+ # 同步更新任务的插件配置
+ sync_task_plugins_config()
# 更新配置
Config.write_json(CONFIG_PATH, config_data)
@@ -961,6 +1086,84 @@ def get_user_info():
return jsonify({"success": True, "data": user_info_list})
+# 重置文件夹(删除文件夹内所有文件和相关记录)
+@app.route("/reset_folder", methods=["POST"])
+def reset_folder():
+ if not is_login():
+ return jsonify({"success": False, "message": "未登录"})
+
+ # 获取请求参数
+ save_path = request.json.get("save_path", "")
+ task_name = request.json.get("task_name", "")
+
+ if not save_path:
+ return jsonify({"success": False, "message": "保存路径不能为空"})
+
+ try:
+ # 初始化夸克网盘客户端
+ account = Quark(config_data["cookie"][0], 0)
+
+ # 1. 获取文件夹ID
+ # 先检查是否已有缓存的文件夹ID
+ folder_fid = account.savepath_fid.get(save_path)
+
+ # 如果没有缓存的ID,则尝试创建文件夹以获取ID
+ if not folder_fid:
+ mkdir_result = account.mkdir(save_path)
+ if mkdir_result.get("code") == 0:
+ folder_fid = mkdir_result["data"]["fid"]
+ account.savepath_fid[save_path] = folder_fid
+ else:
+ return jsonify({"success": False, "message": f"获取文件夹ID失败: {mkdir_result.get('message', '未知错误')}"})
+
+ # 2. 获取文件夹内的所有文件
+ file_list = account.ls_dir(folder_fid)
+ if isinstance(file_list, dict) and file_list.get("error"):
+ return jsonify({"success": False, "message": f"获取文件列表失败: {file_list.get('error', '未知错误')}"})
+
+ # 收集所有文件ID
+ file_ids = []
+ for item in file_list:
+ file_ids.append(item["fid"])
+
+ # 3. 删除所有文件
+ deleted_files = 0
+ if file_ids:
+ delete_result = account.delete(file_ids)
+ if delete_result.get("code") == 0:
+ deleted_files = len(file_ids)
+
+ # 4. 删除相关的历史记录
+ deleted_records = 0
+ try:
+ # 初始化数据库
+ db = RecordDB()
+
+ # 查询与该保存路径相关的所有记录
+ cursor = db.conn.cursor()
+ cursor.execute("SELECT id FROM transfer_records WHERE save_path = ?", (save_path,))
+ record_ids = [row[0] for row in cursor.fetchall()]
+
+ # 删除找到的所有记录
+ for record_id in record_ids:
+ deleted_records += db.delete_record(record_id)
+
+ except Exception as e:
+ logging.error(f">>> 删除记录时出错: {str(e)}")
+ # 即使删除记录失败,也返回文件删除成功
+
+ return jsonify({
+ "success": True,
+ "message": f"重置成功,删除了 {deleted_files} 个文件和 {deleted_records} 条记录",
+ "deleted_files": deleted_files,
+ "deleted_records": deleted_records
+ })
+
+ except Exception as e:
+ logging.error(f">>> 重置文件夹时出错: {str(e)}")
+ return jsonify({"success": False, "message": f"重置文件夹时出错: {str(e)}"})
+
+
if __name__ == "__main__":
init()
reload_tasks()
diff --git a/app/sdk/db.py b/app/sdk/db.py
index 302d8c5..316de4e 100644
--- a/app/sdk/db.py
+++ b/app/sdk/db.py
@@ -31,9 +31,17 @@ class RecordDB:
resolution TEXT,
modify_date INTEGER NOT NULL,
file_id TEXT,
- file_type TEXT
+ file_type TEXT,
+ save_path TEXT
)
''')
+
+ # 检查save_path字段是否存在,如果不存在则添加
+ cursor.execute("PRAGMA table_info(transfer_records)")
+ columns = [column[1] for column in cursor.fetchall()]
+ if 'save_path' not in columns:
+ cursor.execute('ALTER TABLE transfer_records ADD COLUMN save_path TEXT')
+
self.conn.commit()
def close(self):
@@ -41,19 +49,19 @@ class RecordDB:
self.conn.close()
def add_record(self, task_name, original_name, renamed_to, file_size, modify_date,
- duration="", resolution="", file_id="", file_type=""):
+ duration="", resolution="", file_id="", file_type="", save_path=""):
"""添加一条转存记录"""
cursor = self.conn.cursor()
cursor.execute(
"INSERT INTO transfer_records (transfer_time, task_name, original_name, renamed_to, file_size, "
- "duration, resolution, modify_date, file_id, file_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ "duration, resolution, modify_date, file_id, file_type, save_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(int(time.time()), task_name, original_name, renamed_to, file_size,
- duration, resolution, modify_date, file_id, file_type)
+ duration, resolution, modify_date, file_id, file_type, save_path)
)
self.conn.commit()
return cursor.lastrowid
- def update_renamed_to(self, file_id, original_name, renamed_to, task_name=""):
+ def update_renamed_to(self, file_id, original_name, renamed_to, task_name="", save_path=""):
"""更新最近一条记录的renamed_to字段
Args:
@@ -61,6 +69,7 @@ class RecordDB:
original_name: 原文件名
renamed_to: 重命名后的文件名
task_name: 任务名称,可选项,如提供则作为附加筛选条件
+ save_path: 保存路径,可选项,如提供则同时更新保存路径
Returns:
更新的记录数量
@@ -97,11 +106,17 @@ class RecordDB:
if result:
record_id = result[0]
- # 更新记录
- cursor.execute(
- "UPDATE transfer_records SET renamed_to = ? WHERE id = ?",
- (renamed_to, record_id)
- )
+ # 根据是否提供save_path决定更新哪些字段
+ if save_path:
+ cursor.execute(
+ "UPDATE transfer_records SET renamed_to = ?, save_path = ? WHERE id = ?",
+ (renamed_to, save_path, record_id)
+ )
+ else:
+ cursor.execute(
+ "UPDATE transfer_records SET renamed_to = ? WHERE id = ?",
+ (renamed_to, record_id)
+ )
self.conn.commit()
return cursor.rowcount
@@ -124,7 +139,7 @@ class RecordDB:
# 构建SQL查询
valid_columns = ["transfer_time", "task_name", "original_name", "renamed_to",
- "file_size", "duration", "resolution", "modify_date"]
+ "file_size", "duration", "resolution", "modify_date", "save_path"]
if sort_by not in valid_columns:
sort_by = "transfer_time"
@@ -140,7 +155,8 @@ class RecordDB:
params.append(task_name_filter)
if keyword_filter:
- where_clauses.append("task_name LIKE ?")
+ where_clauses.append("(task_name LIKE ? OR original_name LIKE ?)")
+ params.append(f"%{keyword_filter}%")
params.append(f"%{keyword_filter}%")
where_clause = " AND ".join(where_clauses)
@@ -192,4 +208,33 @@ class RecordDB:
cursor = self.conn.cursor()
cursor.execute("DELETE FROM transfer_records WHERE id = ?", (record_id,))
self.conn.commit()
- return cursor.rowcount
\ No newline at end of file
+ return cursor.rowcount
+
+ def get_records_by_save_path(self, save_path, include_subpaths=False):
+ """根据保存路径查询记录
+
+ Args:
+ save_path: 要查询的保存路径
+ include_subpaths: 是否包含子路径下的文件
+
+ Returns:
+ 匹配的记录列表
+ """
+ cursor = self.conn.cursor()
+
+ if include_subpaths:
+ # 如果包含子路径,使用LIKE查询
+ query = "SELECT * FROM transfer_records WHERE save_path LIKE ? ORDER BY transfer_time DESC"
+ cursor.execute(query, [f"{save_path}%"])
+ else:
+ # 精确匹配路径
+ query = "SELECT * FROM transfer_records WHERE save_path = ? ORDER BY transfer_time DESC"
+ cursor.execute(query, [save_path])
+
+ records = cursor.fetchall()
+
+ # 将结果转换为字典列表
+ if records:
+ columns = [col[0] for col in cursor.description]
+ return [dict(zip(columns, row)) for row in records]
+ return []
\ No newline at end of file
diff --git a/app/static/css/main.css b/app/static/css/main.css
index f4a70c1..1c1d86c 100644
--- a/app/static/css/main.css
+++ b/app/static/css/main.css
@@ -805,6 +805,11 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child {
font-size: 0.98rem;
}
+/* 重置文件夹图标样式 */
+.bi-folder-x {
+ font-size: 0.98rem;
+}
+
/* 恢复图标样式 */
.bi-reply {
color: var(--dark-text-color);
@@ -1120,7 +1125,7 @@ textarea.form-control {
border-bottom-right-radius: 0;
}
-/* --------------- 扩展按钮样式 --------------- */
+/* --------------- 转存记录展开按钮样式 --------------- */
.expand-button {
position: absolute;
right: 5px;
@@ -1180,8 +1185,8 @@ textarea.form-control {
vertical-align: middle;
border-bottom: 1px solid var(--border-color); /* 修改底部边框为1px */
border-top: 1px solid var(--border-color); /* 添加上边框线 */
- padding-top: 8px; /* 增加上内边距 */
- padding-bottom: 8px; /* 增加下内边距 */
+ padding-top: 8.5px; /* 增加上内边距 */
+ padding-bottom: 8.5px; /* 增加下内边距 */
padding-left: 9px !important; /* 表头左内边距,与按钮一致 */
padding-right: 9px !important; /* 表头右内边距,与按钮一致 */
background-color: var(--button-gray-background-color); /* 表头背景色 */
@@ -1206,6 +1211,7 @@ textarea.form-control {
padding-left: 9px !important; /* 单元格左内边距,与按钮一致 */
padding-right: 9px !important; /* 单元格右内边距,与按钮一致 */
border-bottom: 1px solid var(--border-color); /* 单元格分割线颜色 */
+ transform: translateY(0.5px); /* 文本下移0.5px,不影响元素实际高度 */
}
/* 表格行悬停样式 */
@@ -1273,8 +1279,8 @@ button.close:focus,
align-items: center;
border-width: 1px;
border-style: solid;
- height: 35.5px;
- min-height: 35.5px;
+ height: 36px;
+ min-height: 36px;
}
/* 任务列表中的警告框样式 */
@@ -1499,7 +1505,7 @@ button.close:focus,
background-color: var(--button-gray-background-color);
border-radius: 0px;
font-size: 0.85rem;
- padding: 6px 12px;
+ padding: 6.5px 12px 6px 12px; /* 上右下左内边距 */
margin-bottom: 8px;
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
@@ -1591,7 +1597,7 @@ button.close:focus,
border-bottom: 1px solid var(--border-color);
color: var(--dark-text-color);
font-weight: 600;
- padding: 6.5px 12.5px !important; /* 表头上下边距,左右边距 */
+ padding: 7px 12.5px 6.5px 12.5px !important; /* 表头上右下左内边距 */
font-size: 0.85rem;
white-space: nowrap;
position: sticky;
@@ -1632,10 +1638,11 @@ button.close:focus,
/* 操作列 - 固定宽度 */
#fileSelectModal .table .col-action {
- width: 53px;
- min-width: 53px;
- max-width: 53px;
- text-align: center;
+ width: 188px;
+ min-width: 188px;
+ max-width: 188px;
+ text-align: left;
+ padding-left: 12px !important;
}
/* 确保单元格内容溢出时正确显示 */
@@ -1655,7 +1662,7 @@ button.close:focus,
}
#fileSelectModal .table td {
- padding: 6px 12px !important; /* 单元格上下边距,左右边距 */
+ padding: 5.5px 12.5px 7px 12px !important; /* 单元格上右下左内边距 */
vertical-align: middle;
border-bottom: 1px solid var(--border-color);
color: var(--dark-text-color);
@@ -1685,12 +1692,16 @@ button.close:focus,
color: #ffc107;
font-size: 0.9rem;
margin-right: 5px;
+ position: relative;
+ top: 1px; /* 负值向上移动,正值向下移动 */
}
#fileSelectModal .bi-file-earmark {
color: var(--dark-text-color);
font-size: 0.9rem;
margin-right: 5px;
+ position: relative;
+ top: 0px !important; /* 负值向上移动,正值向下移动 */
}
/* 弹窗删除链接样式 */
@@ -1717,8 +1728,14 @@ button.close:focus,
padding-right: 12px; /* 右边距 */
}
+/* 添加文件选择模态框左下角文件信息文本的左边距样式 */
+#fileSelectModal .modal-footer .file-selection-info {
+ margin-left: 0px; /* 与表格左边距保持一致 */
+ font-size: 0.85rem !important; /* 覆盖内联样式 */
+}
+
#fileSelectModal .modal-footer span {
- font-size: 0.9rem;
+ font-size: 0.85rem;
color: var(--dark-text-color);
margin-right: auto;
}
@@ -1781,7 +1798,7 @@ button.close:focus,
background-color: transparent !important;
color: inherit !important;
font-size: inherit !important;
- font-weight: bold !important;
+ font-weight: normal !important;
padding: 0 !important;
margin: 0 !important;
display: inline-flex !important;
@@ -3294,7 +3311,7 @@ div[id^="collapse_"][id*="plugin"] .input-group {
background-color: transparent;
color: inherit;
font-size: inherit;
- font-weight: inherit;
+ font-weight: normal;
padding: 0;
margin: 0;
}
@@ -3565,11 +3582,11 @@ input::-moz-list-button {
/* 针对选择保存到的文件夹模式 - 带操作列的表格 */
#fileSelectModal[data-modal-type="target"] .breadcrumb {
- min-width: 513px; /* 4列表格总宽度: 230px + 90px + 140px + 53px */
+ min-width: 648px; /* 4列表格总宽度: 230px + 90px + 140px + 188px */
}
#fileSelectModal[data-modal-type="target"] .table {
- width: 513px;
+ width: 648px;
}
/* 针对命名预览模式 - 2列表格 */
@@ -3665,7 +3682,7 @@ input::-moz-list-button {
#fileSelectModal .expand-button {
position: absolute;
right: 5px;
- top: 8px; /* 固定高度,不再使用百分比定位 */
+ top: 7.5px; /* 固定高度,不再使用百分比定位 */
transform: none; /* 移除垂直居中转换 */
cursor: pointer;
opacity: 0;
@@ -3774,7 +3791,13 @@ input.no-spinner {
background-color: var(--button-gray-background-color) !important;
}
-/* 删除按钮样式 */
+/* 文件大小值的文本位置调整 */
+.file-size-cell .file-size-value {
+ transform: translateY(-1px); /* 文本下移 */
+ display: inline-block; /* 确保transform生效 */
+}
+
+/* 转存记录删除按钮样式 */
.delete-record-btn {
color: #dc3545;
cursor: pointer;
@@ -3786,6 +3809,8 @@ input.no-spinner {
border-radius: 4px;
transition: background-color 0.2s ease;
visibility: hidden; /* 默认隐藏 */
+ position: relative; /* 添加相对定位 */
+ top: -0.5px; /* 上移 */
}
/* 删除按钮图标大小 */
@@ -3853,3 +3878,46 @@ tr.selected-record .file-size-cell .delete-record-btn,
table.selectable-records .expand-button:hover {
background-color: #fff !important; /* 保持展开按钮原有的白色背景 */
}
+
+/* 模态框表格单元格文本垂直对齐调整 */
+#fileSelectModal .table td {
+ transform: translateY(0.5px); /* 文本下移0.5px,不影响元素实际高度 */
+}
+
+/* 确保文件大小、修改日期和操作列的文本位置与文件名一致 */
+#fileSelectModal .table td:not(.col-filename) {
+ transform: translateY(1.5px); /* 非文件名列文本下移1.5px */
+}
+
+/* 特别调整红色"×"符号的位置 */
+#fileSelectModal .table td.col-rename.text-danger div:not(.expand-button) {
+ position: relative;
+ top: -1px; /* 将"×"标记上移1px */
+}
+
+/* 文件名列已经通过图标微调过,保持原样或细微调整 */
+#fileSelectModal .bi-folder-fill {
+ color: #ffc107;
+ font-size: 0.9rem;
+ margin-right: 5px;
+ position: relative;
+ top: 1px; /* 负值向上移动,正值向下移动 */
+}
+
+#fileSelectModal .bi-file-earmark {
+ color: var(--dark-text-color);
+ font-size: 0.9rem;
+ margin-right: 5px;
+ position: relative;
+ top: 0px; /* 负值向上移动,正值向下移动 */
+}
+
+/* 添加选中文件的样式 */
+.selected-file {
+ background-color: var(--button-gray-background-color);
+}
+
+/* 确保文件选择模态框中的表格行在选中状态下保持可见 */
+#fileSelectModal .table tr.selected-file:hover {
+ background-color: var(--button-gray-background-color);
+}
diff --git a/app/templates/index.html b/app/templates/index.html
index fe2363b..eb0e9bd 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -554,6 +554,7 @@
+
@@ -912,7 +913,14 @@
-
+
|
{{ file.include_items }} 项 |
{{file.size | size}} |
{{file.updated_at | ts2date}} |
- 删除 |
+
+ 删除文件
+ 删除文件和记录
+ |
@@ -953,6 +964,9 @@
@@ -1046,7 +1060,9 @@
selectShare: true,
previewRegex: false,
sortBy: "updated_at", // 默认排序字段
- sortOrder: "desc" // 默认排序顺序
+ sortOrder: "desc", // 默认排序顺序
+ selectedFiles: [], // 存储选中的文件ID
+ lastSelectedFileIndex: -1 // 记录最后选择的文件索引
},
historyParams: {
sortBy: "transfer_time",
@@ -1203,13 +1219,20 @@
this.checkNewVersion();
this.fetchUserInfo(); // 获取用户信息
- // 从本地存储中恢复之前的标签页状态
+ // 添加点击事件监听
+ document.addEventListener('click', this.handleOutsideClick);
+ document.addEventListener('click', this.handleModalOutsideClick);
+
+ // 添加模态框关闭事件监听
+ $('#fileSelectModal').on('hidden.bs.modal', () => {
+ this.fileSelect.selectedFiles = [];
+ this.fileSelect.lastSelectedFileIndex = -1;
+ });
+
+ // 检查本地存储中的标签页状态
const savedTab = localStorage.getItem('quarkAutoSave_activeTab');
if (savedTab) {
this.activeTab = savedTab;
- } else {
- // 默认显示任务列表页面
- this.activeTab = 'tasklist';
}
// 从本地存储中恢复侧边栏折叠状态
@@ -1251,6 +1274,15 @@
// 添加点击事件监听器,用于在点击表格外区域时取消选择记录
document.addEventListener('click', this.handleOutsideClick);
+ // 添加点击事件监听器,用于在点击模态框表格外区域时取消选择文件
+ document.addEventListener('click', this.handleModalOutsideClick);
+
+ // 添加模态框关闭事件监听,清空选中文件列表
+ $('#fileSelectModal').on('hidden.bs.modal', () => {
+ this.fileSelect.selectedFiles = [];
+ this.fileSelect.lastSelectedFileIndex = -1;
+ });
+
window.addEventListener('beforeunload', this.handleBeforeUnload);
// 监听模态框显示事件,检查滚动条状态
@@ -2260,18 +2292,57 @@
this.$delete(this.formData.magic_regex, key);
}
},
- deleteFile(fid, fname, isDir) {
- if (fid != "" && confirm(`确定要删除${isDir ? '目录' : '文件'} [${fname}] 吗?`))
- axios.post('/delete_file', {
- fid: fid
- }).then(response => {
- if (response.data.code == 0) {
- this.fileSelect.fileList = this.fileSelect.fileList.filter(item => item.fid != fid);
+ deleteFile(fid, fname, isDir, deleteRecords = false) {
+ // 根据是否删除记录显示不同的确认提示
+ let confirmMessage = deleteRecords
+ ? `确定要删除此项目及其关联记录吗?`
+ : `确定要删除此项目吗?`;
+
+ if (!confirm(confirmMessage)) {
+ return;
+ }
+
+ // 获取当前路径作为save_path参数
+ let save_path = "";
+ if (this.fileSelect && this.fileSelect.paths) {
+ save_path = this.fileSelect.paths.map(item => item.name).join("/");
+ }
+
+ axios.post('/delete_file', { fid: fid, file_name: fname, delete_records: deleteRecords, save_path: save_path })
+ .then(response => {
+ if (response.data.code === 0) {
+ // 从列表中移除文件
+ this.fileSelect.fileList = this.fileSelect.fileList.filter(item => item.fid !== fid);
+
+ // 如果文件在选中列表中,也从选中列表中移除
+ if (this.fileSelect.selectedFiles.includes(fid)) {
+ this.fileSelect.selectedFiles = this.fileSelect.selectedFiles.filter(id => id !== fid);
+
+ // 如果选中列表为空,重置最后选择的索引
+ if (this.fileSelect.selectedFiles.length === 0) {
+ this.fileSelect.lastSelectedFileIndex = -1;
+ }
+ }
+
+ // 显示成功消息,根据是否删除记录显示不同的消息
+ if (deleteRecords) {
+ const deletedRecords = response.data.deleted_records || 0;
+ this.showToast(`成功删除 1 个项目${deletedRecords > 0 ? `及其关联的 ${deletedRecords} 条记录` : ''}`);
+ } else {
+ this.showToast('成功删除 1 个项目');
+ }
+
+ // 如果同时删除了记录,无论当前在哪个页面,都刷新历史记录
+ if (deleteRecords) {
+ this.loadHistoryRecords();
+ }
} else {
- alert('删除失败:' + response.data.message);
+ alert('删除失败: ' + response.data.message);
}
- }).catch(error => {
- // 错误处理
+ })
+ .catch(error => {
+ console.error('删除项目出错:', error);
+ alert('删除项目出错: ' + (error.response?.data?.message || error.message));
});
},
getSavepathDetail(params = 0) {
@@ -3288,6 +3359,231 @@
this.lastSelectedRecordIndex = -1;
}
},
+ selectFileItem(event, fileId) {
+ // 如果是在预览模式或选择分享模式,不允许选择
+ if (this.fileSelect.previewRegex || this.fileSelect.selectShare) return;
+
+ // 获取当前文件的索引
+ const currentIndex = this.fileSelect.fileList.findIndex(file => file.fid === fileId);
+ if (currentIndex === -1) return;
+
+ // 如果是Shift+点击,选择范围
+ if (event.shiftKey && this.fileSelect.selectedFiles.length > 0) {
+ // 找出所有已选中文件的索引
+ const selectedIndices = this.fileSelect.selectedFiles.map(id =>
+ this.fileSelect.fileList.findIndex(file => file.fid === id)
+ ).filter(index => index !== -1); // 过滤掉未找到的文件
+
+ if (selectedIndices.length > 0) {
+ // 找出已选中文件中最靠前的索引
+ const earliestSelectedIndex = Math.min(...selectedIndices);
+ // 确定最终的选择范围
+ const startIndex = Math.min(earliestSelectedIndex, currentIndex);
+ const endIndex = Math.max(earliestSelectedIndex, currentIndex);
+
+ // 获取范围内所有文件的ID(排除文件夹)
+ this.fileSelect.selectedFiles = this.fileSelect.fileList
+ .slice(startIndex, endIndex + 1)
+ .filter(file => !file.dir) // 只选择文件,不选择文件夹
+ .map(file => file.fid);
+ } else {
+ // 如果没有有效的选中文件(可能是由于列表刷新),则只选择当前文件
+ const file = this.fileSelect.fileList[currentIndex];
+ if (!file.dir) { // 不选择文件夹
+ this.fileSelect.selectedFiles = [fileId];
+ }
+ }
+ }
+ // 如果是Ctrl/Cmd+点击,切换单个文件选择状态
+ else if (event.ctrlKey || event.metaKey) {
+ const file = this.fileSelect.fileList[currentIndex];
+ if (file.dir) return; // 不允许选择文件夹
+
+ if (this.fileSelect.selectedFiles.includes(fileId)) {
+ this.fileSelect.selectedFiles = this.fileSelect.selectedFiles.filter(id => id !== fileId);
+ } else {
+ this.fileSelect.selectedFiles.push(fileId);
+ }
+ }
+ // 普通点击,清除当前选择并选择当前文件
+ else {
+ const file = this.fileSelect.fileList[currentIndex];
+ if (file.dir) return; // 不允许选择文件夹
+
+ if (this.fileSelect.selectedFiles.length === 1 && this.fileSelect.selectedFiles.includes(fileId)) {
+ this.fileSelect.selectedFiles = [];
+ } else {
+ this.fileSelect.selectedFiles = [fileId];
+ }
+ }
+
+ // 更新最后选择的文件索引,只有在有选择文件时才更新
+ if (this.fileSelect.selectedFiles.length > 0) {
+ this.fileSelect.lastSelectedFileIndex = currentIndex;
+ } else {
+ this.fileSelect.lastSelectedFileIndex = -1;
+ }
+
+ // 阻止事件冒泡
+ event.stopPropagation();
+ },
+ deleteSelectedFiles(clickedFid, clickedFname, isDir, deleteRecords = false) {
+ // 如果是文件夹或者没有选中的文件,则按原来的方式删除单个文件
+ if (isDir || this.fileSelect.selectedFiles.length === 0) {
+ this.deleteFile(clickedFid, clickedFname, isDir, deleteRecords);
+ return;
+ }
+
+ // 如果点击的文件不在选中列表中,也按原来的方式删除单个文件
+ if (!this.fileSelect.selectedFiles.includes(clickedFid)) {
+ this.deleteFile(clickedFid, clickedFname, isDir, deleteRecords);
+ return;
+ }
+
+ // 多选删除
+ const selectedCount = this.fileSelect.selectedFiles.length;
+
+ // 根据选中数量和是否删除记录使用不同的确认提示
+ let confirmMessage = '';
+ if (deleteRecords) {
+ confirmMessage = selectedCount === 1
+ ? `确定要删除此项目及其关联记录吗?`
+ : `确定要删除选中的 ${selectedCount} 个项目及其关联记录吗?`;
+ } else {
+ confirmMessage = selectedCount === 1
+ ? `确定要删除此项目吗?`
+ : `确定要删除选中的 ${selectedCount} 个项目吗?`;
+ }
+
+ if (confirm(confirmMessage)) {
+ // 获取当前路径作为save_path参数
+ let save_path = "";
+ if (this.fileSelect && this.fileSelect.paths) {
+ save_path = this.fileSelect.paths.map(item => item.name).join("/");
+ }
+
+ // 创建一个Promise数组来处理所有删除请求
+ const deletePromises = this.fileSelect.selectedFiles.map(fid => {
+ // 查找对应的文件对象,获取文件名
+ const fileObj = this.fileSelect.fileList.find(file => file.fid === fid);
+ const fileName = fileObj ? fileObj.file_name : '';
+
+ return axios.post('/delete_file', { fid: fid, file_name: fileName, delete_records: deleteRecords, save_path: save_path })
+ .then(response => {
+ return { fid: fid, success: response.data.code === 0, deleted_records: response.data.deleted_records || 0 };
+ })
+ .catch(error => {
+ return { fid: fid, success: false, deleted_records: 0 };
+ });
+ });
+
+ // 等待所有删除请求完成
+ Promise.all(deletePromises)
+ .then(results => {
+ // 统计成功和失败的数量
+ const successCount = results.filter(r => r.success).length;
+ const failCount = results.length - successCount;
+ // 统计删除的记录数
+ const totalDeletedRecords = results.reduce((sum, r) => sum + (r.deleted_records || 0), 0);
+
+ // 从文件列表中移除成功删除的文件
+ const successfullyDeletedFids = results.filter(r => r.success).map(r => r.fid);
+ this.fileSelect.fileList = this.fileSelect.fileList.filter(item => !successfullyDeletedFids.includes(item.fid));
+
+ // 清空选中文件列表
+ this.fileSelect.selectedFiles = [];
+ this.fileSelect.lastSelectedFileIndex = -1;
+
+ // 显示结果
+ if (failCount > 0) {
+ alert(`成功删除 ${successCount} 个项目,${failCount} 个项目删除失败`);
+ } else {
+ // 根据是否删除记录显示不同的消息
+ if (deleteRecords) {
+ this.showToast(`成功删除 ${successCount} 个项目${totalDeletedRecords > 0 ? `及其关联的 ${totalDeletedRecords} 条记录` : ''}`);
+ } else {
+ this.showToast(`成功删除 ${successCount} 个项目`);
+ }
+ }
+
+ // 如果同时删除了记录,无论当前在哪个页面都刷新历史记录
+ if (deleteRecords) {
+ this.loadHistoryRecords();
+ }
+ });
+ }
+ },
+ handleModalOutsideClick(event) {
+ // 如果当前不是文件选择模式或者没有选中的文件,则不处理
+ if (this.fileSelect.previewRegex || this.fileSelect.selectShare || this.fileSelect.selectedFiles.length === 0) {
+ return;
+ }
+
+ // 检查点击是否在表格内
+ const tableElement = document.querySelector('#fileSelectModal .table');
+
+ // 如果点击不在表格内,则清除选择
+ if (tableElement && !tableElement.contains(event.target)) {
+ this.fileSelect.selectedFiles = [];
+ this.fileSelect.lastSelectedFileIndex = -1;
+ }
+ },
+ preventTextSelection(event, isDir) {
+ // 如果是文件夹,不阻止默认行为
+ if (isDir) return;
+
+ // 如果是Shift点击或Ctrl/Cmd点击,阻止文本选择
+ if (event.shiftKey || event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ }
+ },
+ resetFolder(index) {
+ // 重置文件夹
+ this.formData.tasklist[index].savepath = this.formData.tasklist[index].savepath.split('/').slice(0, -1).join('/');
+ if (this.formData.tasklist[index].savepath.endsWith('/')) {
+ this.formData.tasklist[index].savepath = this.formData.tasklist[index].savepath.slice(0, -1);
+ }
+ this.showToast('文件夹已重置');
+ },
+ resetFolder(index) {
+ // 获取当前任务的保存路径
+ const savePath = this.formData.tasklist[index].savepath;
+ const taskName = this.formData.tasklist[index].taskname;
+
+ if (!savePath) {
+ this.showToast('保存路径为空,无法重置');
+ return;
+ }
+
+ // 显示确认对话框
+ if (confirm(`确定要重置文件夹「${savePath}」吗?`)) {
+ // 显示加载状态
+ this.modalLoading = true;
+
+ // 调用后端API
+ axios.post('/reset_folder', {
+ save_path: savePath,
+ task_name: taskName
+ })
+ .then(response => {
+ if (response.data.success) {
+ this.showToast(`重置成功:删除了 ${response.data.deleted_files || 0} 个文件,${response.data.deleted_records || 0} 条记录`);
+ // 如果当前是历史记录页面,刷新记录
+ if (this.activeTab === 'history') {
+ this.loadHistoryRecords();
+ }
+ } else {
+ alert(response.data.message || '重置文件夹失败');
+ }
+ this.modalLoading = false;
+ })
+ .catch(error => {
+ console.error('重置文件夹出错:', error);
+ alert('重置文件夹出错: ' + (error.response?.data?.message || error.message));
+ this.modalLoading = false;
+ });
+ }
+ },
}
});
diff --git a/plugins/aria2.py b/plugins/aria2.py
index 9c78a26..f462469 100644
--- a/plugins/aria2.py
+++ b/plugins/aria2.py
@@ -29,6 +29,7 @@ class Aria2:
default_task_config = {
"auto_download": False, # 是否自动添加下载任务
"pause": False, # 添加任务后为暂停状态,不自动开始(手动下载)
+ "auto_delete_quark_files": False, # 是否在添加下载任务后自动删除夸克网盘文件
}
is_active = False
rpc_url = None
@@ -116,6 +117,7 @@ class Aria2:
# 筛选出当次转存的文件
file_fids = []
file_paths = []
+ file_info = [] # 存储文件的完整信息,包括ID和路径
for file in dir_files:
if file.get("dir", False):
@@ -132,7 +134,14 @@ class Aria2:
if is_current_file:
file_fids.append(file["fid"])
- file_paths.append(f"{savepath}/{file_name}")
+ file_path = f"{savepath}/{file_name}"
+ file_paths.append(file_path)
+ # 保存完整信息
+ file_info.append({
+ "fid": file["fid"],
+ "path": file_path,
+ "name": file_name
+ })
if not file_fids:
print("📝 Aria2: 未能匹配到需要下载的文件")
@@ -161,6 +170,9 @@ class Aria2:
# 使用全局排序函数对文件进行排序
download_items.sort(key=lambda x: x["sort_key"])
+
+ # 记录成功添加到下载队列的文件信息,用于后续删除
+ downloaded_files = []
# 按排序后的顺序下载文件
for item in download_items:
@@ -193,8 +205,34 @@ class Aria2:
]
try:
self.add_uri(aria2_params)
+ # 记录成功添加到下载队列的文件信息
+ idx = file_paths.index(file_path)
+ if idx >= 0 and idx < len(file_info):
+ downloaded_files.append(file_info[idx])
except Exception as e:
print(f"📥 Aria2 添加下载任务失败: {e}")
+
+ # 如果配置了自动删除且有成功添加下载任务的文件,则删除夸克网盘中的文件
+ if task_config.get("auto_delete_quark_files") and downloaded_files:
+ try:
+ # 提取要删除的文件ID
+ files_to_delete = []
+ for file_data in downloaded_files:
+ # 再次确认文件路径,确保只删除指定目录下的文件
+ if file_data["path"].startswith(savepath):
+ files_to_delete.append(file_data["fid"])
+
+ if files_to_delete:
+ account.delete(files_to_delete)
+ except Exception as e:
+ print(f"📝 Aria2: 删除夸克网盘文件失败: {e}")
+ else:
+ if not task_config.get("auto_delete_quark_files"):
+ # 未启用自动删除,不需要输出信息
+ pass
+ elif not downloaded_files:
+ # 没有需要删除的文件,不需要输出信息
+ pass
def _make_rpc_request(self, method, params=None):
"""发出 JSON-RPC 请求."""
diff --git a/quark_auto_save.py b/quark_auto_save.py
index 4bfb170..2ef97d7 100644
--- a/quark_auto_save.py
+++ b/quark_auto_save.py
@@ -819,7 +819,7 @@ class Quark:
fids += response["data"]
file_paths = file_paths[50:]
else:
- print(f"获取目录ID:失败, {response['message']}")
+ print(f"获取目录ID: 失败, {response['message']}")
break
if len(file_paths) == 0:
break
@@ -1146,6 +1146,19 @@ class Quark:
# 目前只是添加占位符,未来可以扩展功能
pass
+ # 获取保存路径
+ save_path = task.get("savepath", "")
+ # 如果file_info中有子目录路径信息,则拼接完整路径
+ subdir_path = file_info.get("subdir_path", "")
+ if subdir_path:
+ # 确保路径格式正确,避免双斜杠
+ if save_path.endswith('/') and subdir_path.startswith('/'):
+ save_path = save_path + subdir_path[1:]
+ elif not save_path.endswith('/') and not subdir_path.startswith('/'):
+ save_path = save_path + '/' + subdir_path
+ else:
+ save_path = save_path + subdir_path
+
# 添加记录到数据库
db.add_record(
task_name=task.get("taskname", ""),
@@ -1156,7 +1169,8 @@ class Quark:
duration=duration,
resolution=resolution,
file_id=file_id,
- file_type=file_type
+ file_type=file_type,
+ save_path=save_path
)
# 关闭数据库连接
@@ -1195,12 +1209,26 @@ class Quark:
file_id = file_info.get("fid", "")
task_name = task.get("taskname", "")
+ # 获取保存路径
+ save_path = task.get("savepath", "")
+ # 如果file_info中有子目录路径信息,则拼接完整路径
+ subdir_path = file_info.get("subdir_path", "")
+ if subdir_path:
+ # 确保路径格式正确,避免双斜杠
+ if save_path.endswith('/') and subdir_path.startswith('/'):
+ save_path = save_path + subdir_path[1:]
+ elif not save_path.endswith('/') and not subdir_path.startswith('/'):
+ save_path = save_path + '/' + subdir_path
+ else:
+ save_path = save_path + subdir_path
+
# 更新记录
updated = db.update_renamed_to(
file_id=file_id,
original_name=original_name,
renamed_to=renamed_to,
- task_name=task_name
+ task_name=task_name,
+ save_path=save_path
)
# 关闭数据库连接
@@ -1255,13 +1283,39 @@ class Quark:
# 使用原文件名和任务名查找记录
task_name = task.get("taskname", "")
+ # 获取保存路径
+ save_path = task.get("savepath", "")
+ # 注意:从日志中无法获取子目录信息,只能使用任务的主保存路径
+
+ # 检查文件是否已存在于记录中
+ # 先查询是否有匹配的记录
+ cursor = db.conn.cursor()
+ query = "SELECT file_id FROM transfer_records WHERE original_name = ? AND task_name = ? AND save_path = ?"
+ cursor.execute(query, (old_name, task_name, save_path))
+ result = cursor.fetchone()
+
+ # 如果找到了匹配的记录,使用file_id进行更新
+ file_id = result[0] if result else ""
+
# 更新记录
- updated = db.update_renamed_to(
- file_id="", # 不使用file_id查询,因为在日志中无法获取
- original_name=old_name,
- renamed_to=new_name,
- task_name=task_name
- )
+ if file_id:
+ # 使用file_id更新
+ updated = db.update_renamed_to(
+ file_id=file_id,
+ original_name="", # 不使用原文件名,因为已有file_id
+ renamed_to=new_name,
+ task_name=task_name,
+ save_path=save_path
+ )
+ else:
+ # 使用原文件名更新
+ updated = db.update_renamed_to(
+ file_id="", # 不使用file_id查询,因为在日志中无法获取
+ original_name=old_name,
+ renamed_to=new_name,
+ task_name=task_name,
+ save_path=save_path
+ )
# 关闭数据库连接
db.close()
@@ -1270,7 +1324,7 @@ class Quark:
except Exception as e:
print(f"根据日志更新转存记录失败: {e}")
return False
-
+
# 批量处理重命名日志
def process_rename_logs(self, task, rename_logs):
"""处理重命名日志列表,更新数据库记录
@@ -1282,12 +1336,55 @@ class Quark:
for log in rename_logs:
if "重命名:" in log and "→" in log and "失败" not in log:
self.update_transfer_record_from_log(task, log)
+
+ def check_file_exists_in_records(self, file_id, task=None):
+ """检查文件ID是否存在于转存记录中
+
+ Args:
+ file_id: 要检查的文件ID
+ task: 可选的任务信息,用于进一步筛选
+
+ Returns:
+ bool: 文件是否已存在于记录中
+ """
+ if not file_id:
+ return False
+
+ try:
+ # 初始化数据库
+ db = RecordDB()
+
+ # 构建查询条件
+ conditions = ["file_id = ?"]
+ params = [file_id]
+
+ # 如果提供了任务信息,添加任务名称条件
+ if task and task.get("taskname"):
+ conditions.append("task_name = ?")
+ params.append(task.get("taskname"))
+
+ # 构建WHERE子句
+ where_clause = " AND ".join(conditions)
+
+ # 查询是否存在匹配的记录
+ cursor = db.conn.cursor()
+ query = f"SELECT COUNT(*) FROM transfer_records WHERE {where_clause}"
+ cursor.execute(query, params)
+ count = cursor.fetchone()[0]
+
+ # 关闭数据库连接
+ db.close()
+
+ return count > 0
+ except Exception as e:
+ print(f"检查文件记录时出错: {e}")
+ return False
def do_save_task(self, task):
# 判断资源失效记录
if task.get("shareurl_ban"):
- print(f"分享资源已失效:{task['shareurl_ban']}")
- add_notify(f"❗《{task['taskname']}》分享资源已失效:{task['shareurl_ban']}\n")
+ print(f"分享资源已失效: {task['shareurl_ban']}")
+ add_notify(f"❗《{task['taskname']}》分享资源已失效: {task['shareurl_ban']}\n")
return
# 标准化保存路径,去掉可能存在的首位斜杠,然后重新添加
@@ -1304,8 +1401,8 @@ class Quark:
is_sharing, stoken = self.get_stoken(pwd_id, passcode)
if not is_sharing:
task["shareurl_ban"] = stoken
- print(f"分享详情获取失败:{stoken}")
- add_notify(f"❗《{task['taskname']}》分享详情获取失败:{stoken}\n")
+ print(f"分享详情获取失败: {stoken}")
+ add_notify(f"❗《{task['taskname']}》分享详情获取失败: {stoken}\n")
return
share_detail = self.get_detail(pwd_id, stoken, pdir_fid, _fetch_share=1)
# 获取保存路径fid
@@ -1393,7 +1490,7 @@ class Quark:
if not share_file_list:
if subdir_path == "":
task["shareurl_ban"] = "分享为空,文件已被分享者删除"
- add_notify(f"❌《{task['taskname']}》:{task['shareurl_ban']}\n")
+ add_notify(f"❌《{task['taskname']}》: {task['shareurl_ban']}\n")
return tree
elif (
len(share_file_list) == 1
@@ -1445,7 +1542,7 @@ class Quark:
if task.get("use_sequence_naming") or task.get("use_episode_naming"):
# 计算剩余的实际可用文件数(排除文件夹)
remaining_usable_count = len([f for f in share_file_list if not f.get("dir", False)])
- print(f"📑 应用过滤词: {task['filterwords']},剩余{remaining_usable_count}个项目")
+ print(f"📑 应用过滤词: {task['filterwords']},剩余 {remaining_usable_count} 个项目")
else:
# 正则模式下,需要先检查哪些文件/文件夹会被实际转存
pattern, replace = "", ""
@@ -1479,7 +1576,7 @@ class Quark:
print(f"⚠️ 计算可处理项目时出错: {str(e)}")
remaining_count = len([f for f in share_file_list if re.search(pattern, f["file_name"])])
- print(f"📑 应用过滤词: {task['filterwords']},剩余{remaining_count}个项目")
+ print(f"📑 应用过滤词: {task['filterwords']},剩余 {remaining_count} 个项目")
print()
# 获取目标目录文件列表
@@ -1563,8 +1660,14 @@ class Quark:
filtered_share_files = []
for share_file in share_file_list:
if share_file["dir"]:
- # 不再直接添加目录到filtered_share_files
# 目录处理会在后续专门的循环中进行
+ filtered_share_files.append(share_file)
+ continue
+
+ # 检查文件ID是否存在于转存记录中
+ file_id = share_file.get("fid", "")
+ if file_id and self.check_file_exists_in_records(file_id, task):
+ # 文件ID已存在于记录中,跳过处理
continue
file_size = share_file.get("size", 0)
@@ -1775,6 +1878,12 @@ class Quark:
# 添加符合的
for share_file in share_file_list:
+ # 检查文件ID是否存在于转存记录中
+ file_id = share_file.get("fid", "")
+ if file_id and self.check_file_exists_in_records(file_id, task):
+ # 文件ID已存在于记录中,跳过处理
+ continue
+
# 检查文件是否已存在(通过大小和扩展名)- 新增的文件查重逻辑
is_duplicate = False
if not share_file["dir"]: # 文件夹不进行内容查重
@@ -2215,7 +2324,7 @@ class Quark:
else:
err_msg = save_file_return["message"]
if err_msg:
- add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n")
+ add_notify(f"❌《{task['taskname']}》转存失败: {err_msg}\n")
else:
# 没有新文件需要转存
if not subdir_path: # 只在顶层(非子目录)打印一次消息
@@ -2369,7 +2478,7 @@ class Quark:
# 移除直接打印的部分,由do_save负责打印
# print(rename_log)
except Exception as e:
- rename_log = f"重命名出错: {dir_file['file_name']} → {save_name},错误:{str(e)}"
+ rename_log = f"重命名出错: {dir_file['file_name']} → {save_name},错误: {str(e)}"
rename_logs.append(rename_log)
# 移除直接打印的部分,由do_save负责打印
# print(rename_log)
@@ -2455,7 +2564,7 @@ class Quark:
# 获取分享详情
is_sharing, stoken = self.get_stoken(pwd_id, passcode)
if not is_sharing:
- print(f"分享详情获取失败:{stoken}")
+ print(f"分享详情获取失败: {stoken}")
return False, []
# 获取分享文件列表
@@ -2472,6 +2581,12 @@ class Quark:
if task.get("update_subdir") and re.search(task["update_subdir"], share_file["file_name"]):
filtered_share_files.append(share_file)
continue
+
+ # 检查文件ID是否存在于转存记录中
+ file_id = share_file.get("fid", "")
+ if file_id and self.check_file_exists_in_records(file_id, task):
+ # 文件ID已存在于记录中,跳过处理
+ continue
# 从共享文件中提取剧集号
episode_num = extract_episode_number_local(share_file["file_name"])
@@ -2758,11 +2873,11 @@ class Quark:
return True, rename_logs
else:
err_msg = query_task_return["message"]
- add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n")
+ add_notify(f"❌《{task['taskname']}》转存失败: {err_msg}\n")
return False, []
else:
print(f"❌ 保存文件失败: {save_file_return['message']}")
- add_notify(f"❌《{task['taskname']}》转存失败:{save_file_return['message']}\n")
+ add_notify(f"❌《{task['taskname']}》转存失败: {save_file_return['message']}\n")
return False, []
else:
# print("没有需要保存的新文件")
@@ -2950,7 +3065,7 @@ class Quark:
if new_name != orig_name:
rename_operations.append((dir_file, new_name))
except Exception as e:
- print(f"正则替换出错: {dir_file['file_name']},错误:{str(e)}")
+ print(f"正则替换出错: {dir_file['file_name']},错误: {str(e)}")
# 按原始文件名字母顺序排序,使重命名操作有序进行
# rename_operations.sort(key=lambda x: x[0]["file_name"])
@@ -2989,7 +3104,7 @@ class Quark:
rename_logs.append(error_log)
except Exception as e:
# 收集错误日志但不打印
- error_log = f"重命名出错: {dir_file['file_name']} → {new_name},错误:{str(e)}"
+ error_log = f"重命名出错: {dir_file['file_name']} → {new_name},错误: {str(e)}"
rename_logs.append(error_log)
else:
# 重名警告但不打印