← fenyochek.ru

music_api

Last.fm scrobble cache & statistics API.
Данные берутся из локального кэша и обновляются автоматически каждые 10 минут, а также после каждой смены трека.

Base URL
https://api.fenyochek.ru

HTTP эндпоинты

GET /lastfm/stats поведение определяется параметром 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
curl "https://api.fenyochek.ru/lastfm/stats?method=top&type=tracks&limit=3&from=2026-02-01"

Пример ответа

JSON
{
  "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
curl "https://api.fenyochek.ru/lastfm/stats?method=history&type=tracks&name=The%20Scientist&artist=Coldplay&days=30"

Пример ответа

JSON
{
  "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
curl "https://api.fenyochek.ru/lastfm/stats?method=now_playing"

Ответ — что-то играет

JSON
{
  "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
}

Ответ — ничего не играет

JSON
{ "status": "nothing playing" }

method=last_scrobble

Последний записанный трек из локального кэша (не обязательно играет сейчас).

Пример запроса

curl
curl "https://api.fenyochek.ru/lastfm/stats?method=last_scrobble"

Пример ответа

JSON
{
  "name":      "The Scientist",
  "artist":    "Coldplay",
  "album":     "A Rush of Blood to the Head",
  "cover_url": "https://lastfm.freetls.fastly.net/...",
  "timestamp": 1740994823
}

WebSocket

WS /lastfm/ws подписка на обновления в реальном времени

Постоянное соединение. Сервер сам шлёт команды когда меняется трек или статистика. Клиент ничего не отправляет.

Команды от сервера

Команда
Когда шлётся
Действие
refresh_now_playing
Сменился трек или музыка остановилась
Перезапросить now_playing
refresh_stats
Изменился топ-1 за 30 дней
Перезапросить top
ping
Каждые 30 секунд
Игнорировать (keepalive)

Подключение

JavaScript
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
curl \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  "https://api.fenyochek.ru/api/admin/..."

GET /api/admin/db-dump

Создаёт консистентный snapshot SQLite базы и отдаёт его как файл.

Пример запроса

curl
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
curl \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  "https://api.fenyochek.ru/api/admin/duplicates/artists"

Пример ответа

JSON
{
  "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
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"

Пример ответа

JSON
{
  "canonical_uid": 17,
  "canonical_name": "Muse",
  "merged_aliases": 2
}

GET /api/admin/spotify/stats

Даёт сводную статистику по enrichment и покрытию Last.fm треков ссылками на Spotify.

Пример запроса

curl
curl \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  "https://api.fenyochek.ru/api/admin/spotify/stats"

Пример ответа

JSON
{
  "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
curl \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  "https://api.fenyochek.ru/api/admin/spotify/missing?limit=100"

Пример ответа

JSON
[
  {
    "track_uid": 12345,
    "title": "Unreleased Demo",
    "artist": "Some Artist",
    "scrobbles": 57
  }
]

Ручная привязка Last.fm трека к Spotify track. В spotify_id можно передать либо чистый Spotify track ID, либо полную URL.

Пример запроса

curl
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"

Пример ответа

JSON
{
  "ok": true,
  "lastfm_track_uid": 12345,
  "spotify_id": "3n3Ppam7vgaVa1iaRUc9Lp",
  "spotify_name": "Mr. Brightside"
}

Логика синхронизации

Кэш скроблов обновляется автоматически — вручную ничего дёргать не нужно.

Условие Поведение
Отстали < 200 скроблов 1 страница по 50 — быстро, легко
Отстали > 200 скроблов Страницы по 1000 (первый запуск / даунтайм)
Фоновый синк Каждые 10 минут
После смены трека Дополнительный синк через 4 секунды