Commit 97976df8370f46c8f29a53d22b85d71b3bc5109a
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>
Showing
2 changed files
with
20 additions
and
9 deletions
phoneuse/wechat_hook_send.py
| ... | ... | @@ -196,7 +196,9 @@ def send_image_via_hook(wxid: str, image_path: str) -> 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) -> 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.)" | ... | ... |