From 243ddff74d1669aa097ac3c92f260d3e183e48ba Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sun, 20 Apr 2025 23:24:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=BF=87=E6=BB=A4=E8=A7=84?= =?UTF-8?q?=E5=88=99=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=96=B0=E5=A2=9E=E9=A1=BA?= =?UTF-8?q?=E5=BA=8F=E5=91=BD=E5=90=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/templates/index.html | 60 ++++++- quark_auto_save.py | 369 +++++++++++++++++++++++++++++++++------ 2 files changed, 368 insertions(+), 61 deletions(-) diff --git a/app/templates/index.html b/app/templates/index.html index 1cf73de..24c020c 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -255,20 +255,26 @@
- +
-
+
- 正则处理 +
- - + +
 忽略后缀
+
+
+ +
+ +
@@ -279,10 +285,7 @@
-
- 过滤词汇 -
- +
@@ -435,6 +438,8 @@ addition: {}, ignore_extension: false, filterwords: "", + sequence_naming: "", + use_sequence_naming: false, runweek: [1, 2, 3, 4, 5, 6, 7] }, run_log: "", @@ -471,6 +476,17 @@ } }, watch: { + 'task.use_sequence_naming': function(newVal, oldVal) { + if (newVal) { + if (!this.task.sequence_naming) { + this.task.sequence_naming = this.task.taskname + " - E{}"; + } + this.task.pattern_disabled = true; + } else { + this.task.sequence_naming = null; + this.task.pattern_disabled = false; + } + } }, mounted() { this.fetchData(); @@ -791,6 +807,32 @@ this.changeShareurl(task); this.smart_param.showSuggestions = false; }, + toggleNamingMode(task, use_sequence_naming) { + // 设置命名模式 + task.use_sequence_naming = use_sequence_naming; + + if (use_sequence_naming) { + // 启用顺序命名 + if (!task.sequence_naming) { + task.sequence_naming = task.taskname ? (task.taskname + " - E{}") : "E{}"; + } + // 禁用正则匹配 + task._pattern_backup = task.pattern; + task._replace_backup = task.replace; + task.pattern = ""; + task.replace = ""; + } else { + // 禁用顺序命名 + task._sequence_backup = task.sequence_naming; + // 恢复正则匹配 + if (task._pattern_backup) { + task.pattern = task._pattern_backup; + task.replace = task._replace_backup; + } + } + // 强制Vue更新视图 + this.$forceUpdate(); + }, } }); diff --git a/quark_auto_save.py b/quark_auto_save.py index be5f508..367035a 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -215,17 +215,40 @@ class Quark: } ) del headers["cookie"] - try: - response = requests.request(method, url, headers=headers, **kwargs) - # print(f"{response.text}") - # response.raise_for_status() # 检查请求是否成功,但返回非200也会抛出异常 - return response - except Exception as e: - print(f"_send_request error:\n{e}") - fake_response = requests.Response() - fake_response.status_code = 500 - fake_response._content = b'{"status": 500, "message": "request error"}' - return fake_response + + # 添加重试机制 + max_retries = 3 + retry_count = 0 + + while retry_count < max_retries: + try: + response = requests.request(method, url, headers=headers, timeout=30, **kwargs) + # 请求成功,返回结果 + return response + except requests.exceptions.SSLError as e: + retry_count += 1 + if retry_count >= max_retries: + print(f"SSL错误,已重试{retry_count}次,放弃请求: {str(e)}") + fake_response = requests.Response() + fake_response.status_code = 500 + fake_response._content = b'{"status": 500, "message": "SSL error", "code": 500}' + return fake_response + # 等待一段时间后重试 + wait_time = retry_count * 2 + print(f"SSL错误,{wait_time}秒后进行第{retry_count+1}次重试: {str(e)}") + time.sleep(wait_time) + except requests.exceptions.RequestException as e: + retry_count += 1 + if retry_count >= max_retries: + print(f"请求错误,已重试{retry_count}次,放弃请求: {str(e)}") + fake_response = requests.Response() + fake_response.status_code = 500 + fake_response._content = b'{"status": 500, "message": "request error", "code": 500}' + return fake_response + # 等待一段时间后重试 + wait_time = retry_count * 2 + print(f"请求错误,{wait_time}秒后进行第{retry_count+1}次重试: {str(e)}") + time.sleep(wait_time) def init(self): account_info = self.get_account_info() @@ -673,10 +696,41 @@ class Quark: # 需保存的文件清单 need_save_list = [] + + # 顺序命名模式下获取当前序号和正则表达式 + regex_pattern = None + if task.get("use_sequence_naming") and task.get("sequence_naming"): + # 获取目录中符合顺序命名格式的最大序号 + sequence_pattern = task["sequence_naming"] + # 替换占位符为正则表达式捕获组 + regex_pattern = re.escape(sequence_pattern).replace('\\{\\}', '(\\d+)') + max_sequence = 0 + + for dir_file in dir_file_list: + matches = re.match(regex_pattern, dir_file["file_name"]) + if matches: + try: + current_seq = int(matches.group(1)) + max_sequence = max(max_sequence, current_seq) + except (IndexError, ValueError): + pass + + # 从最大序号开始计数 + current_sequence = max_sequence + # 添加符合的 for share_file in share_file_list: if share_file["dir"] and task.get("update_subdir", False): pattern, replace = task["update_subdir"], "" + elif task.get("use_sequence_naming") and task.get("sequence_naming"): + # 使用顺序命名 + pattern = ".*" # 匹配任何文件 + # 序号暂时留空,等收集完所有文件后再按优先级排序赋值 + replace = "TO_BE_REPLACED_LATER" + # 保留文件扩展名 + if not share_file["dir"]: + file_ext = os.path.splitext(share_file["file_name"])[1] + replace = replace + file_ext else: pattern, replace = self.magic_regex_func( task["pattern"], task["replace"], task["taskname"] @@ -697,15 +751,51 @@ class Quark: ) else: compare_func = lambda a, b1, b2: (a == b1 or a == b2) + # 判断目标目录文件是否存在 - file_exists = any( - compare_func( - dir_file["file_name"], share_file["file_name"], save_name + file_exists = False + + # 顺序命名模式下增强去重功能 + if task.get("use_sequence_naming") and task.get("sequence_naming") and not share_file["dir"]: + # 根据文件大小和修改时间判断文件是否已经存在 + file_ext = os.path.splitext(share_file["file_name"])[1].lower() + + for dir_file in dir_file_list: + # 检查是否为相同的文件(根据大小和扩展名判断) + dir_file_ext = os.path.splitext(dir_file["file_name"])[1].lower() + + if (not dir_file["dir"] and + dir_file["size"] == share_file["size"] and + dir_file_ext == file_ext): + + # 文件大小相同,扩展名相同,很可能是同一个文件 + # 额外检查是否已经有符合顺序命名格式的文件(防止重复转存后重命名) + if re.match(regex_pattern, dir_file["file_name"]): + print(f"📌 顺序命名去重: {share_file['file_name']} 已存在于目录 {dir_file['file_name']},大小: {format_bytes(share_file['size'])},跳过") + file_exists = True + break + + # 如果文件大小相同和扩展名相同,需要进一步检查修改时间是否接近 + share_time = share_file.get("last_update_at", 0) + dir_time = dir_file.get("updated_at", 0) + + # 如果修改时间在30天内,或者差距不大,认为是同一个文件 + if abs(share_time - dir_time) < 2592000 or abs(1 - (share_time / dir_time if dir_time else 1)) < 0.1: + print(f"📌 顺序命名去重: {share_file['file_name']} 与 {dir_file['file_name']} 匹配,大小: {format_bytes(share_file['size'])},跳过") + file_exists = True + break + else: + # 原有的文件名匹配判断 + file_exists = any( + compare_func( + dir_file["file_name"], share_file["file_name"], save_name + ) + for dir_file in dir_file_list ) - for dir_file in dir_file_list - ) + if not file_exists: share_file["save_name"] = save_name + share_file["original_name"] = share_file["file_name"] # 保存原文件名,用于排序 need_save_list.append(share_file) elif share_file["dir"]: # 存在并是一个文件夹 @@ -734,6 +824,71 @@ class Quark: if share_file["fid"] == task.get("startfid", ""): break + # 如果是顺序命名模式,需要重新排序并生成文件名 + if task.get("use_sequence_naming") and task.get("sequence_naming") and need_save_list: + def custom_sort(file): + file_name = file["original_name"] + + # 1. 提取文件名中的数字(期数/集数等) + episode_num = 0 + + # 尝试匹配"第X期/集/话"格式 + episode_match = re.search(r'第(\d+)[期集话]', file_name) + if episode_match: + episode_num = int(episode_match.group(1)) + + # 尝试匹配常见视频格式 S01E01, E01, 1x01 等 + elif re.search(r'[Ss](\d+)[Ee](\d+)', file_name): + match = re.search(r'[Ss](\d+)[Ee](\d+)', file_name) + season = int(match.group(1)) + episode = int(match.group(2)) + episode_num = season * 1000 + episode # 确保季和集的排序正确 + elif re.search(r'[Ee](\d+)', file_name): + match = re.search(r'[Ee](\d+)', file_name) + episode_num = int(match.group(1)) + elif re.search(r'(\d+)[xX](\d+)', file_name): + match = re.search(r'(\d+)[xX](\d+)', file_name) + season = int(match.group(1)) + episode = int(match.group(2)) + episode_num = season * 1000 + episode + + # 尝试匹配日期格式 YYYYMMDD + elif re.search(r'(\d{4})(\d{2})(\d{2})', file_name): + match = re.search(r'(\d{4})(\d{2})(\d{2})', file_name) + year = int(match.group(1)) + month = int(match.group(2)) + day = int(match.group(3)) + episode_num = year * 10000 + month * 100 + day + + # 尝试匹配纯数字格式(文件名开头是纯数字) + elif re.search(r'^(\d+)', file_name): + match = re.search(r'^(\d+)', file_name) + episode_num = int(match.group(1)) + + # 2. 检查文件名中是否包含"上中下"等排序提示 + position_order = 10 # 默认顺序值 + if '上' in file_name: + position_order = 1 + elif '中' in file_name: + position_order = 2 + elif '下' in file_name: + position_order = 3 + + # 3. 返回排序元组:先按集数排序,再按上中下,最后按更新时间 + return (episode_num, position_order, file["last_update_at"] if "last_update_at" in file else 0) + + # 按自定义逻辑排序 + need_save_list = sorted(need_save_list, key=custom_sort) + + # 重新生成命名 + for index, file in enumerate(need_save_list): + current_sequence += 1 + if file["dir"]: + file["save_name"] = sequence_pattern.replace("{}", f"{current_sequence:02d}") + else: + file_ext = os.path.splitext(file["file_name"])[1] + file["save_name"] = sequence_pattern.replace("{}", f"{current_sequence:02d}") + file_ext + fid_list = [item["fid"] for item in need_save_list] fid_token_list = [item["share_fid_token"] for item in need_save_list] if fid_list: @@ -771,40 +926,144 @@ class Quark: return tree def do_rename_task(self, task, subdir_path=""): - pattern, replace = self.magic_regex_func( - task["pattern"], task["replace"], task["taskname"] - ) - if not pattern or not replace: - return 0 - savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}{subdir_path}") - if not self.savepath_fid.get(savepath): - self.savepath_fid[savepath] = self.get_fids([savepath])[0]["fid"] - dir_file_list = self.ls_dir(self.savepath_fid[savepath]) - dir_file_name_list = [item["file_name"] for item in dir_file_list] - is_rename_count = 0 - for dir_file in dir_file_list: - if dir_file["dir"]: - is_rename_count += self.do_rename_task( - task, f"{subdir_path}/{dir_file['file_name']}" - ) - if re.search(pattern, dir_file["file_name"]): - save_name = ( - re.sub(pattern, replace, dir_file["file_name"]) - if replace != "" - else dir_file["file_name"] - ) - if save_name != dir_file["file_name"] and ( - save_name not in dir_file_name_list - ): - rename_return = self.rename(dir_file["fid"], save_name) - if rename_return["code"] == 0: - print(f"重命名:{dir_file['file_name']} → {save_name}") - is_rename_count += 1 - else: - print( - f"重命名:{dir_file['file_name']} → {save_name} 失败,{rename_return['message']}" - ) - return is_rename_count > 0 + if task.get("use_sequence_naming") and task.get("sequence_naming"): + # 使用顺序命名模式 + sequence_pattern = task["sequence_naming"] + # 替换占位符为正则表达式捕获组 + regex_pattern = re.escape(sequence_pattern).replace('\\{\\}', '(\\d+)') + savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}{subdir_path}") + if not self.savepath_fid.get(savepath): + self.savepath_fid[savepath] = self.get_fids([savepath])[0]["fid"] + dir_file_list = self.ls_dir(self.savepath_fid[savepath]) + dir_file_name_list = [item["file_name"] for item in dir_file_list] + + # 找出当前最大序号 + max_sequence = 0 + for dir_file in dir_file_list: + matches = re.match(regex_pattern, dir_file["file_name"]) + if matches: + try: + current_seq = int(matches.group(1)) + max_sequence = max(max_sequence, current_seq) + except (IndexError, ValueError): + pass + + # 重命名文件 + current_sequence = max_sequence + is_rename_count = 0 + + # 定义一个排序函数,支持多种格式的排序 + def custom_sort(file): + file_name = file["file_name"] + + # 1. 提取文件名中的数字(期数/集数等) + episode_num = 0 + + # 尝试匹配"第X期/集/话"格式 + episode_match = re.search(r'第(\d+)[期集话]', file_name) + if episode_match: + episode_num = int(episode_match.group(1)) + + # 尝试匹配常见视频格式 S01E01, E01, 1x01 等 + elif re.search(r'[Ss](\d+)[Ee](\d+)', file_name): + match = re.search(r'[Ss](\d+)[Ee](\d+)', file_name) + season = int(match.group(1)) + episode = int(match.group(2)) + episode_num = season * 1000 + episode # 确保季和集的排序正确 + elif re.search(r'[Ee](\d+)', file_name): + match = re.search(r'[Ee](\d+)', file_name) + episode_num = int(match.group(1)) + elif re.search(r'(\d+)[xX](\d+)', file_name): + match = re.search(r'(\d+)[xX](\d+)', file_name) + season = int(match.group(1)) + episode = int(match.group(2)) + episode_num = season * 1000 + episode + + # 尝试匹配日期格式 YYYYMMDD + elif re.search(r'(\d{4})(\d{2})(\d{2})', file_name): + match = re.search(r'(\d{4})(\d{2})(\d{2})', file_name) + year = int(match.group(1)) + month = int(match.group(2)) + day = int(match.group(3)) + episode_num = year * 10000 + month * 100 + day + + # 尝试匹配纯数字格式(文件名开头是纯数字) + elif re.search(r'^(\d+)', file_name): + match = re.search(r'^(\d+)', file_name) + episode_num = int(match.group(1)) + + # 2. 检查文件名中是否包含"上中下"等排序提示 + position_order = 10 # 默认顺序值 + if '上' in file_name: + position_order = 1 + elif '中' in file_name: + position_order = 2 + elif '下' in file_name: + position_order = 3 + + # 3. 返回排序元组:先按集数排序,再按上中下,最后按创建时间 + return (episode_num, position_order, file["created_at"]) + + # 按自定义逻辑排序 + sorted_files = sorted([f for f in dir_file_list if not f["dir"] and not re.match(regex_pattern, f["file_name"])], key=custom_sort) + + for dir_file in sorted_files: + current_sequence += 1 + file_ext = os.path.splitext(dir_file["file_name"])[1] + save_name = sequence_pattern.replace("{}", f"{current_sequence:02d}") + file_ext + + if save_name != dir_file["file_name"] and save_name not in dir_file_name_list: + try: + rename_return = self.rename(dir_file["fid"], save_name) + # 防止网络问题导致的错误 + if isinstance(rename_return, dict) and rename_return.get("code") == 0: + print(f"重命名:{dir_file['file_name']} → {save_name}") + is_rename_count += 1 + dir_file_name_list.append(save_name) + else: + error_msg = rename_return.get("message", "未知错误") + print(f"重命名:{dir_file['file_name']} → {save_name} 失败,{error_msg}") + except Exception as e: + print(f"重命名出错:{dir_file['file_name']} → {save_name},错误:{str(e)}") + return is_rename_count > 0 + else: + # 原有的正则匹配模式 + pattern, replace = self.magic_regex_func( + task["pattern"], task["replace"], task["taskname"] + ) + if not pattern or not replace: + return 0 + savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}{subdir_path}") + if not self.savepath_fid.get(savepath): + self.savepath_fid[savepath] = self.get_fids([savepath])[0]["fid"] + dir_file_list = self.ls_dir(self.savepath_fid[savepath]) + dir_file_name_list = [item["file_name"] for item in dir_file_list] + is_rename_count = 0 + for dir_file in dir_file_list: + if dir_file["dir"]: + is_rename_count += self.do_rename_task( + task, f"{subdir_path}/{dir_file['file_name']}" + ) + if re.search(pattern, dir_file["file_name"]): + save_name = ( + re.sub(pattern, replace, dir_file["file_name"]) + if replace != "" + else dir_file["file_name"] + ) + if save_name != dir_file["file_name"] and ( + save_name not in dir_file_name_list + ): + try: + rename_return = self.rename(dir_file["fid"], save_name) + if isinstance(rename_return, dict) and rename_return.get("code") == 0: + print(f"重命名:{dir_file['file_name']} → {save_name}") + is_rename_count += 1 + else: + error_msg = rename_return.get("message", "未知错误") + print(f"重命名:{dir_file['file_name']} → {save_name} 失败,{error_msg}") + except Exception as e: + print(f"重命名出错:{dir_file['file_name']} → {save_name},错误:{str(e)}") + return is_rename_count > 0 def verify_account(account): @@ -897,8 +1156,14 @@ def do_save(account, tasklist=[]): print(f"任务名称: {task['taskname']}") print(f"分享链接: {task['shareurl']}") print(f"保存路径: {task['savepath']}") - print(f"正则匹配: {task['pattern']}") - print(f"正则替换: {task['replace']}") + + # 打印重命名规则信息 + if task.get("use_sequence_naming") and task.get("sequence_naming"): + print(f"顺序命名: {task['sequence_naming']}") + else: + print(f"正则匹配: {task['pattern']}") + print(f"正则替换: {task['replace']}") + if task.get("enddate"): print(f"任务截止: {task['enddate']}") if task.get("ignore_extension"):