feat(douyin): 支持终端交互输入短信验证码
This commit is contained in:
@@ -204,6 +204,7 @@ sau youtube upload-video --account <account_name> --file videos/demo.mp4 --title
|
||||
|
||||
- `creator` 之类的名字只是示例值,真正含义是 `account_name`
|
||||
- 一个 `account_name` 对应一个账号文件,可以准备多个账号,也可以按账号名并发执行任务
|
||||
- 抖音视频发布若触发短信二次验证,程序会优先读取项目根目录下的 `verify_code.txt`;如果你是在本地交互式终端手动运行 CLI,也可以直接按终端提示输入验证码
|
||||
- 浏览器平台统一约定:
|
||||
- 视频使用 `title + desc + tags`
|
||||
- 图文使用 `title + note + tags`
|
||||
|
||||
@@ -52,6 +52,13 @@ sau douyin upload-video --account <account_name> --file videos/demo.mp4 --title
|
||||
sau douyin upload-note --account <account_name> --images videos/1.png videos/2.png --title "图文标题" --note "图文示例" --tags 图文,测试
|
||||
```
|
||||
|
||||
抖音短信验证码补充说明:
|
||||
|
||||
- 视频发布过程中如果触发短信二次验证,CLI 会优先读取项目根目录下的 `verify_code.txt`
|
||||
- 如果未找到 `verify_code.txt`,并且当前命令是在交互式终端中手动运行,CLI 会直接在终端提示输入验证码
|
||||
- 对 agent、自动任务、远程桥接这类场景,仍然可以继续用写入 `verify_code.txt` 的方式喂验证码
|
||||
- 验证通过后,程序会自动清理 `verify_code.txt`
|
||||
|
||||
## 快手 CLI 子命令
|
||||
|
||||
```bash
|
||||
|
||||
@@ -112,6 +112,13 @@ sau douyin upload-note --account <account_name> --images videos/demo1.png videos
|
||||
sau douyin upload-note --account <account_name> --images videos/demo1.png videos/demo2.png --title "图文标题" --note $noteText --tags 'tag1,tag2' --bgm '音乐名称'
|
||||
```
|
||||
|
||||
抖音短信验证码补充说明:
|
||||
|
||||
- 视频发布过程中如果触发短信二次验证,程序会优先读取项目根目录下的 `verify_code.txt`
|
||||
- 如果当前是你手动运行的交互式终端,没提供 `verify_code.txt` 时,CLI 会直接提示你在终端输入验证码
|
||||
- 如果是 agent 或自动化桥接场景,仍然可以继续通过写入 `verify_code.txt` 来提供验证码
|
||||
- 验证通过后,程序会自动删除 `verify_code.txt`
|
||||
|
||||
抖音卡login手动获取cookie:
|
||||
|
||||
- 目标服务器使用vnc
|
||||
|
||||
@@ -4,6 +4,7 @@ from datetime import datetime
|
||||
import asyncio
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from patchright.async_api import Page
|
||||
@@ -48,6 +49,20 @@ def _build_login_result(success: bool, status: str, message: str, account_file:
|
||||
}
|
||||
|
||||
|
||||
async def _read_verify_code(code_file: str) -> str:
|
||||
if os.path.exists(code_file):
|
||||
with open(code_file, encoding="utf-8") as file_obj:
|
||||
return file_obj.read().strip()
|
||||
|
||||
if not sys.stdin or not sys.stdin.isatty():
|
||||
return ""
|
||||
|
||||
try:
|
||||
return (await asyncio.to_thread(input, "请输入抖音短信验证码(直接回车可稍后重试): ")).strip()
|
||||
except (EOFError, OSError):
|
||||
return ""
|
||||
|
||||
|
||||
async def cookie_auth(account_file):
|
||||
# 抖音无头会撞反爬墙→content/upload 跳登录→误判 cookie 失效(间歇性)。校验必须有头。
|
||||
# 即便有头,页面慢/瞬时跳转仍会让 wait_for_url(精确URL,5s) 误判→重试3次+宽松判定(URL含 content/upload 且无登录文案)。
|
||||
@@ -534,6 +549,38 @@ class DouYinVideo(DouYinBaseUploader):
|
||||
self.productTitle = productTitle
|
||||
self.desc = desc or ""
|
||||
|
||||
async def _submit_sms_verify_code(self, page: Page, sms_input, code: str, code_file: str) -> bool:
|
||||
douyin_logger.info(_msg("✍️", f"已获取验证码,准备填入: {code}"))
|
||||
await sms_input.click()
|
||||
await sms_input.fill(code)
|
||||
douyin_logger.info(_msg("✅", "验证码已填入输入框"))
|
||||
await page.wait_for_timeout(500)
|
||||
|
||||
verify_btn = page.locator('div.uc-ui-verify_sms-verify_button:has-text("验证")').first
|
||||
if await verify_btn.count() and await verify_btn.is_visible():
|
||||
try:
|
||||
await verify_btn.click(force=True)
|
||||
douyin_logger.success(_msg("✅", "已点击「验证」按钮 (force)"))
|
||||
except Exception:
|
||||
await page.eval_on_selector('div.uc-ui-verify_sms-verify_button', 'el => el.click()')
|
||||
douyin_logger.success(_msg("✅", "已点击「验证」按钮 (JS)"))
|
||||
else:
|
||||
verify_by_text = page.get_by_text("验证", exact=True).first
|
||||
if await verify_by_text.count():
|
||||
await verify_by_text.click(force=True)
|
||||
douyin_logger.success(_msg("✅", "已点击「验证」按钮 (text)"))
|
||||
else:
|
||||
douyin_logger.warning(_msg("⚠️", "未找到验证按钮,尝试按Enter"))
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
if os.path.exists(code_file):
|
||||
os.remove(code_file)
|
||||
douyin_logger.info(_msg("🧹", "验证码文件已清理"))
|
||||
|
||||
await page.wait_for_timeout(3000)
|
||||
douyin_logger.info(_msg("🔄", "验证码处理完成,继续发布流程"))
|
||||
return True
|
||||
|
||||
async def validate_upload_args(self):
|
||||
await self.validate_base_args()
|
||||
if not self.title or not str(self.title).strip():
|
||||
@@ -694,6 +741,7 @@ class DouYinVideo(DouYinBaseUploader):
|
||||
if self.publish_strategy == DOUYIN_PUBLISH_STRATEGY_SCHEDULED and self.publish_date != 0:
|
||||
await self.set_schedule_time_douyin(page, self.publish_date)
|
||||
|
||||
sms_prompt_logged = False
|
||||
while True:
|
||||
try:
|
||||
# 移除会拦截发布按钮点击的新手引导/话题下拉浮层
|
||||
@@ -709,40 +757,14 @@ class DouYinVideo(DouYinBaseUploader):
|
||||
if await get_code_btn.count() and await get_code_btn.is_visible():
|
||||
await get_code_btn.click()
|
||||
douyin_logger.info(_msg("📤", "已点击「获取验证码」,请查看手机短信"))
|
||||
# 等待验证码文件
|
||||
code_file = os.path.join(BASE_DIR, "verify_code.txt")
|
||||
if os.path.exists(code_file):
|
||||
code = open(code_file).read().strip()
|
||||
code = await _read_verify_code(code_file)
|
||||
if code:
|
||||
douyin_logger.info(_msg("✍️", f"从文件读取到验证码: {code}"))
|
||||
await sms_input.click()
|
||||
await sms_input.fill(code)
|
||||
douyin_logger.info(_msg("✅", "验证码已填入输入框"))
|
||||
await page.wait_for_timeout(500)
|
||||
# 点击「验证」按钮(div 类型按钮,用 force=True + JS 兜底)
|
||||
verify_btn = page.locator('div.uc-ui-verify_sms-verify_button:has-text("验证")').first
|
||||
if await verify_btn.count() and await verify_btn.is_visible():
|
||||
try:
|
||||
await verify_btn.click(force=True)
|
||||
douyin_logger.success(_msg("✅", "已点击「验证」按钮 (force)"))
|
||||
except Exception:
|
||||
await page.eval_on_selector('div.uc-ui-verify_sms-verify_button', 'el => el.click()')
|
||||
douyin_logger.success(_msg("✅", "已点击「验证」按钮 (JS)"))
|
||||
else:
|
||||
# 兜底:按文本找
|
||||
verify_by_text = page.get_by_text("验证", exact=True).first
|
||||
if await verify_by_text.count():
|
||||
await verify_by_text.click(force=True)
|
||||
douyin_logger.success(_msg("✅", "已点击「验证」按钮 (text)"))
|
||||
else:
|
||||
douyin_logger.warning(_msg("⚠️", "未找到验证按钮,尝试按Enter"))
|
||||
await page.keyboard.press("Enter")
|
||||
os.remove(code_file)
|
||||
douyin_logger.info(_msg("🧹", "验证码文件已清理"))
|
||||
await page.wait_for_timeout(3000)
|
||||
douyin_logger.info(_msg("🔄", "验证码处理完成,继续发布流程"))
|
||||
else:
|
||||
douyin_logger.warning(_msg("⏳", "等待你发送验证码到微信..."))
|
||||
sms_prompt_logged = False
|
||||
await self._submit_sms_verify_code(page, sms_input, code, code_file)
|
||||
elif not sms_prompt_logged:
|
||||
douyin_logger.warning(_msg("⏳", f"等待验证码输入;可在交互终端直接输入,或写入文件: {code_file}"))
|
||||
sms_prompt_logged = True
|
||||
publish_button = page.get_by_role("button", name="发布", exact=True)
|
||||
if await publish_button.count():
|
||||
await publish_button.click(force=True)
|
||||
|
||||
Reference in New Issue
Block a user