diff --git a/quark_auto_save.py b/quark_auto_save.py index cf11b8e..d0e4b36 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -1,6 +1,6 @@ # !/usr/bin/env python3 # -*- coding: utf-8 -*- -# Modify: 2024-04-03 +# Modify: 2024-11-13 # Repo: https://github.com/Cp0204/quark_auto_save # ConfigFile: quark_config.json """ @@ -14,9 +14,12 @@ import json import time import random import requests +import importlib from datetime import datetime -MAX_SAVE_FILES = 2 # 最大保存文件数量 +# ========== 修改内容开始 ========== +MAX_SAVE_FILES = 0 # 最大保存文件数量,0 为不限制保存数量 +# ========== 修改内容结束 ========== # 兼容青龙 try: @@ -34,22 +37,12 @@ GH_PROXY = os.environ.get("GH_PROXY", "https://ghproxy.net/") MAGIC_REGEX = { "$TV": { - "pattern": ".*?(S\\d{1,2}E)?P?(\\d{1,3}).*?\\.(mp4|mkv)", - "replace": "\\1\\2.\\3", + "pattern": r".*?(?= response["metadata"]["_total"]: + if len(list_merge) >= response["metadata"]["_total"]: break - return file_list + response["data"]["list"] = list_merge + return response["data"] def get_fids(self, file_paths): fids = [] while True: - url = "https://drive-m.quark.cn/1/clouddrive/file/info/path_list" + url = f"{self.BASE_URL}/1/clouddrive/file/info/path_list" querystring = {"pr": "ucpro", "fr": "pc"} payload = {"file_path": file_paths[:50], "namespace": "0"} - headers = self.common_headers() - response = requests.request( - "POST", url, json=payload, headers=headers, params=querystring + response = self._send_request( + "POST", url, json=payload, params=querystring ).json() if response["code"] == 0: fids += response["data"] @@ -287,11 +354,11 @@ class Quark: break return fids - def ls_dir(self, pdir_fid): + def ls_dir(self, pdir_fid, **kwargs): file_list = [] page = 1 while True: - url = "https://drive-m.quark.cn/1/clouddrive/file/sort" + url = f"{self.BASE_URL}/1/clouddrive/file/sort" querystring = { "pr": "ucpro", "fr": "pc", @@ -302,11 +369,9 @@ class Quark: "_fetch_total": "1", "_fetch_sub_dirs": "0", "_sort": "file_type:asc,updated_at:desc", + "_fetch_full_path": kwargs.get("fetch_full_path", 0), } - headers = self.common_headers() - response = requests.request( - "GET", url, headers=headers, params=querystring - ).json() + response = self._send_request("GET", url, params=querystring).json() if response["data"]["list"]: file_list += response["data"]["list"] page += 1 @@ -317,7 +382,7 @@ class Quark: return file_list def save_file(self, fid_list, fid_token_list, to_pdir_fid, pwd_id, stoken): - url = "https://drive-m.quark.cn/1/clouddrive/share/sharepage/save" + url = f"{self.BASE_URL}/1/clouddrive/share/sharepage/save" querystring = { "pr": "ucpro", "fr": "pc", @@ -326,40 +391,73 @@ class Quark: "__dt": int(random.uniform(1, 5) * 60 * 1000), "__t": datetime.now().timestamp(), } - querystring["fr"] = "h5" if self.st else "pc" - - # 限制最多保存2个文件 - files_to_save = fid_list[:MAX_SAVE_FILES] - tokens_to_save = fid_token_list[:MAX_SAVE_FILES] - + + # ========== 修改内容开始 ========== + # 当 MAX_SAVE_FILES = 0 时,不限制保存文件数量 + if MAX_SAVE_FILES > 0: + files_to_save = fid_list[:MAX_SAVE_FILES] + tokens_to_save = fid_token_list[:MAX_SAVE_FILES] + else: + files_to_save = fid_list + tokens_to_save = fid_token_list + # ========== 修改内容结束 ========== + payload = { - "fid_list": files_to_save, - "fid_token_list": tokens_to_save, + "fid_list": files_to_save, # 修改为 files_to_save + "fid_token_list": tokens_to_save, # 修改为 tokens_to_save "to_pdir_fid": to_pdir_fid, "pwd_id": pwd_id, "stoken": stoken, "pdir_fid": "0", "scene": "link", } - headers = self.common_headers() - - try: - response = requests.request( - "POST", url, json=payload, headers=headers, params=querystring - ).json() - except requests.RequestException as e: - print(f"请求失败: {e}") - return {"code": -1, "message": "请求失败"} - - return { - "code": response.get("code", -1), # 确保返回包含 'code' - "message": response.get("message", ""), - "data": response.get("data", {}) - } + response = self._send_request( + "POST", url, json=payload, params=querystring + ).json() + return response + def query_task(self, task_id): + retry_index = 0 + while True: + url = f"{self.BASE_URL}/1/clouddrive/task" + querystring = { + "pr": "ucpro", + "fr": "pc", + "uc_param_str": "", + "task_id": task_id, + "retry_index": retry_index, + "__dt": int(random.uniform(1, 5) * 60 * 1000), + "__t": datetime.now().timestamp(), + } + response = self._send_request("GET", url, params=querystring).json() + if response["data"]["status"] != 0: + if retry_index > 0: + print() + break + else: + if retry_index == 0: + print( + f"正在等待[{response['data']['task_title']}]执行结果", + end="", + flush=True, + ) + else: + print(".", end="", flush=True) + retry_index += 1 + time.sleep(0.500) + return response + + def download(self, fids): + url = f"{self.BASE_URL}/1/clouddrive/file/download" + querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""} + payload = {"fids": fids} + response = self._send_request("POST", url, json=payload, 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-m.quark.cn/1/clouddrive/file" + url = f"{self.BASE_URL}/1/clouddrive/file" querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""} payload = { "pdir_fid": "0", @@ -367,34 +465,31 @@ class Quark: "dir_path": dir_path, "dir_init_lock": False, } - headers = self.common_headers() - response = requests.request( - "POST", url, json=payload, headers=headers, params=querystring + response = self._send_request( + "POST", url, json=payload, params=querystring ).json() return response def rename(self, fid, file_name): - url = "https://drive-m.quark.cn/1/clouddrive/file/rename" + url = f"{self.BASE_URL}/1/clouddrive/file/rename" querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""} payload = {"fid": fid, "file_name": file_name} - headers = self.common_headers() - response = requests.request( - "POST", url, json=payload, headers=headers, params=querystring + response = self._send_request( + "POST", url, json=payload, params=querystring ).json() return response def delete(self, filelist): - url = "https://drive-m.quark.cn/1/clouddrive/file/delete" + url = f"{self.BASE_URL}/1/clouddrive/file/delete" querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""} payload = {"action_type": 2, "filelist": filelist, "exclude_fids": []} - headers = self.common_headers() - response = requests.request( - "POST", url, json=payload, headers=headers, params=querystring + response = self._send_request( + "POST", url, json=payload, params=querystring ).json() return response def recycle_list(self, page=1, size=30): - url = "https://drive-m.quark.cn/1/clouddrive/file/recycle/list" + url = f"{self.BASE_URL}/1/clouddrive/file/recycle/list" querystring = { "_page": page, "_size": size, @@ -402,25 +497,48 @@ class Quark: "fr": "pc", "uc_param_str": "", } - headers = self.common_headers() - response = requests.request( - "GET", url, headers=headers, params=querystring - ).json() + response = self._send_request("GET", url, params=querystring).json() return response["data"]["list"] def recycle_remove(self, record_list): - url = "https://drive-m.quark.cn/1/clouddrive/file/recycle/remove" + url = f"{self.BASE_URL}/1/clouddrive/file/recycle/remove" querystring = {"uc_param_str": "", "fr": "pc", "pr": "ucpro"} payload = { "select_mode": 2, "record_list": record_list, } - headers = self.common_headers() - response = requests.request( - "POST", url, json=payload, headers=headers, params=querystring + response = self._send_request( + "POST", url, json=payload, params=querystring ).json() return response + # ↑ 请求函数 + # ↓ 操作函数 + + # 魔法正则匹配 + def magic_regex_func(self, pattern, replace, taskname=None): + magic_regex = CONFIG_DATA.get("magic_regex") or MAGIC_REGEX or {} + keyword = pattern + if keyword in magic_regex: + pattern = magic_regex[keyword]["pattern"] + if replace == "": + replace = magic_regex[keyword]["replace"] + if taskname: + replace = replace.replace("$TASKNAME", taskname) + return pattern, replace + + def get_id_from_url(self, url): + url = url.replace("https://pan.quark.cn/s/", "") + pattern = r"(\w+)(\?pwd=(\w+))?(#/list/share.*/(\w+))?" + match = re.search(pattern, url) + if match: + pwd_id = match.group(1) + passcode = match.group(3) if match.group(3) else "" + pdir_fid = match.group(5) if match.group(5) else 0 + return pwd_id, passcode, pdir_fid + else: + return None + def update_savepath_fid(self, tasklist): dir_paths = [ re.sub(r"/{2,}", "/", f"/{item['savepath']}") @@ -454,36 +572,21 @@ class Quark: def do_save_check(self, shareurl, savepath): try: - pwd_id, pdir_fid = self.get_id_from_url(shareurl) - is_sharing, stoken = self.get_stoken(pwd_id) - share_file_list = self.get_detail(pwd_id, stoken, pdir_fid) + pwd_id, passcode, pdir_fid = self.get_id_from_url(shareurl) + is_sharing, stoken = self.get_stoken(pwd_id, passcode) + share_file_list = self.get_detail(pwd_id, stoken, pdir_fid)["list"] fid_list = [item["fid"] for item in share_file_list] fid_token_list = [item["share_fid_token"] for item in share_file_list] file_name_list = [item["file_name"] for item in share_file_list] - if not fid_list: return - - # 获取目标目录的fid get_fids = self.get_fids([savepath]) to_pdir_fid = ( get_fids[0]["fid"] if get_fids else self.mkdir(savepath)["data"]["fid"] ) - - # 限制转存的文件总数为2个 - files_to_save = fid_list[:MAX_SAVE_FILES] - tokens_to_save = fid_token_list[:MAX_SAVE_FILES] - names_to_save = file_name_list[:MAX_SAVE_FILES] - - print(f"转存文件ID列表: {files_to_save}") - print(f"转存文件Token列表: {tokens_to_save}") - print(f"转存文件名称列表: {names_to_save}") - - # 保存文件 save_file = self.save_file( - files_to_save, tokens_to_save, to_pdir_fid, pwd_id, stoken + fid_list, fid_token_list, to_pdir_fid, pwd_id, stoken ) - if save_file["code"] == 41017: return elif save_file["code"] == 0: @@ -491,19 +594,24 @@ class Quark: del_list = [ item["fid"] for item in dir_file_list - if (item["file_name"] in names_to_save) + if (item["file_name"] in file_name_list) and ((datetime.now().timestamp() - item["created_at"]) < 60) ] if del_list: - for fid in del_list: - self.del_file(fid, pwd_id, stoken) - return "转存成功" + self.delete(del_list) + recycle_list = self.recycle_list() + record_id_list = [ + item["record_id"] + for item in recycle_list + if item["fid"] in del_list + ] + self.recycle_remove(record_id_list) + return save_file else: - return f"转存失败: {save_file['message']}" + return False except Exception as e: - return f"转存过程中出现错误: {str(e)}" - - + if os.environ.get("DEBUG") == True: + print(f"转存测试失败: {str(e)}") def do_save_task(self, task): # 判断资源失效记录 @@ -512,34 +620,35 @@ class Quark: return # 链接转换所需参数 - pwd_id, pdir_fid = self.get_id_from_url(task["shareurl"]) + pwd_id, passcode, pdir_fid = self.get_id_from_url(task["shareurl"]) + # print("match: ", pwd_id, pdir_fid) # 获取stoken,同时可验证资源是否失效 - is_sharing, stoken = self.get_stoken(pwd_id) + is_sharing, stoken = self.get_stoken(pwd_id, passcode) if not is_sharing: add_notify(f"❌《{task['taskname']}》:{stoken}\n") task["shareurl_ban"] = stoken return + # print("stoken: ", stoken) 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) - + share_file_list = self.get_detail(pwd_id, stoken, pdir_fid)["list"] + # print("share_file_list: ", share_file_list) + if not share_file_list: if subdir_path == "": task["shareurl_ban"] = "分享为空,文件已被分享者删除" - add_notify(f"《{task['taskname']}》:{task['shareurl_ban']}") + add_notify(f"❌《{task['taskname']}》:{task['shareurl_ban']}\n") return tree elif ( len(share_file_list) == 1 @@ -547,8 +656,10 @@ class Quark: and subdir_path == "" ): # 仅有一个文件夹 print("🧠 该分享是一个文件夹,读取文件夹内列表") - share_file_list = self.get_detail(pwd_id, stoken, share_file_list[0]["fid"]) - + share_file_list = self.get_detail( + pwd_id, stoken, share_file_list[0]["fid"] + )["list"] + # 获取目标目录文件列表 savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}{subdir_path}") if not self.savepath_fid.get(savepath): @@ -559,17 +670,26 @@ class Quark: return tree to_pdir_fid = self.savepath_fid[savepath] 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 = [] - # 添加符合的 for share_file in share_file_list: if share_file["dir"] and task.get("update_subdir", False): pattern, replace = task["update_subdir"], "" else: - pattern, replace = magic_regex_func(task["pattern"], task["replace"]) - + pattern, replace = self.magic_regex_func( + task["pattern"], task["replace"], task["taskname"] + ) # 正则文件名匹配 if re.search(pattern, share_file["file_name"]): # 替换后的文件名 @@ -586,7 +706,6 @@ class Quark: ) else: compare_func = lambda a, b1, b2: (a == b1 or a == b2) - # 判断目标目录文件是否存在 file_exists = any( compare_func( @@ -615,34 +734,48 @@ 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) - - # 限制最多保存2个文件 - need_save_list = need_save_list[:MAX_SAVE_FILES] + # 指定文件开始订阅/到达指定文件(含)结束历遍 + if share_file["fid"] == task.get("startfid", ""): + break + + # ========== 修改内容开始 ========== + # 当 MAX_SAVE_FILES = 0 时,不限制保存文件数量 + if MAX_SAVE_FILES > 0: + need_save_list = need_save_list[:MAX_SAVE_FILES] + # ========== 修改内容结束 ========== + 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 ) err_msg = None if save_file_return["code"] == 0: - task_id = save_file_return["data"].get("task_id", "") + 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"] @@ -650,52 +783,13 @@ class Quark: err_msg = save_file_return["message"] if err_msg: add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n") - - # 打印实际保存的文件名 - if save_name_list: - print(f"实际保存的文件名: {', '.join(save_name_list)}") - return tree - - - - def query_task(self, task_id): - retry_index = 0 - while True: - url = "https://drive-m.quark.cn/1/clouddrive/task" - querystring = { - "pr": "ucpro", - "fr": "pc", - "uc_param_str": "", - "task_id": task_id, - "retry_index": retry_index, - "__dt": int(random.uniform(1, 5) * 60 * 1000), - "__t": datetime.now().timestamp(), - } - headers = self.common_headers() - response = requests.request( - "GET", url, headers=headers, params=querystring - ).json() - if response["data"]["status"] != 0: - if retry_index > 0: - print() - break - else: - if retry_index == 0: - print( - f"正在等待[{response['data']['task_title']}]执行结果", - end="", - flush=True, - ) - else: - print(".", end="", flush=True) - retry_index += 1 - time.sleep(0.500) - return response - def do_rename_task(self, task, subdir_path=""): - if not task["pattern"] or not task["replace"]: + 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): @@ -708,7 +802,6 @@ class Quark: is_rename_count += self.do_rename_task( task, f"{subdir_path}/{dir_file['file_name']}" ) - pattern, replace = magic_regex_func(task["pattern"], task["replace"]) if re.search(pattern, dir_file["file_name"]): save_name = ( re.sub(pattern, replace, dir_file["file_name"]) @@ -729,81 +822,6 @@ class Quark: return is_rename_count > 0 -class Emby: - def __init__(self, emby_url, emby_apikey): - self.is_active = False - if emby_url and emby_apikey: - self.emby_url = emby_url - self.emby_apikey = emby_apikey - if self.get_info(): - self.is_active = True - - def get_info(self): - url = f"{self.emby_url}/emby/System/Info" - headers = {"X-Emby-Token": self.emby_apikey} - querystring = {} - response = requests.request("GET", url, headers=headers, params=querystring) - if "application/json" in response.headers["Content-Type"]: - response = response.json() - print( - f"Emby媒体库: {response.get('ServerName','')} v{response.get('Version','')}" - ) - return True - else: - print(f"Emby媒体库: 连接失败❌ {response.text}") - return False - - def refresh(self, emby_id): - if emby_id: - url = f"{self.emby_url}/emby/Items/{emby_id}/Refresh" - headers = {"X-Emby-Token": self.emby_apikey} - querystring = { - "Recursive": "true", - "MetadataRefreshMode": "FullRefresh", - "ImageRefreshMode": "FullRefresh", - "ReplaceAllMetadata": "false", - "ReplaceAllImages": "false", - } - response = requests.request( - "POST", url, headers=headers, params=querystring - ) - if response.text == "": - print(f"🎞 刷新Emby媒体库:成功✅") - return True - else: - print(f"🎞 刷新Emby媒体库:{response.text}❌") - return False - - def search(self, media_name): - if media_name: - url = f"{self.emby_url}/emby/Items" - headers = {"X-Emby-Token": self.emby_apikey} - querystring = { - "IncludeItemTypes": "Series", - "StartIndex": 0, - "SortBy": "SortName", - "SortOrder": "Ascending", - "ImageTypeLimit": 0, - "Recursive": "true", - "SearchTerm": media_name, - "Limit": 10, - "IncludeSearchTypes": "false", - } - response = requests.request("GET", url, headers=headers, params=querystring) - if "application/json" in response.headers["Content-Type"]: - response = response.json() - if response.get("Items"): - for item in response["Items"]: - if item["IsFolder"]: - print( - f"🎞 《{item['Name']}》匹配到Emby媒体库ID:{item['Id']}" - ) - return item["Id"] - else: - print(f"🎞 搜索Emby媒体库:{response.text}❌") - return False - - def verify_account(account): # 验证账号 print(f"▶️ 验证第{account.index}个账号") @@ -848,7 +866,10 @@ def do_sign(account): sign_message = f"📅 执行签到: 今日签到+{int(sign_return/1024/1024)}MB,连签进度({growth_info['cap_sign']['sign_progress']+1}/{growth_info['cap_sign']['sign_target']})✅" message = f"{sign_message}\n{growth_message}" if ( - CONFIG_DATA.get("push_config", {}).get("QUARK_SIGN_NOTIFY") == False + str( + CONFIG_DATA.get("push_config", {}).get("QUARK_SIGN_NOTIFY") + ).lower() + == "false" or os.environ.get("QUARK_SIGN_NOTIFY") == "false" ): print(message) @@ -861,14 +882,19 @@ def do_sign(account): def do_save(account, tasklist=[]): - emby = Emby( - CONFIG_DATA.get("emby", {}).get("url", ""), - CONFIG_DATA.get("emby", {}).get("apikey", ""), + print(f"🧩 载入插件") + plugins, CONFIG_DATA["plugins"], task_plugins_config = Config.load_plugins( + CONFIG_DATA.get("plugins", {}) ) print(f"转存账号: {account.nickname}") # 获取全部保存目录fid account.update_savepath_fid(tasklist) + # ========== 修改内容开始 ========== + # 初始化计数器 + total_files_transferred = 0 # 无论 MAX_SAVE_FILES 的值如何,都初始化计数器 + # ========== 修改内容结束 ========== + def check_date(task): return ( not task.get("enddate") @@ -882,53 +908,68 @@ def do_save(account, tasklist=[]): or (datetime.today().weekday() + 1 in task.get("runweek")) ) - total_files_transferred = 0 # 计数器初始化 - # 执行任务 for index, task in enumerate(tasklist): # 判断任务期限 if check_date(task): - if total_files_transferred >= 2: # 检查是否已转存超过2个文件 + # ========== 修改内容开始 ========== + # 当 MAX_SAVE_FILES > 0 时,检查是否已转存超过限制 + if MAX_SAVE_FILES > 0 and total_files_transferred >= MAX_SAVE_FILES: print("⚠️ 已达到转存文件总数上限,停止转存任务") break - + # ========== 修改内容结束 ========== print() 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("emby_id"): - print(f"刷媒体库: {task['emby_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: + + # ========== 修改内容开始 ========== + # 当 MAX_SAVE_FILES > 0 时,更新计数器 + if MAX_SAVE_FILES > 0 and (is_new_tree or is_rename): total_files_transferred += 1 # 每成功转存或重命名一个文件,计数器加1 + # ========== 修改内容结束 ========== - # 刷新媒体库 - if emby.is_active and (is_new or is_rename) and task.get("emby_id") != "0": - if task.get("emby_id"): - emby.refresh(task["emby_id"]) - else: - match_emby_id = emby.search(task["taskname"]) - if match_emby_id: - task["emby_id"] = match_emby_id - emby.refresh(match_emby_id) + # 补充任务的插件配置 + 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", {}), task_plugins_config + ) + # 调用插件 + if is_new_tree or is_rename: + 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 main(): global CONFIG_DATA start_time = datetime.now() @@ -949,19 +990,20 @@ def main(): else: print(f"⚙️ 配置文件 {config_path} 不存在❌,正远程从下载配置模版") config_url = f"{GH_PROXY}https://raw.githubusercontent.com/Cp0204/quark_auto_save/main/quark_config.json" - if download_file(config_url, config_path): + if Config.download_file(config_url, config_path): print("⚙️ 配置模版下载成功✅,请到程序目录中手动配置") return else: print(f"⚙️ 正从 {config_path} 文件中读取配置") with open(config_path, "r", encoding="utf-8") as file: CONFIG_DATA = json.load(file) + Config.breaking_change_update(CONFIG_DATA) cookie_val = CONFIG_DATA.get("cookie") if not CONFIG_DATA.get("magic_regex"): CONFIG_DATA["magic_regex"] = MAGIC_REGEX cookie_form_file = True # 获取cookie - cookies = get_cookies(cookie_val) + cookies = Config.get_cookies(cookie_val) if not cookies: print("❌ cookie 未配置") return @@ -994,7 +1036,7 @@ def main(): if cookie_form_file: # 更新配置 with open(config_path, "w", encoding="utf-8") as file: - json.dump(CONFIG_DATA, file, ensure_ascii=False, indent=2) + json.dump(CONFIG_DATA, file, ensure_ascii=False, sort_keys=False, indent=2) print(f"===============程序结束===============") duration = datetime.now() - start_time