@dvxch/yandex-music
v0.2.6
Published
Typed, async TypeScript client for the Yandex Music API
Readme
Типизированный асинхронный 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), кидают типизированную ошибку, а не падают молча.
Оглавление
- Возможности
- Установка
- Быстрый старт
- Архитектура
- Конфигурация
- Обработка ошибок
- Реализованные эндпоинты
- Realtime
- Примеры
- Документация
- Благодарности
- Лицензия
Возможности
- Ноль рантайм-зависимостей. На встроенном
fetch; только ESM, Node ≥ 20. - Полная типизация. У каждого эндпоинта и модели — ручные типы и статический
deJson, безanyв публичном API. - Полное покрытие API. ~145 методов по всем доменам, держим паритет с актуальным API.
- Надёжный транспорт. Автоматические ретраи с экспоненциальным backoff + jitter на транзиентных сбоях (только идемпотентные запросы); типизированные ошибки; пер-вызов таймауты.
- Стриминговое скачивание.
download()льёт сразу на диск (константная память), на лету расшифровывая lossless (encraw/FLAC), с опциональной гонкой CDN-зеркал и watchdog'ом зависшего стрима. - Realtime «сейчас играет» через Ynison — переподключающийся
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. Внутри клиент собирается из миксинов по доменам — каждая область в своём файле. - Модели в camelCase. API уже возвращает ключи в camelCase, поэтому модели
хранят их как есть — без слоя нормализации snake_case. У каждой модели есть
статический
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-расшифровка на
лету), описанный в типах пакета.
Realtime
У Яндекс Музыки нет 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. - Реализованные эндпоинты — полный каталог методов по доменам, скачивание и качество.
- Realtime «сейчас играет» — Ynison: события, снимок
nowPlaying, стабильныйdeviceId. - Примеры и разработка — запускаемые примеры и команды сборки.
Плюс автогенерируемый API-reference по всем классам, методам и моделям (TypeDoc).
Благодарности
Спасибо MarshalX за библиотеку yandex-music — бесценный ориентир по API Яндекс Музыки.
Лицензия
MIT © dvxch
