music_api
Last.fm scrobble cache & statistics API.
Данные берутся из локального кэша и обновляются автоматически каждые 10 минут,
а также после каждой смены трека.
https://api.fenyochek.ru
HTTP эндпоинты
method
method=top
Топ N треков, артистов или альбомов за указанный период.
Параметры
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| methodreq | string | — | top |
| type | string | tracks | tracks / artists / albums |
| limit | number | 10 | Количество позиций в ответе |
| from | string | — | Начало периода. Форматы: YYYY-MM-DD или DD.MM.YY |
Пример запроса
curl "https://api.fenyochek.ru/lastfm/stats?method=top&type=tracks&limit=3&from=2026-02-01"
Пример ответа
{
"type_of": "Tracks",
"top_list": [
{
"name": "The Scientist",
"artist": "Coldplay",
"album": "A Rush of Blood to the Head",
"cover_url": "https://lastfm.freetls.fastly.net/...",
"value": 42
}
]
}
method=history
История прослушиваний конкретного трека / артиста / альбома по дням за последние N дней.
Параметры
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| methodreq | string | — | history |
| type | string | tracks | tracks / artists / albums |
| name | string | "" | Название трека / артиста / альбома. Если пустой — история всех прослушиваний |
| artist | string | — | Артист (уточнение при type=tracks) |
| days | number | 30 | Глубина истории в днях. Максимум 365 |
Пример запроса
curl "https://api.fenyochek.ru/lastfm/stats?method=history&type=tracks&name=The%20Scientist&artist=Coldplay&days=30"
Пример ответа
{
"name": "The Scientist",
"artist": "Coldplay",
"total": 7,
"history": [
{ "date": "2026-02-01", "plays": 0 },
{ "date": "2026-02-02", "plays": 3 },
{ "date": "2026-02-03", "plays": 4 }
]
}
method=now_playing
Возвращает текущий трек из кэша. Ответ мгновенный — без обращения к Last.fm.
nothing playing отправляется только после 5 подряд пустых poll-ов Last.fm. При текущем интервале это примерно 25 секунд.
Пример запроса
curl "https://api.fenyochek.ru/lastfm/stats?method=now_playing"
Ответ — что-то играет
{
"album": { "name": "A Rush of Blood to the Head", "mbid": "" },
"artist": { "name": "Coldplay", "mbid": "" },
"title": { "name": "The Scientist", "url": "https://..." },
"image": {
"extralarge": { "url": "https://..." }
},
"utc_timestamp": 0
}
Ответ — ничего не играет
{ "status": "nothing playing" }
method=last_scrobble
Последний записанный трек из локального кэша (не обязательно играет сейчас).
Пример запроса
curl "https://api.fenyochek.ru/lastfm/stats?method=last_scrobble"
Пример ответа
{
"name": "The Scientist",
"artist": "Coldplay",
"album": "A Rush of Blood to the Head",
"cover_url": "https://lastfm.freetls.fastly.net/...",
"timestamp": 1740994823
}
WebSocket
Постоянное соединение. Сервер сам шлёт команды когда меняется трек или статистика. Клиент ничего не отправляет.
Команды от сервера
now_playingtopПодключение
const ws = new WebSocket('wss://api.fenyochek.ru/lastfm/ws');
ws.onmessage = (e) => {
const { cmd } = JSON.parse(e.data);
if (cmd === 'refresh_now_playing') fetchNowPlaying();
if (cmd === 'refresh_stats') fetchStats();
};
Admin API
Все /api/admin/* эндпоинты закрыты и требуют заголовок Authorization: Bearer <ADMIN_TOKEN>.
music_api/.env через переменную ADMIN_TOKEN. После изменения значения нужно перезапустить контейнер.
Общий шаблон
curl \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ "https://api.fenyochek.ru/api/admin/..."
GET /api/admin/db-dump
Создаёт консистентный snapshot SQLite базы и отдаёт его как файл.
Пример запроса
curl \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ -o music.db \ "https://api.fenyochek.ru/api/admin/db-dump"
GET /api/admin/duplicates/artists
Ищет потенциальные дубликаты артистов, группируя имена по нормализованной форме. Возвращает список артистов, число скробблов и suggested canonical uid.
Пример запроса
curl \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ "https://api.fenyochek.ru/api/admin/duplicates/artists"
Пример ответа
{
"summary": {
"groups": 1,
"artists": 2,
"scrobbles": 133
},
"groups": [
{
"normalized_name": "muse",
"total_scrobbles": 133,
"suggested_canonical_uid": 17,
"artists": [
{
"uid": 17,
"name": "Muse",
"scrobbles": 120,
"current_canonical_uid": null,
"current_canonical_name": null
},
{
"uid": 42,
"name": "muse",
"scrobbles": 13,
"current_canonical_uid": null,
"current_canonical_name": null
}
]
}
]
}
POST /api/admin/duplicates/artists/merge
Сохраняет alias -> canonical связи в lastfm_artist_aliases. После merge публичная статистика начинает учитывать эти связи.
Пример запроса
curl \
-X POST \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"canonical_uid":17,"alias_uids":[42,84],"reason":"manual_merge"}' \
"https://api.fenyochek.ru/api/admin/duplicates/artists/merge"
Пример ответа
{
"canonical_uid": 17,
"canonical_name": "Muse",
"merged_aliases": 2
}
GET /api/admin/spotify/stats
Даёт сводную статистику по enrichment и покрытию Last.fm треков ссылками на Spotify.
Пример запроса
curl \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ "https://api.fenyochek.ru/api/admin/spotify/stats"
Пример ответа
{
"total_lastfm_tracks": 5401,
"linked_lastfm_tracks": 4872,
"primary_linked_tracks": 4872,
"missing_tracks": 211,
"pending_tracks": 318,
"total_scrobbles": 203445,
"linked_scrobbles": 189004,
"unlinked_scrobbles": 14441
}
GET /api/admin/spotify/missing
Возвращает треки из spotify_not_found, отсортированные по количеству скробблов. Можно указать limit.
Пример запроса
curl \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ "https://api.fenyochek.ru/api/admin/spotify/missing?limit=100"
Пример ответа
[
{
"track_uid": 12345,
"title": "Unreleased Demo",
"artist": "Some Artist",
"scrobbles": 57
}
]
POST /api/admin/spotify/link
Ручная привязка Last.fm трека к Spotify track. В spotify_id можно передать либо чистый Spotify track ID, либо полную URL.
Пример запроса
curl \
-X POST \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"lastfm_track_uid":12345,"spotify_id":"https://open.spotify.com/track/3n3Ppam7vgaVa1iaRUc9Lp"}' \
"https://api.fenyochek.ru/api/admin/spotify/link"
Пример ответа
{
"ok": true,
"lastfm_track_uid": 12345,
"spotify_id": "3n3Ppam7vgaVa1iaRUc9Lp",
"spotify_name": "Mr. Brightside"
}
Логика синхронизации
Кэш скроблов обновляется автоматически — вручную ничего дёргать не нужно.
| Условие | Поведение |
|---|---|
| Отстали < 200 скроблов | 1 страница по 50 — быстро, легко |
| Отстали > 200 скроблов | Страницы по 1000 (первый запуск / даунтайм) |
| Фоновый синк | Каждые 10 минут |
| После смены трека | Дополнительный синк через 4 секунды |