Commit 76cb01ea6f90c2417b34a2106a9ef474f78abf72

Authored by yibo
1 parent a5606b3f

feat(wechathook): 图片发送接入 Hermes skill + wechat_send.sh 内部 API

图片 CDN 上传打通后,把发图能力接进使用层:

- skill_wechat_send/SKILL.md: 图片段 🟢→✅,给出 SEND_IMAGE_INTERNAL 广播 +
  wechat_send.sh 封装用法,标注真机验证(filehelper + 火星人全图收到)。
- wechat_send.sh image: 弃用旧的 share intent + 手动选联系人(UI 模拟),
  改走内部 API 广播 SEND_IMAGE_INTERNAL;先 stage 到 /sdcard 保证 WeChat
  进程可读,再 fire-and-forget 到 <wxid>。真机验证 oj()+CDN submit 均 DONE。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
xhs_lsposed_a16_fix/backups/wechat_send_20260707.sh 0 → 100755
  1 +#!/bin/bash
  2 +# wechat_send.sh — unified WeChat send CLI via the internal hook
  3 +#
  4 +# Usage:
  5 +# ./wechat_send.sh text <wxid> <message>
  6 +# ./wechat_send.sh image <wxid> <device_image_path>
  7 +# ./wechat_send.sh voice <wxid> <local_silk_path> [duration_ms]
  8 +#
  9 +# All send operations go through the wechathook LSPosed module's internal API,
  10 +# which calls WeChat's own w11.r0/w11.s1 factory — no UI simulation.
  11 +# <wxid>: talker id as stored in WeChat DB (e.g. 'filehelper', 'wxid_xxx').
  12 +# IMPORTANT: test-only with 'filehelper' to avoid account risk.
  13 +#
  14 +# Voice notes:
  15 +# <local_silk_path> — local SILK V3 file (Tencent prefix \x02#!SILK_V3).
  16 +# [duration_ms] — playback duration in ms (default: 3000).
  17 +# The script pushes the SILK to WeChat's cache dir and fires the broadcast.
  18 +#
  19 +# Examples:
  20 +# ./wechat_send.sh text filehelper "hello world"
  21 +# ./wechat_send.sh image filehelper /tmp/screenshot.png
  22 +# ./wechat_send.sh voice filehelper /tmp/tts_out.silk 4617
  23 +
  24 +set -e
  25 +
  26 +ADB="${ADB:-adb}"
  27 +WECHAT_CACHE="/data/data/com.tencent.mm/cache/hook_voice_src.amr"
  28 +
  29 +usage() {
  30 + echo "Usage: $0 text <wxid> <message>"
  31 + echo " $0 image <wxid> <device_path>"
  32 + echo " $0 voice <wxid> <local_silk_path> [duration_ms]"
  33 + exit 1
  34 +}
  35 +
  36 +[ $# -lt 3 ] && usage
  37 +
  38 +CMD="$1"; WXID="$2"; shift 2
  39 +
  40 +case "$CMD" in
  41 +text)
  42 + PAYLOAD="$*"
  43 + B64_WXID=$(printf '%s' "$WXID" | base64 | tr -d '\n')
  44 + B64_TEXT=$(printf '%s' "$PAYLOAD" | base64 | tr -d '\n')
  45 + INNER=$(printf 'TO=$(printf "%%s" "%s" | base64 -d); TEXT=$(printf "%%s" "%s" | base64 -d); am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL --es to "$TO" --es text "$TEXT"' \
  46 + "$B64_WXID" "$B64_TEXT")
  47 + B64_INNER=$(printf '%s' "$INNER" | base64 | tr -d '\n')
  48 + echo "Sending text to $WXID..."
  49 + $ADB shell "su -c 'echo $B64_INNER | base64 -d | sh'"
  50 + ;;
  51 +
  52 +image)
  53 + # Internal API: WeChat's own b80.u.oj (insert+prebuild) + v70.w.oj (CDN upload).
  54 + # No UI, no contact picker — fire-and-forget to <wxid>. Verified full-res delivery.
  55 + IMAGE_PATH="$1"
  56 + [ -z "$IMAGE_PATH" ] && usage
  57 + EXT="${IMAGE_PATH##*.}"
  58 + # Stage into /sdcard so the WeChat process (runs the hook) can read it, regardless
  59 + # of whether the source path is root-only. If already under /sdcard this is a no-op copy.
  60 + SHARE="/sdcard/wechat_send_tmp.$EXT"
  61 + echo "Staging image to $SHARE..."
  62 + $ADB shell "su -c 'cp \"$IMAGE_PATH\" \"$SHARE\" && chmod 644 \"$SHARE\"'"
  63 + echo "Sending image to $WXID via internal API..."
  64 + $ADB shell "su -c 'am broadcast -a com.hermes.wechat.SEND_IMAGE_INTERNAL --es to \"$WXID\" --es path \"$SHARE\"'"
  65 + echo "Sent. (Verify via WeChat UI screenshot; full image uploads async via CDN.)"
  66 + ;;
  67 +
  68 +voice)
  69 + SILK_PATH="$1"
  70 + DURATION="${2:-3000}"
  71 + [ -z "$SILK_PATH" ] && usage
  72 + [ ! -f "$SILK_PATH" ] && { echo "File not found: $SILK_PATH"; exit 1; }
  73 +
  74 + echo "Pushing SILK to WeChat cache..."
  75 + $ADB push "$SILK_PATH" /sdcard/hook_voice_tmp.amr
  76 + $ADB shell "su -c 'cp /sdcard/hook_voice_tmp.amr $WECHAT_CACHE && chmod 644 $WECHAT_CACHE && chown u0_a330:u0_a330 $WECHAT_CACHE && rm /sdcard/hook_voice_tmp.amr'"
  77 +
  78 + echo "Sending voice to $WXID (duration=${DURATION}ms)..."
  79 + $ADB shell "su -c 'am broadcast -a com.hermes.wechat.SEND_VOICE_INTERNAL --es to $WXID --es src_path $WECHAT_CACHE --ei duration $DURATION'"
  80 + ;;
  81 +
  82 +*)
  83 + echo "Unknown command: $CMD"
  84 + usage
  85 + ;;
  86 +esac
