Files
godeez/.github/copilot-instructions.md
T
arnef 765595133f docs: add copilot instructions for future sessions
Document build/test/lint commands, the cmd -> download -> deezer
pipeline architecture, and codebase conventions so future Copilot
sessions can work effectively in this repository.
2026-08-14 20:58:51 +02:00

4.2 KiB

GoDeez

GoDeez is a Go CLI that downloads music (tracks, albums, playlists, artist top tracks) from Deezer in MP3 or lossless FLAC/WAV, embeds metadata tags, and tracks what has already been downloaded so repeat runs can skip it.

Build, test, lint

Mirror what CI (.github/workflows/ci.yml) runs:

gofmt -l .        # must print nothing; CI fails on unformatted files
go vet ./...
go test ./...
go build ./...

Run a single test:

go test ./internal/download/ -run TestHashFile
go test ./internal/download/ -run TestHashFile -v

Release packaging is validated with goreleaser check (config in .goreleaser.yaml); releases are cut by pushing a v* tag, which requires a matching section in CHANGELOG.md (.github/scripts/release-notes.sh extracts it and fails the release otherwise).

Architecture

The flow is cmddownload.Downloaderdeezer client → store/tag/audio:

  • cmd is a thin cobra layer (download, login, logout, update, version). It parses flags and delegates; it does not contain download logic. cmd.Execute also kicks off an async update check (internal/update.StartCheck) before running the command and prints the result after, so the network round trip overlaps with the command instead of adding to startup time.
  • internal/deezer is the API client. Kind (album/playlist/ artist/track) is the single source of truth mapping a resource type to its gw-light page method, request id field, and Resource implementation (Album/Playlist/Artist/Single). Adding a new resource kind means extending every switch in kind.go. Resource's decode method is unexported to seal the interface to this package.
  • internal/download (package download) drives the per-track pipeline via Downloader.Run: fetch resource → for each track, resolve a media source → decide skip/hash-dedupe → stream and decrypt → optionally convert FLAC to WAV → write tags → record in the store. Tracks are processed sequentially, one at a time. Most per-track failures (bad cover, failed BPM lookup, etc.) are collected as warnings rather than aborting the whole run; only context.Canceled stops the loop early.
  • internal/store is a bbolt-backed ledger (.tracks.db) written as a hidden file inside the output directory, so it travels with the music library it describes. Losing it is harmless — the only cost is re-downloading.
  • internal/tag writes metadata per container format (ID3v2 for MP3, Vorbis comments for FLAC, ID3 chunk + RIFF LIST/INFO for WAV), dispatching on file extension. Taggers write through a temp file so a mid-write failure can't corrupt existing audio.
  • internal/config has no config file — the output directory (~/Music/GoDeez) is fixed, and the only setting is the DEEZER_ARL env var, which is optional (falls back to keyring-stored credentials from godeez login).
  • internal/buildinfo holds the version/commit/date injected by goreleaser via -ldflags; unreleased builds report dev, which disables update-related behavior (IsDev()).
  • internal/update implements self-update: checks GitHub releases, verifies the downloaded binary's SHA256 against the published checksums.txt, and replaces the running binary in place.

Conventions

  • Package- and function-level doc comments consistently explain why, not just what — e.g. why a check happens where it does, or why an error is handled a particular way. Preserve this style when touching existing code or adding new exported functions.
  • Most per-track errors during a download are non-fatal and surfaced as warnings in the run summary; only cancellation (context.Canceled) should abort the loop. Keep new failure modes inside this pattern unless they are truly unrecoverable for the whole run.
  • Kind in internal/deezer/kind.go is deliberately exhaustive with no default case that silently returns a zero value in the id/method switches — follow the same pattern (explicit cases, empty/error fallback) if extending it.
  • Credentials: DEEZER_ARL always takes precedence over keyring-stored login credentials when both are present.