feat: 增加项目一键整理、打包及自动上传夸克/百度网盘功能

This commit is contained in:
王鹏
2026-04-15 10:24:38 +08:00
parent 245f9cdf41
commit a6d79d9a14
9 changed files with 933 additions and 19 deletions

166
quark_uploader.py Normal file
View File

@@ -0,0 +1,166 @@
from __future__ import annotations
import os
import time
from typing import Optional, Callable
from playwright.sync_api import sync_playwright
class QuarkUploader:
"""
夸克网盘上传工具 - 终极稳健版 (双重状态锁)
"""
def __init__(self, chrome_path: str, cookies_dir: str, log_callback: Optional[Callable[[str], None]] = None):
self.chrome_path = chrome_path
self.cookies_dir = cookies_dir
self.log_callback = log_callback
self.url = "https://pan.quark.cn/"
if not os.path.exists(self.cookies_dir):
os.makedirs(self.cookies_dir, exist_ok=True)
def _log(self, message: str):
print(message)
if self.log_callback:
self.log_callback(message)
def upload_file(self, file_path: str, target_folder_name: str, root_path: str = "精品项目整理") -> bool:
"""
上传文件到夸克网盘
"""
if not os.path.exists(file_path):
self._log(f"错误: 本地文件不存在 {file_path}")
return False
filename = os.path.basename(file_path)
try:
with sync_playwright() as p:
self._log(f"正在启动浏览器...")
launch_args = {
"user_data_dir": self.cookies_dir,
"headless": False,
"viewport": {"width": 1280, "height": 800}
}
if self.chrome_path and os.path.exists(self.chrome_path):
launch_args["executable_path"] = self.chrome_path
context = p.chromium.launch_persistent_context(**launch_args)
page = context.new_page()
self._log("正在打开夸克网盘...")
page.goto(self.url, wait_until="networkidle")
# 1. 登录检测
try:
page.wait_for_selector("text=全部文件", timeout=10000)
except:
self._log("请在浏览器中完成登录...")
page.wait_for_selector("text=全部文件", timeout=180000)
# 2. 导航到目录
self._log(f"定位目录: {root_path} > {target_folder_name}")
page.get_by_text("全部文件").first.click()
page.wait_for_timeout(2000)
# 进入根目录
if not page.get_by_text(root_path, exact=True).first.is_visible():
self._create_folder(page, root_path)
page.get_by_text(root_path, exact=True).first.dblclick()
page.wait_for_timeout(1000)
# 进入项目目录
if not page.get_by_text(target_folder_name, exact=True).first.is_visible():
self._create_folder(page, target_folder_name)
page.get_by_text(target_folder_name, exact=True).first.dblclick()
page.wait_for_timeout(2000)
# 3. 查重
if page.get_by_text(filename, exact=True).count() > 0:
self._log(f"云端已存在 '{filename}',跳过上传。")
context.close()
return True
# 4. 执行上传
self._log(f"开始上传: {filename}")
# 清除可能存在的旧“上传成功”提示
page.evaluate("() => { const els = document.querySelectorAll('.ant-notification-notice'); els.forEach(e => e.remove()); }")
page.set_input_files("input[type=file]", file_path)
# 5. 确认上传已启动 (关键一步)
self._log("等待上传任务初始化...")
try:
# 等待出现任何进度指示元素
page.wait_for_selector("text=% , .ant-progress-inner, text=正在上传", timeout=20000)
self._log("检测到上传流已建立。")
except:
self._log("警告:未检测到明显的进度条,可能由于文件较小或 UI 延迟,继续监测列表状态。")
# 6. 深度监测循环
self._log("进入深度监测模式,请保持浏览器前台运行...")
start_time = time.time()
timeout = 1800 # 30分钟
# 连续稳定次数计数
stable_count = 0
while time.time() - start_time < timeout:
# A. 检查全局进度标志
is_uploading = page.get_by_text("%").is_visible() or \
page.get_by_text("正在上传").is_visible() or \
page.locator(".ant-progress-inner").is_visible()
# B. 检查列表中该行的具体状态 (精准匹配)
# 找到包含文件名的那一行 tr看里面是否有“正在上传”字样
row_status_text = ""
try:
row = page.locator(f"tr:has-text('{filename}')").first
if row.is_visible():
row_status_text = row.inner_text()
except:
pass
is_item_active = "正在上传" in row_status_text or "%" in row_status_text or "等待上传" in row_status_text
# C. 确认列表里确实有这个文件
in_list = page.get_by_text(filename, exact=True).count() > 0
if not is_uploading and not is_item_active and in_list:
stable_count += 1
if stable_count >= 5: # 连续 5 次检测(约 15 秒)都处于稳定态
self._log("检测到上传任务已从任务列表中消失,且列表文件状态正常。")
self._log("为了绝对安全,最后等待 15 秒进行数据落盘同步...")
page.wait_for_timeout(15000)
self._log("上传确认圆满成功!")
context.close()
return True
else:
self._log(f"上传疑似完成,正在进行稳定性校验 ({stable_count}/5)...")
else:
if is_uploading or is_item_active:
self._log("监测到活跃传输流...")
stable_count = 0 # 只要发现还在传,重置计数
time.sleep(3)
self._log("传输任务监测超时,请确认网速或手动核实。")
context.close()
return False
except Exception as e:
self._log(f"代码执行异常: {str(e)}")
return False
def _create_folder(self, page, folder_name: str):
"""创建目录"""
try:
page.get_by_text("新建").first.click()
page.wait_for_timeout(500)
page.get_by_text("新建文件夹").first.click()
page.wait_for_timeout(500)
page.keyboard.type(folder_name)
page.keyboard.press("Enter")
self._log(f"已创建目录: {folder_name}")
page.wait_for_timeout(1500)
except:
pass