jmcomic · git:20260919.d66ad4d · 2026-09-19 · sha256 efc8119fa30b4af8
jmcomic git:20260919.d66ad4dB
Immutable. This exact content never changes and is served at /api/v1/blob/efc8119fa30b4af8.
---
name: jmcomic
description: Use jmcomic (JMComic-Crawler-Python) to query JMComic metadata (album/photo details, search, rankings, comments) and to download albums or chapters, optionally exporting them to PDF, ZIP, or a long image. All commands return a single JSON object from one stable CLI entry point.
whenToUse: Use when the user supplies a JMComic album/photo id ("车号", JM123, or an 18comic album URL) and wants its metadata, wants to search/browse/rank JMComic works, wants comments, or wants to download/export an album or chapter. Do not use when the user is only asking about the upstream library's source code.
metadata:
upstream: https://github.com/hect0x7/JMComic-Crawler-Python
cli: scripts/jmctl.py
---
# JMComic via `jmctl.py`
Drive **JMComic-Crawler-Python** (`jmcomic`) through one wrapper that turns the library
into a stable JSON contract. The same engine (`scripts/jmcore.py`) backs the graphical
builds for Windows / Linux / macOS / Android, so anything done here the user can also do
by clicking.
## The one rule
**Call `scripts/jmctl.py` for everything. Never import `jmcomic` directly.**
The library logs progress to **stdout**, which corrupts machine parsing; `jmctl.py`
reroutes that logging to stderr and writes exactly one JSON object to stdout.
```bash
python <skill-dir>/scripts/jmctl.py <command> [options]
```
Success is `{"status": "ok", ...}`, handled failure is
`{"status": "error", "error": "...", "hint": "..."}` with exit code `1`. Run long
downloads as a background job and read stdout only when it settles — stderr is the live
progress log.
## Step 0 — environment
```bash
python <skill-dir>/scripts/jmctl.py doctor
```
Reports the interpreter, the `jmcomic` version, optional export dependencies, and site
reachability. To install, use **the interpreter `doctor` prints** (several Pythons often
coexist, and installing into the wrong one looks like "installed but still not
importable"):
```bash
"<that path>" -m pip install jmcomic
```
Install only the export extras the task needs:
| Format | Package |
|---|---|
| PDF | `Pillow` |
| ZIP | `pyzipper` (or `py7zr` for 7z) |
| long image | `Pillow` |
A missing dependency makes the download succeed while the export is silently absent, so
always check `exportFiles` in the result.
If `doctor` reports `networkOk: false`, do not retry blindly: switch `--client-impl`
between `api` (default) and `html`, or set a proxy in `option.yml`. Never guess a
replacement domain — the library resolves one itself.
## Commands
### `info` — metadata, no download
```bash
jmctl.py info 438696
jmctl.py info JM438696 https://18comic.vip/album/438696 --pretty
jmctl.py info 438696 --kind photo --image-urls
```
Accepts bare numbers, `JM123`, or URLs. `--kind album` (default) returns `title`,
`authors`, `tags`, `works`, `actors`, `pageCount`, `pubDate`, `updateDate`, `likes`,
`views`, `commentCount`, and `episodes`; `--kind photo` describes one chapter, and
`--image-urls` adds every page URL. An empty `episodes` is normal for a single-chapter
album — the album is then its own chapter and shares its id.
### `search` — find albums
```bash
jmctl.py search "MANA"
jmctl.py search "無修正" --mode tag --page 1
jmctl.py search "原神" --mode work
jmctl.py search "神里绫华" --mode actor
```
`--mode`: `site` (default), `tag`, `work`, `actor`. Filters: `--page`, `--order-by`
(`mr` latest, `mv` views, `mp` pictures, `tf` likes, `tr` score, `md` comments), `--time`
(`a` all, `t` today, `w` week, `m` month), `--category` (`0` all, `doujin`, `single`,
`short`, `another`, `hanman`, `meiman`, `doujin_cosplay`, `3D`, `english_site`),
`--sub-category` (html impl only, e.g. `chinese`, `japanese`, `CG`, `cosplay`, `youth`,
`3d`). `+tag` requires a tag, `-tag` excludes it: `"+全彩 +人妻"`.
Returns `total`, `pageCount`, `page`, and `works[]` of `{id, title, tags}`; feed an `id`
straight into `info` or `download`. When `total` disagrees with `count × pageCount`,
trust `works[]`.
### `rank` — rankings and category browsing
```bash
jmctl.py rank --period day
jmctl.py rank --period week --category doujin
jmctl.py rank --period custom --time m --order-by tf --category hanman
```
`--period` is `day`, `week`, `month`, or `custom` (arbitrary query from `--time` +
`--order-by`). Same result shape as `search`.
### `comments`
```bash
jmctl.py comments --id 438696
jmctl.py comments --forum --page 1
```
Returns `comments[]` with `content`, `nickname`, `likes`, `isSpoiler`, `createdAt`, and
nested `replies[]`. `--forum` reads site-wide comments, each carrying its `albumId`.
### `download` — fetch and optionally export
```bash
jmctl.py download 438696
jmctl.py download 438696 --export pdf
jmctl.py download 438696 123456 --export pdf zip
jmctl.py download 438696 --kind photo
```
`--kind album` (default) downloads every chapter, `--kind photo` one chapter. `--export`
takes `pdf`, `zip`, `png` (long image) and may combine them. Multiple ids download
concurrently and are reported individually — one failure does not abort the rest.
Single id returns `id`, `title`, `savePath`, `durationSec`, `imageCount`, `imageFiles[]`,
plus `exportFiles[]` with `--export`. Multiple ids return
`{total, succeeded, allSucceeded, results[], failed{}}`.
Images are cached on disk, so re-running the same command is cheap and is the correct
response to a partial failure. Incremental "only new chapters" needs the upstream
`find_update` plugin and is out of scope.
### `config` — configuration baseline
```bash
jmctl.py config # print the effective default option.yml
jmctl.py config --write ./option.yml # write a starter file
```
### `doctor`
```bash
jmctl.py doctor
jmctl.py doctor --no-network # interpreter and deps only
```
## Configuration
Three layers, most specific first: **CLI flags** (`--client-impl`, everything under
`download`) → **`option.yml`** (`--option <path>` or `$JM_OPTION_PATH`) → **jmcomic
defaults**. Generate a baseline with `jmctl.py config --write ./option.yml`, or read
`assets/option.example.yml` (ships with this skill, fully commented). The essentials:
```yaml
client:
impl: api # api (default) | html; html is the only one with --sub-category
postman:
meta_data:
proxies: system # or null, or "127.0.0.1:7890"
cookies: {AVS: <value>} # only for login-gated albums, domain-scoped
download:
cache: true # reuse images already on disk
image: {decode: true, suffix: null} # decode restores JM's scrambled images
threading: {image: 30, photo: 16} # site allows <= 50 images
dir_rule:
base_dir: ./downloads
rule: Bd / Aid / Ptitle # Bd=base_dir, A*=album field, P*=chapter field
```
`dir_rule.rule` composes album (`A`) and chapter (`P`) fields, e.g.
`Bd / Aauthor / (JM{Aid}-{Pindex})-{Pname}`; available fields are visible in
`jmctl.py info <id> --pretty`.
Cookies are domain-scoped: a cookie from one JM domain does nothing on another. Pass
credentials only via `option.yml` or `$JM_OPTION_PATH`, never on the command line, and
never echo them back into the conversation.
Login-gated features (favourites, check-in, favourite export) are deliberately not
wrapped. When needed, use the library's `login` plugin through a custom `option.yml`
instead of extending this CLI.
## Reporting results
- Quote `title`, the JM id, and the absolute `savePath` so the user can open the files.
- `likes` and `views` are site-formatted strings (e.g. `"77K"`), not integers.
- `updateDate: "0"` means unknown.
- For a cover, use `https://18comic.vip/album/<id>/`; do not invent a CDN URL.
- Respect rate limits: keep `threading.image` at or below 50, do not fan out many albums,
and never loop `download` over a large id list — pass the ids to one invocation.
- If the user would rather click, point them at `python webui/server.py` (serves
`http://127.0.0.1:<port>/?token=...` and opens the browser) or the packaged desktop /
Android builds; they are front ends over this same engine.
## Troubleshooting
| Symptom | Cause and fix |
|---|---|
| `jmcomic is not importable` | Install into the interpreter `doctor` prints. |
| `could not parse a JM id` | Non-numeric id; extract the digits from the user's text first. |
| `MissingAlbumPhotoException` | Wrong id, or a login-gated album. Confirm the id before adding cookies. |
| `networkOk: false` | Try the other `--client-impl`, or set a proxy in `option.yml`. |
| Download ok but `exportFiles` empty | The format's dependency is missing, or the id produced no images. |
| `partial download failure` | Re-run the identical command; cached images are reused. Check `failedPhotos`/`failedImages`. |
| JSON parse error from stdout | Something wrote to stdout; confirm `jmctl.py` was invoked, not `jmcomic`/`jmv`. |
## Security and privacy
This skill downloads adult content. Keep it to what the user asked for, do not
auto-download from search results without confirmation, and never write credentials or
cookies into anything reported back.