Compare commits

...

10 Commits

Author SHA1 Message Date
Cp0204
47d05dc37b 🐛 修正默认调试模式值
Some checks are pending
Docker Publish / build-and-push (push) Waiting to run
- 将默认 DEBUG 值设置为 False,以避免意外的调试模式激活
2024-12-04 16:21:27 +08:00
Cp0204
c588312f81 🎨 调整底部栏显示层级
- 设置z-index为99,确保底部按钮显示在其他元素之上
2024-12-04 16:18:04 +08:00
Cp0204
b3e0eead61 🔧 优化 JSON 写入格式
- 调整 JSON 写入时的缩进为 2 个空格,以保持一致性
- 添加 `sort_keys=False` 参数以避免键排序,保持原有顺序
2024-12-04 15:12:17 +08:00
Cp0204
09109fe9bf ♻️ 重构插件加载逻辑,优化配置更新
- 配置操作类函数整合到 class 中
- 容器运行时补充缺失配置
2024-12-04 14:45:37 +08:00
Cp0204
fdc4cbb2e8 ♻️ 使用基础URL常量,便于维护 2024-12-04 11:03:00 +08:00
Cp0204
95d1449651 🐛 修复目录路径拼接无 / 开头问题 #44 2024-12-04 10:02:10 +08:00
Cp0204
b21c75e125 ♻️ 优化移动端请求封装
- 使用kwargs.get方法动态获取请求头,提高代码灵活性
- 将请求方法封装到_send_request方法中,减少重复代码
- 移动端更新URL为drive-m.quark.cn,确保请求地址正确
2024-12-03 22:37:39 +08:00
Cp0204
8b1f5067f2 🔧 移动方法位置,提高代码可读性 2024-12-03 22:37:39 +08:00
Cp0204
9fd7b5934d ♻️ 重构请求方法和私有化函数
- 将 `match_mparam_form_cookie` 方法私有化并重命名为 `_match_mparam_form_cookie`
- 新增 `_send_request` 方法,统一处理请求并添加错误处理
- 将所有请求方法替换为 `_send_request`,简化代码并提高可维护性
2024-12-03 22:37:39 +08:00
Cp0204
53b4f94995 🔧 捕获所有异常以增强错误处理
- 将所有 `requests.exceptions.RequestException` 替换为 `Exception` 以捕获所有可能的异常
- 增强插件在遇到错误时的健壮性
2024-12-03 21:32:46 +08:00
9 changed files with 237 additions and 236 deletions

View File

