"""极简 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 msg_ok and not info["error_desc"] and info["server_message_id"] is not None: # message=OK + server_message_id:消息已写入服务端。 # type=70 网页链接卡常见 status_code=8004 + raw_check_code=1,仍正常投递, # 与是否互关无关;勿仅凭 8004 判失败。 info["ok"] = True if info["status_code"] != 0 or info["raw_check_code"] not in (None, 0): info["delivered_with_notice"] = True else: 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