Files
tiennm99 f400a83678 feat: lower the default body font size to 10pt
On the default 90x160mm phone page, 12pt fits about 36 characters per line;
10pt fits about 43 across 26 lines, which reads denser without becoming small
on a phone at 100% zoom. -font-size still overrides it.
2026-07-30 00:10:20 +07:00

158 lines
5.7 KiB
Markdown
Raw Permalink 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.
# monkeyd-crawler
Downloads every chapter of a [monkeydd.com](https://monkeydd.com) novel and exports it as a
single PDF sized for reading on a phone.
## Install
Requires Go 1.22+.
```sh
go build ./cmd/monkeyd-crawler
```
## Usage
Pass the novel's landing page URL:
```sh
./monkeyd-crawler -url https://monkeydd.com/tro-lai-nam-thang-cu.html
```
The PDF is named after the novel unless you pass `-out`.
```sh
# Bigger type, A5 page for a tablet
./monkeyd-crawler -url https://monkeydd.com/tro-lai-nam-thang-cu.html \
-page a5 -font-size 14 -out truyen.pdf
# Try the layout on 3 chapters before fetching the whole book
./monkeyd-crawler -url https://monkeydd.com/tro-lai-nam-thang-cu.html -limit 3
```
### Flags
| Flag | Default | Purpose |
| --- | --- | --- |
| `-url` | *required* | Novel landing page URL |
| `-out` | novel title | Output PDF path |
| `-page` | `phone` | Page size: `phone`, `a5`, `a4` |
| `-font-size` | `10` | Body font size in points |
| `-line-spacing` | `1.55` | Line height as a multiple of font size |
| `-margin` | `6` | Page margin in mm |
| `-font` | auto | Path to a `.ttf`; defaults to a system font, else the bundled one |
| `-workers` | `4` | Concurrent chapter fetches |
| `-delay` | `400ms` | Minimum delay between requests |
| `-retries` | `3` | Retries per request |
| `-limit` | `0` | Fetch only the first N chapters (0 = all) |
| `-cache` | `.cache` | Cache directory for raw pages (empty to disable) |
## Why the default page is 90×160 mm
Phone readability is governed by page *shape* more than by font size. A PDF viewer scales a
whole page to fit the screen, so a large font on an A4 page still ends up small: the page is
about three times wider than a phone screen and gets shrunk to match. The default page is cut
to a 9:16 ratio so it fills the screen at 100% zoom, where the default 10 pt renders at a
comfortable size with roughly 43 characters per line and 26 lines per page. Raising it to
12 pt gives about 36 characters per line; use `-font-size` to suit your screen and eyes.
Use `-page a5` or `-page a4` for a tablet or for printing.
## How it works
1. Fetch the landing page and read the chapter list from `div.list-chapters`.
2. Cross-check that list against the `#selected_chapter` dropdown embedded in the first
chapter page. When the dropdown is a superset it wins; a disagreement is reported.
3. Fetch each chapter concurrently and extract its text.
4. Render one PDF, each chapter starting on a new page.
### Two site behaviours the extractor has to handle
**Chapter text is partly served through CSS.** The markup contains empty elements, and the
stylesheet supplies the missing word:
```html
Nghe <span class="t-3e625e…"></span> trưởng tử
```
```css
.t-3e625e:before { content: "vị"; }
```
Reading DOM text alone silently drops these — about 19% of the words in a sampled chapter. The
extractor parses the `:before` rules and substitutes each word back in.
**Chapter URLs cannot be generated.** Numbering has gaps (the sample novel has no chapter 4)
and slugs are not uniform across novels (`/14.html` on one, `/chuong-12.html` on another), so
chapter links are always parsed from the page rather than constructed from a count.
## Politeness and caching
Requests are spaced by `-delay` globally, so raising `-workers` does not raise the request
rate. Raw pages are cached under `.cache/`, so re-exporting with different font or page
settings costs no requests. Delete the directory to refetch.
## Tests
```sh
go test ./...
```
Tests run against synthetic fixtures that reproduce the CSS-injected words, the newest-first
chapter ordering, and the numbering gap. No network access required.
## Layout
```
cmd/monkeyd-crawler/ CLI
export/ URL -> PDF in one call; shared by the CLI and importers
monkeyd/ fetching, HTML/CSS parsing, crawl orchestration
pdfout/ PDF rendering, font resolution, bundled fallback font
```
## Use as a library
The packages are importable, so another Go program can produce the same PDF
without shelling out to the binary. `export.Export` is the whole pipeline —
chapter list, fetch, font resolution, render:
```go
result, err := export.Export(ctx, export.Request{
NovelURL: "https://monkeydd.com/tro-lai-nam-thang-cu.html",
OutDir: tmpDir,
})
```
Only `NovelURL` is required; each zero-valued field falls back to the same
default as the matching CLI flag. Because zero means "unset", ask for *no*
cache or *no* request delay with the `NoCache` and `NoDelay` fields rather than
by zeroing `CacheDir` or `Delay`. Pass a `Log` function to receive the progress
messages the CLI prints to stderr.
## Fonts
The PDF embeds a TrueType font, and Vietnamese needs one that covers the Latin
Extended Additional block — a basic-Latin font silently drops the diacritics.
The font is resolved in this order:
1. the path given to `-font` / `Request.FontFile`, which is an error if it
cannot be read — a named font is not silently substituted;
2. a system font known to cover Vietnamese (see `pdfout.FindFont`);
3. the bundled DejaVu Sans, compiled into the binary.
Step 3 means rendering never depends on the host having fonts installed, which
is what a minimal container usually looks like. See
[`pdfout/fonts/NOTICE.md`](pdfout/fonts/NOTICE.md) for the bundled font's
provenance and licensing.
Font data is handed to the PDF writer as bytes, not as a path. `fpdf`'s
path-taking `AddUTF8Font` joins the name onto its own font directory, which it
defaults to `"."`; an absolute path is thereby rewritten into a
working-directory-relative one and fails wherever the process does not run from
the filesystem root.
## Scope
Downloaded text stays on your machine; only fetch content you are allowed to read offline, and
respect the site's terms.