@ -24,6 +24,7 @@ import os
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, parent_dir) sys.path.insert(0, parent_dir)
from quark_auto_save import Quark from quark_auto_save import Quark
from quark_auto_save import Config
def get_app_ver(): def get_app_ver():
@ -43,6 +44,8 @@ SCRIPT_PATH = os.environ.get("SCRIPT_PATH", "./quark_auto_save.py")
CONFIG_PATH = os.environ.get("CONFIG_PATH", "./config/quark_config.json") CONFIG_PATH = os.environ.get("CONFIG_PATH", "./config/quark_config.json")
DEBUG = os.environ.get("DEBUG", False) DEBUG = os.environ.get("DEBUG", False)
task_plugins_config = {}
app = Flask(__name__) app = Flask(__name__)
app.config["APP_VERSION"] = get_app_ver() app.config["APP_VERSION"] = get_app_ver()
app.secret_key = "ca943f6db6dd34823d36ab08d8d6f65d" app.secret_key = "ca943f6db6dd34823d36ab08d8d6f65d"
@ -79,7 +82,7 @@ def read_json():
# 将数据写入 JSON 文件 # 将数据写入 JSON 文件
def write_json(data): def write_json(data):
with open(CONFIG_PATH, "w", encoding="utf-8") as f: with open(CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4, ensure_ascii=False, sort_keys=False) json.dump(data, f, ensure_ascii=False, sort_keys=False, indent=2)
def is_login(): def is_login():
@ -145,6 +148,7 @@ def get_data():
return redirect(url_for("login")) return redirect(url_for("login"))
data = read_json() data = read_json()
del data["webui"] del data["webui"]
data["task_plugins_config"] = task_plugins_config
return jsonify(data) return jsonify(data)
@ -153,10 +157,10 @@ def get_data():
def update(): def update():
if not is_login(): if not is_login():
return "未登录" return "未登录"
data = read_json()
webui = data["webui"]
data = request.json data = request.json
data["webui"] = webui data["webui"] = read_json()["webui"]
if "task_plugins_config" in data:
del data["task_plugins_config"]
write_json(data) write_json(data)
# 重新加载任务 # 重新加载任务
if reload_tasks(): if reload_tasks():
@ -279,6 +283,7 @@ def reload_tasks():
def init(): def init():
global task_plugins_config
logging.info(f">>> 初始化配置") logging.info(f">>> 初始化配置")
# 检查配置文件是否存在 # 检查配置文件是否存在
if not os.path.exists(CONFIG_PATH): if not os.path.exists(CONFIG_PATH):
@ -287,6 +292,7 @@ def init():
with open("quark_config.json", "rb") as src, open(CONFIG_PATH, "wb") as dest: with open("quark_config.json", "rb") as src, open(CONFIG_PATH, "wb") as dest:
dest.write(src.read()) dest.write(src.read())
data = read_json() data = read_json()
Config.breaking_change_update(data)
# 默认管理账号 # 默认管理账号
data["webui"] = { data["webui"] = {
"username": os.environ.get("WEBUI_USERNAME") "username": os.environ.get("WEBUI_USERNAME")
@ -297,6 +303,10 @@ def init():
# 默认定时规则 # 默认定时规则
if not data.get("crontab"): if not data.get("crontab"):
data["crontab"] = "0 8,18,20 * * *" data["crontab"] = "0 8,18,20 * * *"
# 初始化插件配置
_, plugins_config_default, task_plugins_config = Config.load_plugins()
plugins_config_default.update(data.get("plugins", {}))
data["plugins"] = plugins_config_default
write_json(data) write_json(data)

View File

@ -14,6 +14,7 @@
} }
.bottom-buttons { .bottom-buttons {
z-index: 99;
position: fixed; position: fixed;
left: 0; left: 0;
bottom: 0; bottom: 0;

View File

@ -51,7 +51,7 @@ try:
# 处理响应数据 # 处理响应数据
# ...... # ......
# 返回 # 返回
except requests.exceptions.RequestException as e: except Exception as e:
print(f"Error: {e}") print(f"Error: {e}")
return False return False
``` ```

View File

@ -108,7 +108,7 @@ class Alist:
return data.get("data", []) return data.get("data", [])
else: else:
print(f"Alist刷新: 存储{storage_id}连接失败❌ {data.get('message')}") print(f"Alist刷新: 存储{storage_id}连接失败❌ {data.get('message')}")
except requests.exceptions.RequestException as e: except Exception as e:
print(f"Alist刷新: 获取Alist存储出错 {e}") print(f"Alist刷新: 获取Alist存储出错 {e}")
return [] return []
@ -173,10 +173,10 @@ class Alist:
"GET", url, headers=headers, params=querystring "GET", url, headers=headers, params=querystring
).json() ).json()
if response["code"] == 0: if response["code"] == 0:
file_names = [ path = ""
item["file_name"] for item in response["data"]["full_path"] for item in response["data"]["full_path"]:
] path = f"{path}/{item['file_name']}"
return "/".join(file_names) return path
except requests.exceptions.RequestException as e: except Exception as e:
print(f"Alist刷新: 获取Quark路径出错 {e}") print(f"Alist刷新: 获取Quark路径出错 {e}")
return "" return ""

View File

@ -51,7 +51,7 @@ class Alist_strm:
return True return True
else: else:
print(f"alist-strm配置运行: 匹配失败❌") print(f"alist-strm配置运行: 匹配失败❌")
except requests.exceptions.RequestException as e: except Exception as e:
print(f"获取alist-strm配置信息出错: {e}") print(f"获取alist-strm配置信息出错: {e}")
return False return False
@ -77,6 +77,6 @@ class Alist_strm:
return True return True
else: else:
print(f"🔗 alist-strm配置运行: 失败❌") print(f"🔗 alist-strm配置运行: 失败❌")
except requests.exceptions.RequestException as e: except Exception as e:
print(f"Error: {e}") print(f"Error: {e}")
return False return False

View File

