From 97976df8370f46c8f29a53d22b85d71b3bc5109a Mon Sep 17 00:00:00 2001 From: yibo Date: Tue, 7 Jul 2026 17:46:39 +0800 Subject: [PATCH] fix: 图片发送 CDN 上传失败(红叹号) — stage 到 MediaStore 索引目录 + media scan --- phoneuse/wechat_hook_send.py | 17 ++++++++++++----- xhs_lsposed_a16_fix/wechat_send.sh | 12 ++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/phoneuse/wechat_hook_send.py b/phoneuse/wechat_hook_send.py index 61250bf..b78d927 100644 --- a/phoneuse/wechat_hook_send.py +++ b/phoneuse/wechat_hook_send.py @@ -196,7 +196,9 @@ def send_image_via_hook(wxid: str, image_path: str) -> dict[str, Any]: This is the fast path — no share sheet, no contact search, no wrong-contact risk. Preferred over send_image_via_intent. Verified 2026-07-07 on WeChat - 8.0.74 (filehelper + real contact, full-res delivery). + 8.0.74 (filehelper + real contact, full-res delivery). NOTE: the image MUST be + staged into a MediaStore-indexed dir (/sdcard/Pictures/) + media-scanned — a fresh + file in /sdcard root uploads as a broken image (red !); this fn handles that. Args: wxid: talker id as stored in rcontact.username (e.g. 'filehelper', @@ -214,14 +216,19 @@ def send_image_via_hook(wxid: str, image_path: str) -> dict[str, Any]: return {"status": "error", "error": "WeChat not running and failed to start"} time.sleep(3) # allow App.onCreate + BroadcastReceiver registration - # Stage into /sdcard so the WeChat process (which runs the hook) can read it, - # regardless of whether the source path is root-only. No-op-ish if already there. + # Stage into /sdcard/Pictures/ (a MediaStore-indexed media dir) and trigger a media + # scan. WeChat's image CDN upload resolves the file via MediaStore — a fresh file in + # /sdcard root (not indexed) uploads as a broken image (red !, receiver gets nothing). + # Unique per-pid name so a fixed path isn't overwritten mid-upload by a concurrent send. ext = os.path.splitext(image_path)[1].lower() or ".jpg" - share_path = f"/sdcard/wechat_send_tmp{ext}" + share_path = f"/sdcard/Pictures/wx_send_{os.getpid()}{ext}" cp = _root_sh(f'cp "{image_path}" "{share_path}" && chmod 644 "{share_path}"') if cp.returncode != 0: return {"status": "error", - "error": f"failed to stage image to /sdcard: {cp.stderr or cp.stdout}"} + "error": f"failed to stage image to /sdcard/Pictures: {cp.stderr or cp.stdout}"} + # Register with MediaStore so WeChat's image pipeline can resolve it (required). + _root_sh(f'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d "file://{share_path}"') + time.sleep(1.0) # let the scan land before WeChat reads it b64_wxid = base64.b64encode(wxid.encode()).decode() cmd = ( diff --git a/xhs_lsposed_a16_fix/wechat_send.sh b/xhs_lsposed_a16_fix/wechat_send.sh index 01eac79..7138868 100755 --- a/xhs_lsposed_a16_fix/wechat_send.sh +++ b/xhs_lsposed_a16_fix/wechat_send.sh @@ -58,11 +58,15 @@ image) 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..." + # Stage into /sdcard/Pictures/ (MediaStore-indexed) + media scan. WeChat's image CDN + # upload resolves the file via MediaStore — a fresh file in /sdcard root (unindexed) + # uploads as a broken image (red !, receiver gets nothing). Unique name avoids a fixed + # path being overwritten mid-upload by a concurrent send. + SHARE="/sdcard/Pictures/wx_send_$$.$EXT" + echo "Staging image to $SHARE + media scan..." $ADB shell "su -c 'cp \"$IMAGE_PATH\" \"$SHARE\" && chmod 644 \"$SHARE\"'" + $ADB shell "su -c 'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d \"file://$SHARE\"'" >/dev/null 2>&1 + sleep 1 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.)" -- libgit2 0.21.4