88 lines
3.5 KiB
Python
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()
|