@ -122,7 +122,7 @@ class Alist_strm_gen:
return data.get("data", []) return data.get("data", [])
else: else:
print(f"Alist-Strm生成: 获取存储失败❌ {data.get('message')}") print(f"Alist-Strm生成: 获取存储失败❌ {data.get('message')}")
except requests.exceptions.RequestException as e: except Exception as e:
print(f"Alist-Strm生成: 获取存储出错 {e}") print(f"Alist-Strm生成: 获取存储出错 {e}")
return [] return []
@ -198,10 +198,10 @@ class Alist_strm_gen:
"GET", url, headers=headers, params=querystring "GET", url, headers=headers, params=querystring
).json() ).json()
if response["code"] == 0: if response["code"] == 0:
file_names = [ path = ""
item["file_name"] for item in response["data"]["full_path"] for item in response["data"]["full_path"]:
] path = f"{path}/{item['file_name']}"
return "/".join(file_names) return path
except requests.exceptions.RequestException as e: except Exception as e:
print(f"Alist-Strm生成: 获取Quark路径出错 {e}") print(f"Alist-Strm生成: 获取Quark路径出错 {e}")
return "" return ""

View File

@ -74,7 +74,7 @@ class Aria2:
response = requests.post(self.rpc_url, json=jsonrpc_data) response = requests.post(self.rpc_url, json=jsonrpc_data)
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()
except requests.exceptions.RequestException as e: except Exception as e:
print(f"Aria2下载: 错误{e}") print(f"Aria2下载: 错误{e}")
return {} return {}

View File

@ -53,7 +53,7 @@ class Emby:
return True return True
else: else:
print(f"Emby媒体库: 连接失败❌ {response.text}") print(f"Emby媒体库: 连接失败❌ {response.text}")
except requests.exceptions.RequestException as e: except Exception as e:
print(f"获取Emby媒体库信息出错: {e}") print(f"获取Emby媒体库信息出错: {e}")
return False return False
@ -78,7 +78,7 @@ class Emby:
return True return True
else: else:
print(f"🎞️ 刷新Emby媒体库{response.text}") print(f"🎞️ 刷新Emby媒体库{response.text}")
except requests.exceptions.RequestException as e: except Exception as e:
print(f"刷新Emby媒体库出错: {e}") print(f"刷新Emby媒体库出错: {e}")
return False return False
@ -111,6 +111,6 @@ class Emby:
return item["Id"] return item["Id"]
else: else:
print(f"🎞️ 搜索Emby媒体库{response.text}") print(f"🎞️ 搜索Emby媒体库{response.text}")
except requests.exceptions.RequestException as e: except Exception as e:
print(f"搜索Emby媒体库出错: {e}") print(f"搜索Emby媒体库出错: {e}")
return "" return ""

View File

