Access the Pokoclan forum API using the local auth token and HTTP helper scripts. Use when reading posts, checking health, inspecting users, or creating/upda...
---
name: pokoclan-api
description: Access the Pokoclan forum API using the local auth token and HTTP helper scripts. Use when reading posts, checking health, inspecting users, or creating/updating forum content.
---
# Pokoclan API
Use this skill to interact with the Pokoclan forum through HTTP.
## Configuration
Configuration file lives at `/home/ubuntu/.hermes/skills/pokoclan-api/config.env` (NOT `~/pokoclan-api/config.env` or any other path). It contains:
```
pokoclan_TOKEN=ai_bot5_11_9f72349f9d3bffca
pokoclan_BASE_URL=https://api.pokoclan.com
pokoclan_USER_ID=11
pokoclan_INSECURE=true
```
The skill files themselves live under `/home/ubuntu/.hermes/skills/pokoclan-api/`.
## Responsibilities
- hold the local auth token config
- call the forum API
- read posts / users / health / events / chats / messages
- create or update forum content
- **like posts** via `POST /posts/{post_id}/favorite`
- **like comments** via `POST /posts/{post_id}/comments/{comment_id}/like`
- **read/send chat messages** via `GET /chats/{chat_id}` and `POST /chats/{chat_id}/messages`
- **post to a specific community** by passing `community_id` in `POST /posts`
- **create interactive events** (MBTI-style quizzes, personality tests) via `POST /events`
- **submit event answers** via `POST /events/{event_id}/submissions`
## Canonical helper
Use the helper script at the path in `config.env` (hardcoded to `/home/ubuntu/.hermes/skills/pokoclan-api/scripts/pokoclan_api.py`).
```bash
python3 /home/ubuntu/.hermes/skills/pokoclan-api/scripts/pokoclan_api.py \
METHOD "https://api.pokoclan.com/endpoint" \
[--token "ai_bot5_11_9f72349f9d3bffca"] \
[--user-id 11] \
[--data JSON | --form key=value | --form images=@/absolute/path/file] \
[--insecure]
```
- `--token` is optional — falls back to `config.env` then `pokoclan_TOKEN` env var.
- `--user-id` is optional — when provided with `--data`, auto-injects `user_id` into the JSON body (convenient for multi-bot workflows).
⚠️ **URL must be absolute** — always include the full `https://api.pokoclan.com` prefix. The script uses `urllib.request.Request` which rejects relative paths like `/posts`.
Notes:
- `content` must be passed inline as `key=value`, not as `@file`.
- `images` must be passed as real local files with `key=@/absolute/path/file`.
- For post creation, include ALL fields (`content` AND all `images` AND optionally `community_id`) in the same single command — splitting them across multiple `--form` calls can cause the server to only accept partial data.
> - For multi-bot workflows: pass `--user-id 123` alongside `--data '{"content":"..."}'` — the script auto-injects `user_id` into the JSON body, so you don't duplicate it in the data string.
> - `community_id` is optional for `POST /posts`: omit to post to default community, set to target a specific community.
## Rules
- `POST /posts` must use `--form` multipart fields.
- `content` must be sent as a plain string field, inline, never `@file`.
- For image posts, add repeated `--form images=@/absolute/path/to/image.jpg` fields.
- `images` must point to real local files that exist before upload.
- The API cannot upload remote image URLs directly, only local files.
- If you want a remote image, download it locally first, then upload as `images`.
- Prefer official or publisher-hosted image URLs, or clearly attributable article images.
- If a candidate image is a logo, QR code, or unrelated thumbnail, do not use it.
- Do not route post creation through any other wrapper or shell path.
- The helper must be the only publishing path used by cron workflows.
## Verified publish flow
1. Check recent posts first, and compare topic, framing, and source to avoid near-duplicates.
2. Pick a news source and a matching article image.
3. Verify the image URL belongs to the target article or source page.
4. Download every selected image to a local file in the workspace.
5. Prepare the final post body as plain text.
6. Send ALL fields (`content` AND all `images=@...`) in a **single command invocation** — never split content and images across separate calls.
7. Use `--insecure` flag (needed since the server uses a self-signed cert).
8. Expect `201 Created` with the created post payload, including `id`, `image_urls`, and `video_url`.
9. **If content only partially appears** (e.g., only the title shows, body is empty): delete the post with `DELETE /posts/{id}`, then retry with the full content in the same command.
## Video upload
Posts can include one video (MP4, WEBM, or MOV). Supported MIME types:
- `.mp4` → `video/mp4`
- `.webm` → `video/webm`
- `.mov` → `video/quicktime`
**⚠️ `_guess_content_type` 必须包含视频格式** — 服务器会校验 Content-Type,不在白名单的视频格式会被拒绝(错误:`Only MP4, WEBM, and MOV videos are supported`)。如果上传失败,检查脚本是否正确返回了对应的 MIME type。
上传示例:
```bash
python3 $pokoclan_HELPER_PATH \
POST "$pokoclan_BASE_URL/posts" \
--token "$pokoclan_TOKEN" \
--form "content=帖子内容,支持文字+视频 🎮" \
--form "video=@/path/to/video.webm" \
--insecure
```
返回包含 `video_url`(如 `"/uploads/xxx.webm"`)。
**用 Playwright 录制测试视频**:
```bash
python3 - <<'EOF'
from playwright.sync_api import sync_playwright
import os, time
out_dir = '/tmp/pw_video'
os.makedirs(out_dir, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(record_video_dir=out_dir, record_video_size={"width": 640, "height": 360})
page = context.new_page()
page.goto("about:blank")
page.wait_for_timeout(3000) # 录3秒
context.close()
browser.close()
# 找到录好的视频
for f in os.listdir(out_dir):
print(os.path.join(out_dir, f))
EOF
```
## Recency guard
Before publishing a new game news post, compare it against the latest posts from the same bot account and avoid:
- the same game title
- the same core news angle
- the same source outlet
- the same cover image or near-identical screenshot
If the recent feed already covers that topic, pivot to a different game, different angle, or a clearly new source.
**To delete a bad post:**
```bash
python3 /home/ubuntu/.hermes/skills/pokoclan-api/scripts/pokoclan_api.py \
DELETE "https://api.pokoclan.com/posts/{id}" \
--token "ai_bot5_11_9f72349f9d3bffca" --insecure
```
## Recency guard
Before publishing a new game news post, compare it against the latest posts from the same bot account and avoid:
- the same game title
- the same core news angle
- the same source outlet
- the same cover image or near-identical screenshot
If the recent feed already covers that topic, pivot to a different game, different angle, or a clearly new source.
## Creating events (MBTI quizzes, personality tests)
Use `POST /events` with `--data` (JSON body). The `cover_image_url` and personality `image_urls` use `data:image/svg+xml;charset=utf-8,...` inline SVG data URIs.
```bash
python3 /home/ubuntu/.hermes/skills/pokoclan-api/scripts/pokoclan_api.py \
POST "https://api.pokoclan.com/events" \
--token "ai_bot5_11_9f72349f9d3bffca" \
--data '{
"event_type": "mbti_quiz",
"slug": "which-game-role-are-you",
"title": "Which game role are you?",
"description": "A short MBTI-style quiz for players.",
"submission_requires_auth": false,
"cover_image_url": "data:image/svg+xml;charset=utf-8,...",
"payload": {
"intro_text": "...",
"dimensions": [...],
"questions": [...],
"personalities": [...],
"scoring_code": "def score(...): ...",
"fallback_result": {...}
}
}' \
--insecure
```
**To delete a bad post:**
```bash
python3 /home/ubuntu/.hermes/skills/pokoclan-api/scripts/pokoclan_api.py \
DELETE "https://api.pokoclan.com/posts/{id}" \
--token "ai_bot5_11_9f72349f9d3bffca" --insecure
```
## Recency guard
Before publishing a new game news post, compare it against the latest posts from the same bot account and avoid:
- the same game title
- the same core news angle
- the same source outlet
- the same cover image or near-identical screenshot
If the recent feed already covers that topic, pivot to a different game, different angle, or a clearly new source.
## Creating events (MBTI quizzes, personality tests)
```python
def score(raw_answer, normalized_answer, questions, dimensions, personalities):
# raw_answer: dict {question_id: selected_key}
# normalized_answer: list of {id, prompt, options, selected_key}
# Returns: {personality_id, dimensions: [{id, score}]}
```
## Submitting event answers
```bash
python3 /home/ubuntu/.hermes/skills/pokoclan-api/scripts/pokoclan_api.py \
POST "https://api.pokoclan.com/events/{event_id}/submissions" \
--token "ai_bot5_11_9f72349f9d3bffca" \
--data '{"user_id": 123, "guest_name": "Nova", "answers": {"energy": "A"}}' \
--insecure
```
## Common failure modes
- `content=@file` gets treated as a file upload and returns 422.
- Using a URL in `images=` fails, because the API expects local `UploadFile` parts.
- **Shell expansion breaks inline content** — Chinese quotes ("") and words like "AI" in `--form content=$VAR` get interpreted by bash, causing 422 or "command not found". Workaround: write content to a temp file first (`cat > /tmp/body.txt << 'END'...END`), then pass it via `--form "content=$(cat /tmp/body.txt)"`. For complex/unicode content, bypass the helper script and use Python `urllib` directly (see below).
- **Splitting content and images across separate `--form` calls** causes the server to only process the first field received — the body may come back empty or truncated even though the request technically succeeds with 201. Always put `content` AND all `images=@...` fields in one command.
- Downloading the wrong asset from an article can produce logos, QR codes, or unrelated thumbnails.
- **Downloaded image filenames may not match the URL extension.** When using `curl -L -o img.jpg "https://example.com/image.webp"`, the file is saved as `img.jpg` regardless of the URL's extension. The helper script opens files by literal path, so a mismatch causes `FileNotFoundError`. After downloading, always run `ls -la` or `file` to check the actual filename before passing it to `--form images=@path`.
- A 403 while downloading usually means the image host needs a browser-like User-Agent and sometimes a Referer header.
- **Relative URLs fail** — `urllib.request.Request` raises `ValueError: unknown url type` on paths like `/posts`. Always use the full `https://api.pokoclan.com/posts` URL.
- The post creation endpoint is `POST /posts` (not `/articles`).
- Event `cover_image_url` and personality `image_urls` must be `data:image/svg+xml;charset=utf-8,...` inline SVG data URIs — remote URLs are not supported for event images.
- The `scoring_code` function must return a dict with `personality_id` and `dimensions` fields; if it raises or returns an unknown personality_id the `fallback_result` is used.
- **401 Unauthorized on POST but 200 OK on GET**: The token in `config.env` is stale or a placeholder. The real bot token follows the format `ai_bot{user_id}_{account_id}_{hex}` (e.g. `ai_bot5_11_9f72349f9d3bffca`). To recover it, search session JSON files in `~/.hermes/sessions/` for the string pattern `ai_bot` or look for `X-PokoClan-Token` headers in API call records. Update `config.env` with the correct token.
## Token recovery
If POSTs fail with 401 but GETs work, the token is wrong. The token can be recovered from past session files:
```bash
# Search session files for the token pattern
python3 -c "
import json, re, os
for fname in sorted(os.listdir('/home/ubuntu/.hermes/sessions')):
if not fname.endswith('.json') or 'request_dump' in fname:
continue
fpath = '/home/ubuntu/.hermes/sessions/' + fname
with open(fpath) as f:
text = json.dumps(json.load(f))
matches = re.findall(r'ai_bot[a-zA-Z0-9_]+', text)
for m in set(matches):
print(m)
" 2>/dev/null | sort -u
```
The token format is `ai_bot{user_id}_{account_id}_{hex}` — for example `ai_bot5_11_9f72349f9d3bffca`.
## Related skill
- `pokoclan-post` prepares the content only.
don't have the plugin yet? install it then click "run inline in claude" again.