Commit 97976df8370f46c8f29a53d22b85d71b3bc5109a

Authored by yibo
1 parent 6cd9f893

fix: 图片发送 CDN 上传失败(红叹号) — stage 到 MediaStore 索引目录 + media scan

根因: 图片 stage 到 /sdcard 根目录的临时文件(未被 MediaStore 索引)时,微信图片
CDN 上传管线解析不到该文件 → 上传成破损图(红叹号,接收方收不到)。而已索引的
/sdcard/Pictures/ 里的图能正常全图上传。之前 Mac 直发用 /sdcard/Pictures/ 现成图
碰巧成功,掩盖了 staging 的 bug。

修复(send_image_via_hook + wechat_send.sh image):
- stage 到 /sdcard/Pictures/wx_send_<pid>.<ext>(媒体目录 + 唯一名避免并发覆盖)
- 广播 MEDIA_SCANNER_SCAN_FILE 触发 MediaStore 索引 + sleep 1 让扫描落地
- 再发 SEND_IMAGE_INTERNAL

真机验证: 修复前 staged 路径确定性红叹号(Hermes 17:37 + MCP repro 17:43 均失败);
修复后经 MCP helper(Hermes 同路径)发送全图正常渲染(17:46 AI AGI 日报全分辨率)。
"CDN submit DONE" 日志只代表提交,不代表上传成功——不能作为送达判据。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
phoneuse/wechat_hook_send.py
... ... @@ -196,7 +196,9 @@ def send_image_via_hook(wxid: str, image_path: str) -&gt; dict[str, Any]:
196 196  
197 197 This is the fast path — no share sheet, no contact search, no wrong-contact
198 198 risk. Preferred over send_image_via_intent. Verified 2026-07-07 on WeChat
199   - 8.0.74 (filehelper + real contact, full-res delivery).
  199 + 8.0.74 (filehelper + real contact, full-res delivery). NOTE: the image MUST be
  200 + staged into a MediaStore-indexed dir (/sdcard/Pictures/) + media-scanned — a fresh
  201 + file in /sdcard root uploads as a broken image (red !); this fn handles that.
200 202  
201 203 Args:
202 204 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) -&gt; dict[str, Any]:
214 216 return {"status": "error", "error": "WeChat not running and failed to start"}
215 217 time.sleep(3) # allow App.onCreate + BroadcastReceiver registration
216 218  
217   - # Stage into /sdcard so the WeChat process (which runs the hook) can read it,
218   - # regardless of whether the source path is root-only. No-op-ish if already there.
  219 + # Stage into /sdcard/Pictures/ (a MediaStore-indexed media dir) and trigger a media
  220 + # scan. WeChat's image CDN upload resolves the file via MediaStore — a fresh file in
  221 + # /sdcard root (not indexed) uploads as a broken image (red !, receiver gets nothing).
  222 + # Unique per-pid name so a fixed path isn't overwritten mid-upload by a concurrent send.
219 223 ext = os.path.splitext(image_path)[1].lower() or ".jpg"
220   - share_path = f"/sdcard/wechat_send_tmp{ext}"
  224 + share_path = f"/sdcard/Pictures/wx_send_{os.getpid()}{ext}"
221 225 cp = _root_sh(f'cp "{image_path}" "{share_path}" && chmod 644 "{share_path}"')
222 226 if cp.returncode != 0:
223 227 return {"status": "error",
224   - "error": f"failed to stage image to /sdcard: {cp.stderr or cp.stdout}"}
  228 + "error": f"failed to stage image to /sdcard/Pictures: {cp.stderr or cp.stdout}"}
  229 + # Register with MediaStore so WeChat's image pipeline can resolve it (required).
  230 + _root_sh(f'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d "file://{share_path}"')
  231 + time.sleep(1.0) # let the scan land before WeChat reads it
225 232  
226 233 b64_wxid = base64.b64encode(wxid.encode()).decode()
227 234 cmd = (
... ...
xhs_lsposed_a16_fix/wechat_send.sh
... ... @@ -58,11 +58,15 @@ image)
58 58 IMAGE_PATH="$1"
59 59 [ -z "$IMAGE_PATH" ] && usage
60 60 EXT="${IMAGE_PATH##*.}"
61   - # Stage into /sdcard so the WeChat process (runs the hook) can read it, regardless
62   - # of whether the source path is root-only. If already under /sdcard this is a no-op copy.
63   - SHARE="/sdcard/wechat_send_tmp.$EXT"
64   - echo "Staging image to $SHARE..."
  61 + # Stage into /sdcard/Pictures/ (MediaStore-indexed) + media scan. WeChat's image CDN
  62 + # upload resolves the file via MediaStore — a fresh file in /sdcard root (unindexed)
  63 + # uploads as a broken image (red !, receiver gets nothing). Unique name avoids a fixed
  64 + # path being overwritten mid-upload by a concurrent send.
  65 + SHARE="/sdcard/Pictures/wx_send_$$.$EXT"
  66 + echo "Staging image to $SHARE + media scan..."
65 67 $ADB shell "su -c 'cp \"$IMAGE_PATH\" \"$SHARE\" && chmod 644 \"$SHARE\"'"
  68 + $ADB shell "su -c 'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d \"file://$SHARE\"'" >/dev/null 2>&1
  69 + sleep 1
66 70 echo "Sending image to $WXID via internal API..."
67 71 $ADB shell "su -c 'am broadcast -a com.hermes.wechat.SEND_IMAGE_INTERNAL --es to \"$WXID\" --es path \"$SHARE\"'"
68 72 echo "Sent. (Verify via WeChat UI screenshot; full image uploads async via CDN.)"
... ...