xhs_lsposed_a16_fix/skill_wechat_send/SKILL.md
@@ -38,11 +38,15 @@ adb shell su -c &quot;am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL --es to yib @@ -38,11 +38,15 @@ adb shell su -c &quot;am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL --es to yib
38 ``` 38 ```
39 或用封装脚本:`./wechat_send.sh text yibooo "你好火星人"` 39 或用封装脚本:`./wechat_send.sh text yibooo "你好火星人"`
40 40
41 -### 🟢 图片(消息+缩略图已可发,全图 CDN 上传是最后一公里) 41 +### ✅ 图片(全图 CDN 上传打通,已验证全分辨率送达)
42 ```bash 42 ```bash
43 -adb shell su -c 'am broadcast -a com.hermes.wechat.SEND_IMAGE_INTERNAL --es to filehelper --es path /sdcard/Pictures/x.png' 43 +adb shell su -c 'am broadcast -a com.hermes.wechat.SEND_IMAGE_INTERNAL --es to yibooo --es path /sdcard/Pictures/x.png'
44 ``` 44 ```
45 -现状:图片消息建成、缩略图渲染、进真实发送管线;全图 CDN 上传仍在完善(详见技术文档 §3)。 45 +或用封装脚本:`./wechat_send.sh image yibooo /sdcard/Pictures/x.png`
  46 +
  47 +图片走微信自己的 CDN 上传,接收方点开是全分辨率原图(可保存)。`path` 必须是设备上
  48 +`su` 能读到的路径(如 `/sdcard/...`)。发前若图片在 Mac 上,先 `adb push` 到设备。
  49 +2026-07-07 真机验证:filehelper + 真人(火星人)均全图收到。详见技术文档 §3。
46 50
47 ### ✅ 语音(TTS→SILK,半自动 hot-swap) 51 ### ✅ 语音(TTS→SILK,半自动 hot-swap)
48 纯合成卡「发送中」,用 hot-swap:让微信真录一段 + 偷换 SILK 字节。 52 纯合成卡「发送中」,用 hot-swap:让微信真录一段 + 偷换 SILK 字节。
xhs_lsposed_a16_fix/wechat_send.sh
@@ -50,18 +50,19 @@ text) @@ -50,18 +50,19 @@ text)
50 ;; 50 ;;
51 51
52 image) 52 image)
  53 + # Internal API: WeChat's own b80.u.oj (insert+prebuild) + v70.w.oj (CDN upload).
  54 + # No UI, no contact picker — fire-and-forget to <wxid>. Verified full-res delivery.
53 IMAGE_PATH="$1" 55 IMAGE_PATH="$1"
  56 + [ -z "$IMAGE_PATH" ] && usage
54 EXT="${IMAGE_PATH##*.}" 57 EXT="${IMAGE_PATH##*.}"
55 - SHARE="/sdcard/wechat_share_tmp.$EXT"  
56 - MIME="image/jpeg"  
57 - [ "$EXT" = "png" ] && MIME="image/png"  
58 - echo "Copying image to $SHARE..." 58 + # Stage into /sdcard so the WeChat process (runs the hook) can read it, regardless
  59 + # of whether the source path is root-only. If already under /sdcard this is a no-op copy.
  60 + SHARE="/sdcard/wechat_send_tmp.$EXT"
  61 + echo "Staging image to $SHARE..."
59 $ADB shell "su -c 'cp \"$IMAGE_PATH\" \"$SHARE\" && chmod 644 \"$SHARE\"'" 62 $ADB shell "su -c 'cp \"$IMAGE_PATH\" \"$SHARE\" && chmod 644 \"$SHARE\"'"
60 - echo "Sending image to $WXID via share intent..."  
61 - $ADB shell "am start -a android.intent.action.SEND -t $MIME \  
62 - --eu android.intent.extra.STREAM 'file://$SHARE' \  
63 - -p com.tencent.mm"  
64 - echo "WeChat share intent fired. Select '$WXID' in WeChat's contact picker and tap 发送." 63 + echo "Sending image to $WXID via internal API..."
  64 + $ADB shell "su -c 'am broadcast -a com.hermes.wechat.SEND_IMAGE_INTERNAL --es to \"$WXID\" --es path \"$SHARE\"'"
  65 + echo "Sent. (Verify via WeChat UI screenshot; full image uploads async via CDN.)"
65 ;; 66 ;;
66 67
67 voice) 68 voice)