---
name: nba-wechat-image
description: Fetch one current NBA.com image, normalize it into a WeChat-friendly JPG, and send it through Hermes WeChat gateway with bounded retry and delivery diagnostics. Use when a user asks to抓取/下载/发送 NBA 图片、赛场图、比赛图、今日 NBA 图片，or requests an NBA image delivery test from WeChat or CLI.
---

# NBA → WeChat Image Sender

A Hermes skill for reliably fetching a current image from NBA.com and sending it through the WeChat gateway.

This skill is designed for lightweight, repeatable media-delivery tasks. It avoids expensive model-driven browsing loops by delegating deterministic work—page fetch, image selection, download, conversion, and compression—to a local script. Hermes only reads the script result and performs the final message delivery.

## Use Cases

Use this skill when the user asks for tasks such as:

- Fetch an NBA image and send it to WeChat
- Send today’s NBA game/arena/news image
- Test WeChat media delivery with an NBA image
- Run a bounded NBA image capture workflow from Hermes

Example prompts:

```text
发送一张 NBA 图片到微信
抓取一张 NBA 图片并发送到微信
从 NBA.com 获取一张赛场图
测试微信图片通道
执行一次 NBA 图片抓取与微信发送流程
```

## What This Skill Does

1. Fetches NBA.com pages in a fixed priority order.
2. Selects a usable image URL from Open Graph, Twitter card, or NBA CDN metadata.
3. Downloads the image to a local temporary file.
4. Converts and compresses it to a WeChat-friendly JPG.
5. Returns a compact JSON result containing the local image path and source URL.
6. Sends the local image through Hermes messaging using `MEDIA:/absolute/path`.
7. Limits retries to avoid long-running loops and unnecessary token usage.

## Requirements

- Hermes Agent with the following toolsets enabled:
  - `terminal`
  - `messaging`
  - `skills`
- A configured WeChat / Weixin gateway in Hermes.
- macOS is recommended for the bundled script because it uses `/usr/bin/sips` for image conversion. On non-macOS systems, replace this step with ImageMagick, Pillow, or another image converter.

## Installation

Place this skill under your Hermes skills directory, for example:

```text
~/.hermes/skills/media/nba-wechat-image/
```

Expected structure:

```text
nba-wechat-image/
├── SKILL.md
└── scripts/
    └── fetch_nba_image.py
```

After adding or updating the skill, restart the Hermes gateway so messaging sessions can load the latest skill metadata:

```bash
hermes gateway restart
```

On macOS launchd installations, if the restart command hangs, use:

```bash
launchctl kickstart -k gui/$(id -u)/ai.hermes.gateway
```

## Configuration

### WeChat target

For private use, you can send to the WeChat home channel:

```text
weixin
```

For explicit routing, replace the placeholder below with the actual Weixin DM target from:

```bash
hermes targets list
```

or by using Hermes messaging target discovery.

```text
weixin:<YOUR_WEIXIN_DM_ID>@im.wechat
```

Do not publish personal chat IDs, account IDs, tokens, or API keys in shared versions of this skill.

### Recommended safety settings

For this workflow, low-risk terminal commands should not require repeated manual approval. A practical setting is:

```bash
hermes config set approvals.mode smart
```

To prevent media tasks from running too long:

```bash
hermes config set agent.max_turns 20
hermes config set terminal.timeout 60
```

Restart the gateway after changing these settings.

## Workflow

### Step 1 — Run the image fetch script

```bash
python3 ~/.hermes/skills/media/nba-wechat-image/scripts/fetch_nba_image.py
```

The script returns JSON like:

```json
{
  "ok": true,
  "image_path": "/tmp/nba_wechat_latest.jpg",
  "image_url": "https://cdn.nba.com/...jpg",
  "source_page": "https://www.nba.com/news",
  "bytes": 277368
}
```

### Step 2 — Send the image through WeChat

If `ok=true`, send the image as a media attachment:

```text
MEDIA:/tmp/nba_wechat_latest.jpg
```

Use `send_message` with the configured WeChat target.

Recommended message body:

```text
MEDIA:/tmp/nba_wechat_latest.jpg
```

For better delivery reliability, keep the media message short. Avoid Markdown image syntax for WeChat delivery.

### Step 3 — Apply bounded retry

If the first send fails due to rate limiting:

1. Wait about 10 seconds.
2. Retry once.
3. If it still fails, stop and report the failure reason.

Do not repeatedly resend the same image. This workflow is intentionally bounded to avoid excessive token and API usage.

## Delivery Troubleshooting

### Send API returns success but the image does not appear

Treat this as a delivery-layer issue rather than an image-fetching issue.

Recommended checks:

```bash
hermes gateway status
```

```bash
tail -120 ~/.hermes/logs/gateway.log ~/.hermes/logs/gateway.error.log
```

Look for:

- `rate limited`
- `session timeout`
- `poll error`
- `Server disconnected`
- Weixin / iLink send errors

If needed, compress the image smaller once and retry once. If the image still does not appear, stop resending and investigate the WeChat gateway or iLink delivery channel.

