Files
pokemon-radar-api/README.md
Timmy f7ef105436 init: Redis-backed Pokémon 雷達 API
- radar_fetcher.py:呼叫 Function Server 抓 twpkinfo.com 資料後寫入 Redis (TTL 300s)
- pokemon_radar_api.py:FastAPI /scan,從 Redis 讀取並依 request 即時二次篩選;Redis 不可用時回 200 空結果,不再 500
- pokemon_location_fetcher.py:舊版 CLI,仍由 fetch_and_upload.sh 使用
- 文件:README (參考手冊)、QUICKSTART (操作指南)、SUMMARY (故事線)、CLAUDE.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:13:59 +08:00

123 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pokemon-radar-api
把 twpkinfo.com 的寶可夢雷達資料包成 Redis-backed 的 HTTP API給前端 / n8n 呼叫。
- 上手操作:`QUICKSTART.md`
- 設計脈絡與演進:`SUMMARY.md`
- Claude Code 指引:`CLAUDE.md`
本檔為參考手冊組件一覽、指令、環境變數、HTTP API、Redis schema。
---
## 組件
| 檔案 | 角色 |
|---|---|
| `radar_fetcher.py` | **現行 fetcher**:呼叫 Function Server 抓資料 → 篩選 → 寫入 Redis`SETEX`,預設 TTL 300s |
| `pokemon_radar_api.py` | **FastAPI 服務**`:8008``/ping``/scan`;從 Redis 讀取,依 request 參數即時二次篩選 |
| `twpk_radar.mjs` | Function Server 上跑的 Puppeteer worker負責開瀏覽器拉 `twpkinfo.com/ipoke.aspx` 的資料 |
| `pokemon_location_fetcher.py` | **舊版 CLI**:直接呼叫 Function Server輸出本地 `pokemon_data.{json,csv}` + `screenshot.png`。API 仍重用它的 `PokemonRadarClient.postprocess` 做二次篩選 |
| `pokemon_radar_example.py` | 舊 CLI 的 Python 範例程式(非 CLI argparse 介面) |
| `reader.py` | 從 Redis 讀取並用 `rich` 表格顯示目前快取內容 |
| `check_redis.py` | 從 Redis 讀取並 dump 原始 JSON |
| `fetch_and_upload.sh` | 生產環境 cron`flock` 鎖定,呼叫**舊 CLI**產 `pokemon_data.json``scp``192.168.42.104:/opt/caddy/www/pokemon/spots.json` |
| `run_pokemon_radar_api.sh` | 啟動 FastAPI`uvicorn`port 8008的 shell 腳本 |
| `state.json` | twpkinfo.com 登入後的 cookies / localStorage / UA / langs 快照,注入到 MJS 繞人機驗證 |
| `logging.ini` | uvicorn 的 logging 設定 |
## 執行指令
全部以 `uv`Python 3.12,見 `.python-version``pyproject.toml`)。
| 目的 | 指令 |
|---|---|
| 啟動 API | `./run_pokemon_radar_api.sh`= `uv run uvicorn pokemon_radar_api:app --host 0.0.0.0 --port 8008 --log-config logging.ini` |
| 跑 fetcher寫入 Redis | `uv run python radar_fetcher.py` |
| 跑舊 CLI輸出本地檔案 | `uv run python pokemon_location_fetcher.py --script twpk_radar.mjs --lat 25.0478 --lng 121.5170 --zoom 16 --state state.json --ua-from-state --min-remaining 300 --min-iv 90` |
| 查 Redis 內容(表格) | `uv run python reader.py` |
| 查 Redis 內容raw JSON | `uv run python check_redis.py` |
| 生產排程(含 scp 上傳) | `./fetch_and_upload.sh` |
本專案沒有 test / lint / build 流程。
## 環境變數(`.env`
| 變數 | 預設 | 誰用 | 說明 |
|---|---|---|---|
| `FUNCTION_URL` | `http://192.168.42.124:13000/function` | `radar_fetcher` | Function Server endpoint |
| `FUNCTION_TOKEN` | (必填) | `radar_fetcher` | 未設會直接 `raise` |
| `SCRIPT_PATH` | `twpk_radar.mjs` | `radar_fetcher` | JS worker 檔名 |
| `STATE_PATH` | `state.json` | `radar_fetcher` | 登入狀態快照路徑 |
| `UA_FROM_STATE` | `true` | `radar_fetcher` | 是否從 state 帶 UA / langs |
| `RADAR_LAT` / `RADAR_LNG` | `25.0478` / `121.5170` | `radar_fetcher` | 掃描中心座標 |
| `RADAR_ZOOM` | `11` | `radar_fetcher` | 地圖縮放 |
| `RADAR_SHOT` | `false` | `radar_fetcher` | 是否擷取畫面base64 |
| `MIN_REMAINING` | `300` | `radar_fetcher` | 剩餘秒數門檻 |
| `MIN_IV` | 空 | `radar_fetcher` | IV% 下限 |
| `PERFECT_ONLY` | `false` | `radar_fetcher` | 只保留 100% IV |
| `SPECIES` | 空 | `radar_fetcher` | 逗號分隔的 dex id 或名稱關鍵字(例:`133,伊布` |
| `HTTP_TIMEOUT` | `160` | `radar_fetcher` | 呼叫 Function Server 的 HTTP timeout |
| `REDIS_HOST` | `localhost`fetcher/ `192.168.42.211`API、reader | 全部 | 注意兩邊預設不同,統一在 `.env` 指定 |
| `REDIS_PORT` | `6379` | 全部 | |
| `REDIS_DB` | `0` | 全部 | |
| `REDIS_PASSWORD` | 空 | 全部 | 空字串視為無密碼 |
| `REDIS_TTL` | `300` | `radar_fetcher` | 寫入 TTL |
| `REDIS_KEY` | `pokemon:radar:latest` | 全部 | |
## HTTP API
### `GET /ping`
```
200 → {"status":"ok","backend":"redis"}
```
### `POST /scan`
從 Redis 讀取目前快取,依 body 篩選後回傳。**永遠回 200**Redis 連不上或空資料都回空陣列,不會 500。
**Request body**
| 欄位 | 型別 | 說明 |
|---|---|---|
| `lat`, `lng`, `zoom`, `shot`, `state_path`, `ua_from_state`, `save_files` | — | 相容性欄位Redis 模式下**忽略** |
| `min_remaining` | `int ≥ 0` | 剩餘秒數下限 |
| `min_iv` | `int 0-100` | IV% 下限 |
| `perfect_only` | `bool` | 只保留 100% IV |
| `species` | `str` | 逗號分隔 dex id 或名稱關鍵字 |
**Response**
| 欄位 | 說明 |
|---|---|
| `source` | `"redis"` / `"redis (empty)"` / `"redis (unreachable)"` |
| `ttl` | Redis key 剩餘秒數 |
| `count_raw` | Redis 內筆數 |
| `count_filtered` | 經 request 參數篩選後筆數 |
| `items` | 物件陣列,見下 |
| `debug` | 含 `notes`MJS 輸出)、`redis_ttl``note`(錯誤訊息) |
**`items[]` 欄位**
`id / name / gender / remaining / expire_time / expire_at / latitude / longitude / iv_pct / iv_atk / iv_def / iv_sta / cp / level / is_perfect / fast_move / charge_move`
## Redis schema
| 項目 | 值 |
|---|---|
| Key | `pokemon:radar:latest` |
| 型別 | StringJSON |
| TTL | `REDIS_TTL`(預設 300s |
| Writer | `radar_fetcher.py` |
| Readers | `pokemon_radar_api.py``reader.py``check_redis.py` |
**Value 結構**
```json
{
"source": "fetcher",
"generated_at": "2026-04-20T09:06:36+08:00",
"count_raw": 120,
"count_filtered": 79,
"items": [ /* items[] */ ],
"debug": { "notes": ["..."], "api_source": "Fresh from Function Server" }
}
```