The GitHub stargazer API used by star-history.com has become unreliable. This switches the chart/links in README.md to our alternative instance (star-history.dera.page) which requires no API token and works for both API and frontend on the same domain.
cv2.imread cannot open files under non-ASCII (e.g. Chinese) paths on
Windows, so decode_qrcode_from_path() always returned None when the
project lived under a directory like D:\视频全平台同步\, and the
terminal QR re-print silently failed. Use np.fromfile + cv2.imdecode
instead, which handles arbitrary paths.
The WeChat Channels login page now embeds the QR code in an
open.weixin.qq.com/connect/qrconnect iframe whose img.qrcode src is a
relative path (e.g. /connect/qrcode/xxxx) instead of an inline
data:image/ URL. The previous logic looked for a login-for-iframe
frame and only accepted data:image/ sources, so login always failed
with '未获取到视频号登录二维码地址'.
Changes:
- Detect the qrconnect iframe and resolve the relative img src,
download the QR image via the browser context and convert it to a
data URL so downstream save/decode logic stays unchanged
- Keep the legacy login-for-iframe and selector fallbacks
- Tolerate QR extraction failure: the login flow now continues and
lets the user scan directly in the headed browser instead of
aborting the whole login
4 commits squashed into 1 due to force-push via API. See PR #229 body for full description.
Key changes:
- cookie_auth: polling loop (10s) instead of single count() snapshot (race fix)
- cookie_auth: env var DOUYIN_COOKIE_AUTH_HEADLESS for linux server headless override
- _extract_douyin_qrcode_src: 4-tier selector fallback (animate_qrcode_container)
- _wait_for_douyin_login: 2FA detection with warning
- export_douyin_cookie: USERNAME from os.environ
- ks/xiaohongshu uploader: chromium default + cdp_url support for ks
Title and description were typed character-by-character, so a '#'
(e.g. '#Shorts') triggered YouTube's topic-autocomplete dropdown that
follows the caret and covers the Next/Publish buttons, stalling the
upload. Switch to fill() (one-shot insert, no per-keystroke autocomplete)
with a type() fallback, and add _dismiss_autocomplete (blur, then Escape
only when a dropdown is actually visible so the upload dialog is never
closed by mistake).
Same reason as the ks uploader: bundled patchright cannot reliably resolve the system 'chrome' channel on every platform, so use the bundled 'chromium' channel by default and rely on conf.py:LOCAL_CHROME_PATH for users who want real Chrome.
Two related changes for kuaishou uploader:
- Default browser channel is switched from 'chrome' to 'chromium'. The launcher binary that ships with patchright no longer reliably resolves the system Chrome channel on every platform, so falling back to bundled chromium avoids launch-time channel-resolution errors. The user can still force a real Chrome via conf.py:LOCAL_CHROME_PATH.
- New cdp_url parameter on ks_setup / get_ks_cookie. When set, the login flow uses connect_over_cdp() to drive the user's already-running real Chrome instead of spawning a separate browser. This is the supported way around strict anti-bot detection: real Chrome has a fingerprint that passes verification, while the bundled chromium does not. should_close_context tracks ownership so we don't close a context we didn't create.
The shell passed USERNAME as an environment variable to the embedded python heredoc (USERNAME=$USERNAME python3 ...), but the python block referenced USERNAME as if it were already a local variable. The result was NameError on every run, so the export script could never produce a cookies/<platform>_<account>.json file.
Fix: read the env var explicitly with os.environ.get('USERNAME', '') at the top of the embedded block, then use the local USERNAME as before.
The Douyin creator center UI (https://creator.douyin.com/creator-micro/) has been refactored and breaks the existing login flow in three independent ways. This commit fixes all of them.
1. QR code selector no longer matches
The previous selector chain assumed the QR sat in a sibling div with an aria-label of 二维码. After the refactor, the QR is rendered as a base64 PNG inside div#animate_qrcode_container and has no aria-label. The new chain is a 4-tier fallback (id -> class partial -> container partial -> aria-label) preceded by a networkidle wait so the SPA is fully hydrated before we look.
2. cookie_auth() race condition
page.goto + wait_for_url(5000) + immediate get_by_text count() was racy: the SPA had not finished hydrating, so count() could return 0 even when the page actually shows the login card, leading to false-positive 'cookie valid' results. Replaced with a 10s polling loop that returns invalid if neither the login-card text nor the upload-page text is observed.
3. _wait_for_douyin_login() only waited for sessionid cookie (120s)
During 2FA the cookie is not yet set; the page is still on the verification screen and the URL has already changed. The new loop detects 2FA input fields, logs a warning so the user knows to type the SMS code, and keeps polling. Timeout raised to 200s to accommodate the slower human-in-the-loop flow.
Verified the selector chain against the live creator.douyin.com page: returns a 2.9 KB base64 PNG that decodes into a valid douyin scan_login URL.
- Wait until the upload progress reaches 100% before clicking publish. The
browser-based upload only progresses while the page is open, so publishing
(and closing the browser) mid-upload cut it off and left the video stuck
partway (e.g. 76%).
- Add optional YT_PROXY (conf.py): where youtube.com is blocked, direct
connections time out and the chromium does not use the system proxy, so
allow pointing it at a local proxy explicitly.
Add a YouTube uploader that follows the existing cookie-based browser
automation pattern (uploader/youtube_uploader + sau youtube CLI:
login/check/upload-video). Supports title/description/tags, thumbnail,
adding to a playlist (for series), and visibility (public/unlisted/private).
Browser automation is used instead of the official Data API on purpose:
videos uploaded by an unaudited API project are force-locked to private
and cannot be made public without Google's compliance audit, which is
impractical for personal/single-channel use. Browser automation has no
such restriction and matches how every other platform here works.