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

177
baidu_uploader.py Normal file
View File

@@ -0,0 +1,177 @@
from __future__ import annotations
import os
import time
import re
from typing import Optional, Callable
from playwright.sync_api import sync_playwright
class BaiduUploader:
"""
百度网盘上传工具 - 纯净上传版 (移除分享逻辑)
"""
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.baidu.com/"
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)
# 提取项目编号
project_id = ""
match = re.search(r'(【[A-Za-z0-9]+】)', target_folder_name)
if match:
project_id = match.group(1)
try:
with sync_playwright() as p:
self._log(f"正在启动浏览器...")
launch_args = {
"user_data_dir": self.cookies_dir,
"headless": False,
"executable_path": self.chrome_path if self.chrome_path and os.path.exists(self.chrome_path) else None,
"viewport": {"width": 1280, "height": 800}
}
if not launch_args["executable_path"]:
launch_args.pop("executable_path")
context = p.chromium.launch_persistent_context(**launch_args)
context.add_init_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
page = context.new_page()
page.set_default_timeout(60000)
# 1. 登录
self._log("检测登录状态...")
page.goto(self.url, wait_until="domcontentloaded")
if not self._wait_for_login(page):
self._log("请手动扫码登录...")
page.wait_for_selector("text=全部文件", timeout=180000)
# 2. 导航
self._log("登录成功,进入目标路径...")
page.get_by_text("全部文件").first.click(force=True)
time.sleep(4)
if not self._scan_and_enter(page, root_path):
self._create_folder(page, root_path)
time.sleep(2); self._scan_and_enter(page, root_path)
search_key = project_id if project_id else target_folder_name
if not self._scan_and_enter(page, search_key, is_fuzzy=True):
self._create_folder(page, target_folder_name)
time.sleep(2); self._scan_and_enter(page, search_key, is_fuzzy=True)
# 3. 查重
if self._check_file_exists_robust(page, filename):
self._log(f"云端已存在文件 '{filename}',跳过上传。")
time.sleep(2)
context.close()
return True
# 4. 执行上传
self._log(f"开始传输: {filename}")
page.set_input_files("input[type=file]", file_path)
# 5. 监测列表确认成功
self._log("上传已启动,监测云端状态中...")
success = self._wait_for_file_in_list(page, filename)
if success:
self._log("\n" + "="*30)
self._log("百度网盘上传圆满成功!")
self._log("="*30 + "\n")
time.sleep(3)
else:
self._log("警告:监测超时,未能在列表中看到文件,请手动核实。")
context.close()
return success
except Exception as e:
self._log(f"程序异常: {str(e)}")
return False
def _wait_for_login(self, page) -> bool:
for _ in range(10):
if page.get_by_text("全部文件").count() > 0: return True
time.sleep(2)
return False
def _scan_and_enter(self, page, target_name: str, is_fuzzy: bool = False) -> bool:
try:
page.evaluate("window.scrollTo(0, 300)")
found = page.evaluate(f"""
(args) => {{
const {{ name, fuzzy }} = args;
const els = [...document.querySelectorAll('span[title], a[title], .wp-s-file-list-drag-copy__item-title-text')];
const target = els.find(e => {{
const title = e.getAttribute('title') || e.innerText.trim();
return fuzzy ? title.includes(name) : title === name;
}});
if (target) {{
target.click();
const ev = new MouseEvent('dblclick', {{ 'view': window, 'bubbles': true, 'cancelable': true }});
target.dispatchEvent(ev);
return true;
}}
return false;
}}
""", {"name": target_name, "fuzzy": is_fuzzy})
if found: time.sleep(3); return True
return False
except: return False
def _create_folder(self, page, folder_name: str):
try:
page.keyboard.press("Escape")
btn = page.get_by_text("新建文件夹").first
if btn.count() > 0:
btn.click(force=True)
time.sleep(1.5)
page.keyboard.type(folder_name, delay=50)
page.keyboard.press("Enter")
time.sleep(3)
except: pass
def _check_file_exists_robust(self, page, filename: str) -> bool:
return page.evaluate(f"""
(name) => {{
const els = [...document.querySelectorAll('span[title], a[title], .wp-s-file-list-drag-copy__item-title-text')];
return els.some(e => (e.getAttribute('title') || e.innerText.trim()) === name);
}}
""", filename)
def _wait_for_file_in_list(self, page, filename: str) -> bool:
start_time = time.time(); last_refresh = time.time()
while time.time() - start_time < 1800:
if self._check_file_exists_robust(page, filename):
content = page.content()
# 如果文件在列表里,且页面内容没在报错或重试
if not any(x in content for x in ["正在上传", "传输队列", "0B/s"]):
return True
if time.time() - last_refresh > 25:
try: page.locator(".wp-s-file-list-drag-copy__header-breadcrumb-item").last.click(); time.sleep(3)
except: pass
last_refresh = time.time()
time.sleep(5)
return False