Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mkfm

Ластфм для приватного комьюнити: скробблинг, статистика, Last.fm-совместимое API и вход только через OAuth-провайдер из MKbot.

Что умеет

  • Скробблинг по двум протоколам: Last.fm API 2.0 (/2.0/) и Audioscrobbler 1.2 (/scrobble/), ответы в XML и JSON.
  • Статистика: лента комьюнити, «сейчас играет», профили, чарты исполнителей, альбомов и треков за 7 дней / месяц / 3 / 6 / 12 месяцев / всё время, активность по часам и дням недели, календарь за 90 дней, страницы исполнителей, альбомов, треков с топом слушателей, любимые треки.
  • Аккаунты создаются сами при первом входе через OAuth. Ник берётся из Telegram-ника и меняется в настройках.
  • Приватность: при PRIVATE_SITE=true сайт целиком закрыт от анонимов.
  • Акцентный цвет каждый выбирает себе сам в настройках (8 вариантов).

Установка

python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp example.env .env      # и заполнить
python manage.py migrate
python manage.py runserver

Настройка OAuth

  1. В MKbot добавьте клиента в oauth/config.json:
"meowfm": {
    "name": "mkfm",
    "secret": "<длинный случайный секрет>",
    "origins": ["https://meow.example.com"],
    "allowed_callbacks": ["https://meow.example.com/auth/callback/"]
}
  1. В .env mkfm пропишите те же значения:
OIDC_ISSUER = "https://oauth.example.com"     # = OAUTH_ENDPOINT бота
OIDC_CLIENT_ID = "meowfm"
OIDC_CLIENT_SECRET = "<тот же секрет>"
OIDC_REDIRECT_URI = "https://meow.example.com/auth/callback/"

redirect_uri сверяется с allowed_callbacks точным совпадением строки до ?, поэтому слеш на конце важен.

Особенности провайдера, которые уже учтены

  • /.well-known/jwks.json в MKbot отдаёт JSON-строку (двойное кодирование), а n и e лежат целыми числами вместо base64url — стандартные библиотеки такой JWKS не читают, поэтому в accounts/oidc.py свой разбор.
  • id_token не содержит nonce, защита строится на state + PKCE (S256).
  • Бот открывает redirect_uri кнопкой в Telegram, и колбэк может прийти в другой браузер. Поэтому state и code_verifier хранятся в БД (accounts.LoginRequest), а не только в сессии — вход завершится в том браузере, где открылась ссылка.
  • Кнопка в Telegram требует http(s)-ссылку, localhost Telegram принимает не всегда: для отладки удобнее пробросить домен (или тестировать через OIDC_VERIFY_ID_TOKEN=false и фикстуры).

Подключение скробблеров

Способ Адрес Как авторизуется
API 2.0, веб-подтверждение https://<сайт>/2.0/ auth.getToken → страница /api/auth/auth.getSession
API 2.0, логин с паролем https://<сайт>/2.0/ auth.getMobileSession с «паролем для скробблеров»
Audioscrobbler 1.2 https://<сайт>/scrobble/ handshake с тем же паролем

Пароль для скробблеров создаётся в настройках профиля и показывается один раз. Он нужен только протоколам, которые требуют пароль; хранится в виде md5, потому что именно md5 участвует в обеих схемах Last.fm.

Незнакомые api_key (скробблеры со своими ключами Last.fm) регистрируются автоматически — подпись api_sig для них не проверяется, потому что секрет клиента серверу неизвестен. Для своих приложений ключи заводятся в настройках, и тогда подпись проверяется. Отключается флагами LFM_ALLOW_UNKNOWN_API_KEYS и LFM_VERIFY_SIGNATURES.

Поддержанные методы API 2.0

auth.getToken, auth.getSession, auth.getMobileSession, track.updateNowPlaying, track.scrobble (в том числе пачкой до 50), track.love, track.unlove, track.getInfo, artist.getInfo, album.getInfo, user.getInfo, user.getRecentTracks, user.getTopArtists, user.getTopAlbums, user.getTopTracks, user.getLovedTracks, user.getFriends.

Перенос истории из Last.fm

Каждый переносит себя сам на странице /settings/import/: вводит свой ник на last.fm и жмёт «Начать перенос». Выкачка идёт в фоновом потоке, прогресс виден на той же странице, уйти со страницы можно. Повторный запуск ничего не задвоит — скроббы уникальны по паре «трек + время».

Чтобы участникам не пришлось заводить ключ Last.fm, пропишите общий в .env:

LASTFM_API_KEY = "<ключ с last.fm/api/account/create>"

Верхняя граница времени фиксируется на старте задания, иначе новые прослушивания на Last.fm сдвигали бы страницы прямо во время выкачки. Место остановки хранится в задании: если сервер перезапустить посреди переноса, на странице появится кнопка «Продолжить» — работа возобновится с той же страницы, а не с начала.

Из консоли то же самое:

python manage.py import_lastfm <ник в mkfm> --lastfm-user <ник на last.fm> --api-key <ключ>
python manage.py import_lastfm <ник> --lastfm-user <ник> --resume <id задания>

Тесты

python manage.py test

51 тест: протоколы скробблинга, подписи, OIDC-разбор нестандартного JWKS, защита от повторного использования state, перенос с Last.fm, отрисовка страниц.

Оформление

Тема одна — тёмная, меняется только акцентный цвет. В CSS задаётся единственная переменная --accent (её подставляет base.html из настроек пользователя), а полоски чартов, календарь активности, гистограммы и кнопки выводятся из неё через color-mix. Чтобы добавить цвет в список, достаточно дописать строку в web/theme.py — CSS править не нужно. Выбор анонимов (страница входа) запоминается в куке accent.

Структура

accounts/   пользователи, OIDC-клиент, middleware приватности
music/      Artist/Album/Track/Scrobble/NowPlaying/Love, приём скробблов, агрегации
lfmapi/     Last.fm API 2.0, страница подтверждения токена, протокол 1.2
web/        страницы сайта и настройки

Прод

  • DEBUG=false, заполнить ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, SITE_URL, SECRET_KEY.
  • python manage.py collectstatic (статику отдаёт whitenoise).
  • Postgres включается переменными POSTGRES_*, иначе используется SQLite.
  • Запуск: gunicorn meowfm.wsgi за nginx.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages