fix: allow configuring Xiaohongshu creator domain
This commit is contained in:
@@ -70,6 +70,12 @@ sau xiaohongshu upload-video --account <account_name> --file videos/demo.mp4 --t
|
||||
sau xiaohongshu upload-note --account <account_name> --images videos/1.png videos/2.png videos/3.png --title "图文标题" --note "图文示例" --tags 图文,测试
|
||||
```
|
||||
|
||||
海外环境如果无法登录默认创作者后台,可以通过环境变量切换到 RedNote 域名。该设置同时作用于登录、cookie 校验、视频发布和图文发布:
|
||||
|
||||
```bash
|
||||
SAU_XHS_CREATOR_BASE_URL=https://creator.rednote.com sau xiaohongshu login --account <account_name>
|
||||
```
|
||||
|
||||
## Bilibili CLI 子命令
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
@@ -89,6 +90,29 @@ class RecordingPage:
|
||||
|
||||
|
||||
class XiaohongshuUploaderTests(unittest.TestCase):
|
||||
def test_creator_urls_keep_xiaohongshu_domain_by_default(self):
|
||||
with patch.dict(os.environ, {"SAU_XHS_CREATOR_BASE_URL": ""}):
|
||||
self.assertEqual(
|
||||
xhs_main._build_xhs_creator_url("/login"),
|
||||
"https://creator.xiaohongshu.com/login",
|
||||
)
|
||||
|
||||
def test_creator_urls_use_configured_rednote_domain(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"SAU_XHS_CREATOR_BASE_URL": "https://creator.rednote.com/"},
|
||||
):
|
||||
self.assertEqual(
|
||||
xhs_main._build_xhs_creator_url("/login"),
|
||||
"https://creator.rednote.com/login",
|
||||
)
|
||||
self.assertEqual(
|
||||
xhs_main._build_xhs_creator_url(
|
||||
"/publish/publish?from=homepage&target=video"
|
||||
),
|
||||
"https://creator.rednote.com/publish/publish?from=homepage&target=video",
|
||||
)
|
||||
|
||||
def test_find_xhs_qrcode_locator_prefers_scan_sibling_inside_login_box(self):
|
||||
qrcode_locator = FakeLocator("qrcode", count=1, src="data:image/png;base64,abc")
|
||||
scan_text_locator = FakeLocator(
|
||||
|
||||
@@ -21,9 +21,8 @@ from utils.login_qrcode import remove_qrcode_file
|
||||
from utils.login_qrcode import save_data_url_image
|
||||
from utils.log import xiaohongshu_logger
|
||||
|
||||
XHS_LOGIN_URL = "https://creator.xiaohongshu.com/login"
|
||||
XHS_PUBLISH_VIDEO_URL = "https://creator.xiaohongshu.com/publish/publish?from=homepage&target=video"
|
||||
XHS_PUBLISH_NOTE_URL = "https://creator.xiaohongshu.com/publish/publish?from=homepage&target=image"
|
||||
XHS_DEFAULT_CREATOR_BASE_URL = "https://creator.xiaohongshu.com"
|
||||
XHS_CREATOR_BASE_URL_ENV = "SAU_XHS_CREATOR_BASE_URL"
|
||||
XHS_PUBLISH_SUCCESS_URL_PATTERN = "**/publish/success?**"
|
||||
XHS_LOGIN_BOX_SELECTOR = "div[class*='login-box']"
|
||||
XHS_LOGIN_SWITCH_SELECTOR = "img.css-wemwzq"
|
||||
@@ -31,6 +30,16 @@ XIAOHONGSHU_PUBLISH_STRATEGY_IMMEDIATE = "immediate"
|
||||
XIAOHONGSHU_PUBLISH_STRATEGY_SCHEDULED = "scheduled"
|
||||
|
||||
|
||||
def _build_xhs_creator_url(path: str) -> str:
|
||||
base_url = os.getenv(
|
||||
XHS_CREATOR_BASE_URL_ENV,
|
||||
XHS_DEFAULT_CREATOR_BASE_URL,
|
||||
).strip().rstrip("/")
|
||||
if not base_url:
|
||||
base_url = XHS_DEFAULT_CREATOR_BASE_URL
|
||||
return f"{base_url}/{path.lstrip('/')}"
|
||||
|
||||
|
||||
def _msg(emoji: str, text: str) -> str:
|
||||
return f"{emoji} {text}"
|
||||
|
||||
@@ -132,7 +141,7 @@ async def _save_xhs_qrcode(
|
||||
|
||||
|
||||
async def _is_xhs_login_completed(page: Page) -> bool:
|
||||
if page.url.startswith(XHS_LOGIN_URL):
|
||||
if page.url.startswith(_build_xhs_creator_url("/login")):
|
||||
return False
|
||||
|
||||
login_box = page.locator(XHS_LOGIN_BOX_SELECTOR).first
|
||||
@@ -158,10 +167,14 @@ async def cookie_auth(account_file):
|
||||
context = await browser.new_context(storage_state=account_file)
|
||||
context = await set_init_script(context)
|
||||
page = await context.new_page()
|
||||
await page.goto(XHS_PUBLISH_VIDEO_URL)
|
||||
await page.goto(
|
||||
_build_xhs_creator_url(
|
||||
"/publish/publish?from=homepage&target=video"
|
||||
)
|
||||
)
|
||||
await page.wait_for_timeout(3000)
|
||||
|
||||
if page.url.startswith(XHS_LOGIN_URL):
|
||||
if page.url.startswith(_build_xhs_creator_url("/login")):
|
||||
xiaohongshu_logger.info(_msg("🥹", "cookie 已失效,得重新登录一下"))
|
||||
return False
|
||||
|
||||
@@ -228,7 +241,7 @@ async def xiaohongshu_cookie_gen(
|
||||
result = _build_login_result(False, "failed", "小红书登录失败", account_file)
|
||||
try:
|
||||
page = await context.new_page()
|
||||
await page.goto(XHS_LOGIN_URL)
|
||||
await page.goto(_build_xhs_creator_url("/login"))
|
||||
qrcode_info = await _save_xhs_qrcode(page, account_file, qrcode_callback=qrcode_callback)
|
||||
qrcode_path = Path(qrcode_info["image_path"])
|
||||
xiaohongshu_logger.info(_msg("🧍", "请扫码,小人正在耐心等待登录完成"))
|
||||
@@ -527,8 +540,11 @@ class XiaoHongShuVideo(XiaoHongShuBaseUploader):
|
||||
async def upload_video_content(self, page: Page) -> None:
|
||||
xiaohongshu_logger.info(_msg("🏃", f"小人开始搬运视频: {self.title}.mp4"))
|
||||
xiaohongshu_logger.info(_msg("🧭", "小人正在赶往视频发布页"))
|
||||
await page.goto(XHS_PUBLISH_VIDEO_URL)
|
||||
await page.wait_for_url(XHS_PUBLISH_VIDEO_URL)
|
||||
publish_url = _build_xhs_creator_url(
|
||||
"/publish/publish?from=homepage&target=video"
|
||||
)
|
||||
await page.goto(publish_url)
|
||||
await page.wait_for_url(publish_url)
|
||||
await page.locator("div[class^='upload-content'] input[class='upload-input']").set_input_files(self.file_path)
|
||||
|
||||
while True:
|
||||
@@ -588,7 +604,7 @@ class XiaoHongShuVideo(XiaoHongShuBaseUploader):
|
||||
else:
|
||||
await page.locator('button:has-text("发布")').click()
|
||||
await page.wait_for_url(
|
||||
"https://creator.xiaohongshu.com/publish/success?**",
|
||||
XHS_PUBLISH_SUCCESS_URL_PATTERN,
|
||||
timeout=3000
|
||||
)
|
||||
xiaohongshu_logger.success(_msg("🥳", "视频发布成功,小人开心收工"))
|
||||
@@ -672,8 +688,11 @@ class XiaoHongShuNote(XiaoHongShuBaseUploader):
|
||||
async def upload_note_content(self, page: Page) -> None:
|
||||
xiaohongshu_logger.info(_msg("🏃", f"小人开始搬运图文,共 {len(self.image_paths)} 张图片"))
|
||||
xiaohongshu_logger.info(_msg("🧭", "小人正在赶往图文发布页"))
|
||||
await page.goto(XHS_PUBLISH_NOTE_URL)
|
||||
await page.wait_for_url(XHS_PUBLISH_NOTE_URL)
|
||||
publish_url = _build_xhs_creator_url(
|
||||
"/publish/publish?from=homepage&target=image"
|
||||
)
|
||||
await page.goto(publish_url)
|
||||
await page.wait_for_url(publish_url)
|
||||
|
||||
upload_input = page.locator('input[type="file"][accept*="image"]').first
|
||||
if not await upload_input.count():
|
||||
|
||||
Reference in New Issue
Block a user