📖 Документация · 🇬🇧 In English · npm
Типизированный асинхронный TypeScript-клиент для API Яндекс Музыки.
@dvxch/yandex-music — независимая реализация HTTP API Яндекс Музыки на
TypeScript. Ориентирован на современный Node.js (≥ 20), поставляется как ESM с
полными декларациями типов и без рантайм-зависимостей (использует встроенный
fetch).
Статус: готово. Реализован и протестирован весь HTTP API —
account/tracks/albums/artists/search/likes/playlists/device auth/landing/radio/queue/history/clips/credits/disclaimers/labels/metatags/pins/presaves/concerts, — включая мутации плейлистов, дизлайки и обратную связь rotor, а также realtime («сейчас играет») через Ynison.
Каждый эндпоинт прогнан на живом API Яндекс Музыки с реальным токеном. Те немногие эндпоинты, которые сервер больше не отдаёт обычному аккаунту (например, устаревшее создание очереди, rotor feedback), кидают типизированную ошибку, а не падают молча.
fetch; только ESM, Node ≥ 20.deJson, без any в публичном API.download() льёт сразу на диск (константная память), на лету расшифровывая lossless (encraw/FLAC), с опциональной гонкой CDN-зеркал и watchdog'ом зависшего стрима.EventEmitter + синхронный снимок nowPlaying.onUnknownField репортит любое поле API, которое модели ещё не мапят, — изменения сервера ловятся, а не теряются.fetch. Свой транспорт (прокси, TLS-имперсонация, тюнинг пула соединений).npm install @dvxch/yandex-music
Требования: Node.js ≥ 20. Для realtime («сейчас играет») дополнительно нужен
опциональный пакет ws:
npm install ws
import { Client } from '@dvxch/yandex-music';
// Создаём с OAuth-токеном и один раз вызываем init() для загрузки данных аккаунта.
const client = await new Client({ token: process.env.YM_TOKEN }).init();
// Получаем трек по id.
const [track] = await client.tracks(2);
console.log(track?.title, '—', track?.artists?.[0]?.name);
// Получаем прямую ссылку и скачиваем.
const [info] = await track!.getDownloadInfo();
await info.download('track.mp3');
// Текст песни (TEXT или LRC).
const lyrics = await client.tracksLyrics(2, 'LRC');
console.log(await lyrics?.fetchLyrics());
new Client({ token }) дёшев. await client.init() один раз подгружает данные
аккаунта, чтобы методы, подставляющие user id по умолчанию (usersLikes*,
usersPlaylists*, pins, presaves), работали без явного userId. Каталожные
read-only запросы (tracks, albums, search, …) работают и без init().
client.tracks(...), client.tracksDownloadInfo(...)), повторяя исходный API.
Внутри клиент собирается из миксинов по доменам — каждая область в своём файле.deJson(raw, client), который строит типизированный экземпляр и
рекурсивно разбирает вложенные модели; массивы обрабатывает
deList(Model.deJson, raw, client).Track.getDownloadInfo() или
DownloadInfo.download() переиспользуют клиент-владелец, так что вручную его
прокидывать почти не нужно.YandexMusicError; сетевые
дополнительно наследуют NetworkError (BadRequestError, NotFoundError,
TimedOutError, UnauthorizedError).JSON.stringify модель — она держит обратную ссылку на клиент
(циклическая структура). Читайте поля напрямую.Всё, чем клиент представляется API, задаётся через конструктор:
const client = new Client({
token: process.env.YM_TOKEN,
userAgent: 'my-app/1.0', // по умолчанию — UA библиотеки
headers: { 'X-Yandex-Music-Client': 'custom' }, // мержатся с дефолтными
device: 'os=Linux; model=my-app; ...', // device-дескриптор для очередей
language: 'en', // язык ответов: en/uz/uk/us/ru/kk/hy
fetch: myFetch, // свой транспорт (напр. node-wreq)
retries: 2, // ретраи транзиентных GET-сбоев (0 — выкл.)
onUnknownField: ({ model, fields }) => // хук дрейфа: немаппленные поля API
console.warn(`${model}: unmapped ${fields.join(', ')}`),
});
userAgent и headers влияют только на автоматически создаваемый транспорт —
если передаёшь свой request, задавай их прямо в нём. Идентичность устройства
для realtime настраивается отдельно через client.realtime({ deviceInfo }).
ClientOptions| Опция | Назначение |
|---|---|
token |
OAuth-токен |
baseUrl |
переопределить origin API |
language |
язык ответов: en/uz/uk/us/ru/kk/hy (по умолчанию ru) |
userAgent |
переопределить User-Agent |
headers |
доп. заголовки, мержатся с дефолтными |
device |
device-дескриптор для запросов очереди |
fetch |
свой fetch (прокси, моки, ретраи) |
retries |
ретраи транзиентных сбоев для GET (по умолчанию 2, 0 — выкл.) |
reportUnknownFields |
подсветить поля API, которые модели ещё не маппят |
onUnknownField |
хук вместо console.warn для того же дрейфа |
request |
полностью преднастроенный транспорт (перекрывает userAgent/headers/fetch) |
Каждая ошибка библиотеки наследует YandexMusicError — одного catch
достаточно, чтобы перехватить всё. Сетевые и транспортные сбои дополнительно
наследуют NetworkError:
import {
YandexMusicError,
UnauthorizedError,
NotFoundError,
TimedOutError,
} from '@dvxch/yandex-music';
try {
await client.tracksLyrics(trackId);
} catch (error) {
if (error instanceof UnauthorizedError) {
// токен отсутствует или истёк — переавторизуйтесь
} else if (error instanceof NotFoundError) {
// у трека нет текста
} else if (error instanceof TimedOutError) {
// запрос завис — можно повторить
} else if (error instanceof YandexMusicError) {
// любая другая ошибка библиотеки
} else {
throw error; // не наша ошибка
}
}
GET-запросы автоматически повторяются при транзиентных сбоях (экспоненциальный
backoff + jitter, retries попыток сверху, по умолчанию 2). Мутирующие запросы
(POST/PUT/DELETE) никогда не повторяются автоматически — это защищает от
двойных сайд-эффектов (например, двойного лайка).
| Домен | Методы |
|---|---|
| account | init, accountStatus, accountSettings, accountSettingsSet, settings, permissionAlerts, accountExperiments, accountExperimentsDetails, consumePromoCode |
| tracks | tracks, tracksDownloadInfo, tracksLosslessInfo, tracksLyrics, tracksSimilar, tracksFullInfo, tracksTrailer, trackSupplement, playAudio, afterTrack |
| albums | albums, albumsWithTracks, albumsSimilarEntities, albumsTrailer |
| artists | artists, artistsBriefInfo, artistsInfo, artistsAbout, artistsClips, artistsDonation, artistsSkeleton, artistsTracks, artistsTrackIds, artistsDirectAlbums, artistsAlsoAlbums, artistsDiscographyAlbums, artistsSafeDirectAlbums, artistsSimilar, artistsLinks, artistsTrailer |
| search | search, searchSuggest |
| likes | usersLikesTracks/Albums/Artists/Playlists/Clips + add/remove для каждого; usersDislikesTracks/Artists + add/remove |
| playlists | playlist, playlists, playlistsList, playlistsPersonal, usersPlaylists, usersPlaylistsList, usersPlaylistsKinds, usersPlaylistsCreate, usersPlaylistsDelete, usersPlaylistsName, usersPlaylistsVisibility, usersPlaylistsDescription, usersPlaylistsChange, usersPlaylistsInsertTrack, usersPlaylistsDeleteTrack, usersPlaylistsRecommendations, usersPlaylistsTrailer, usersSettings, playlistSimilarEntities, playlistsCollectiveJoin |
| device auth | requestDeviceCode, pollDeviceToken, deviceAuth (блокирующий flow), refreshAccessToken (обновление по refresh-токену) |
| landing | landing, feed, feedWizardIsPassed, tags, chart, newReleases, newPlaylists, podcasts, genres |
| radio | rotorStationsDashboard, rotorStationsList, rotorStationInfo, rotorStationTracks, rotorAccountStatus, rotorStationFeedback (+ шорткаты radioStarted/trackStarted/trackFinished/skip), rotorStationSettings2 |
| queue | queuesList, queue, queueUpdatePosition, queueCreate |
| history | musicHistory, musicHistoryItems |
| clips | clips, clipsWillLike |
| credits | tracksCredits, clipsCredits |
| disclaimers | tracksDisclaimer, clipsDisclaimer, albumsDisclaimer, artistsDisclaimer |
| labels | label, labelAlbums, labelArtists |
| metatags | metatags, metatag, metatagAlbums, metatagArtists, metatagPlaylists |
| pins | pins, pin{Album,Artist,Playlist,Wave}, unpin{Album,Artist,Playlist,Wave} |
| presaves | usersPresaves, usersPresavesAdd, usersPresavesRemove |
| concerts | artistsConcerts, concertInfo, concertSkeleton, concertsFeed, concertsLocations, concertsTabConfig |
| realtime | client.realtime() → RealtimeClient (Ynison; нужен ws) |
Для lossy-вариантов используйте tracksDownloadInfo / track.getDownloadInfo()
и выбирайте по битрейту:
const variants = await track!.getDownloadInfo(); // DownloadInfo[]
const best = [...variants].sort((a, b) => (b.bitrateInKbps ?? 0) - (a.bitrateInKbps ?? 0))[0];
await best!.download('track.mp3');
Для lossless (FLAC) — отдельный путь через tracksLosslessInfo /
track.getLosslessDownloadInfo() (LosslessDownloadInfo, AES-CTR-расшифровка на
лету), описанный в типах пакета.
У Яндекс Музыки нет HTTP-вебхуков; единственный канал server-push — Ynison
(WebSocket-протокол синхронизации воспроизведения между устройствами).
client.realtime() оборачивает его в типизированный EventEmitter, который сам
делает рукопожатие, keep-alive и переподключение. Нужен опциональный пакет ws
(npm install ws); остальная библиотека рантайм-зависимостей не имеет.
const rt = client.realtime();
rt.on('trackChange', ({ track }) => console.log('сейчас играет:', track?.title));
rt.on('playStateChange', (paused) => console.log(paused ? 'пауза' : 'играет'));
await rt.start(); // резолвится, когда вы вызовете rt.stop()
Для боевого «наблюдателя» удобнее синхронный снимок, чем ожидание следующего события — например, чтобы ответить на HTTP-запрос прямо сейчас:
const rt = client.realtime({
staleTimeoutMs: 120_000, // форс-реконнект, если фреймы замолчали (broken pipe без RST)
});
rt.start(); // не await — крутится до rt.stop()
const np = rt.nowPlaying; // null до первого фрейма / когда ничего не играет
if (np?.track) {
console.log(np.track.title, np.paused ? '⏸' : '▶',
`${Math.round(np.progressMs / 1000)}/${Math.round(np.durationMs / 1000)}с`); // progressMs экстраполируется живьём
}
deviceId по умолчанию генерируется один раз и переиспользуется между
переподключениями — не меняйте его на каждый реконнект: Ynison дедуплицирует
рассылку по устройству, и новый id выкидывает вашего наблюдателя из fan-out,
после чего состояние молча замирает. Геттеры state / lastStateAgeMs /
liveProgressMs() дают остальную часть снимка для /health.
Запускаемые примеры лежат в examples/:
01-quickstart.ts — трек, прямая ссылка и текст песни;02-search.ts — поиск и саджесты;03-device-auth.ts — получение токена через device flow;04-realtime.ts — слежение за «сейчас играет» через Ynison;05-likes-library.ts — просмотр библиотеки, цикл лайк/анлайк;06-playlists.ts — полный жизненный цикл плейлиста (создать → изменить → удалить);07-radio.ts — станции «Моей волны», пакеты треков, обратная связь;08-charts-and-new.ts — чарт, новинки, жанры;09-lyrics.ts — синхронизированный по времени (LRC) текст песни;10-download-quality.ts — выбор качества/кодека, включая lossless (FLAC);11-artist.ts — краткая информация об артисте, треки, дискография, похожие;12-custom-fetch.ts — инъекция кастомного fetch (например, node-wreq для TLS-имперсонации).Подробности и команды запуска — в examples/README.md.
Полный справочник — на сайте документации, разбитый по разделам:
init(), ошибки.ClientOptions.nowPlaying, стабильный deviceId.Плюс автогенерируемый API-reference по всем классам, методам и моделям (TypeDoc).
Спасибо MarshalX за библиотеку yandex-music — бесценный ориентир по API Яндекс Музыки.
MIT © dvxch