Files
163-music-dl/CLAUDE.md
T
2026-07-22 16:43:50 +08:00

4.5 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

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.

Running

# Interactive menu (TTY required for pause hotkey)
node 163_music_downloader.js

# Command-line batch mode (bypasses the menu entirely)
node 163_music_downloader.js <songId> <songId> ...
node 163_music_downloader.js --playlist=<playlistId>

# Flags (batch mode only)
--level=lossless    # override quality; see QUALITY_LEVELS
--retries=5         # download retry count (default 3)
--no-lyric          # skip .lrc download
--no-cover          # skip cover/tag embedding

There are no tests, linter, or build step. Verify changes by running the script.

External dependencies (all optional / runtime-detected)

  • api.chksz.top — third-party API (API object at top of file) for song URLs, search, lyrics, and playlists. This is the core download source.
  • music.163.com — queried directly (fetchNeteaseSongDetail, fetchNeteaseAlbumDetail) for rich metadata (album, publish date, popularity). Requires Cookie: os=pc header.
  • node-id3 (npm) — preferred for MP3 tag/lyric/cover embedding. required lazily via hasNodeID3(); absent by default.
  • ffmpeg (system) — fallback embedder for non-MP3 (FLAC etc.) and when node-id3 is missing. Detected via hasFFmpeg().
  • Audio players (mpv, ffplay, play, cvlc, aplay) — for the preview/play features, detected via which. Note: which is Unix-only, so preview won't find players on Windows even if installed.

If a dependency is missing the relevant feature degrades gracefully (skips embedding, prints the URL, etc.).

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:

  • Network helpersfetchJSON, 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.
  • LyricsfetchLyric returns { lrc, tlyric, ... }; mergeLrc interleaves original + translation by matching [mm:ss.xx] timestamps into a .合并翻译.lrc file.
  • Cover/tagsembedCover 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.
  • 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).
  • Output layout — audio + .lrc files go to downloads/. Deletes move files to .trash/ (not permanent). ZIP packaging uses .tmp_zip/. The c menu option cleans these up.

Conventions & gotchas

  • Quality levelsQUALITY_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).
  • Batch loops sleep ~300500ms between songs to avoid hammering the API — preserve this when editing download loops.