refactor: split downloader into modules

This commit is contained in:
吴璨
2026-07-22 17:46:35 +08:00
parent 1dfaed4dba
commit 536685e327
15 changed files with 935 additions and 881 deletions
+9 -7
View File
@@ -4,7 +4,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t
## Overview
A single-file Node.js CLI for downloading music from NetEase Cloud Music (网易云音乐), with lyrics, cover art, and ID3 tag embedding. All logic lives in `163_music_downloader.js` (~2600 lines). There is no `package.json` — the script runs on Node built-ins only (`https`, `http`, `fs`, `path`, `readline`, `child_process`). The UI, comments, and console output are entirely in Chinese.
A modular CommonJS Node.js CLI for downloading music from NetEase Cloud Music (网易云音乐), with lyrics, cover art, and ID3 tag embedding. `163_music_downloader.js` owns CLI argument handling and the interactive menu; reusable logic lives under `lib/`. There is no `package.json` — the script runs on Node built-ins only (`https`, `http`, `fs`, `path`, `readline`, `child_process`). The UI, comments, and console output are entirely in Chinese.
## Running
@@ -37,12 +37,14 @@ If a dependency is missing the relevant feature degrades gracefully (skips embed
## Architecture
Everything is one flat module — functions defined top-to-bottom, then a `main()` at the bottom drives either batch mode or an infinite interactive menu loop (`while (true)` + `switch` on single-char menu choices). Key layers:
`163_music_downloader.js` drives batch mode or the interactive menu loop, while `lib/` contains feature modules:
- **Network helpers** — `fetchJSON`, `downloadFile` (streams with a live progress bar, follows redirects), `downloadBuffer`, all Promise-wrapped around `https.get`.
- **`downloadSong``_downloadSong`** — the core pipeline: `downloadSong` wraps `_downloadSong` with retry/backoff; `_downloadSong` resolves the URL, skips/overwrites based on existing-file size comparison (1% tolerance), writes audio, then lyrics, then embeds cover+tags, then records history.
- **Lyrics** — `fetchLyric` returns `{ lrc, tlyric, ... }`; `mergeLrc` interleaves original + translation by matching `[mm:ss.xx]` timestamps into a `.合并翻译.lrc` file.
- **Cover/tags** — `embedCover` downloads the image to a temp `.cover.jpg`, tries node-id3 (MP3) then ffmpeg (`-map` audio+cover, `-c copy`), always cleans up the temp file in `finally`.
- **`lib/network.js`** — `fetchJSON`, `downloadFile` (streams with a live progress bar, follows redirects), and `downloadBuffer`.
- **`lib/downloader.js`** — the core retry/download pipeline: resolves the URL, handles existing files, writes audio and lyrics, embeds cover/tags, then records history.
- **`lib/lyrics.js`** — lyric fetching, translation merging, credit parsing, and standalone `.lrc` saving.
- **`lib/metadata.js`** — NetEase song/album metadata, album directory naming, and `cover.jpg` saving.
- **`lib/tagging.js`** — node-id3/ffmpeg detection and audio tag embedding.
- **`lib/config.js`, `history.js`, `pause.js`, `quality.js`, `catalog.js`, `utils.js`** — configuration, persistence, batch state, quality selection, search, and shared helpers.
- **Persistence** (all JSON in the script's `__dirname`, not the download dir):
- `download_history.json` — capped at 500 entries, newest-first (`addHistory`).
- `downloader_config.json` — key/value config, currently only the `level` quality setting (`getConfig`/`setConfig`).
@@ -53,5 +55,5 @@ Everything is one flat module — functions defined top-to-bottom, then a `main(
- **Quality levels** — `QUALITY_LEVELS` array is the source of truth; `DEFAULT_LEVEL = 'jymaster'`. The chksz API may return a lower `actualLevel` than requested.
- **Filenames** — always `sanitize(`${artist} - ${name}`)` (strips `/\:*?"<>|`). Lyric/rename logic depends on this exact `"artist - name"` shape when parsing filenames back out.
- **Menu dispatch** — adding a feature means adding a `case` in the `main()` switch AND a line in both `printMenu()` and the `h` help text. Note the switch currently has a duplicated `case 'p'/'P'` block (the second is unreachable) and the keypress listener is registered twice — mirror the existing style rather than assuming it's clean.
- **Pause** — global `isPaused`/`waitIfPaused()` gate batch loops; toggled by the `p` hotkey (only wired up in interactive mode with a TTY).
- **Pause** — `lib/pause.js` owns pause state; `waitIfPaused()` gates batch loops and the `p` hotkey toggles it in interactive TTY mode.
- Batch loops sleep ~300500ms between songs to avoid hammering the API — preserve this when editing download loops.