Merge pull request #213 from muy97212-spec/feat/douyin-self-declaration
feat(douyin): 发布时自动选择「自主声明」;fix(xiaohongshu): 话题候选超时不再中断发布
This commit is contained in:
@@ -367,6 +367,35 @@ class DouYinBaseUploader(BaseVideoUploader):
|
|||||||
douyin_logger.error(_msg("😢", f"设置商品链接时出错: {str(e)}"))
|
douyin_logger.error(_msg("😢", f"设置商品链接时出错: {str(e)}"))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def set_self_declaration(self, page: Page, declaration: str = "内容为个人观点或见解") -> None:
|
||||||
|
"""抖音「自主声明」为发布必选项:打开声明弹窗 → 选指定类型 → 确定。
|
||||||
|
|
||||||
|
入口和弹窗都是异步渲染,等不到就记 warning 跳过、继续发布,绝不因此中断
|
||||||
|
(与小红书话题、视频号声明原创的容错策略保持一致)。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 发布页底部「自主声明」行,未选时显示占位文案「请选择自主声明」
|
||||||
|
entry = page.get_by_text("请选择自主声明").first
|
||||||
|
await entry.wait_for(state="visible", timeout=6000)
|
||||||
|
await entry.click()
|
||||||
|
|
||||||
|
# 弹窗标题「对作品内容添加声明」
|
||||||
|
dialog = page.locator(".semi-modal-content").filter(has_text="对作品内容添加声明").first
|
||||||
|
await dialog.wait_for(state="visible", timeout=6000)
|
||||||
|
|
||||||
|
# 单选项:Semi 的文字是 .semi-radio-addon(常带 pointer-events:none,直接点会卡 30s 超时),
|
||||||
|
# 要点可交互的 .semi-radio 外层;找不到外层再退回 force 强制点文字。exact 避免误命中预览「作者声明:…」。
|
||||||
|
option = dialog.locator(".semi-radio").filter(has_text=declaration).first
|
||||||
|
if await option.count():
|
||||||
|
await option.click(timeout=6000)
|
||||||
|
else:
|
||||||
|
await dialog.get_by_text(declaration, exact=True).first.click(timeout=6000, force=True)
|
||||||
|
await dialog.get_by_role("button", name="确定").click(timeout=6000)
|
||||||
|
await dialog.wait_for(state="hidden", timeout=6000)
|
||||||
|
douyin_logger.info(_msg("🧾", f"自主声明已选择「{declaration}」"))
|
||||||
|
except Exception as exc:
|
||||||
|
douyin_logger.warning(_msg("🧾", f"自主声明设置失败,跳过该步骤继续发布:{exc}"))
|
||||||
|
|
||||||
|
|
||||||
class DouYinVideo(DouYinBaseUploader):
|
class DouYinVideo(DouYinBaseUploader):
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -532,6 +561,8 @@ class DouYinVideo(DouYinBaseUploader):
|
|||||||
|
|
||||||
await self.set_thumbnail(page)
|
await self.set_thumbnail(page)
|
||||||
|
|
||||||
|
await self.set_self_declaration(page)
|
||||||
|
|
||||||
third_part_element = '[class^="info"] > [class^="first-part"] div div.semi-switch'
|
third_part_element = '[class^="info"] > [class^="first-part"] div div.semi-switch'
|
||||||
if await page.locator(third_part_element).count():
|
if await page.locator(third_part_element).count():
|
||||||
if "semi-switch-checked" not in await page.eval_on_selector(third_part_element, "div => div.className"):
|
if "semi-switch-checked" not in await page.eval_on_selector(third_part_element, "div => div.className"):
|
||||||
|
|||||||
@@ -400,14 +400,25 @@ class XiaoHongShuBaseUploader(BaseVideoUploader):
|
|||||||
await desc.click()
|
await desc.click()
|
||||||
|
|
||||||
for tag in self.tags: # 循环处理所有 tags
|
for tag in self.tags: # 循环处理所有 tags
|
||||||
await page.keyboard.type("#" + tag, delay=30)
|
# 话题候选下拉框依赖小红书联想接口实时返回,网络抖动/无匹配时会等不到。
|
||||||
await page.locator('#creator-editor-topic-container').wait_for(
|
# 标签是可选增强项:等不到候选框就跳过该标签继续,不让整条发布因此失败。
|
||||||
state="visible",
|
try:
|
||||||
timeout=3000
|
await page.keyboard.type("#" + tag, delay=30)
|
||||||
)
|
await page.locator('#creator-editor-topic-container').wait_for(
|
||||||
first_item = page.locator('#creator-editor-topic-container .item').first
|
state="visible",
|
||||||
await first_item.wait_for(state="visible", timeout=2000)
|
timeout=6000
|
||||||
await first_item.click()
|
)
|
||||||
|
first_item = page.locator('#creator-editor-topic-container .item').first
|
||||||
|
await first_item.wait_for(state="visible", timeout=4000)
|
||||||
|
await first_item.click()
|
||||||
|
except Exception as exc:
|
||||||
|
xiaohongshu_logger.warning(
|
||||||
|
_msg("🏷️", f"话题『{tag}』未出现候选,跳过该标签继续发布: {exc}")
|
||||||
|
)
|
||||||
|
# 清掉已键入但未成词的 "#tag" 文本,避免它残留进正文
|
||||||
|
for _ in range(len("#" + tag)):
|
||||||
|
await page.keyboard.press("Backspace")
|
||||||
|
continue
|
||||||
|
|
||||||
async def fill_meta(self, page: Page) -> None:
|
async def fill_meta(self, page: Page) -> None:
|
||||||
await self.fill_title(page)
|
await self.fill_title(page)
|
||||||
|
|||||||
Reference in New Issue
Block a user