Files
kefu/wechat_rpa/tmp/probe_name_hash.py
T
2026-07-31 11:48:16 +08:00

88 lines
3.5 KiB
Python

"""实拍会话列表,逐行报告名称字形哈希的置位数,并导出取样带截图。
用途:定位为什么某些联系人的 256 位名称哈希退化成全零。
"""
import os
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import cv2 # noqa: E402
from wechat_bot import WeChatBot, _NAME_FP_BYTES # noqa: E402
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "namehash")
def main() -> None:
os.makedirs(OUT, exist_ok=True)
bot = WeChatBot()
if not bot.connect(activate=False):
raise SystemExit("挂载企业微信失败")
img = bot.capture_session_list()
if img is None:
raise SystemExit("截取会话列表失败")
cv2.imwrite(os.path.join(OUT, "list.png"), img)
scale = max(0.75, float(bot.scale or 1.0))
print(f"列表尺寸 {img.shape[1]}x{img.shape[0]} scale={scale}")
centers = bot._avatar_row_centers(img)
print(f"检出 {len(centers)} 行头像,中心={centers}")
# 名称哈希实际取样的两条带(与 _session_name_fingerprint 保持一致)
bands = {
"prefix": (60 * scale, 84 * scale, -8 * scale, -2 * scale),
"main": (80 * scale, img.shape[1] - 60 * scale, -21 * scale, -2 * scale),
}
for idx, raw_center in enumerate(centers):
y_c = bot._avatar_anchor(img, raw_center)
name_fp = bot._session_name_fingerprint(img, y_c, row_center=True)
avatar_fp = bot._session_fingerprint(img, y_c, row_center=True)
bits = int.from_bytes(name_fp or b"", "big").bit_count()
head = (avatar_fp or b"")[:8].hex()
print(
f"行{idx}: 锚点 {raw_center}->{y_c} 名称置位={bits:3d}/256 "
f"长度={len(name_fp or b'')} 头像={head}"
)
for tag, (x1, x2, dy1, dy2) in bands.items():
xa, xb = int(max(0, x1)), int(min(img.shape[1], x2))
ya, yb = int(max(0, y_c + dy1)), int(min(img.shape[0], y_c + dy2))
crop = img[ya:yb, xa:xb]
if crop.size:
gray = crop[:, :, :3].astype(np.float32).mean(axis=2)
background = float(np.median(gray))
ink = float((np.abs(gray - background) >= 16.0).mean())
print(
f" {tag:6s} x[{xa},{xb}) y[{ya},{yb}) "
f"底色={background:.0f} 墨迹占比={ink:.3f}"
)
cv2.imwrite(os.path.join(OUT, f"row{idx}_{tag}.png"), crop)
else:
print(f" {tag:6s} 取样区为空 x[{xa},{xb}) y[{ya},{yb})")
print(f"\n截图已写入 {OUT}")
print(f"_NAME_FP_BYTES={_NAME_FP_BYTES}")
# 不同联系人之间名称哈希的距离下界,决定容差能开到多大
print("\n当前列表内不同行的名称哈希距离:")
fps = []
for idx, raw_center in enumerate(centers):
y_c = bot._avatar_anchor(img, raw_center)
fps.append((idx, bot._session_name_fingerprint(img, y_c, row_center=True)))
worst = 999
for i in range(len(fps)):
for j in range(i + 1, len(fps)):
a, b = fps[i][1], fps[j][1]
if len(a) != _NAME_FP_BYTES or len(b) != _NAME_FP_BYTES:
continue
dist = (int.from_bytes(a, "big") ^ int.from_bytes(b, "big")).bit_count()
worst = min(worst, dist)
print(f" 行{fps[i][0]} vs 行{fps[j][0]}: {dist}")
print(f"不同联系人名称距离下界 = {worst}")
if __name__ == "__main__":
main()