feat: 增加项目一键整理、打包及自动上传夸克/百度网盘功能
This commit is contained in:
177
baidu_uploader.py
Normal file
177
baidu_uploader.py
Normal 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
|
||||
Reference in New Issue
Block a user