fix(tencent): adapt to Channels revamped editor and improve publish flow
- Open homepage first, then click the visible 'publish video' entry button instead of loading /platform/post/create directly, which gets bounced back to the homepage and leaves the form iframe un-mounted - Detect expired cookies up front by watching for the /login.html redirect and the qrconnect iframe - Drop the redundant confirm_thumbnail_crop call that caused a 5s timeout on cover upload - Select collection by exact name after upload completes (form resets selections made while the video is still uploading); keep it unselected when no matching name is found instead of picking a random one - Scroll the target option into view and fall back to forced click / native dispatch for headless visibility issues - Tighten short-title length to 7~15 chars to pass platform validation - Dismiss the occasional 'switch account' dialog that blocks the description box and the publish button
This commit is contained in:
+214
-152
@@ -19,6 +19,7 @@ from utils.base_social_media import set_init_script
|
||||
from utils.log import tencent_logger
|
||||
|
||||
TENCENT_LOGIN_URL = "https://channels.weixin.qq.com"
|
||||
TENCENT_HOME_URL = "https://channels.weixin.qq.com/platform"
|
||||
TENCENT_UPLOAD_URL = "https://channels.weixin.qq.com/platform/post/create"
|
||||
TENCENT_MANAGE_URL = "https://channels.weixin.qq.com/platform/post/list"
|
||||
TENCENT_PUBLISH_STRATEGY_IMMEDIATE = "immediate"
|
||||
@@ -97,10 +98,14 @@ def format_str_for_short_title(origin_title: str) -> str:
|
||||
filtered_chars = [char if char.isalnum() or char in allowed_special_chars else " " if char == "," else "" for char in origin_title]
|
||||
formatted_string = "".join(filtered_chars)
|
||||
|
||||
if len(formatted_string) > 16:
|
||||
formatted_string = formatted_string[:16]
|
||||
elif len(formatted_string) < 6:
|
||||
formatted_string += " " * (6 - len(formatted_string))
|
||||
# 视频号「短标题」要求 6~16 个字符/汉字;本项目按 >6 且 <16 从严控制在 7~15。
|
||||
formatted_string = formatted_string.strip()
|
||||
if len(formatted_string) > 15:
|
||||
formatted_string = formatted_string[:15]
|
||||
if len(formatted_string) < 7:
|
||||
# 不足下限时补足到 7;不能用尾部空格(会被平台 trim 掉导致仍不达标)
|
||||
filler = ",精彩内容分享"
|
||||
formatted_string = (formatted_string + filler)[:7] if formatted_string else "精彩视频内容分享"
|
||||
|
||||
return formatted_string
|
||||
|
||||
@@ -544,17 +549,39 @@ class TencentBaseUploader(BaseVideoUploader):
|
||||
await page.keyboard.press("Escape")
|
||||
|
||||
async def open_upload_page(self, page: Page) -> None:
|
||||
await page.goto(TENCENT_UPLOAD_URL, timeout=120000, wait_until="domcontentloaded")
|
||||
# 视频号已改版:直接全页加载 /platform/post/create 会被跳回 /platform 首页,
|
||||
# 发布表单 iframe 只加载空壳(Vue 不挂载),页面上没有任何 input[type=file]。
|
||||
# 正确入口:先进首页,再点可见的「发表视频」按钮做客户端跳转,表单才会真正挂载。
|
||||
await page.goto(TENCENT_HOME_URL, timeout=120000, wait_until="domcontentloaded")
|
||||
# cookie 失效时前端 JS 会跳转到登录页, 提前发现并报明确的错误
|
||||
redirected = True
|
||||
try:
|
||||
await page.wait_for_url("**/login.html**", timeout=8000)
|
||||
except Exception:
|
||||
redirected = False # 8 秒内未跳转, 正常
|
||||
if redirected or any(
|
||||
"open.weixin.qq.com/connect/qrconnect" in fr.url for fr in page.frames
|
||||
):
|
||||
raise RuntimeError("视频号 cookie 已失效(被跳转到登录页),请重新扫码登录后再发布")
|
||||
except TimeoutError:
|
||||
pass # 8 秒内未跳转, 正常
|
||||
except RuntimeError:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
if any("open.weixin.qq.com/connect/qrconnect" in fr.url for fr in page.frames):
|
||||
raise RuntimeError("视频号 cookie 已失效(被跳转到登录页),请重新扫码登录后再发布")
|
||||
try:
|
||||
await page.wait_for_load_state("networkidle", timeout=15000)
|
||||
except Exception:
|
||||
pass
|
||||
# 注意:get_by_text("发表视频") 会命中一个隐藏的说明 <p>(不可点);
|
||||
# 首页真正可点的入口是 button.weui-desktop-btn。
|
||||
publish_entry = page.locator("button.weui-desktop-btn", has_text="发表视频").first
|
||||
try:
|
||||
await publish_entry.wait_for(state="visible", timeout=30000)
|
||||
await publish_entry.click()
|
||||
except Exception:
|
||||
# 兜底:按钮没点到时退回老逻辑直接跳转(可能仍是空壳,但保持向后兼容)
|
||||
await page.goto(TENCENT_UPLOAD_URL, timeout=120000, wait_until="domcontentloaded")
|
||||
try:
|
||||
await page.wait_for_url("**/platform/post/create", timeout=120000)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def upload_video_file(self, page: Page, file_path: str) -> None:
|
||||
async def find_file_input():
|
||||
@@ -569,11 +596,15 @@ class TencentBaseUploader(BaseVideoUploader):
|
||||
|
||||
fi = await find_file_input()
|
||||
if fi is None:
|
||||
# 助手落在首页:先点「发表视频」唤出编辑器与上传控件
|
||||
publish_btn = page.get_by_text("发表视频").first
|
||||
if await publish_btn.count():
|
||||
await publish_btn.click()
|
||||
await asyncio.sleep(3)
|
||||
# 助手落在首页:点可见的「发表视频」按钮(button.weui-desktop-btn)唤出发布表单。
|
||||
# 不能用 get_by_text("发表视频")——它会命中隐藏的说明 <p>,点了无效。
|
||||
publish_btn = page.locator("button.weui-desktop-btn", has_text="发表视频").first
|
||||
try:
|
||||
if await publish_btn.count() and await publish_btn.is_visible():
|
||||
await publish_btn.click()
|
||||
await asyncio.sleep(3)
|
||||
except Exception:
|
||||
pass
|
||||
for _ in range(20):
|
||||
fi = await find_file_input()
|
||||
if fi is not None:
|
||||
@@ -584,17 +615,54 @@ class TencentBaseUploader(BaseVideoUploader):
|
||||
await fi.set_input_files(file_path)
|
||||
|
||||
async def set_short_title(self, page: Page, title: str, short_title: str | None = None) -> None:
|
||||
short_title_element = (
|
||||
page.get_by_text("短标题", exact=True)
|
||||
.locator("..")
|
||||
.locator("xpath=following-sibling::div")
|
||||
.locator('span input[type="text"]')
|
||||
)
|
||||
if await short_title_element.count():
|
||||
await short_title_element.fill(short_title or format_str_for_short_title(title))
|
||||
# 视频号「短标题」即界面上要求填写的“标题”(那个大编辑区其实是“视频描述”)。
|
||||
# 走 format_str_for_short_title 保证长度落在 7~15,避免发布时被校验拦下。
|
||||
value = format_str_for_short_title(short_title or title)
|
||||
# 优先用 placeholder 定位(已 dump 验证更稳),兜底旧的“短标题”相邻 input。
|
||||
field = page.locator('input[placeholder="填写短标题有机会获得更多流量"]').first
|
||||
if not await field.count():
|
||||
field = (
|
||||
page.get_by_text("短标题", exact=True)
|
||||
.locator("..")
|
||||
.locator("xpath=following-sibling::div")
|
||||
.locator('span input[type="text"]')
|
||||
)
|
||||
if await field.count():
|
||||
await field.fill(value)
|
||||
tencent_logger.info(_msg("🏷️", f"短标题已填写({len(value)}字):{value}"))
|
||||
else:
|
||||
tencent_logger.info(_msg("🧾", "未找到短标题输入框,跳过短标题"))
|
||||
|
||||
async def _dismiss_switch_account_dialog(self, page: Page) -> None:
|
||||
# 视频号上传后偶发弹出「切换视频号」对话框(.changeAccount-dialog / .common-dialog)遮挡发布表单。
|
||||
# 它带「取消」按钮、非强制,点「取消」/ 右上角 × / Esc 跳过即可,用当前账号继续发布。
|
||||
cancel = page.locator('.changeAccount-dialog button:has-text("取消")').first
|
||||
closeb = page.locator('.changeAccount-dialog .weui-desktop-dialog__close-btn').first
|
||||
for cand in (cancel, closeb):
|
||||
try:
|
||||
if await cand.count() and await cand.is_visible():
|
||||
await cand.click(timeout=2000)
|
||||
await page.wait_for_timeout(600)
|
||||
return
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
await page.keyboard.press("Escape")
|
||||
await page.wait_for_timeout(600)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def fill_title_and_tags(self, page: Page) -> None:
|
||||
await page.locator("div.input-editor").click()
|
||||
# 上传后偶发「切换视频号」弹窗遮挡描述框,点不动就关弹窗重试(以能点中描述框为成功标志)。
|
||||
for _ in range(4):
|
||||
try:
|
||||
await page.locator("div.input-editor").click(timeout=5000)
|
||||
break
|
||||
except Exception:
|
||||
await self._dismiss_switch_account_dialog(page)
|
||||
await page.wait_for_timeout(500)
|
||||
else:
|
||||
await page.locator("div.input-editor").click(timeout=8000)
|
||||
await page.keyboard.type(self.title)
|
||||
await page.keyboard.press("Enter")
|
||||
for tag in self.tags:
|
||||
@@ -608,113 +676,77 @@ class TencentBaseUploader(BaseVideoUploader):
|
||||
tencent_logger.info(_msg("🏷️", f"成功添加 desc: {len(self.desc)}"))
|
||||
|
||||
async def apply_collection(self, page: Page) -> None:
|
||||
collection_elements = (
|
||||
page.get_by_text("添加到合集")
|
||||
.locator("xpath=following-sibling::div")
|
||||
.locator(".option-list-wrap > div")
|
||||
)
|
||||
if await collection_elements.count() > 1:
|
||||
await page.get_by_text("添加到合集").locator("xpath=following-sibling::div").click()
|
||||
await collection_elements.first.click()
|
||||
"""在发布表单页"添加到合集"下拉框按合集名精确选中(页面结构:option-item > .item > .name/.desc)。
|
||||
|
||||
找不到匹配名字的合集时不展开/不选(保持未选状态直接发布,界面允许留空,
|
||||
不阻断主发布流程)。旧实现是"下拉项数>1就选第一项",等价于随机选,已改为精确匹配。
|
||||
"""
|
||||
if not self.collection_name:
|
||||
return
|
||||
try:
|
||||
trigger = page.get_by_text("添加到合集").first
|
||||
if await trigger.count() == 0:
|
||||
tencent_logger.info(_msg("🧾", "当前页面未发现「添加到合集」入口,跳过归集"))
|
||||
return
|
||||
dropdown = trigger.locator("xpath=following-sibling::div").first
|
||||
await dropdown.click(timeout=8000)
|
||||
await page.wait_for_timeout(800)
|
||||
|
||||
option = dropdown.locator(".option-list-wrap .option-item").filter(
|
||||
has=page.locator(f'.name:text-is("{self.collection_name}")')
|
||||
)
|
||||
if await option.count() == 0:
|
||||
tencent_logger.warning(
|
||||
_msg("😵", f"合集下拉框未找到「{self.collection_name}」,跳过归集,保持未选状态")
|
||||
)
|
||||
await page.keyboard.press("Escape")
|
||||
await page.wait_for_timeout(300)
|
||||
return
|
||||
|
||||
# headless 下 option 常报 "element is not visible":下拉列表开在视口外/
|
||||
# 在 .option-list-wrap 滚动容器内,headful(大窗口)时在视野里能直接点中,
|
||||
# headless 默认视口小就点不中。先把目标 option 滚进视野再点;普通 click
|
||||
# 仍被判不可见时 → force 点击(跳过可见性 actionability)→ 派发原生 click 兜底。
|
||||
target = option.first
|
||||
try:
|
||||
await target.scroll_into_view_if_needed(timeout=3000)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
await target.click(timeout=4000)
|
||||
except Exception:
|
||||
try:
|
||||
await target.click(force=True, timeout=4000)
|
||||
except Exception:
|
||||
await target.dispatch_event("click")
|
||||
await page.wait_for_timeout(500)
|
||||
tencent_logger.success(_msg("🥳", f"已选择合集:{self.collection_name}"))
|
||||
except Exception as exc:
|
||||
tencent_logger.warning(_msg("😵", f"选择合集失败,跳过归集继续发布: {exc}"))
|
||||
try:
|
||||
await page.keyboard.press("Escape")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def apply_original_statement(self, page: Page) -> None:
|
||||
original_set = False
|
||||
if await page.get_by_label("视频为原创").count():
|
||||
await page.get_by_label("视频为原创").check()
|
||||
original_set = True
|
||||
|
||||
# 视频号「视频标注」下拉:本项目成片经 AI 处理(TTS 配音、AI 字幕、AI 前贴片),
|
||||
# 依平台合规要求如实选「含AI生成内容」(与「内容为转载」等并列,选定即可、无需填写来源)。
|
||||
# 注意:这与上方独立的「声明原创」复选框是两个不同字段,本项目走 AI 标注、不勾原创声明。
|
||||
label_text = getattr(self, "content_label", None) or "含AI生成内容"
|
||||
try:
|
||||
label_locator = await page.locator('label:has-text("我已阅读并同意 《视频号原创声明使用条款》")').is_visible()
|
||||
except Exception:
|
||||
label_locator = False
|
||||
|
||||
if label_locator:
|
||||
await page.get_by_label("我已阅读并同意 《视频号原创声明使用条款》").check()
|
||||
await page.get_by_role("button", name="声明原创").click()
|
||||
original_set = True
|
||||
|
||||
declaration_entry = page.locator(
|
||||
'div.label span:has-text("声明原创"), '
|
||||
'div:has-text("声明原创"):has(input.ant-checkbox-input), '
|
||||
'div:has-text("原创声明"):has(input.ant-checkbox-input)'
|
||||
).first
|
||||
if await declaration_entry.count():
|
||||
original_checkbox = page.locator("div.declare-original-checkbox input.ant-checkbox-input").first
|
||||
if await original_checkbox.count() and not await original_checkbox.is_disabled():
|
||||
await original_checkbox.click()
|
||||
await page.wait_for_timeout(500)
|
||||
checked_locator = page.locator(
|
||||
"div.declare-original-dialog "
|
||||
"label.ant-checkbox-wrapper.ant-checkbox-wrapper-checked:visible"
|
||||
)
|
||||
if not await checked_locator.count():
|
||||
await page.locator("div.declare-original-dialog input.ant-checkbox-input:visible").first.click()
|
||||
|
||||
original_type_form = page.locator('div.original-type-form > div.form-label:has-text("原创类型"):visible')
|
||||
if await original_type_form.count():
|
||||
category = getattr(self, "category", None)
|
||||
await page.locator("div.form-content:visible").click()
|
||||
option = None
|
||||
if category:
|
||||
option = page.locator(
|
||||
"ul.weui-desktop-dropdown__list "
|
||||
f'li.weui-desktop-dropdown__list-ele:has-text("{category}")'
|
||||
).first
|
||||
if not await option.count():
|
||||
option = None
|
||||
if option is None:
|
||||
option = page.locator(
|
||||
"ul.weui-desktop-dropdown__list "
|
||||
"li.weui-desktop-dropdown__list-ele:visible"
|
||||
).first
|
||||
if await option.count():
|
||||
await option.click()
|
||||
await page.wait_for_timeout(1000)
|
||||
|
||||
declare_button = page.locator('button:has-text("声明原创"):visible')
|
||||
if await declare_button.count():
|
||||
await declare_button.first.click()
|
||||
original_set = True
|
||||
await page.wait_for_timeout(1000)
|
||||
|
||||
if not original_set:
|
||||
for original_text in ("声明原创", "原创声明", "视频为原创"):
|
||||
try:
|
||||
modern_original = page.locator(f'text="{original_text}"').first
|
||||
if await modern_original.count() and await modern_original.is_visible():
|
||||
await modern_original.click()
|
||||
original_set = True
|
||||
await page.wait_for_timeout(1000)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
content_declaration = page.locator('text="内容声明"').first
|
||||
try:
|
||||
if await content_declaration.count() and await content_declaration.is_visible():
|
||||
await content_declaration.click()
|
||||
for option_text in ("无需声明", "不声明", "无"):
|
||||
option = page.locator(f'text="{option_text}"').first
|
||||
if await option.count() and await option.is_visible():
|
||||
await option.click()
|
||||
tencent_logger.info(_msg("🧾", f"内容声明已选择: {option_text}"))
|
||||
break
|
||||
else:
|
||||
tencent_logger.info(_msg("🧾", "当前页面未发现内容声明字段"))
|
||||
entry = page.get_by_text("选择视频标注", exact=True).first
|
||||
if not await entry.count():
|
||||
tencent_logger.info(_msg("🧾", "当前页面未发现「视频标注」入口,跳过标注继续发布"))
|
||||
return
|
||||
await entry.click()
|
||||
await page.wait_for_timeout(800)
|
||||
option = page.get_by_text(label_text, exact=True).first
|
||||
await option.wait_for(state="visible", timeout=5000)
|
||||
await option.click()
|
||||
await page.wait_for_timeout(500)
|
||||
tencent_logger.success(_msg("🏷️", f"视频标注已选择:{label_text}"))
|
||||
except Exception as exc:
|
||||
tencent_logger.warning(_msg("😵", f"内容声明设置失败,继续前先人工确认页面: {exc}"))
|
||||
|
||||
if not original_set:
|
||||
try:
|
||||
diagnostic_path = Path(BASE_DIR) / "debug_tencent_original_missing.png"
|
||||
await page.screenshot(path=str(diagnostic_path), full_page=True)
|
||||
visible_text = (await page.locator("body").first.inner_text())[-4000:]
|
||||
tencent_logger.warning(_msg("😵", f"未确认声明原创,诊断截图: {diagnostic_path}"))
|
||||
tencent_logger.warning(_msg("🧾", f"页面末尾文本: {visible_text}"))
|
||||
except Exception as exc:
|
||||
tencent_logger.warning(_msg("😵", f"生成原创声明诊断信息失败: {exc}"))
|
||||
# 视频号「声明原创」为可选项:页面无对应入口时跳过并继续发布,而非中止。
|
||||
tencent_logger.warning(_msg("📭", "本视频未声明原创(页面无入口或为可选项),跳过并继续发布"))
|
||||
tencent_logger.warning(_msg("😵", f"设置视频标注「{label_text}」失败,跳过继续发布:{exc}"))
|
||||
|
||||
async def wait_for_upload_complete(self, page: Page) -> None:
|
||||
while True:
|
||||
@@ -738,34 +770,61 @@ class TencentBaseUploader(BaseVideoUploader):
|
||||
await asyncio.sleep(2)
|
||||
|
||||
async def submit_publish(self, page: Page) -> None:
|
||||
while True:
|
||||
is_draft = getattr(self, "is_draft", False)
|
||||
# 先等待并清理遮罩/弹窗,再等发表按钮出现
|
||||
for wait_round in range(60):
|
||||
await self._dismiss_switch_account_dialog(page)
|
||||
try:
|
||||
if getattr(self, "is_draft", False):
|
||||
draft_button = page.locator('div.form-btns button:has-text("保存草稿")')
|
||||
if await draft_button.count():
|
||||
await draft_button.click()
|
||||
await page.evaluate("""() => document.querySelectorAll('.mask, .changeAccount-dialog, .common-dialog').forEach(e => e.remove())""")
|
||||
except Exception:
|
||||
pass
|
||||
publish_btn = page.get_by_role("button", name="发表", exact=True).first if not is_draft else page.get_by_role("button", name="保存草稿").first
|
||||
try:
|
||||
if await publish_btn.count() and await publish_btn.is_visible():
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
tencent_logger.warning(_msg("😵", "60s 内未找到可见的发表/草稿按钮,尝试强制继续"))
|
||||
# 点发表/草稿
|
||||
for attempt in range(20):
|
||||
try:
|
||||
if await publish_btn.count():
|
||||
try:
|
||||
await publish_btn.click(timeout=4000)
|
||||
except Exception:
|
||||
await publish_btn.evaluate("el => el.click()")
|
||||
if is_draft:
|
||||
await page.wait_for_url("**/post/list**", timeout=5000)
|
||||
tencent_logger.success(_msg("🥳", "视频草稿保存成功"))
|
||||
else:
|
||||
publish_button = page.locator('div.form-btns button:has-text("发表")')
|
||||
if await publish_button.count():
|
||||
await publish_button.click()
|
||||
await page.wait_for_url(TENCENT_MANAGE_URL, timeout=5000)
|
||||
tencent_logger.success(_msg("🥳", "视频发布成功"))
|
||||
break
|
||||
# 发表成功后视频号可能跳 /platform(首页)、/post/list 或留在 create 页但按钮消失。
|
||||
# 综合判断:URL 离开 /post/create 或 发表按钮不再存在。
|
||||
for _ in range(10):
|
||||
await asyncio.sleep(1)
|
||||
cur = page.url
|
||||
if "/post/create" not in cur:
|
||||
tencent_logger.success(_msg("🥳", "视频发布成功"))
|
||||
return
|
||||
if not await publish_btn.count():
|
||||
tencent_logger.success(_msg("🥳", "视频发布成功(按钮已消失)"))
|
||||
return
|
||||
raise Exception("发表后 10s 页面未变化")
|
||||
return
|
||||
except Exception as exc:
|
||||
current_url = page.url
|
||||
if getattr(self, "is_draft", False):
|
||||
if "post/list" in current_url or "draft" in current_url:
|
||||
tencent_logger.success(_msg("🥳", "视频草稿保存成功"))
|
||||
break
|
||||
else:
|
||||
if TENCENT_MANAGE_URL in current_url:
|
||||
tencent_logger.success(_msg("🥳", "视频发布成功"))
|
||||
break
|
||||
tencent_logger.exception(f" [-] Exception: {exc}")
|
||||
if is_draft and ("post/list" in current_url or "draft" in current_url):
|
||||
tencent_logger.success(_msg("🥳", "视频草稿保存成功"))
|
||||
return
|
||||
if (not is_draft) and "/post/create" not in current_url:
|
||||
tencent_logger.success(_msg("🥳", "视频发布成功"))
|
||||
return
|
||||
if attempt and attempt % 5 == 0:
|
||||
tencent_logger.warning(_msg("😵", f"发布仍未完成(第{attempt}次),异常: {str(exc)[:60]}"))
|
||||
tencent_logger.info(_msg("🏃", "视频正在发布中..."))
|
||||
await asyncio.sleep(0.5)
|
||||
await asyncio.sleep(1)
|
||||
raise RuntimeError("发布未在预期时间内完成,请检查发布页面")
|
||||
|
||||
|
||||
class TencentVideo(TencentBaseUploader):
|
||||
@@ -862,8 +921,7 @@ class TencentVideo(TencentBaseUploader):
|
||||
file_input = cover_dialog.locator('.single-cover-uploader-wrap input[type="file"]').first
|
||||
await file_input.wait_for(state="attached", timeout=10000)
|
||||
await file_input.set_input_files(thumbnail_path)
|
||||
await page.wait_for_timeout(1000)
|
||||
await self.confirm_thumbnail_crop(page)
|
||||
await page.wait_for_timeout(2000)
|
||||
|
||||
confirm_button = cover_dialog.locator(
|
||||
'div.weui-desktop-dialog__ft button.weui-desktop-btn_primary:has-text("确认")'
|
||||
@@ -928,7 +986,9 @@ class TencentVideo(TencentBaseUploader):
|
||||
async def prepare_video_for_publish(self, page: Page) -> None:
|
||||
await self.fill_title_and_tags(page)
|
||||
await self.fill_description(page)
|
||||
await self.apply_collection(page)
|
||||
# 合集不在这里选:此时视频还在上传,上传完成后表单会刷新,
|
||||
# 上传中选的合集会被重置/不绑定("日志说选了、后台没加"的根因)。
|
||||
# 改到 wait_for_upload_complete 之后再选,见 upload()。
|
||||
|
||||
async def upload(self, playwright: Playwright) -> None:
|
||||
tencent_logger.info(_msg("🧍", "小人先检查 cookie、视频文件和发布时间"))
|
||||
@@ -946,6 +1006,8 @@ class TencentVideo(TencentBaseUploader):
|
||||
await self.upload_video_file(page, self.file_path)
|
||||
await self.prepare_video_for_publish(page)
|
||||
await self.wait_for_upload_complete(page)
|
||||
# 上传完成、表单稳定后再选合集(否则上传中选的会被重置)
|
||||
await self.apply_collection(page)
|
||||
await self.apply_original_statement(page)
|
||||
await self.set_thumbnail(page)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user