### WeChat replies become slow or stop

Common causes:

- The gateway is waiting for command approval.
- A previous task is still running.
- Weixin / iLink session has timed out.
- Proxy/VPN interrupted long polling.
- The gateway needs to be restarted after a skill/config update.

Recommended checks:

```bash
hermes gateway status
```

```bash
tail -120 ~/.hermes/logs/gateway.log ~/.hermes/logs/gateway.error.log
```

If the logs show repeated Weixin session errors, restart the gateway.

## Operational Rules

- Use the bundled script for fetching and converting images.
- Do not ask the model to manually browse NBA.com unless the script fails.
- Use local absolute image paths.
- Convert or compress images to JPG before sending to WeChat.
- Prefer `MEDIA:/path/to/image.jpg` for media delivery.
- Retry at most once after rate limiting.
- If delivery remains unreliable, switch to gateway diagnostics instead of repeating the fetch.

## Verification Checklist

After each run, report:

- Source page
- Local image path
- Image size
- WeChat send status
- Whether a retry was used

Example concise report:

```text
NBA image fetched and sent.
Source: https://www.nba.com/news
Image: /tmp/nba_wechat_latest.jpg
Size: 277 KB
WeChat send: success
Retry: no
```

## Bundled Script

Save the following script as:

```text
scripts/fetch_nba_image.py
```

```python
#!/usr/bin/env python3
"""Fetch one current NBA.com image and normalize it for WeChat.

Prints JSON:
  {ok, image_path, image_url, source_page, bytes}

Hermes should run this script, read the JSON, then send image_path via
MEDIA:/... to WeChat.
"""

from __future__ import annotations

import json
import re
import subprocess
import sys
import urllib.parse
import urllib.request
from pathlib import Path

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
        "AppleWebKit/537.36 Chrome/124 Safari/537.36"
    )
}

PAGES = [
    "https://www.nba.com/news",
    "https://www.nba.com/games",
    "https://www.nba.com/",
]

OUT = Path("/tmp/nba_wechat_latest.jpg")
RAW = Path("/tmp/nba_wechat_latest_raw")

PATTERNS = [
    r"<meta[^>]+property=[\"']og:image[\"'][^>]+content=[\"']([^\"']+)",
    r"<meta[^>]+content=[\"']([^\"']+)[\"'][^>]+property=[\"']og:image[\"']",
    r"<meta[^>]+name=[\"']twitter:image[\"'][^>]+content=[\"']([^\"']+)",
    r"https://cdn\.nba\.com/[^\"'<> ]+\.(?:jpg|jpeg|png|webp)(?:\?[^\"'<> ]*)?",
]

SKIP_WORDS = ["logo", "icon", "favicon", "apple-touch", "placeholder"]


def fetch_text(url: str) -> str:
    req = urllib.request.Request(url, headers=HEADERS)
    with urllib.request.urlopen(req, timeout=20) as r:
        return r.read().decode("utf-8", "ignore")


def pick_image() -> tuple[str | None, str | None]:
    for page in PAGES:
        try:
            html = fetch_text(page)
        except Exception:
            continue

        imgs: list[str] = []
        for pattern in PATTERNS:
            imgs.extend(re.findall(pattern, html, re.I))

        seen: set[str] = set()
        for img in imgs:
            img = (img if img.startswith("http") else urllib.parse.urljoin(page, img)).replace("&amp;", "&")
            if img in seen:
                continue
            seen.add(img)
            low = img.lower()
            if any(word in low for word in SKIP_WORDS):
                continue
            return img, page
    return None, None


def download(url: str) -> bytes:
    req = urllib.request.Request(url, headers=HEADERS)
    with urllib.request.urlopen(req, timeout=30) as r:
        return r.read()


def convert_for_wechat(raw_path: Path, out_path: Path) -> None:
    cmd = [
        "/usr/bin/sips",
        "-Z",
        "1280",
        "-s",
        "format",
        "jpeg",
        "-s",
        "formatOptions",
        "70",
        str(raw_path),
        "--out",
        str(out_path),
    ]
    try:
        subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30)
    except Exception:
        out_path.write_bytes(raw_path.read_bytes())


def main() -> int:
    img, page = pick_image()
    if not img:
        print(json.dumps({"ok": False, "error": "no usable NBA image found"}, ensure_ascii=False))
        return 0

    try:
        RAW.write_bytes(download(img))
        convert_for_wechat(RAW, OUT)
        if not OUT.exists() or OUT.stat().st_size < 1000:
            OUT.write_bytes(RAW.read_bytes())
        print(
            json.dumps(
                {
                    "ok": True,
                    "image_path": str(OUT),
                    "image_url": img,
                    "source_page": page,
                    "bytes": OUT.stat().st_size,
                },
                ensure_ascii=False,
            )
        )
        return 0
    except Exception as exc:
        print(json.dumps({"ok": False, "error": str(exc), "image_url": img}, ensure_ascii=False))
        return 0


if __name__ == "__main__":
    sys.exit(main())
```