@ -39,18 +39,6 @@ MAGIC_REGEX = {
} }
# 魔法正则匹配
def magic_regex_func(pattern, replace, taskname=""):
keyword = pattern
if keyword in CONFIG_DATA["magic_regex"]:
pattern = CONFIG_DATA["magic_regex"][keyword]["pattern"]
if replace == "":
replace = CONFIG_DATA["magic_regex"][keyword]["replace"]
if taskname:
replace = replace.replace("$TASKNAME", taskname)
return pattern, replace
# 发送通知消息 # 发送通知消息
def send_ql_notify(title, body): def send_ql_notify(title, body):
try: try:
@ -75,40 +63,104 @@ def add_notify(text):
return text return text
# 下载配置 class Config:
def download_file(url, save_path): # 下载配置
response = requests.get(url) def download_file(url, save_path):
if response.status_code == 200: response = requests.get(url)
with open(save_path, "wb") as file: if response.status_code == 200:
file.write(response.content) with open(save_path, "wb") as file:
return True file.write(response.content)
else: return True
return False
# 读取CK
def get_cookies(cookie_val):
if isinstance(cookie_val, list):
return cookie_val
elif cookie_val:
if "\n" in cookie_val:
return cookie_val.split("\n")
else: else:
return [cookie_val] return False
else:
return False # 读取CK
def get_cookies(cookie_val):
if isinstance(cookie_val, list):
return cookie_val
elif cookie_val:
if "\n" in cookie_val:
return cookie_val.split("\n")
else:
return [cookie_val]
else:
return False
def load_plugins(plugins_config={}, plugins_dir="plugins"):
plugins_available = {}
task_plugins_config = {}
all_modules = [
f.replace(".py", "") for f in os.listdir(plugins_dir) if f.endswith(".py")
]
# 调整模块优先级
priority_path = os.path.join(plugins_dir, "_priority.json")
try:
with open(priority_path, encoding="utf-8") as f:
priority_modules = json.load(f)
if priority_modules:
all_modules = [
module for module in priority_modules if module in all_modules
] + [module for module in all_modules if module not in priority_modules]
except (FileNotFoundError, json.JSONDecodeError):
priority_modules = []
for module_name in all_modules:
try:
module = importlib.import_module(f"{plugins_dir}.{module_name}")
ServerClass = getattr(module, module_name.capitalize())
# 检查配置中是否存在该模块的配置
if module_name in plugins_config:
plugin = ServerClass(**plugins_config[module_name])
plugins_available[module_name] = plugin
else:
plugin = ServerClass()
plugins_config[module_name] = plugin.default_config
# 检查插件是否支持单独任务配置
if hasattr(plugin, "default_task_config"):
task_plugins_config[module_name] = plugin.default_task_config
except (ImportError, AttributeError) as e:
print(f"载入模块 {module_name} 失败: {e}")
print()
return plugins_available, plugins_config, task_plugins_config
def breaking_change_update(config_data):
if config_data.get("emby"):
print("🔼 Update config v0.3.6.1 to 0.3.7")
config_data.setdefault("media_servers", {})["emby"] = {
"url": config_data["emby"]["url"],
"token": config_data["emby"]["apikey"],
}
del config_data["emby"]
for task in config_data.get("tasklist", {}):
task["media_id"] = task.get("emby_id", "")
if task.get("emby_id"):
del task["emby_id"]
if config_data.get("media_servers"):
print("🔼 Update config v0.3.8 to 0.3.9")
config_data["plugins"] = config_data.get("media_servers")
del config_data["media_servers"]
for task in config_data.get("tasklist", {}):
task["addition"] = {
"emby": {
"media_id": task.get("media_id", ""),
}
}
if task.get("media_id"):
del task["media_id"]
class Quark: class Quark:
BASE_URL = "https://drive-pc.quark.cn"
BASE_URL_APP = "https://drive-m.quark.cn"
def __init__(self, cookie, index=None): def __init__(self, cookie, index=None):
self.cookie = cookie.strip() self.cookie = cookie.strip()
self.index = index + 1 self.index = index + 1
self.is_active = False self.is_active = False
self.nickname = "" self.nickname = ""
self.mparam = self.match_mparam_form_cookie(cookie) self.mparam = self._match_mparam_form_cookie(cookie)
self.savepath_fid = {"/": "0"} self.savepath_fid = {"/": "0"}
def match_mparam_form_cookie(self, cookie): def _match_mparam_form_cookie(self, cookie):
mparam = {} mparam = {}
kps_match = re.search(r"(?<!\w)kps=([a-zA-Z0-9%+/=]+)[;&]?", cookie) kps_match = re.search(r"(?<!\w)kps=([a-zA-Z0-9%+/=]+)[;&]?", cookie)
sign_match = re.search(r"(?<!\w)sign=([a-zA-Z0-9%+/=]+)[;&]?", cookie) sign_match = re.search(r"(?<!\w)sign=([a-zA-Z0-9%+/=]+)[;&]?", cookie)
@ -121,13 +173,26 @@ class Quark:
} }
return mparam return mparam
def common_headers(self): def _send_request(self, method, url, **kwargs):
headers = { headers = {
"cookie": self.cookie, "cookie": self.cookie,
"content-type": "application/json", "content-type": "application/json",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/3.14.2 Chrome/112.0.5615.165 Electron/24.1.3.8 Safari/537.36 Channel/pckk_other_ch", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/3.14.2 Chrome/112.0.5615.165 Electron/24.1.3.8 Safari/537.36 Channel/pckk_other_ch",
} }
return headers if "headers" in kwargs:
headers = kwargs["headers"]
del kwargs["headers"]
try:
response = requests.request(method, url, headers=headers, **kwargs)
# print(f"{response.text}")
response.raise_for_status() # 检查请求是否成功
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'{"error": 1}'
return fake_response
def init(self): def init(self):
account_info = self.get_account_info() account_info = self.get_account_info()
@ -141,20 +206,14 @@ class Quark:
def get_account_info(self): def get_account_info(self):
url = "https://pan.quark.cn/account/info" url = "https://pan.quark.cn/account/info"
querystring = {"fr": "pc", "platform": "pc"} querystring = {"fr": "pc", "platform": "pc"}
headers = { response = self._send_request("GET", url, params=querystring).json()
"cookie": self.cookie,
"content-type": "application/json",
}
response = requests.request(
"GET", url, headers=headers, params=querystring
).json()
if response.get("data"): if response.get("data"):
return response["data"] return response["data"]
else: else:
return False return False
def get_growth_info(self): def get_growth_info(self):
url = "https://drive-h.quark.cn/1/clouddrive/capacity/growth/info" url = f"{self.BASE_URL_APP}/1/clouddrive/capacity/growth/info"
querystring = { querystring = {
"pr": "ucpro", "pr": "ucpro",
"fr": "android", "fr": "android",
@ -165,7 +224,7 @@ class Quark:
headers = { headers = {
"content-type": "application/json", "content-type": "application/json",
} }
response = requests.request( response = self._send_request(
"GET", url, headers=headers, params=querystring "GET", url, headers=headers, params=querystring
).json() ).json()
if response.get("data"): if response.get("data"):
@ -174,7 +233,7 @@ class Quark:
return False return False
def get_growth_sign(self): def get_growth_sign(self):
url = "https://drive-h.quark.cn/1/clouddrive/capacity/growth/sign" url = f"{self.BASE_URL_APP}/1/clouddrive/capacity/growth/sign"
querystring = { querystring = {
"pr": "ucpro", "pr": "ucpro",
"fr": "android", "fr": "android",
@ -188,7 +247,7 @@ class Quark:
headers = { headers = {
"content-type": "application/json", "content-type": "application/json",
} }
response = requests.request( response = self._send_request(
"POST", url, json=payload, headers=headers, params=querystring "POST", url, json=payload, headers=headers, params=querystring
).json() ).json()
if response.get("data"): if response.get("data"):
@ -196,26 +255,13 @@ class Quark:
else: else:
return False, response["message"] return False, response["message"]
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 get_stoken(self, pwd_id, passcode=""): def get_stoken(self, pwd_id, passcode=""):
url = "https://drive-h.quark.cn/1/clouddrive/share/sharepage/token" url = f"{self.BASE_URL}/1/clouddrive/share/sharepage/token"
querystring = {"pr": "ucpro", "fr": "pc"} querystring = {"pr": "ucpro", "fr": "pc"}
payload = {"pwd_id": pwd_id, "passcode": passcode} payload = {"pwd_id": pwd_id, "passcode": passcode}
headers = self.common_headers() response = self._send_request(
response = requests.request( "POST", url, json=payload, params=querystring
"POST", url, json=payload, headers=headers, params=querystring
).json() ).json()
if response.get("status") == 200: if response.get("status") == 200:
return True, response["data"]["stoken"] return True, response["data"]["stoken"]
@ -226,7 +272,7 @@ class Quark:
list_merge = [] list_merge = []
page = 1 page = 1
while True: while True:
url = "https://drive-h.quark.cn/1/clouddrive/share/sharepage/detail" url = f"{self.BASE_URL}/1/clouddrive/share/sharepage/detail"
querystring = { querystring = {
"pr": "ucpro", "pr": "ucpro",
"fr": "pc", "fr": "pc",
@ -241,10 +287,7 @@ class Quark:
"_fetch_total": "1", "_fetch_total": "1",
"_sort": "file_type:asc,updated_at:desc", "_sort": "file_type:asc,updated_at:desc",
} }
headers = self.common_headers() response = self._send_request("GET", url, params=querystring).json()
response = requests.request(
"GET", url, headers=headers, params=querystring
).json()
if response["data"]["list"]: if response["data"]["list"]:
list_merge += response["data"]["list"] list_merge += response["data"]["list"]
page += 1 page += 1
@ -258,12 +301,11 @@ class Quark:
def get_fids(self, file_paths): def get_fids(self, file_paths):
fids = [] fids = []
while True: while True:
url = "https://drive-h.quark.cn/1/clouddrive/file/info/path_list" url = f"{self.BASE_URL}/1/clouddrive/file/info/path_list"
querystring = {"pr": "ucpro", "fr": "pc"} querystring = {"pr": "ucpro", "fr": "pc"}
payload = {"file_path": file_paths[:50], "namespace": "0"} payload = {"file_path": file_paths[:50], "namespace": "0"}
headers = self.common_headers() response = self._send_request(
response = requests.request( "POST", url, json=payload, params=querystring
"POST", url, json=payload, headers=headers, params=querystring
).json() ).json()
if response["code"] == 0: if response["code"] == 0:
fids += response["data"] fids += response["data"]
@ -279,7 +321,7 @@ class Quark:
file_list = [] file_list = []
page = 1 page = 1
while True: while True:
url = "https://drive-h.quark.cn/1/clouddrive/file/sort" url = f"{self.BASE_URL}/1/clouddrive/file/sort"
querystring = { querystring = {
"pr": "ucpro", "pr": "ucpro",
"fr": "pc", "fr": "pc",
@ -292,10 +334,7 @@ class Quark:
"_sort": "file_type:asc,updated_at:desc", "_sort": "file_type:asc,updated_at:desc",
"_fetch_full_path": kwargs.get("fetch_full_path", 0), "_fetch_full_path": kwargs.get("fetch_full_path", 0),
} }
headers = self.common_headers() response = self._send_request("GET", url, params=querystring).json()
response = requests.request(
"GET", url, headers=headers, params=querystring
).json()
if response["data"]["list"]: if response["data"]["list"]:
file_list += response["data"]["list"] file_list += response["data"]["list"]
page += 1 page += 1
@ -306,7 +345,7 @@ class Quark:
return file_list return file_list
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 = "https://drive-h.quark.cn/1/clouddrive/share/sharepage/save" url = f"{self.BASE_URL}/1/clouddrive/share/sharepage/save"
querystring = { querystring = {
"pr": "ucpro", "pr": "ucpro",
"fr": "pc", "fr": "pc",
@ -324,24 +363,53 @@ class Quark:
"pdir_fid": "0", "pdir_fid": "0",
"scene": "link", "scene": "link",
} }
headers = self.common_headers() response = self._send_request(
response = requests.request( "POST", url, json=payload, params=querystring
"POST", url, json=payload, headers=headers, params=querystring
).json() ).json()
return response 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 = requests.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): def download(self, fids):
url = "https://drive-h.quark.cn/1/clouddrive/file/download" url = f"{self.BASE_URL}/1/clouddrive/file/download"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""} querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
payload = {"fids": fids} payload = {"fids": fids}
headers = self.common_headers() response = self._send_request("POST", url, json=payload, params=querystring)
response = requests.post(url, json=payload, headers=headers, params=querystring)
set_cookie = response.cookies.get_dict() set_cookie = response.cookies.get_dict()
cookie_str = "; ".join([f"{key}={value}" for key, value in set_cookie.items()]) cookie_str = "; ".join([f"{key}={value}" for key, value in set_cookie.items()])
return response.json(), cookie_str return response.json(), cookie_str
def mkdir(self, dir_path): def mkdir(self, dir_path):
url = "https://drive-h.quark.cn/1/clouddrive/file" url = f"{self.BASE_URL}/1/clouddrive/file"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""} querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
payload = { payload = {
"pdir_fid": "0", "pdir_fid": "0",
@ -349,34 +417,31 @@ class Quark:
"dir_path": dir_path, "dir_path": dir_path,
"dir_init_lock": False, "dir_init_lock": False,
} }
headers = self.common_headers() response = self._send_request(
response = requests.request( "POST", url, json=payload, params=querystring
"POST", url, json=payload, headers=headers, params=querystring
).json() ).json()
return response return response
def rename(self, fid, file_name): def rename(self, fid, file_name):
url = "https://drive-h.quark.cn/1/clouddrive/file/rename" url = f"{self.BASE_URL}/1/clouddrive/file/rename"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""} querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
payload = {"fid": fid, "file_name": file_name} payload = {"fid": fid, "file_name": file_name}
headers = self.common_headers() response = self._send_request(
response = requests.request( "POST", url, json=payload, params=querystring
"POST", url, json=payload, headers=headers, params=querystring
).json() ).json()
return response return response
def delete(self, filelist): def delete(self, filelist):
url = "https://drive-h.quark.cn/1/clouddrive/file/delete" url = f"{self.BASE_URL}/1/clouddrive/file/delete"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""} querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
payload = {"action_type": 2, "filelist": filelist, "exclude_fids": []} payload = {"action_type": 2, "filelist": filelist, "exclude_fids": []}
headers = self.common_headers() response = self._send_request(
response = requests.request( "POST", url, json=payload, params=querystring
"POST", url, json=payload, headers=headers, params=querystring
).json() ).json()
return response return response
def recycle_list(self, page=1, size=30): def recycle_list(self, page=1, size=30):
url = "https://drive-h.quark.cn/1/clouddrive/file/recycle/list" url = f"{self.BASE_URL}/1/clouddrive/file/recycle/list"
querystring = { querystring = {
"_page": page, "_page": page,
"_size": size, "_size": size,
@ -384,25 +449,48 @@ class Quark:
"fr": "pc", "fr": "pc",
"uc_param_str": "", "uc_param_str": "",
} }
headers = self.common_headers() response = self._send_request("GET", url, params=querystring).json()
response = requests.request(
"GET", url, headers=headers, params=querystring
).json()
return response["data"]["list"] return response["data"]["list"]
def recycle_remove(self, record_list): def recycle_remove(self, record_list):
url = "https://drive-h.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"} querystring = {"uc_param_str": "", "fr": "pc", "pr": "ucpro"}
payload = { payload = {
"select_mode": 2, "select_mode": 2,
"record_list": record_list, "record_list": record_list,
} }
headers = self.common_headers() response = self._send_request(
response = requests.request( "POST", url, json=payload, params=querystring
"POST", url, json=payload, headers=headers, params=querystring
).json() ).json()
return response 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): def update_savepath_fid(self, tasklist):
dir_paths = [ dir_paths = [
re.sub(r"/{2,}", "/", f"/{item['savepath']}") re.sub(r"/{2,}", "/", f"/{item['savepath']}")
@ -551,7 +639,7 @@ class Quark:
if share_file["dir"] and task.get("update_subdir", False): if share_file["dir"] and task.get("update_subdir", False):
pattern, replace = task["update_subdir"], "" pattern, replace = task["update_subdir"], ""
else: else:
pattern, replace = magic_regex_func( pattern, replace = self.magic_regex_func(
task["pattern"], task["replace"], task["taskname"] task["pattern"], task["replace"], task["taskname"]
) )
# 正则文件名匹配 # 正则文件名匹配
@ -643,42 +731,8 @@ class Quark:
add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n") add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n")
return tree return tree
def query_task(self, task_id):
retry_index = 0
while True:
url = "https://drive-h.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=""): def do_rename_task(self, task, subdir_path=""):
pattern, replace = magic_regex_func( pattern, replace = self.magic_regex_func(
task["pattern"], task["replace"], task["taskname"] task["pattern"], task["replace"], task["taskname"]
) )
if not pattern or not replace: if not pattern or not replace:
@ -714,44 +768,6 @@ class Quark:
return is_rename_count > 0 return is_rename_count > 0
def load_plugins(plugins_config, plugins_dir="plugins"):
plugins_available = {}
task_plugins_config = {}
all_modules = [
f.replace(".py", "") for f in os.listdir(plugins_dir) if f.endswith(".py")
]
# 调整模块优先级
priority_path = os.path.join(plugins_dir, "_priority.json")
try:
with open(priority_path, encoding="utf-8") as f:
priority_modules = json.load(f)
if priority_modules:
all_modules = [
module for module in priority_modules if module in all_modules
] + [module for module in all_modules if module not in priority_modules]
except (FileNotFoundError, json.JSONDecodeError):
priority_modules = []
print(f"🧩 载入插件")
for module_name in all_modules:
try:
module = importlib.import_module(f"{plugins_dir}.{module_name}")
ServerClass = getattr(module, module_name.capitalize())
# 检查配置中是否存在该模块的配置
if module_name in plugins_config:
plugin = ServerClass(**plugins_config[module_name])
plugins_available[module_name] = plugin
else:
plugin = ServerClass()
plugins_config[module_name] = plugin.default_config
# 检查插件是否支持单独任务配置
if hasattr(plugin, "default_task_config"):
task_plugins_config[module_name] = plugin.default_task_config
except (ImportError, AttributeError) as e:
print(f"载入模块 {module_name} 失败: {e}")
print()
return plugins_available, task_plugins_config
def verify_account(account): def verify_account(account):
# 验证账号 # 验证账号
print(f"▶️ 验证第{account.index}个账号") print(f"▶️ 验证第{account.index}个账号")
@ -812,7 +828,8 @@ def do_sign(account):
def do_save(account, tasklist=[]): def do_save(account, tasklist=[]):
plugins, CONFIG_DATA["task_plugins_config"] = load_plugins( print(f"🧩 载入插件")
plugins, CONFIG_DATA["plugins"], task_plugins_config = Config.load_plugins(
CONFIG_DATA.get("plugins", {}) CONFIG_DATA.get("plugins", {})
) )
print(f"转存账号: {account.nickname}") print(f"转存账号: {account.nickname}")
@ -868,7 +885,7 @@ def do_save(account, tasklist=[]):
return result return result
task["addition"] = merge_dicts( task["addition"] = merge_dicts(
task.get("addition", {}), CONFIG_DATA["task_plugins_config"] task.get("addition", {}), task_plugins_config
) )
# 调用插件 # 调用插件
if is_new_tree or is_rename: if is_new_tree or is_rename:
@ -881,33 +898,6 @@ def do_save(account, tasklist=[]):
print() print()
def breaking_change_update():
global CONFIG_DATA
if CONFIG_DATA.get("emby"):
print("🔼 Update config v0.3.6.1 to 0.3.7")
CONFIG_DATA.setdefault("media_servers", {})["emby"] = {
"url": CONFIG_DATA["emby"]["url"],
"token": CONFIG_DATA["emby"]["apikey"],
}
del CONFIG_DATA["emby"]
for task in CONFIG_DATA.get("tasklist", {}):
task["media_id"] = task.get("emby_id", "")
if task.get("emby_id"):
del task["emby_id"]
if CONFIG_DATA.get("media_servers"):
print("🔼 Update config v0.3.8 to 0.3.9")
CONFIG_DATA["plugins"] = CONFIG_DATA.get("media_servers")
del CONFIG_DATA["media_servers"]
for task in CONFIG_DATA.get("tasklist", {}):
task["addition"] = {
"emby": {
"media_id": task.get("media_id", ""),
}
}
if task.get("media_id"):
del task["media_id"]
def main(): def main():
global CONFIG_DATA global CONFIG_DATA
start_time = datetime.now() start_time = datetime.now()
@ -928,20 +918,20 @@ def main():
else: else:
print(f"⚙️ 配置文件 {config_path} 不存在❌,正远程从下载配置模版") print(f"⚙️ 配置文件 {config_path} 不存在❌,正远程从下载配置模版")
config_url = f"{GH_PROXY}https://raw.githubusercontent.com/Cp0204/quark_auto_save/main/quark_config.json" 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("⚙️ 配置模版下载成功✅,请到程序目录中手动配置") print("⚙️ 配置模版下载成功✅,请到程序目录中手动配置")
return return
else: else:
print(f"⚙️ 正从 {config_path} 文件中读取配置") print(f"⚙️ 正从 {config_path} 文件中读取配置")
with open(config_path, "r", encoding="utf-8") as file: with open(config_path, "r", encoding="utf-8") as file:
CONFIG_DATA = json.load(file) CONFIG_DATA = json.load(file)
breaking_change_update() Config.breaking_change_update(CONFIG_DATA)
cookie_val = CONFIG_DATA.get("cookie") cookie_val = CONFIG_DATA.get("cookie")
if not CONFIG_DATA.get("magic_regex"): if not CONFIG_DATA.get("magic_regex"):
CONFIG_DATA["magic_regex"] = MAGIC_REGEX CONFIG_DATA["magic_regex"] = MAGIC_REGEX
cookie_form_file = True cookie_form_file = True
# 获取cookie # 获取cookie
cookies = get_cookies(cookie_val) cookies = Config.get_cookies(cookie_val)
if not cookies: if not cookies:
print("❌ cookie 未配置") print("❌ cookie 未配置")
return return
@ -974,7 +964,7 @@ def main():
if cookie_form_file: if cookie_form_file:
# 更新配置 # 更新配置
with open(config_path, "w", encoding="utf-8") as 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"===============程序结束===============") print(f"===============程序结束===============")
duration = datetime.now() - start_time duration = datetime.now() - start_time