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