更新
This commit is contained in:
@@ -320,6 +320,7 @@ class DouyinImService:
|
||||
reply_cooldown_seconds: Optional[int] = None,
|
||||
cooldown_resolver: Optional[Callable[[], Awaitable[int]]] = None,
|
||||
refresh_credentials: Optional[Callable[[], Awaitable[bool]]] = None,
|
||||
send_fallback: Optional[Callable[[str, str], Awaitable[tuple[bool, str]]]] = None,
|
||||
follow_tick: Optional[Callable[[], Awaitable[None]]] = None,
|
||||
on_session_invalid: Optional[Callable[[str], Awaitable[None]]] = None,
|
||||
on_ready: Optional[ReadyFn] = None,
|
||||
@@ -354,6 +355,11 @@ class DouyinImService:
|
||||
self._cooldown_resolver = cooldown_resolver
|
||||
# 由 worker 注入:触发后台重新采集 web_protect/keys(刷新 ts_sign),返回是否刷新成功
|
||||
self.refresh_credentials = refresh_credentials
|
||||
# 由 worker 注入的第二套发送方案:当 HTTP 签名发送被安全网关拒绝
|
||||
# (decision=KICK / 7911 / INVALID_REQUEST)时,用浏览器页面上下文
|
||||
# 重新发送(真实 JS 生成 a_bogus/bd-ticket-guard,可自愈被踢的会话)。
|
||||
# 签名: async (conversation_id, content) -> (ok, detail)
|
||||
self.send_fallback = send_fallback
|
||||
self._running = False
|
||||
self._replied_keys: set[str] = set()
|
||||
self._logged_keys: set[str] = set()
|
||||
@@ -363,6 +369,9 @@ class DouyinImService:
|
||||
self._conv_previews: dict[str, str] = {}
|
||||
self._conv_names: dict[str, str] = {} # uid/conv_id -> nickname
|
||||
self._conv_meta: dict[str, dict] = {} # conversation_id -> meta
|
||||
# 抖音判定会话列表请求本身不合法时置位:这轮托管不再重复轮询该接口,
|
||||
# 实时长连接成为唯一接收通道(已在系统日志里说明)。
|
||||
self._conversation_list_unsupported = False
|
||||
self._ws_client: Optional[DouyinImWsClient] = None
|
||||
self.last_error: str = ""
|
||||
|
||||
@@ -1037,6 +1046,11 @@ class DouyinImService:
|
||||
initial: bool = False,
|
||||
defer_handlers: bool = False,
|
||||
) -> list[dict]:
|
||||
if self._conversation_list_unsupported:
|
||||
# 抖音已明确拒绝过这个请求本身;重复调用只会每轮浪费一次请求,
|
||||
# 并把同一条错误反复写进日志。原因已在首次拒绝时记录。
|
||||
return []
|
||||
|
||||
controller = get_traffic_controller()
|
||||
async with controller.background_slot(
|
||||
self.account_id,
|
||||
@@ -1053,6 +1067,14 @@ class DouyinImService:
|
||||
account_id=self.account_id,
|
||||
) as http:
|
||||
conversations = await http.get_conversations(enrich_profiles=False)
|
||||
if http.conversation_list_unsupported:
|
||||
self._conversation_list_unsupported = True
|
||||
logger.warning(
|
||||
"Account %s disabled conversation reconciliation; "
|
||||
"the realtime WebSocket is now the only receive path",
|
||||
self.account_id,
|
||||
)
|
||||
return []
|
||||
|
||||
# Capture the previous preview before _index_conversations overwrites
|
||||
# _conv_meta. A conversation-list preview is not inherently a new
|
||||
@@ -1445,6 +1467,40 @@ class DouyinImService:
|
||||
if refreshed:
|
||||
continue
|
||||
break
|
||||
|
||||
# 第二套发送方案(浏览器页面内发送):
|
||||
# HTTP 签名发送被安全网关拒绝(KICK/7911/INVALID_REQUEST)时,交给 worker
|
||||
# 用浏览器页面上下文重发——由抖音页面自带的 security-sdk 在真实环境生成
|
||||
# a_bogus/bd-ticket-guard,绕开我们 Node execjs 的签名模拟,可自愈被踢会话。
|
||||
upper_err = (self.last_error or "").upper()
|
||||
if self.send_fallback and (
|
||||
"DECISION=KICK" in upper_err
|
||||
or "STATUS_CODE=7911" in upper_err
|
||||
or "INVALID_REQUEST" in upper_err
|
||||
):
|
||||
try:
|
||||
fb_ok, fb_detail = await self.send_fallback(conversation_id, content)
|
||||
except Exception as exc:
|
||||
logger.warning(f"send_fallback raised for {conversation_id}: {exc}")
|
||||
fb_ok, fb_detail = False, f"浏览器兜底发送异常:{exc}"
|
||||
if fb_ok:
|
||||
self._session_invalid_strikes = 0
|
||||
self._session_invalid_fired = False # 兜底成功说明登录仍有效,撤销自动下线
|
||||
system_logger.record(
|
||||
"浏览器兜底发送成功",
|
||||
detail=f"会话 {conversation_id}:{fb_detail}",
|
||||
level="success",
|
||||
category="send",
|
||||
account_id=self.account_id,
|
||||
)
|
||||
return True, None
|
||||
system_logger.record(
|
||||
"浏览器兜底发送失败",
|
||||
detail=f"会话 {conversation_id}:{fb_detail}",
|
||||
level="error",
|
||||
category="send",
|
||||
account_id=self.account_id,
|
||||
)
|
||||
await self._note_session_invalid(self.last_error)
|
||||
return False, None
|
||||
|
||||
@@ -1479,7 +1535,7 @@ class DouyinImService:
|
||||
system_logger.record(
|
||||
"IM 登录失效,自动下线",
|
||||
detail=f"{reason}({failure_detail})。"
|
||||
"请停止托管后用浏览器模式重新登录并打开私信页,再重新启动托管。",
|
||||
"系统正在自动重登录,请留意账号卡片上的登录二维码并扫码。",
|
||||
level="error",
|
||||
category="auth",
|
||||
account_id=self.account_id,
|
||||
@@ -1495,17 +1551,18 @@ class DouyinImService:
|
||||
"""手动发送私信"""
|
||||
from .conv_util import normalize_conversation_id
|
||||
from .auth import DouyinAuth
|
||||
from .dy_util import DEFAULT_USER_AGENT
|
||||
|
||||
auth = DouyinAuth()
|
||||
auth.perepare_auth(
|
||||
self.session.cookie_header(),
|
||||
self.session.web_protect_str,
|
||||
self.session.keys_str,
|
||||
user_agent=self.session.user_agent or DEFAULT_USER_AGENT,
|
||||
)
|
||||
if getattr(self.session, "uid_verified", False) and self.session.my_uid:
|
||||
my_uid = self.session.my_uid
|
||||
else:
|
||||
my_uid = await asyncio.to_thread(lambda: auth.get_uid()) or self.session.my_uid
|
||||
my_uid = self.session.my_uid
|
||||
if not my_uid:
|
||||
my_uid = await asyncio.to_thread(lambda: auth.get_uid()) or 0
|
||||
if my_uid:
|
||||
conversation_id = normalize_conversation_id(conversation_id, my_uid)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user