Commit 76cb01ea6f90c2417b34a2106a9ef474f78abf72
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>
Showing
3 changed files
with
103 additions
and
12 deletions
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 "am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL --es to yib | @@ -38,11 +38,15 @@ adb shell su -c "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) |