fix(tencent): 登录态校验区分接口层/页面层——接口 auth_data 判快速失效,页面验证「发表视频」按钮判登录完整;修复扫码后过早保存不完整 cookie(finder_uin 空)导致发布弹扫码框
This commit is contained in:
@@ -111,8 +111,10 @@ def format_str_for_short_title(origin_title: str) -> str:
|
|||||||
return formatted_string
|
return formatted_string
|
||||||
|
|
||||||
|
|
||||||
# 视频号助手登录态探测接口:errCode==0 表示登录有效,非 0(如 300330)表示失效/未登录
|
# 视频号助手登录态探测接口:auth_data 会返回 data.finderUser.finderUsername(视频号身份)
|
||||||
_TENCENT_AUTH_INFO_URL = "https://channels.weixin.qq.com/cgi-bin/mmfinderassistant-bin/auth/get_auth_info"
|
# errCode==0 且 finderUsername 非空 => 登录完整有效;
|
||||||
|
# get_auth_info 只返回 authType,无法区分「已登录但未绑定视频号」,不能作为唯一判据
|
||||||
|
_TENCENT_AUTH_DATA_URL = "https://channels.weixin.qq.com/cgi-bin/mmfinderassistant-bin/auth/auth_data"
|
||||||
_TENCENT_AUTH_HEADERS = {
|
_TENCENT_AUTH_HEADERS = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Origin": "https://channels.weixin.qq.com",
|
"Origin": "https://channels.weixin.qq.com",
|
||||||
@@ -124,7 +126,7 @@ _TENCENT_AUTH_HEADERS = {
|
|||||||
def _cookie_auth_via_api(account_file) -> bool | None:
|
def _cookie_auth_via_api(account_file) -> bool | None:
|
||||||
"""纯 HTTP 接口探测登录态(不开浏览器,快且稳)。
|
"""纯 HTTP 接口探测登录态(不开浏览器,快且稳)。
|
||||||
|
|
||||||
返回 True=有效、False=失效、None=探测失败(网络/响应异常,需降级页面行为)。
|
返回 True=有效(已登录且绑定视频号身份)、False=失效/未绑定、None=探测失败(需降级页面行为)。
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
import json
|
import json
|
||||||
@@ -134,62 +136,55 @@ def _cookie_auth_via_api(account_file) -> bool | None:
|
|||||||
state = json.load(fobj)
|
state = json.load(fobj)
|
||||||
cookies = {c["name"]: c["value"] for c in state.get("cookies", [])}
|
cookies = {c["name"]: c["value"] for c in state.get("cookies", [])}
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
_TENCENT_AUTH_INFO_URL,
|
_TENCENT_AUTH_DATA_URL,
|
||||||
headers=_TENCENT_AUTH_HEADERS,
|
headers=_TENCENT_AUTH_HEADERS,
|
||||||
cookies=cookies,
|
cookies=cookies,
|
||||||
json={},
|
json={},
|
||||||
timeout=10,
|
timeout=10,
|
||||||
)
|
)
|
||||||
body = resp.json()
|
body = resp.json()
|
||||||
if body.get("errCode") == 0:
|
if body.get("errCode") != 0:
|
||||||
return True
|
return False # 未登录 / session 失效
|
||||||
if "errCode" in body:
|
finder_user = (body.get("data") or {}).get("finderUser") or {}
|
||||||
return False
|
if finder_user.get("finderUsername"):
|
||||||
return None
|
return True # 已登录且绑定视频号身份
|
||||||
|
return False # 有 session 但未绑定视频号(登录不完整)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
tencent_logger.warning(_msg("⚠️", f"接口探测登录态异常({exc}),降级为页面行为检测"))
|
tencent_logger.warning(_msg("⚠️", f"接口探测登录态异常({exc}),降级为页面行为检测"))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def cookie_auth(account_file):
|
async def cookie_auth(account_file):
|
||||||
"""视频号登录态校验:接口探测优先(统一「测试」与「发布」口径),失败再降级页面行为。"""
|
"""视频号登录态校验:接口快速判失效 + 页面验证「发表视频」按钮判登录完整。
|
||||||
|
|
||||||
|
关键:sessionid 有效时接口 auth_data 仍能返回 finder 信息,但 localStorage 登录态
|
||||||
|
不完整(finder_uin 空)时页面会主动弹扫码框、无发表入口——接口层与页面层不一致。
|
||||||
|
因此接口只用于快速判失效,页面验证「发表视频」按钮才是登录完整的可靠标志。
|
||||||
|
"""
|
||||||
account_file = _resolve_account_file(account_file)
|
account_file = _resolve_account_file(account_file)
|
||||||
|
|
||||||
# 1) 纯 HTTP 接口探测(快、准、统一,不开浏览器)
|
# 1) 接口快速判失效(errCode≠0 或未绑定 finder => 一定失效,无需开浏览器)
|
||||||
via_api = _cookie_auth_via_api(account_file)
|
via_api = _cookie_auth_via_api(account_file)
|
||||||
if via_api is True:
|
|
||||||
tencent_logger.success(_msg("🥳", "cookie 有效(接口探测 errCode=0)"))
|
|
||||||
return True
|
|
||||||
if via_api is False:
|
if via_api is False:
|
||||||
tencent_logger.info(_msg("🥹", "cookie 已失效(接口探测 errCode≠0),得重新登录一下"))
|
tencent_logger.info(_msg("🥹", "cookie 已失效或未绑定视频号身份(接口探测),得重新登录一下"))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 2) 接口探测失败,降级为页面行为检测
|
# 2) 页面验证:登录完整 = 平台首页出现「发表视频」按钮
|
||||||
async with async_playwright() as playwright:
|
async with async_playwright() as playwright:
|
||||||
browser = await playwright.chromium.launch(**_build_launch_kwargs(headless=True))
|
browser = await playwright.chromium.launch(**_build_launch_kwargs(headless=True))
|
||||||
try:
|
try:
|
||||||
context = await browser.new_context(storage_state=account_file)
|
context = await browser.new_context(storage_state=account_file)
|
||||||
context = await set_init_script(context)
|
context = await set_init_script(context)
|
||||||
page = await context.new_page()
|
page = await context.new_page()
|
||||||
await page.goto(TENCENT_UPLOAD_URL, wait_until="domcontentloaded")
|
await page.goto(TENCENT_HOME_URL, timeout=120000, wait_until="domcontentloaded")
|
||||||
|
publish_entry = page.locator("button.weui-desktop-btn", has_text="发表视频").first
|
||||||
# cookie 失效时, 页面先停在 post/create, 随后由前端 JS 跳转到登录页;
|
|
||||||
# 必须等待跳转完成再判断, 否则会误报"cookie 有效"
|
|
||||||
try:
|
try:
|
||||||
await page.wait_for_url("**/login.html**", timeout=8000)
|
await publish_entry.wait_for(state="visible", timeout=10000)
|
||||||
tencent_logger.info(_msg("🥹", "cookie 已失效(页面跳转到登录页),得重新登录一下"))
|
tencent_logger.success(_msg("🥳", "cookie 有效(平台首页发表入口已加载)"))
|
||||||
return False
|
|
||||||
except Exception:
|
|
||||||
pass # 8 秒内未跳转, 大概率已登录
|
|
||||||
|
|
||||||
# 双保险: 页面里出现微信扫码登录 iframe 也视为失效
|
|
||||||
for fr in page.frames:
|
|
||||||
if "open.weixin.qq.com/connect/qrconnect" in fr.url:
|
|
||||||
tencent_logger.info(_msg("🥹", "cookie 已失效(页面出现扫码登录框),得重新登录一下"))
|
|
||||||
return False
|
|
||||||
|
|
||||||
tencent_logger.success(_msg("🥳", "cookie 有效"))
|
|
||||||
return True
|
return True
|
||||||
|
except Exception:
|
||||||
|
tencent_logger.info(_msg("🥹", "cookie 失效(平台首页未加载出发表入口,登录态不完整)"))
|
||||||
|
return False
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
tencent_logger.warning(_msg("😵", f"cookie 校验时出错,按失效处理: {exc}"))
|
tencent_logger.warning(_msg("😵", f"cookie 校验时出错,按失效处理: {exc}"))
|
||||||
return False
|
return False
|
||||||
@@ -302,17 +297,37 @@ async def _has_tencent_login_cookie(context) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
async def _is_tencent_login_completed(page: Page, context=None) -> bool:
|
async def _is_tencent_login_completed(page: Page, context=None) -> bool:
|
||||||
# 首选:登录态 cookie 出现即视为成功(扫码+手机确认后浏览器必种 cookie)
|
|
||||||
if context is not None and await _has_tencent_login_cookie(context):
|
|
||||||
return True
|
|
||||||
|
|
||||||
current_url = page.url or ""
|
current_url = page.url or ""
|
||||||
# 仍在登录页 => 未登录
|
# 1) 仍在登录页 => 未完成(即使 sessionid 已种下,登录流程尚未走完:可能在选号/跳转中)
|
||||||
if "login.html" in current_url:
|
if "login.html" in current_url:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 已跳到平台页(首页/发布页/列表页):只要没有可见的二维码容器就算成功
|
# 2) 页面仍有 qrconnect 扫码 iframe => 未完成登录(登录态不完整会残留扫码框)
|
||||||
# 注意:残留的扫码 iframe 只隐藏未移除时也会在 page.frames 里,不能作为否决条件
|
try:
|
||||||
|
for fr in page.frames:
|
||||||
|
if "open.weixin.qq.com/connect/qrconnect" in (fr.url or ""):
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 3) 平台首页出现「发表视频」按钮 => 登录完整(最可靠标志)
|
||||||
|
# (不完整登录时页面会延迟弹扫码框、且无发表入口)
|
||||||
|
try:
|
||||||
|
publish_entry = page.locator("button.weui-desktop-btn", has_text="发表视频").first
|
||||||
|
if await publish_entry.count() and await publish_entry.is_visible():
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 4) localStorage 已有视频号身份(finder_username 非空)=> 登录完整
|
||||||
|
try:
|
||||||
|
finder_username = await page.evaluate("localStorage.getItem('finder_username')")
|
||||||
|
if finder_username:
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 5) 已跳到平台页(首页/发布页/列表页)且无可见二维码容器 => 成功
|
||||||
if current_url.startswith(TENCENT_HOME_URL) or current_url.startswith(TENCENT_UPLOAD_URL) or current_url.startswith(TENCENT_MANAGE_URL):
|
if current_url.startswith(TENCENT_HOME_URL) or current_url.startswith(TENCENT_UPLOAD_URL) or current_url.startswith(TENCENT_MANAGE_URL):
|
||||||
for marker in [
|
for marker in [
|
||||||
page.locator("div.login-qrcode-wrap").first,
|
page.locator("div.login-qrcode-wrap").first,
|
||||||
@@ -326,6 +341,10 @@ async def _is_tencent_login_completed(page: Page, context=None) -> bool:
|
|||||||
continue
|
continue
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# 6) cookie 兜底(已离开登录页且无扫码框)
|
||||||
|
if context is not None and await _has_tencent_login_cookie(context):
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user