更新
This commit is contained in:
@@ -14,7 +14,12 @@ from rpa_engine.egress_channels import (
|
||||
resolve_fixed_channel,
|
||||
resolve_send_channels,
|
||||
)
|
||||
from .conv_util import build_conversation_id, normalize_conversation_id, resolve_peer_uid
|
||||
from .conv_util import (
|
||||
build_conversation_id,
|
||||
conversation_belongs_to,
|
||||
normalize_conversation_id,
|
||||
resolve_peer_uid,
|
||||
)
|
||||
from .message_content import format_im_message, serialize_message_content
|
||||
from .peer_profile import enrich_conversation_item, fetch_peer_profile, is_generic_peer_name
|
||||
from .protocol import normalize_im_payload_from_bytes, _pick_avatar_url
|
||||
@@ -1144,8 +1149,17 @@ class DouyinImHttpClient:
|
||||
self.session.my_uid,
|
||||
uid,
|
||||
)
|
||||
previous = int(self.session.my_uid or 0)
|
||||
self.session.my_uid = uid
|
||||
self.session.uid_verified = True
|
||||
# 托管注册表按 UID 记录「本系统正在托管谁」。纠正后必须迁移,否则回环
|
||||
# 防护会认错人:旧 UID 永远留在表里,真实 UID 从未登记。只迁移确实已登记
|
||||
# 的托管身份,避免 API 侧的临时客户端把自己也登记进去。
|
||||
from . import hosted_registry
|
||||
|
||||
if hosted_registry.is_hosted(previous):
|
||||
hosted_registry.unregister(previous)
|
||||
hosted_registry.register(uid)
|
||||
|
||||
async def get_conversations(
|
||||
self,
|
||||
@@ -1383,6 +1397,26 @@ class DouyinImHttpClient:
|
||||
self._set_error("无法获取当前账号 UID")
|
||||
self._log_send_failure(conversation_id, "无法获取当前账号 UID(Cookie 可能已失效)")
|
||||
return False
|
||||
|
||||
# 跨账号写入闸门:normalize_conversation_id 会把任何会话 ID 改写成
|
||||
# 0:1:{本账号}:{末段 UID},所以一条属于别的账号的会话流到这里会被
|
||||
# 静默改写并发给对方的好友。发送前先确认本账号确实是该会话的参与方。
|
||||
if not conversation_belongs_to(conversation_id, my_uid):
|
||||
detail = (
|
||||
f"会话 {conversation_id} 的参与方都不是本账号(uid={my_uid}),"
|
||||
"拒绝发送:这条会话属于另一个账号,继续发送会把消息发给别人的好友。"
|
||||
)
|
||||
self._set_error(detail)
|
||||
self.last_send_channel_retryable = False
|
||||
self._log_send_failure(conversation_id, detail)
|
||||
logger.error(
|
||||
"Account %s refused cross-account send to %s (my_uid=%s)",
|
||||
self.account_id,
|
||||
conversation_id,
|
||||
my_uid,
|
||||
)
|
||||
return False
|
||||
|
||||
if not auth.is_sign_ready():
|
||||
self._set_error("缺少 IM 签名密钥,请用浏览器登录补全 localStorage")
|
||||
self._log_send_failure(
|
||||
@@ -1510,10 +1544,14 @@ class DouyinImHttpClient:
|
||||
decision = str(result.get("decision") or "").strip().upper()
|
||||
|
||||
if decision == "KICK":
|
||||
self.last_send_channel_retryable = True
|
||||
# KICK is a terminal, account-session decision. Retrying the
|
||||
# same authenticated write from another source address cannot
|
||||
# repair the session and only adds another high-risk request.
|
||||
self.last_send_needs_refresh = False
|
||||
self.last_send_channel_retryable = False
|
||||
detail = (
|
||||
"抖音安全网关返回 decision=KICK,当前登录/安全会话已被服务端踢下线;"
|
||||
"系统正在自动重登录,请留意账号卡片上的二维码并扫码"
|
||||
"已停止本次发送及公网通道重试,系统正在自动重登录,请留意账号卡片上的二维码并扫码"
|
||||
)
|
||||
elif decision:
|
||||
detail = f"抖音安全网关拒绝发送 decision={decision}"
|
||||
@@ -1527,7 +1565,11 @@ class DouyinImHttpClient:
|
||||
hint = _BUSINESS_REJECT_FALLBACK
|
||||
# 7911 属于“签名凭证失效/安全校验未过”,标记为可刷新后重试
|
||||
self.last_send_needs_refresh = status_code in _CREDENTIAL_EXPIRED_CODES
|
||||
self.last_send_channel_retryable = self.last_send_needs_refresh
|
||||
# 7911 is a credential/signature problem. It may be retried
|
||||
# once only after refreshing the credentials on the same
|
||||
# session; switching egress mid-session makes the fingerprint
|
||||
# less consistent and must not be used as the recovery path.
|
||||
self.last_send_channel_retryable = False
|
||||
detail = f"抖音拒绝投递 status_code={status_code}"
|
||||
if status_reason:
|
||||
detail += f";抖音提示:{status_reason}"
|
||||
@@ -1550,7 +1592,10 @@ class DouyinImHttpClient:
|
||||
detail = ";".join(reason_bits) or "接口返回但未确认投递(无 server_message_id)"
|
||||
|
||||
if "INVALID_REQUEST" in detail.upper():
|
||||
self.last_send_channel_retryable = True
|
||||
# INVALID_REQUEST is a protocol/session rejection, not a
|
||||
# transport failure. A second public IP sends the same invalid
|
||||
# request and can invalidate an otherwise recoverable login.
|
||||
self.last_send_channel_retryable = False
|
||||
full_detail = f"{detail};{target};resp[{result.get('summary')}]"
|
||||
if self.last_request_debug:
|
||||
full_detail += f"\n--- 请求详情 ---\n{self.last_request_debug}"
|
||||
|
||||
Reference in New Issue
Block a user