wechat_send_20260707.sh 3.33 KB
#!/bin/bash
# wechat_send.sh — unified WeChat send CLI via the internal hook
#
# Usage:
#   ./wechat_send.sh text  <wxid> <message>
#   ./wechat_send.sh image <wxid> <device_image_path>
#   ./wechat_send.sh voice <wxid> <local_silk_path> [duration_ms]
#
# All send operations go through the wechathook LSPosed module's internal API,
# which calls WeChat's own w11.r0/w11.s1 factory — no UI simulation.
# <wxid>:  talker id as stored in WeChat DB (e.g. 'filehelper', 'wxid_xxx').
# IMPORTANT: test-only with 'filehelper' to avoid account risk.
#
# Voice notes:
#   <local_silk_path>  — local SILK V3 file (Tencent prefix \x02#!SILK_V3).
#   [duration_ms]      — playback duration in ms (default: 3000).
#   The script pushes the SILK to WeChat's cache dir and fires the broadcast.
#
# Examples:
#   ./wechat_send.sh text  filehelper "hello world"
#   ./wechat_send.sh image filehelper /tmp/screenshot.png
#   ./wechat_send.sh voice filehelper /tmp/tts_out.silk 4617

set -e

ADB="${ADB:-adb}"
WECHAT_CACHE="/data/data/com.tencent.mm/cache/hook_voice_src.amr"

usage() {
    echo "Usage: $0 text  <wxid> <message>"
    echo "       $0 image <wxid> <device_path>"
    echo "       $0 voice <wxid> <local_silk_path> [duration_ms]"
    exit 1
}

[ $# -lt 3 ] && usage

CMD="$1"; WXID="$2"; shift 2

case "$CMD" in
text)
    PAYLOAD="$*"
    B64_WXID=$(printf '%s' "$WXID"    | base64 | tr -d '\n')
    B64_TEXT=$(printf '%s' "$PAYLOAD" | base64 | tr -d '\n')
    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"' \
        "$B64_WXID" "$B64_TEXT")
    B64_INNER=$(printf '%s' "$INNER" | base64 | tr -d '\n')
    echo "Sending text to $WXID..."
    $ADB shell "su -c 'echo $B64_INNER | base64 -d | sh'"
    ;;

image)
    # Internal API: WeChat's own b80.u.oj (insert+prebuild) + v70.w.oj (CDN upload).
    # No UI, no contact picker — fire-and-forget to <wxid>. Verified full-res delivery.
    IMAGE_PATH="$1"
    [ -z "$IMAGE_PATH" ] && usage
    EXT="${IMAGE_PATH##*.}"
    # Stage into /sdcard so the WeChat process (runs the hook) can read it, regardless
    # of whether the source path is root-only. If already under /sdcard this is a no-op copy.
    SHARE="/sdcard/wechat_send_tmp.$EXT"
    echo "Staging image to $SHARE..."
    $ADB shell "su -c 'cp \"$IMAGE_PATH\" \"$SHARE\" && chmod 644 \"$SHARE\"'"
    echo "Sending image to $WXID via internal API..."
    $ADB shell "su -c 'am broadcast -a com.hermes.wechat.SEND_IMAGE_INTERNAL --es to \"$WXID\" --es path \"$SHARE\"'"
    echo "Sent. (Verify via WeChat UI screenshot; full image uploads async via CDN.)"
    ;;

voice)
    SILK_PATH="$1"
    DURATION="${2:-3000}"
    [ -z "$SILK_PATH" ] && usage
    [ ! -f "$SILK_PATH" ] && { echo "File not found: $SILK_PATH"; exit 1; }

    echo "Pushing SILK to WeChat cache..."
    $ADB push "$SILK_PATH" /sdcard/hook_voice_tmp.amr
    $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'"

    echo "Sending voice to $WXID (duration=${DURATION}ms)..."
    $ADB shell "su -c 'am broadcast -a com.hermes.wechat.SEND_VOICE_INTERNAL --es to $WXID --es src_path $WECHAT_CACHE --ei duration $DURATION'"
    ;;

*)
    echo "Unknown command: $CMD"
    usage
    ;;
esac