← Back to skills
extension
Category: Development & EngineeringAPI key requirement unconfirmed

微信文件发送

绕开微信消息只能发送文字的限制,让电脑可以发送本地文件(图片、文档等)给手机微信。可用于手机远程访问取回电脑上的文件。

personAuthor: xiaoheidothubModelScope

Push a local file (image / video / document) to a WeChat user as a real attachment by calling the QwenPaw wechat channel's own ILinkClient.

Why this skill exists: qwenpaw channels send and the cron agent delivery path are text-only — media blocks from send_file_to_user get dropped in the channel renderer (show_tool_results=False on wechat). The wechat channel's native ILinkClient however implements the full attachment pipeline (send_image / send_video / send_file → AES-128-ECB encrypt → upload to WeChat CDN novac2c.cdn.weixin.qq.com → sendmessage). This skill extracts that client from the installed QwenPaw Desktop app, caches the extraction, and sends directly. Verified end-to-end on QwenPaw 2.2.0 (image + .doc + .txt).

Prerequisites

  • QwenPaw Desktop installed at /Applications/QwenPaw Desktop.app (override via env QWENPAW_APP).
  • WeChat channel enabled and QR-login completed (bot_token present in ~/.qwenpaw/workspaces/<agent>/agent.json).
  • The recipient has messaged the bot on WeChat at least once recently; the context token lives in ~/.qwenpaw/wechat_context_tokens.json.

Execution

This skill ships with a batch file scripts/send.json.

Call run_tool_batch strictly in the format below, using file_path to load the file. Do not construct your own actions list inline. Use the absolute directory path of this skill (visible in your system prompt) to build file_path.

run_tool_batch(
  file_path="<this skill dir>/scripts/send.json",
  args={
    "skill_dir": "<this skill dir>",
    "op": "send",
    "file_path": "/absolute/path/to/file.png",
    "mode": "auto",
    "user": "",
    "name": ""
  }
)

Pass all parameters below; "" means "use the default behaviour". The first run extracts and caches channel code (~10s); later runs take ~2s.

Batch Parameters

  • skill_dir: absolute path of this skill's directory (the folder holding this SKILL.md). Required.
  • op: "send" to deliver, "check" for a full self-diagnosis without sending (imports, cache, tokens all verified). Use check when debugging.
  • file_path: absolute path of the local file to deliver. Required. Example: /Users/you/.qwenpaw/workspaces/default/media/abc_photo.png.
  • mode: auto (default, routes by extension: png/jpg/jpeg/gif/bmp/webp/ heic → image message; mp4/mov/avi/mkv/webm → video; everything else → file message). Force image|video|file only when the user explicitly asks (e.g. "把图片当文件发" → file for lossless original).
  • user: WeChat user id (...@im.wechat). Leave "" to auto-use the single logged-in user. Required only when multiple users have context tokens.
  • name: display filename for file messages. "" uses the real name with the 32-hex download-hash prefix auto-stripped (media uploads are prefixed like c2121b06..._photo.png; without stripping, WeChat shows the ugly prefix).

Batch failure handling

If run_tool_batch fails (ok: false or mid-way error):

  1. First verify all Batch Parameters were passed with real values (never args={}).
  2. Run with op: "check" to localize: bot_token empty → user must redo QR login in QwenPaw; no context_token for <user> → ask the user to send any message to the bot on WeChat, then retry; not found in app archive / layout errors → add --refresh semantics by deleting the stale cache dir under ~/.cache/qwenpaw-wechat-send/ (ask before deleting).
  3. If still failing, fall back to manual step-by-step (below) and tell the user the batch needs fixing.

Step-by-step reference

The batch wraps one script; these notes explain its internals for debugging:

  1. Re-exec bundled Python. PYZ bytecode is Python 3.11; marshal.loads requires the same minor version. The script os.execvs to <app>/Contents/Resources/binaries/python-runtime/python/bin/python3.11 (guard env WECHAT_SEND_REEXEC=1 prevents loops). Dead end to skip: the system python3 (3.9) cannot read the pycs.
  2. Parse PyInstaller archive in-process (load_pyz): cookie magic MEI\014\013\012\013\016 via rfind; v21 cookie is 88 bytes (!8sIIii64s, detected by b'python' at cookie+24), v20 is 24 bytes (!8siiii); TOC entries !IIIBc + name; the PYZ.pyz entry holds PYZ\0 + pyc magic + marshal TOC {name: (ispkg, pos, len)}. extract_tree writes sourceless .pyc = pyc_magic + 12 zero bytes + zlib-decompressed code. Dead end to skip: full-archive extraction (pyinstxtractor) dumps ~500MB — unnecessary; this extracts only needed trees into ~/.cache/qwenpaw-wechat-send/<exe-size>-<mtime>/.
  3. Stub package, never real imports. client.pyc uses relative imports (from .utils, from ...exceptions), so build pkg/qwenpaw/app/channels/wechat/{client,utils}.pyc with empty __init__.py files and a ChannelError(Exception) stub at both qwenpaw.exceptions and qwenpaw.app.exceptions. Importing the real qwenpaw package pulls pydantic and fails — skip that dead end.
  4. Dependency closure PRELOAD: httpx, httpcore, h11, anyio, sniffio, idna, h2, hyperframe, hpack, exceptiongroup, typing_extensions, Crypto. Two traps encoded in the script: (a) httpcore swallows a missing async backend into a plain ImportError ("Running with asyncio requires httpcore[asyncio]") — so validation must actually construct httpx.AsyncClient(), not just import httpx; (b) certifi must come from _internal (has cacert.pem) — extracting the PYZ copy (no data file) shadows it and SSL fails. pycryptodome native .sos are mirrored from _internal/Crypto into the cached Crypto tree. A lazy fill loop catches any other ModuleNotFoundError and extracts that top-level package on demand.
  5. Tokens (load_tokens): agent.json → channels.wechat.{bot_token, base_url} (default base https://ilinkai.weixin.qq.com); context tokens keyed by user id. Values are never printed.
  6. Send: ILinkClient(token, base) → await start() → send_image(user, path, ctok) / send_video / send_file(user, path, display_name, ctok) → await stop(). Success = response dict carries message_id. Report the message_id and kind to the user and ask them to confirm in WeChat.

Notes & edge cases

  • This sends a real WeChat message — invoke only when the user asked for this delivery or confirmed it.
  • Context tokens rotate when the user messages the bot; if sends start failing with token errors, have the user ping the bot once and retry.
  • After a QwenPaw Desktop update the cache key (exe size+mtime) changes and the cache rebuilds automatically; --refresh forces a rebuild.
  • Image messages are compressed by WeChat; for lossless originals send with mode: "file" instead.
  • The running backend keeps its own getupdates poll — this script only calls getuploadurl/upload/sendmessage, so it does not steal inbound messages.