178 lines
7.2 KiB
Bash
Executable File
178 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 微信全量验收:等 ADB → oneclick → 采集 Hook 数据 → REST 矩阵 → Hook catalog → 汇总报告
|
||
set -euo pipefail
|
||
SDK_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
SCRIPTS="${SDK_ROOT}/scripts"
|
||
OUT_DIR="${SDK_ROOT}/tmp"
|
||
DOC_DIR="$(cd "${SDK_ROOT}/.." && pwd)/开发文档/5、接口/06-验收与矩阵"
|
||
TS="$(date +%Y%m%d_%H%M%S)"
|
||
REPORT_JSON="${OUT_DIR}/wechat_full_acceptance_${TS}.json"
|
||
REPORT_MD="${DOC_DIR}/2026-05-24_微信全量验收报告_${TS}.md"
|
||
LOG="${OUT_DIR}/wechat_full_acceptance_${TS}.log"
|
||
|
||
DEVICE_SERIAL="${DEVICE_SERIAL:-}"
|
||
ADB_WAIT_SEC="${ADB_WAIT_SEC:-600}"
|
||
SDK_PORT="${SDK_PORT:-8899}"
|
||
SDK_BASE="http://127.0.0.1:${SDK_PORT}"
|
||
|
||
exec > >(tee -a "${LOG}") 2>&1
|
||
echo "=== 微信全量验收 ${TS} ==="
|
||
|
||
# ── 1. 等待任意 ADB 设备(无线 IP 或 USB serial)──
|
||
if [[ -z "${DEVICE_SERIAL}" ]]; then
|
||
for candidate in "192.168.110.80:5555" "192.168.1.126:5555"; do
|
||
if bash "${SCRIPTS}/wait_adb_device.sh" "${candidate}" 30 2; then
|
||
DEVICE_SERIAL="${candidate}"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
if [[ -z "${DEVICE_SERIAL}" ]]; then
|
||
echo "轮询任意 USB/ADB 设备 (${ADB_WAIT_SEC}s)..."
|
||
deadline=$((SECONDS + ADB_WAIT_SEC))
|
||
while (( SECONDS < deadline )); do
|
||
DEVICE_SERIAL="$(adb devices | awk '/\tdevice$/{print $1; exit}')"
|
||
if [[ -n "${DEVICE_SERIAL}" ]]; then
|
||
echo "adb_ready ${DEVICE_SERIAL}"
|
||
break
|
||
fi
|
||
sleep 3
|
||
done
|
||
fi
|
||
[[ -n "${DEVICE_SERIAL}" ]] || { echo "FATAL: 无 ADB 设备"; exit 2; }
|
||
|
||
export DEVICE_SERIAL
|
||
echo "设备: ${DEVICE_SERIAL}"
|
||
|
||
# ── 2. oneclick(WS + Frida)──
|
||
pkill -f "agent.py -d ${DEVICE_SERIAL}" 2>/dev/null || true
|
||
bash "${SCRIPTS}/frida_workphone_oneclick.sh" || { echo "oneclick 失败"; exit 3; }
|
||
|
||
# 等 WS + RPC
|
||
for _i in $(seq 1 40); do
|
||
ws="$(curl -s "${SDK_BASE}/api/v3/connection/status" | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['online_ws_count'])" 2>/dev/null || echo 0)"
|
||
rpc="$(grep -c 'RPC ping: pong' "${SDK_ROOT}/logs/agent.log" 2>/dev/null || echo 0)"
|
||
if [[ "${ws}" -ge 1 && "${rpc}" -ge 1 ]]; then
|
||
echo "WS+Frida 就绪 ws=${ws}"
|
||
break
|
||
fi
|
||
sleep 2
|
||
done
|
||
|
||
DEV="${DEVICE_SERIAL}"
|
||
export SDK_MATRIX_VERIFY=1 WECHAT_WS_HOOK_ONLY=1 WECHAT_BACKEND_ONLY=1
|
||
export SDK_BASE_URL="${SDK_BASE}" SDK_DEVICE_ID="${DEV}" SDK_E2E_TO_ID=filehelper
|
||
|
||
# ── 3. 连接 / 探测 / Hook 数据 ──
|
||
mkdir -p "${OUT_DIR}"
|
||
curl -s "${SDK_BASE}/api/v3/connection/status" > "${OUT_DIR}/acceptance_connection_${TS}.json"
|
||
curl -s "${SDK_BASE}/api/v3/hook/probe/${DEV}" > "${OUT_DIR}/acceptance_hook_probe_${TS}.json"
|
||
curl -s "${SDK_BASE}/api/v3/hook/data/${DEV}?modules=profile,contacts,groups,messages,labels,moments,device_info,wechat_version,hook_status,process_info,storage_info,network_info,login_state,favorites" \
|
||
> "${OUT_DIR}/acceptance_hook_data_${TS}.json"
|
||
|
||
curl -s --max-time 30 -X POST "${SDK_BASE}/api/v3/hook/execute" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"device_id\":\"${DEV}\",\"platform\":\"wechat\",\"action\":\"ping\",\"params\":{},\"hook_only\":true}" \
|
||
> "${OUT_DIR}/acceptance_ping_${TS}.json"
|
||
|
||
curl -s --max-time 45 -X POST "${SDK_BASE}/api/v3/message/send" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"device_id\":\"${DEV}\",\"platform\":\"wechat\",\"to_id\":\"filehelper\",\"content\":\"[全量验收 ${TS}]\",\"msg_type\":\"text\"}" \
|
||
> "${OUT_DIR}/acceptance_send_${TS}.json"
|
||
|
||
# ── 4. REST 矩阵 + Hook catalog ──
|
||
cd "$(cd "${SDK_ROOT}/.." && pwd)"
|
||
python3 sdk/tests/test_wechat_full_e2e.py --matrix 2>&1 | tee "${OUT_DIR}/acceptance_e2e_${TS}.log"
|
||
E2E_JSON="$(ls -t sdk/tmp/matrix_v8056_verify_${DEV//:/_}_*.json 2>/dev/null | head -1 || ls -t sdk/tmp/matrix_v8056_verify_*.json 2>/dev/null | head -1 || true)"
|
||
|
||
python3 sdk/scripts/matrix_hook_catalog_verify.py -d "${DEV}" 2>&1 | tee "${OUT_DIR}/acceptance_catalog_${TS}.log"
|
||
CAT_JSON="$(ls -t sdk/tmp/matrix_hook_catalog_${DEV//:/_}_*.json 2>/dev/null | head -1 || ls -t sdk/tmp/matrix_hook_catalog_*.json 2>/dev/null | head -1 || true)"
|
||
|
||
# ── 5. 汇总 JSON + MD ──
|
||
python3 <<PY
|
||
import json, os, glob
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
|
||
ts = "${TS}"
|
||
dev = "${DEV}"
|
||
out = Path("${REPORT_JSON}")
|
||
doc = Path("${REPORT_MD}")
|
||
tmp = Path("${OUT_DIR}")
|
||
|
||
def load(p):
|
||
try:
|
||
return json.loads(Path(p).read_text())
|
||
except Exception:
|
||
return {}
|
||
|
||
e2e_path = "${E2E_JSON}" or ""
|
||
cat_path = "${CAT_JSON}" or ""
|
||
e2e = load(e2e_path) if e2e_path else {}
|
||
cat = load(cat_path) if cat_path else {}
|
||
|
||
summary = {
|
||
"timestamp": datetime.now().isoformat(),
|
||
"device_id": dev,
|
||
"connection": load(tmp / f"acceptance_connection_{ts}.json"),
|
||
"hook_probe": load(tmp / f"acceptance_hook_probe_{ts}.json"),
|
||
"hook_data": load(tmp / f"acceptance_hook_data_{ts}.json"),
|
||
"ping": load(tmp / f"acceptance_ping_{ts}.json"),
|
||
"send_message": load(tmp / f"acceptance_send_{ts}.json"),
|
||
"e2e_matrix": {"path": e2e_path, "passed": e2e.get("passed"), "total": e2e.get("total"), "success_rate": e2e.get("success_rate")},
|
||
"hook_catalog": {"path": cat_path, "passed": cat.get("passed"), "total": cat.get("total"), "success_rate": cat.get("success_rate")},
|
||
}
|
||
out.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
|
||
|
||
e2e_p, e2e_t = summary["e2e_matrix"].get("passed"), summary["e2e_matrix"].get("total")
|
||
cat_p, cat_t = summary["hook_catalog"].get("passed"), summary["hook_catalog"].get("total")
|
||
probe = summary["hook_probe"].get("data") or summary["hook_probe"]
|
||
supports = probe.get("supports_hook") if isinstance(probe, dict) else None
|
||
|
||
lines = [
|
||
"# 微信全量验收报告",
|
||
"",
|
||
f"- 时间: {summary['timestamp']}",
|
||
f"- 设备: `{dev}`",
|
||
f"- Hook 可用: **{supports}**",
|
||
f"- REST 矩阵: **{e2e_p}/{e2e_t}** ({summary['e2e_matrix'].get('success_rate', 'N/A')})",
|
||
f"- Hook catalog: **{cat_p}/{cat_t}** ({summary['hook_catalog'].get('success_rate', 'N/A')})",
|
||
"",
|
||
"## 通道抽检",
|
||
"",
|
||
]
|
||
for label, key in [("ping", "ping"), ("send_message", "send_message")]:
|
||
d = summary.get(key, {})
|
||
ch = d.get("channel_used") or (d.get("data") or {}).get("_channel_used", "")
|
||
lines.append(f"- **{label}**: channel=`{ch}` code={d.get('code')}")
|
||
|
||
lines += ["", "## 微信实时数据摘要", ""]
|
||
hd = summary.get("hook_data", {}).get("data") or summary.get("hook_data", {})
|
||
if isinstance(hd, dict):
|
||
for mod in ["wechat_version", "login_state", "profile", "hook_status", "process_info", "network_info"]:
|
||
if mod in hd:
|
||
lines.append(f"### {mod}")
|
||
lines.append("```json")
|
||
lines.append(json.dumps(hd[mod], ensure_ascii=False, indent=2)[:2000])
|
||
lines.append("```")
|
||
lines.append("")
|
||
|
||
if cat_path and cat.get("results"):
|
||
failed = [r for r in cat["results"] if not r.get("success")]
|
||
lines += ["## Hook catalog 失败项", ""]
|
||
if not failed:
|
||
lines.append("无(全部通过)")
|
||
else:
|
||
for r in failed[:50]:
|
||
lines.append(f"- {r['action']}: {r.get('error','')[:120]}")
|
||
|
||
lines += ["", "## 原始文件", "", f"- JSON: `{out}`", f"- 日志: `${LOG}`"]
|
||
doc.write_text("\n".join(lines), encoding="utf-8")
|
||
print("REPORT_MD", doc)
|
||
print("E2E", e2e_p, e2e_t, "CATALOG", cat_p, cat_t)
|
||
PY
|
||
|
||
echo "=== 完成 ==="
|
||
echo "报告: ${REPORT_MD}"
|
||
echo "JSON: ${REPORT_JSON}"
|