更新
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
"""极简 protobuf wire 解码器(无第三方依赖)。
|
||||
|
||||
用于解析抖音 IM 发送私信的响应:官方 Response.proto 只建模了
|
||||
create/get_info/new_message_notify 三种 body,没有“发送消息响应”,
|
||||
导致仅凭 error_desc 为空就误判为发送成功。这里直接按 wire 格式解码,
|
||||
读取真实的 status_code / server_message_id,判定是否真的投递成功。
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
def _format_status_json(status_json: dict) -> str:
|
||||
"""把内嵌 status JSON 格式化为更可读的失败原因。"""
|
||||
code = status_json.get("status_code")
|
||||
raw_check = status_json.get("raw_check_code")
|
||||
decision = status_json.get("decision_type")
|
||||
parts = [f"status_code={code}"]
|
||||
if raw_check is not None:
|
||||
parts.append(f"raw_check_code={raw_check}")
|
||||
if decision:
|
||||
parts.append(f"decision_type={decision}")
|
||||
return ";".join(parts)
|
||||
|
||||
|
||||
def _read_varint(buf: bytes, i: int) -> tuple[int, int]:
|
||||
shift = 0
|
||||
result = 0
|
||||
n = len(buf)
|
||||
while i < n:
|
||||
b = buf[i]
|
||||
i += 1
|
||||
result |= (b & 0x7F) << shift
|
||||
if not (b & 0x80):
|
||||
return result, i
|
||||
shift += 7
|
||||
if shift > 70:
|
||||
break
|
||||
raise ValueError("truncated varint")
|
||||
|
||||
|
||||
def decode_fields(buf: bytes) -> list[tuple[int, int, Any]]:
|
||||
"""返回 [(field_num, wire_type, value), ...]。
|
||||
|
||||
wire_type: 0=varint(int), 1=64bit(int), 2=length-delimited(bytes), 5=32bit(int)
|
||||
"""
|
||||
out: list[tuple[int, int, Any]] = []
|
||||
i = 0
|
||||
n = len(buf)
|
||||
while i < n:
|
||||
key, i = _read_varint(buf, i)
|
||||
field = key >> 3
|
||||
wt = key & 7
|
||||
if wt == 0:
|
||||
val, i = _read_varint(buf, i)
|
||||
out.append((field, wt, val))
|
||||
elif wt == 2:
|
||||
ln, i = _read_varint(buf, i)
|
||||
val = buf[i:i + ln]
|
||||
i += ln
|
||||
out.append((field, wt, val))
|
||||
elif wt == 5:
|
||||
val = int.from_bytes(buf[i:i + 4], "little")
|
||||
i += 4
|
||||
out.append((field, wt, val))
|
||||
elif wt == 1:
|
||||
val = int.from_bytes(buf[i:i + 8], "little")
|
||||
i += 8
|
||||
out.append((field, wt, val))
|
||||
else:
|
||||
raise ValueError(f"unsupported wire type {wt}")
|
||||
return out
|
||||
|
||||
|
||||
def _collect_big_varints(buf: bytes, acc: list[int], depth: int = 0) -> None:
|
||||
"""递归收集疑似 ID 的大整数(server_message_id / short_id 等都是大数)。"""
|
||||
if depth > 6:
|
||||
return
|
||||
try:
|
||||
fields = decode_fields(buf)
|
||||
except Exception:
|
||||
return
|
||||
for _field, wt, val in fields:
|
||||
if wt == 0 and isinstance(val, int) and val > 10 ** 12:
|
||||
acc.append(val)
|
||||
elif wt == 2 and isinstance(val, (bytes, bytearray)) and val:
|
||||
_collect_big_varints(bytes(val), acc, depth + 1)
|
||||
|
||||
|
||||
def _extract_status_json(raw: bytes) -> Optional[dict]:
|
||||
"""抖音发送响应的 body 内嵌一段 JSON:{"status_code":x,"tips":"...","status_msg":{...}}。
|
||||
|
||||
这才是“消息是否真正投递”的权威结论(status_code=0 才是真成功)。
|
||||
顶层 message=OK 只是接口层面的“已受理”,不代表已投递。
|
||||
"""
|
||||
marker = b'"status_code"'
|
||||
idx = raw.find(marker)
|
||||
if idx < 0:
|
||||
return None
|
||||
start = raw.rfind(b"{", 0, idx)
|
||||
if start < 0:
|
||||
return None
|
||||
depth = 0
|
||||
in_str = False
|
||||
esc = False
|
||||
end = -1
|
||||
for i in range(start, len(raw)):
|
||||
c = raw[i]
|
||||
if in_str:
|
||||
if esc:
|
||||
esc = False
|
||||
elif c == 0x5C: # backslash
|
||||
esc = True
|
||||
elif c == 0x22: # quote
|
||||
in_str = False
|
||||
continue
|
||||
if c == 0x22:
|
||||
in_str = True
|
||||
elif c == 0x7B: # {
|
||||
depth += 1
|
||||
elif c == 0x7D: # }
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
end = i + 1
|
||||
break
|
||||
if end < 0:
|
||||
return None
|
||||
try:
|
||||
return json.loads(raw[start:end].decode("utf-8", "ignore"))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def analyze_send_response(raw: bytes) -> dict:
|
||||
"""分析“发送私信”的 protobuf 响应,判定是否真正投递成功。
|
||||
|
||||
顶层 Response 字段(见 Response.proto):
|
||||
1=cmd, 2=sequence_id, 3=error_desc(string), 4=message(string),
|
||||
5=inbox_type, 6=body(ResponseBody)。
|
||||
|
||||
注意:顶层没有 status_code 字段。真正“消息已写入服务端”的标志是
|
||||
body(field 6) 里带有服务端分配的 server_message_id(大整数)。
|
||||
sequence_id(field 2) 也是大整数,因此只在 body 内部查找 message_id,
|
||||
避免把 sequence_id 误当成投递成功标志。
|
||||
|
||||
返回:
|
||||
ok: 是否真正发送成功(body 内带服务端 message_id,或 message=OK 且有 body)
|
||||
cmd: 顶层 cmd
|
||||
message: 顶层 message 文本(field 4)
|
||||
error_desc: 顶层 error_desc 文本(field 3)
|
||||
server_message_id: body 内服务端消息 ID(投递成功的强信号)
|
||||
has_body: 是否带 body
|
||||
summary: 顶层字段概览 + hex 片段,便于排查
|
||||
"""
|
||||
info = {
|
||||
"ok": False,
|
||||
"cmd": None,
|
||||
"status": None,
|
||||
"status_code": None,
|
||||
"raw_check_code": None,
|
||||
"delivered_with_notice": False,
|
||||
"status_reason": "",
|
||||
"message": "",
|
||||
"error_desc": "",
|
||||
"server_message_id": None,
|
||||
"has_body": False,
|
||||
"summary": "",
|
||||
}
|
||||
if not raw:
|
||||
info["summary"] = "空响应"
|
||||
return info
|
||||
try:
|
||||
fields = decode_fields(raw)
|
||||
except Exception as e:
|
||||
info["summary"] = f"解码失败: {e}; hex={raw[:120].hex()}"
|
||||
return info
|
||||
|
||||
body = None
|
||||
parts = []
|
||||
for field, wt, val in fields:
|
||||
if field == 1 and wt == 0:
|
||||
info["cmd"] = val
|
||||
elif field == 3 and wt == 2:
|
||||
try:
|
||||
info["error_desc"] = bytes(val).decode("utf-8", "ignore")
|
||||
except Exception:
|
||||
pass
|
||||
elif field == 4 and wt == 2:
|
||||
try:
|
||||
info["message"] = bytes(val).decode("utf-8", "ignore")
|
||||
except Exception:
|
||||
pass
|
||||
elif field == 6 and wt == 2:
|
||||
body = bytes(val)
|
||||
info["has_body"] = len(body) > 0
|
||||
|
||||
if wt == 0:
|
||||
parts.append(f"{field}=int:{val}")
|
||||
elif wt == 2:
|
||||
parts.append(f"{field}=bytes[{len(val)}]")
|
||||
else:
|
||||
parts.append(f"{field}={val}")
|
||||
info["summary"] = " ".join(parts) + f" | hex={raw[:120].hex()}"
|
||||
|
||||
# 只在 body 内部查找服务端 message_id(避免误用顶层 sequence_id)
|
||||
if body:
|
||||
ids: list[int] = []
|
||||
_collect_big_varints(body, ids)
|
||||
if ids:
|
||||
info["server_message_id"] = max(ids)
|
||||
|
||||
# 权威结论:body 内嵌 JSON 的 status_code(0 才是真成功)
|
||||
status_json = _extract_status_json(raw)
|
||||
if status_json is not None:
|
||||
info["status_code"] = status_json.get("status_code")
|
||||
info["raw_check_code"] = status_json.get("raw_check_code")
|
||||
tips = (status_json.get("tips") or "").strip()
|
||||
status_msg = status_json.get("status_msg")
|
||||
msg_text = ""
|
||||
if isinstance(status_msg, dict):
|
||||
# 抖音把人类可读提示放在 status_msg.msg_content.tips
|
||||
mc = status_msg.get("msg_content")
|
||||
if isinstance(mc, dict):
|
||||
msg_text = (mc.get("tips") or mc.get("content") or "").strip()
|
||||
if not msg_text:
|
||||
msg_text = (
|
||||
status_msg.get("toast")
|
||||
or status_msg.get("content")
|
||||
or status_msg.get("msg")
|
||||
or ""
|
||||
)
|
||||
elif isinstance(status_msg, str):
|
||||
msg_text = status_msg
|
||||
info["status_reason"] = tips or msg_text or _format_status_json(status_json)
|
||||
|
||||
msg_ok = info["message"].strip().upper() == "OK"
|
||||
|
||||
# 优先用 status_code 判定:明确给了 status_code 就以它为准(0=成功,非0=另判)
|
||||
if info["status_code"] is not None:
|
||||
if info["status_code"] == 0 and not info["error_desc"]:
|
||||
info["ok"] = True
|
||||
elif info["raw_check_code"] == 0 and msg_ok and not info["error_desc"]:
|
||||
# raw_check_code=0 表示已通过抖音风控/安全校验;配合 message=OK,
|
||||
# 说明消息已实际投递。此时非零 status_code 只是“业务侧提示”
|
||||
# (如营销/陌生人限制提醒),对方仍能收到,不应判为发送失败。
|
||||
info["ok"] = True
|
||||
info["delivered_with_notice"] = True
|
||||
else:
|
||||
# raw_check_code=1(被风控拦截)或缺少 OK 标志:判为未送达
|
||||
info["ok"] = False
|
||||
else:
|
||||
# 没有内嵌 status_code 时,退回“message=OK 且 body 内有服务端 message_id”
|
||||
info["ok"] = bool(
|
||||
not info["error_desc"]
|
||||
and msg_ok
|
||||
and info["server_message_id"] is not None
|
||||
)
|
||||
return info
|
||||
Reference in New Issue
Block a user