├── .dockerignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── release-drafter.yml └── workflows │ ├── build-prod-image.yml │ ├── deploy-image.yml │ ├── frontend-tests.yml │ ├── push-dev-image.yml │ ├── release-drafter.yml │ ├── spark-tests.yml │ └── unit-tests.yml ├── .gitignore ├── .pep8speaks.yml ├── .readthedocs.yaml ├── .stylelintrc.js ├── .vscode └── settings.json ├── .well-known └── funding-manifest-urls ├── Dockerfile ├── Dockerfile.nginx.prod ├── Dockerfile.spark ├── LICENSE ├── README.md ├── admin ├── config.sh.ctmpl ├── config.sh.sample ├── create-dumps.sh ├── cron_lock.py ├── functions.sh ├── rsync-dump-files.sh ├── sql │ ├── create_db.sql │ ├── create_extensions.sql │ ├── create_foreign_keys.sql │ ├── create_indexes.sql │ ├── create_primary_keys.sql │ ├── create_schema.sql │ ├── create_tables.sql │ ├── create_test_db.sql │ ├── create_types.sql │ ├── drop_db.sql │ ├── reset_tables.sql │ ├── updates │ │ ├── 2017-06-05-add-last-login.sql │ │ ├── 2017-06-23-add-latest-import.sql │ │ ├── 2017-07-03-alter-last-login.sql │ │ ├── 2017-07-28-add-stats-tables.sql │ │ ├── 2017-08-03-make-stats-updated-not-null.sql │ │ ├── 2017-09-03-make-column-names-singular.sql │ │ ├── 2017-10-14-add-incremental-dump-table.sql │ │ ├── 2017-10-15-remove-listen-tables.sql │ │ ├── 2018-05-09-add-on-delete-cascade-to-user-foreign-keys.sql │ │ ├── 2018-05-22-add-gdpr-user-columns.sql │ │ ├── 2018-06-13-add-musicbrainz-row-id-column.sql │ │ ├── 2018-06-21-make-musicbrainz-row-id-not-null.sql │ │ ├── 2018-06-22-spotify-listen-importer.sql │ │ ├── 2018-11-17-add-on-delete-cascade-to-spotify-fk.sql │ │ ├── 2019-01-04-add-user-login-id.sql │ │ ├── 2019-02-08-save-spotify-permissions.sql │ │ ├── 2019-02-13-change-login-id-type.sql │ │ ├── 2019-02-26-add-follow-list.sql │ │ ├── 2019-07-09-add-recommendation-table.sql │ │ ├── 2020-05-16-rename-recommendation-table-col.sql │ │ ├── 2020-05-20-change-lovehate-to-feedback.sql │ │ ├── 2020-05-20-change-recommendation-cf-recording-col-type.sql │ │ ├── 2020-05-20-drop-recommendation-cf-recording-col.sql │ │ ├── 2020-05-20-truncate-user-stats-table.sql │ │ ├── 2020-06-17-add-listening-activity-col.sql │ │ ├── 2020-07-13-add-daily-activity-col.sql │ │ ├── 2020-07-17-add-similar-user-table.sql │ │ ├── 2020-07-29-add-artist-map-col.sql │ │ ├── 2020-08-14-add-missing-musicbrainz-data-table.sql │ │ ├── 2020-08-14-add-sitewide-stats-table.sql │ │ ├── 2020-09-06-add-user-relationship-table.sql │ │ ├── 2020-10-03-add-recommendation-feedback-table.sql │ │ ├── 2021-03-01-remove-follow-list.sql │ │ ├── 2021-03-02-add-user-recommendation-event-table.sql │ │ ├── 2021-03-14-add-user-timeline-enum-type-notification.sql │ │ ├── 2021-04-12-1-new-music-services-table.sql │ │ ├── 2021-04-12-2-migrate-existing-spotify-users-to-new-schema.sql │ │ ├── 2021-04-21-1-create-listens-importer-table.sql │ │ ├── 2021-04-21-2-modify-external-service-oauth-table.sql │ │ ├── 2021-04-21-3-migrate-existing-spotify-users-second-version.sql │ │ ├── 2021-05-03-add-email-to-user-table.sql │ │ ├── 2021-05-04-add-report-users-table.sql │ │ ├── 2021-05-30-add-pinned-recording-table.sql │ │ ├── 2021-06-25-add-optional-mbid-to-pinned-recording-table.sql │ │ ├── 2021-07-10-add-lastfm-external-service-type.sql │ │ ├── 2021-07-14-migrate-import-ts-from-users-to-listens-importer.sql │ │ ├── 2021-07-15-add-librefm-external-service-type.sql │ │ ├── 2021-07-27-add-critiquebrainz-as-external-service.sql │ │ ├── 2021-08-28-add-user-timeline-enum-type-critiquebrainz_review.sql │ │ ├── 2021-09-14-new-statistics-table.sql │ │ ├── 2021-09-28-add-statistics-range-quarter.sql │ │ ├── 2021-09-28-add-user-name-search-support.sql │ │ ├── 2021-09-29-add-statistics-range-half-yearly.sql │ │ ├── 2021-10-07-delete-old-statistics-user-table.sql │ │ ├── 2021-11-24-add-this-time-ranges.sql │ │ ├── 2021-12-19-make-msid-optional-for-pinned-recordings.sql │ │ ├── 2021-12-20-add-mbid-to-recording-feedback.sql │ │ ├── 2021-16-11-year-in-music.sql │ │ ├── 2022-03-32-hide-user-timeline-event.sql │ │ ├── 2022-06-26-add-user-timeline-enum-type-personal-recommendation.sql │ │ ├── 2022-06-27-user-setting.sql │ │ ├── 2022-10-12-add-troi-user-setting.sql │ │ ├── 2022-11-18-add-do-not-recommend-table.sql │ │ ├── 2022-11-21-remove-unique-index-recording-table.sql │ │ ├── 2022-12-05-add-year-column-to-yim.sql │ │ ├── 2023-03-23-add-external-user-id-column-to-external-services-oauth-table.sql │ │ ├── 2023-04-20-add-musicbrainz-as-external-service.sql │ │ ├── 2023-04-20-add-soundcloud-as-external-service.sql │ │ ├── 2023-07-06-add-apple-as-external-service.sql │ │ ├── 2024-01-15-add-deployments-as-external-service.sql │ │ ├── 2024-03-18-add-background-tasks.sql │ │ ├── 2024-04-03-add-brainzplayer-settings.sql │ │ ├── 2024-07-17-hide-personal-recommendation.sql │ │ ├── 2024-08-02-make-external-service-access-token-nullable.sql │ │ ├── 2024-08-06-add-background-export.sql │ │ ├── 2024-09-09-add-user-flair.sql │ │ ├── 2025-01-27-add-thanks-event-type.sql │ │ ├── 2025-02-01-add-user-paused.sql │ │ ├── 2025-02-21-add-data-dump-type.sql │ │ └── 2025-05-05-add-import-info.sql │ └── util │ │ ├── logout_all_users.sql │ │ └── restart_spotify_imports.sql └── timescale │ ├── create_db.sql │ ├── create_extensions.sql │ ├── create_foreign_keys.sql │ ├── create_indexes.sql │ ├── create_primary_keys.sql │ ├── create_schemas.sql │ ├── create_tables.sql │ ├── create_test_db.sql │ ├── create_types.sql │ ├── create_views.sql │ ├── drop_db.sql │ ├── insert_default_data.sql │ ├── reset_tables.sql │ └── updates │ ├── 2020-11-21-playlists.sql │ ├── 2021-08-03-before-migration-listens-add-user-id.sql │ ├── 2021-10-27-add-artist-mbids-constraint.sql │ ├── 2021-10-27-add-listen-mbid-mapping-conditional-not-null-constraint.sql │ ├── 2021-11-17-refactor-mbid-mapping │ ├── 2022-01-13-add-listen-helper-table.sql │ ├── 2022-01-13-add-user-id-indexes.sql │ ├── 2022-01-27-fixup-created-column.sql │ ├── 2022-02-02-add-listen-table-delete-column.sql │ ├── 2022-05-29-add-check-again-column.sql │ ├── 2022-09-11-migrate-msb.sql │ ├── 2022-09-21-phase-out-mbid-mapping-metadata.sql │ ├── 2022-10-10-spotify-cache-normalized-1.sql │ ├── 2022-10-10-spotify-cache-normalized-2.sql │ ├── 2022-10-23-add-artist-credit-mbid-similarity.sql │ ├── 2022-10-23-add-artist-similarity.sql │ ├── 2022-10-23-add-recording-similarity.sql │ ├── 2022-11-08-add-additional-metadata-playlists.sql │ ├── 2022-12-16-add-manual-mbid-mapping.sql │ ├── 2023-01-04-update-materialized-mapping-view.sql │ ├── 2023-01-15-new-listens-table.sql │ ├── 2023-05-05-listens-table-switch.sql │ ├── 2023-05-06-rename-similarity-tables.sql │ ├── 2023-05-26-add-popularity-tables.sql │ ├── 2023-06-130-fix-none-in-recording-mbid.sql │ ├── 2023-07-17-add-tags-data-tables.sql │ ├── 2023-09-05-update-column-names-for-spotify-metadata-cache.sql │ ├── 2023-09-13-add-apple-metadata-cache-tables.sql │ ├── 2023-11-11-msb-deduplicate-1.sql │ ├── 2023-11-11-msb-deduplicate-2.sql │ ├── 2023-12-02-listens-table-switch.sql │ ├── 2024-05-28-add-pg_trgm-extension.sql │ ├── 2024-06-26-add-soundcloud-metadata-cache.sql │ ├── 2024-07-05-add-soundcloud-metadata-cache-2.sql │ ├── 2025-02-18-add-listen-delete-metadata.sql │ ├── 2025-02-19-add-user-listen-history-delete.sql │ └── 2025-02-19-change-listen-delete-metadata-status.sql ├── babel.config.js ├── consul_config.py.ctmpl ├── data ├── __init__.py ├── model │ ├── __init__.py │ ├── common_stat.py │ ├── common_stat_spark.py │ ├── entity_listener_stat.py │ ├── external_service.py │ ├── listen.py │ ├── new_releases_stat.py │ ├── sitewide_entity.py │ ├── user_artist_map.py │ ├── user_artist_stat.py │ ├── user_cf_recommendations_recording_message.py │ ├── user_daily_activity.py │ ├── user_entity.py │ ├── user_listening_activity.py │ ├── user_missing_musicbrainz_data.py │ ├── user_recording_stat.py │ ├── user_release_group_stat.py │ ├── user_release_stat.py │ └── validators.py └── postgres │ ├── __init__.py │ ├── artist.py │ ├── artist_credit.py │ ├── feedback.py │ ├── recording.py │ ├── release.py │ ├── release_group.py │ └── tag.py ├── develop.sh ├── docker ├── Dockerfile.spark.base ├── apache-download.sh ├── consul-template.conf ├── couchdb_test.ini ├── docker-compose.labs.api.yml ├── docker-compose.spark.override.yml ├── docker-compose.spark.yml ├── docker-compose.test.yml ├── docker-compose.yml ├── lb-startup-common.sh ├── prod │ └── nginx │ │ └── nginx.conf ├── push.sh ├── rc.local ├── run-lb-command ├── services │ ├── api_compat │ │ ├── api_compat.finish │ │ ├── api_compat.service │ │ ├── consul-template-api-compat.conf │ │ └── uwsgi-api-compat.ini │ ├── apple_metadata_cache │ │ ├── apple_metadata_cache.finish │ │ ├── apple_metadata_cache.service │ │ └── consul-template-apple-metadata-cache.conf │ ├── background_tasks │ │ ├── background_tasks.finish │ │ ├── background_tasks.service │ │ └── consul-template-background-tasks.conf │ ├── cron │ │ ├── consul-template-cron-config.conf │ │ ├── cron-config.service │ │ └── crontab │ ├── labs_api │ │ ├── consul-template-labs-api.conf │ │ ├── labs_api.finish │ │ ├── labs_api.service │ │ └── uwsgi-labs-api.ini │ ├── lastfm_importer │ │ ├── consul-template-lastfm-importer.conf │ │ ├── lastfm_importer.finish │ │ └── lastfm_importer.service │ ├── mbid_mapping_writer │ │ ├── consul-template-mbid-mapping-writer.conf │ │ ├── mbid_mapping_writer.finish │ │ └── mbid_mapping_writer.service │ ├── soundcloud_metadata_cache │ │ ├── consul-template-soundcloud-metadata-cache.conf │ │ ├── soundcloud_metadata_cache.finish │ │ └── soundcloud_metadata_cache.service │ ├── spark_reader │ │ ├── consul-template-spark-reader.conf │ │ ├── spark_reader.finish │ │ └── spark_reader.service │ ├── spotify_metadata_cache │ │ ├── consul-template-spotify-metadata-cache.conf │ │ ├── spotify_metadata_cache.finish │ │ └── spotify_metadata_cache.service │ ├── spotify_reader │ │ ├── consul-template-spotify-reader.conf │ │ ├── spotify_reader.finish │ │ └── spotify_reader.service │ ├── timescale_writer │ │ ├── consul-template-timescale-writer.conf │ │ ├── timescale_writer.finish │ │ └── timescale_writer.service │ ├── uwsgi │ │ ├── consul-template-uwsgi.conf │ │ ├── uwsgi.finish │ │ ├── uwsgi.ini.ctmpl │ │ └── uwsgi.service │ └── websockets │ │ ├── consul-template-websockets.conf │ │ ├── websockets.finish │ │ └── websockets.service ├── spark-cluster-config │ └── test │ │ ├── core-site.xml │ │ ├── hdfs-site.xml │ │ └── spark-env.sh ├── spark-dev-push.sh ├── start-labs-api.sh └── start-spark-request-consumer.sh ├── docs ├── .gitignore ├── Makefile ├── conf.py ├── developers │ ├── architecture.rst │ ├── commands.rst │ ├── devel-env.rst │ ├── develop-sh.rst │ ├── mapping.rst │ ├── spark-architecture.rst │ ├── spark-devel-env.rst │ └── troubleshooting.rst ├── general │ └── data-update-intervals.rst ├── images │ ├── auth-popup.png │ ├── dataflows-graph.png │ ├── listen-flow.dot │ ├── listen-flow.svg │ ├── release-result.png │ ├── release-workflow.png │ ├── request_consumer.png │ └── user-profile.png ├── index.rst ├── maintainers │ ├── deploy.rst │ ├── docker-image.rst │ ├── dumps.rst │ ├── mapping.rst │ ├── pull-requests.rst │ ├── rabbitmq.rst │ ├── spotify-reader.rst │ └── updating-prod-db-schema.rst ├── requirements.txt └── users │ ├── api-compat.rst │ ├── api-usage.rst │ ├── api │ ├── art.rst │ ├── core.rst │ ├── index.rst │ ├── metadata.rst │ ├── misc.rst │ ├── playlist.rst │ ├── popularity.rst │ ├── recommendation.rst │ ├── recordings.rst │ ├── settings.rst │ ├── social.rst │ └── statistics.rst │ ├── api_usage_examples │ ├── get_latest_import.py │ ├── get_listens.py │ ├── lookup_metadata.py │ ├── set_latest_import.py │ ├── submit_feedback.py │ └── submit_listens.py │ ├── clients.rst │ ├── feedback-json.rst │ ├── json.rst │ └── listenbrainz-dumps.rst ├── enzyme.config.ts ├── frontend ├── .gitignore ├── css │ ├── .gitignore │ ├── DatePicker.less │ ├── DateTimePicker.less │ ├── TimePicker.less │ ├── accordion.less │ ├── add-listen-modal.less │ ├── brainzplayer.less │ ├── cbreviewmodal.less │ ├── colors.less │ ├── donations.less │ ├── donors-page.less │ ├── entity-pages.less │ ├── explore.less │ ├── flairs.less │ ├── follow.less │ ├── fresh-releases.less │ ├── homepage.less │ ├── huesound.less │ ├── import-playlist-modal.less │ ├── listens-page.less │ ├── main.less │ ├── messybrainz.less │ ├── metadata-viewer.less │ ├── music-neighborhood.less │ ├── music-services.less │ ├── musicbrainz-entity-icons.less │ ├── new-navbar.less │ ├── personal-recommendation-modal.less │ ├── pill.less │ ├── pinned-recordings.less │ ├── playlists.less │ ├── preferences.less │ ├── rc-slider.less │ ├── recommendation-page.less │ ├── release-card.less │ ├── sass │ │ └── bootstrap.scss │ ├── scaffolding.less │ ├── scroll-container.less │ ├── search-track.less │ ├── search.less │ ├── sidebar.less │ ├── static │ │ └── widgets.css │ ├── stats-art-creator.less │ ├── stats.less │ ├── switch.less │ ├── tags.less │ ├── theme │ │ ├── bootstrap │ │ │ ├── mixins.less │ │ │ ├── mixins │ │ │ │ ├── alerts.less │ │ │ │ ├── background-variant.less │ │ │ │ ├── border-radius.less │ │ │ │ ├── buttons.less │ │ │ │ ├── center-block.less │ │ │ │ ├── clearfix.less │ │ │ │ ├── forms.less │ │ │ │ ├── gradients.less │ │ │ │ ├── grid-framework.less │ │ │ │ ├── grid.less │ │ │ │ ├── hide-text.less │ │ │ │ ├── image.less │ │ │ │ ├── labels.less │ │ │ │ ├── list-group.less │ │ │ │ ├── nav-divider.less │ │ │ │ ├── nav-vertical-align.less │ │ │ │ ├── opacity.less │ │ │ │ ├── pagination.less │ │ │ │ ├── panels.less │ │ │ │ ├── progress-bar.less │ │ │ │ ├── reset-filter.less │ │ │ │ ├── reset-text.less │ │ │ │ ├── resize.less │ │ │ │ ├── responsive-visibility.less │ │ │ │ ├── size.less │ │ │ │ ├── tab-focus.less │ │ │ │ ├── table-row.less │ │ │ │ ├── text-emphasis.less │ │ │ │ ├── text-overflow.less │ │ │ │ └── vendor-prefixes.less │ │ │ ├── utilities.less │ │ │ └── variables.less │ │ ├── buttons.less │ │ ├── google-fonts.less │ │ ├── links.less │ │ ├── navbars.less │ │ ├── theme.less │ │ └── variables.less │ ├── timeline.less │ ├── utilities.less │ └── year-in-music.less ├── fonts │ └── Inter.woff2 ├── img │ ├── ListenBrainz_logo_no_text.png │ ├── art │ │ ├── cover-art-on-floor.png │ │ ├── yim-2022-shareable-bg.png │ │ ├── yim-2022-shareable-flames.png │ │ ├── yim-2022-shareable-magnify.png │ │ └── yim-2022-shareable-stereo.png │ ├── broken-cd.jpg │ ├── cover-art-placeholder.jpg │ ├── critiquebrainz-logo.svg │ ├── explore │ │ ├── cover-art-collage.jpg │ │ ├── fresh-releases.jpg │ │ ├── huesound.jpg │ │ ├── lb-radio-beta.jpg │ │ ├── link-listens.jpg │ │ ├── made-with-postgres.png │ │ ├── music-neighborhood.jpg │ │ ├── similar-users.jpg │ │ ├── stats-art-beta.jpg │ │ ├── stats-art │ │ │ ├── template-designer-top-10.png │ │ │ ├── template-designer-top-5.png │ │ │ ├── template-grid-stats-2.png │ │ │ ├── template-grid-stats.png │ │ │ └── template-lps-on-the-floor.png │ │ ├── year-in-music-2021.jpg │ │ ├── year-in-music-2022.jpg │ │ ├── year-in-music-2023.jpg │ │ └── year-in-music-2024.png │ ├── favicon-16.png │ ├── favicon-256.png │ ├── favicon-32.png │ ├── homepage │ │ ├── LB-Data-Provider-Info.png │ │ ├── LB-Data-Provider.png │ │ ├── LB-Ethical-Info.png │ │ ├── LB-Ethical.png │ │ ├── LB-Headphone.png │ │ ├── LB-Open-Source-Info.png │ │ ├── LB-Open-Source.png │ │ └── LB-Speaker.png │ ├── icons │ │ └── angle_double_right_icon.svg │ ├── listenbrainz-logo.png │ ├── listenbrainz-logo.svg │ ├── listenbrainz_logo_icon.svg │ ├── logo_big.svg │ ├── mb-entity-icons │ │ ├── artist.svg │ │ ├── group.svg │ │ ├── recording.svg │ │ └── release.svg │ ├── meb-icons │ │ ├── AcousticBrainz.svg │ │ ├── BookBrainz.svg │ │ ├── CoverArtArchive.svg │ │ ├── CritiqueBrainz.svg │ │ ├── MetaBrainz.svg │ │ ├── MusicBrainz.svg │ │ └── Picard.svg │ ├── messybrainz.svg │ ├── musicbrainz-16.svg │ ├── navbar_logo.svg │ ├── playlist-cover-art │ │ ├── cover-art_1-0.svg │ │ ├── cover-art_2-0.svg │ │ ├── cover-art_3-0.svg │ │ ├── cover-art_3-1.svg │ │ ├── cover-art_3-2.svg │ │ ├── cover-art_4-0.svg │ │ ├── cover-art_4-1.svg │ │ ├── cover-art_4-2.svg │ │ ├── cover-art_4-3.svg │ │ ├── cover-art_5-0.svg │ │ └── cover-art_5-1.svg │ ├── recommendations │ │ ├── blue-1.svg │ │ ├── blue-2.svg │ │ ├── blue-3.svg │ │ ├── blue-4.svg │ │ ├── green-1.svg │ │ ├── green-2.svg │ │ ├── green-3.svg │ │ ├── green-4.svg │ │ ├── no-freshness.png │ │ ├── red-1.svg │ │ ├── red-2.svg │ │ ├── red-3.svg │ │ └── red-4.svg │ ├── selfie.jpg │ ├── share-header.png │ ├── year-in-music-2021.png │ ├── year-in-music-2021.svg │ ├── year-in-music-22 │ │ ├── buddy.png │ │ ├── magnify.png │ │ ├── map.png │ │ ├── stereo.png │ │ ├── yim-22-logo-small-compressed.png │ │ └── yim22-logo.png │ ├── year-in-music-23 │ │ ├── buddies-square.png │ │ ├── cat.png │ │ ├── dog-tall.png │ │ ├── dog.png │ │ ├── fish.png │ │ ├── flower.png │ │ ├── ghost-square.png │ │ ├── heart-square.png │ │ ├── heart.png │ │ ├── peep-square.png │ │ ├── peep.png │ │ ├── pick-color.png │ │ ├── shareable-image-arrows.png │ │ ├── shareable-image-hug.png │ │ ├── trunk.png │ │ ├── worm.png │ │ ├── yim-23-header.png │ │ ├── yim-23-logo-small-compressed.png │ │ └── yim23-logo.png │ └── year-in-music-24 │ │ ├── autumn │ │ ├── buddies │ │ │ ├── yim24-buddy-01.png │ │ │ ├── yim24-buddy-02.png │ │ │ ├── yim24-buddy-03.png │ │ │ ├── yim24-buddy-04.png │ │ │ ├── yim24-buddy-05.png │ │ │ ├── yim24-buddy-06.png │ │ │ ├── yim24-buddy-07.png │ │ │ ├── yim24-buddy-08.png │ │ │ ├── yim24-buddy-09.png │ │ │ └── yim24-buddy-10.png │ │ ├── playlists │ │ │ ├── yim24-playlist-01-no-bg.png │ │ │ ├── yim24-playlist-01.png │ │ │ ├── yim24-playlist-02-no-bg.png │ │ │ ├── yim24-playlist-02.png │ │ │ ├── yim24-playlist-03.png │ │ │ ├── yim24-playlist-04.png │ │ │ ├── yim24-playlist-05.png │ │ │ └── yim24-playlist-06.png │ │ ├── yim24-01.png │ │ ├── yim24-02.png │ │ ├── yim24-03.png │ │ └── yim24-04.png │ │ ├── icon-autumn.svg │ │ ├── icon-spring.svg │ │ ├── icon-summer.svg │ │ ├── icon-winter.svg │ │ ├── spring │ │ ├── buddies │ │ │ ├── yim24-buddy-01.png │ │ │ ├── yim24-buddy-02.png │ │ │ ├── yim24-buddy-03.png │ │ │ ├── yim24-buddy-04.png │ │ │ ├── yim24-buddy-05.png │ │ │ ├── yim24-buddy-06.png │ │ │ ├── yim24-buddy-07.png │ │ │ ├── yim24-buddy-08.png │ │ │ └── yim24-buddy-09.png │ │ ├── playlists │ │ │ ├── yim24-playlist-01-no-bg.png │ │ │ ├── yim24-playlist-01.png │ │ │ ├── yim24-playlist-02-no-bg.png │ │ │ ├── yim24-playlist-02.png │ │ │ ├── yim24-playlist-03.png │ │ │ ├── yim24-playlist-04.png │ │ │ ├── yim24-playlist-05.png │ │ │ └── yim24-playlist-06.png │ │ ├── yim24-01.png │ │ ├── yim24-02.png │ │ ├── yim24-03.png │ │ └── yim24-04.png │ │ ├── summer │ │ ├── buddies │ │ │ ├── yim24-buddy-01.png │ │ │ ├── yim24-buddy-02.png │ │ │ ├── yim24-buddy-03.png │ │ │ ├── yim24-buddy-04.png │ │ │ ├── yim24-buddy-05.png │ │ │ ├── yim24-buddy-06.png │ │ │ └── yim24-buddy-07.png │ │ ├── playlists │ │ │ ├── yim24-playlist-01-no-bg.png │ │ │ ├── yim24-playlist-01.png │ │ │ ├── yim24-playlist-02-no-bg.png │ │ │ ├── yim24-playlist-02.png │ │ │ ├── yim24-playlist-03.png │ │ │ ├── yim24-playlist-04.png │ │ │ ├── yim24-playlist-05.png │ │ │ └── yim24-playlist-06.png │ │ ├── yim24-01.png │ │ ├── yim24-02.png │ │ ├── yim24-03.png │ │ └── yim24-04.png │ │ ├── winter │ │ ├── buddies │ │ │ ├── yim24-buddy-01.png │ │ │ ├── yim24-buddy-02.png │ │ │ ├── yim24-buddy-03.png │ │ │ ├── yim24-buddy-04.png │ │ │ └── yim24-buddy-05.png │ │ ├── playlists │ │ │ ├── yim24-playlist-01-no-bg.png │ │ │ ├── yim24-playlist-01.png │ │ │ ├── yim24-playlist-02-no-bg.png │ │ │ ├── yim24-playlist-02.png │ │ │ ├── yim24-playlist-03.png │ │ │ ├── yim24-playlist-04.png │ │ │ ├── yim24-playlist-05.png │ │ │ └── yim24-playlist-06.png │ │ ├── yim24-01.png │ │ ├── yim24-02.png │ │ ├── yim24-03.png │ │ └── yim24-04.png │ │ ├── yim24-header-all-email.png │ │ └── yim24-header.png ├── js │ ├── lib │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── dragscroll.js │ │ ├── htmx.min.js │ │ └── soundcloud-player-api.js │ ├── src │ │ ├── about │ │ │ ├── About.tsx │ │ │ ├── add-data │ │ │ │ └── AddData.tsx │ │ │ ├── current-status │ │ │ │ └── CurrentStatus.tsx │ │ │ ├── data │ │ │ │ └── Data.tsx │ │ │ ├── donations │ │ │ │ └── Donate.tsx │ │ │ ├── layout.tsx │ │ │ ├── routes │ │ │ │ └── index.tsx │ │ │ └── terms-of-service │ │ │ │ └── TermsOfService.tsx │ │ ├── album │ │ │ ├── AlbumPage.tsx │ │ │ └── utils.tsx │ │ ├── api │ │ │ └── auth │ │ │ │ └── AuthPage.tsx │ │ ├── artist │ │ │ └── ArtistPage.tsx │ │ ├── cb-review │ │ │ ├── CBReview.tsx │ │ │ ├── CBReviewForm.tsx │ │ │ └── CBReviewModal.tsx │ │ ├── common │ │ │ ├── Accordion.tsx │ │ │ ├── Pagination.tsx │ │ │ ├── UserSearch.tsx │ │ │ ├── Username.tsx │ │ │ ├── brainzplayer │ │ │ │ ├── AppleMusicPlayer.tsx │ │ │ │ ├── BrainzPlayer.tsx │ │ │ │ ├── BrainzPlayerContext.tsx │ │ │ │ ├── BrainzPlayerUI.tsx │ │ │ │ ├── MenuOptions.tsx │ │ │ │ ├── MusicPlayer.tsx │ │ │ │ ├── ProgressBar.tsx │ │ │ │ ├── Queue.tsx │ │ │ │ ├── QueueItemCard.tsx │ │ │ │ ├── SoundcloudPlayer.tsx │ │ │ │ ├── SpotifyPlayer.tsx │ │ │ │ ├── VolumeControlButton.tsx │ │ │ │ ├── YoutubePlayer.tsx │ │ │ │ └── utils.ts │ │ │ ├── flairs │ │ │ │ ├── FlairsExplanationButton.tsx │ │ │ │ └── FlairsExplanationModal.tsx │ │ │ ├── listens │ │ │ │ ├── AddToPlaylist.tsx │ │ │ │ ├── CoverArtWithFallback.tsx │ │ │ │ ├── ListenCard.tsx │ │ │ │ ├── ListenControl.tsx │ │ │ │ ├── ListenCountCard.tsx │ │ │ │ ├── ListenFeedbackComponent.tsx │ │ │ │ ├── ListenPayloadModal.tsx │ │ │ │ ├── MBIDMappingModal.tsx │ │ │ │ └── RecommendationFeedbackComponent.tsx │ │ │ └── stats │ │ │ │ └── StatsExplanationsModal.tsx │ │ ├── components │ │ │ ├── Card.tsx │ │ │ ├── ConfirmationModal.tsx │ │ │ ├── Footer.tsx │ │ │ ├── HorizontalScrollContainer.tsx │ │ │ ├── Loader.tsx │ │ │ ├── Navbar.tsx │ │ │ ├── OpenInMusicBrainz.tsx │ │ │ ├── Pill.tsx │ │ │ ├── Sidebar.tsx │ │ │ ├── Switch.tsx │ │ │ └── SyndicationFeedModal.tsx │ │ ├── donors │ │ │ └── Donors.tsx │ │ ├── error │ │ │ └── ErrorBoundary.tsx │ │ ├── explore │ │ │ ├── Explore.tsx │ │ │ ├── ai-brainz │ │ │ │ └── AIBrainz.tsx │ │ │ ├── art-creator │ │ │ │ ├── ArtCreator.tsx │ │ │ │ ├── components │ │ │ │ │ ├── ColorPicker.tsx │ │ │ │ │ ├── Gallery.tsx │ │ │ │ │ ├── GalleryTile.tsx │ │ │ │ │ ├── IconTray.tsx │ │ │ │ │ ├── Preview.tsx │ │ │ │ │ └── ToggleOption.tsx │ │ │ │ └── utils.tsx │ │ │ ├── cover-art-collage │ │ │ │ ├── 2022 │ │ │ │ │ ├── CoverArtComposite.tsx │ │ │ │ │ └── data │ │ │ │ │ │ └── rainbow1-100-7.json │ │ │ │ ├── 2023 │ │ │ │ │ ├── CoverArtComposite.tsx │ │ │ │ │ └── data │ │ │ │ │ │ └── mosaic-2023.json │ │ │ │ └── SEO.tsx │ │ │ ├── fresh-releases │ │ │ │ ├── FreshReleases.tsx │ │ │ │ ├── components │ │ │ │ │ ├── ReleaseCard.tsx │ │ │ │ │ ├── ReleaseCardsGrid.tsx │ │ │ │ │ ├── ReleaseFilters.tsx │ │ │ │ │ └── ReleaseTimeline.tsx │ │ │ │ └── utils.tsx │ │ │ ├── huesound │ │ │ │ ├── HueSound.tsx │ │ │ │ ├── components │ │ │ │ │ └── ColorWheel.tsx │ │ │ │ └── utils │ │ │ │ │ ├── defaultColors.ts │ │ │ │ │ └── utils.ts │ │ │ ├── layout.tsx │ │ │ ├── lb-radio │ │ │ │ ├── LBRadio.tsx │ │ │ │ └── components │ │ │ │ │ ├── Playlist.tsx │ │ │ │ │ └── Prompt.tsx │ │ │ ├── music-neighborhood │ │ │ │ ├── MusicNeighborhood.tsx │ │ │ │ ├── components │ │ │ │ │ ├── Panel.tsx │ │ │ │ │ ├── SearchBox.tsx │ │ │ │ │ ├── SimilarArtist.tsx │ │ │ │ │ └── SimilarArtistsGraph.tsx │ │ │ │ ├── types.d.ts │ │ │ │ └── utils │ │ │ │ │ ├── generateTransformedArtists.ts │ │ │ │ │ └── utils.tsx │ │ │ ├── routes │ │ │ │ └── index.tsx │ │ │ └── similar-users │ │ │ │ └── SimilarUsers.tsx │ │ ├── gdpr │ │ │ └── GDPR.tsx │ │ ├── home │ │ │ ├── Blob.tsx │ │ │ ├── Homepage.tsx │ │ │ └── NumberCounter.tsx │ │ ├── hooks │ │ │ ├── __mocks__ │ │ │ │ └── useFeedbackMap.tsx │ │ │ └── useFeedbackMap.tsx │ │ ├── import-data │ │ │ └── ImportData.tsx │ │ ├── index.tsx │ │ ├── lastfm-proxy │ │ │ └── LastfmProxy.tsx │ │ ├── lastfm │ │ │ ├── LibreFMImporter.tsx │ │ │ └── LibreFMImporterModal.tsx │ │ ├── layout │ │ │ ├── LayoutWithBackButton.tsx │ │ │ └── index.tsx │ │ ├── listens-offline │ │ │ └── ListensOffline.tsx │ │ ├── login │ │ │ └── Login.tsx │ │ ├── messybrainz │ │ │ └── MessyBrainz.tsx │ │ ├── metadata-viewer │ │ │ ├── MetadataViewerPage.tsx │ │ │ ├── components │ │ │ │ └── MetadataViewer.tsx │ │ │ └── types.d.ts │ │ ├── musicbrainz-offline │ │ │ └── MusicBrainzOffline.tsx │ │ ├── notifications │ │ │ ├── AlertNotificationsHOC.tsx │ │ │ └── Notifications.tsx │ │ ├── personal-recommendations │ │ │ ├── NamePill.tsx │ │ │ ├── PersonalRecommendationsModal.tsx │ │ │ └── SearchDropDown.tsx │ │ ├── pins │ │ │ └── PinRecordingModal.tsx │ │ ├── player │ │ │ ├── PlayerPage.tsx │ │ │ └── routes │ │ │ │ ├── index.tsx │ │ │ │ └── listening-now-routes.tsx │ │ ├── playlists │ │ │ ├── Playlist.tsx │ │ │ ├── components │ │ │ │ ├── CreateOrEditPlaylistModal.tsx │ │ │ │ ├── DeletePlaylistConfirmationModal.tsx │ │ │ │ ├── DuplicateTrackModal.tsx │ │ │ │ ├── PlaylistItemCard.tsx │ │ │ │ └── PlaylistMenu.tsx │ │ │ └── utils.tsx │ │ ├── recent │ │ │ ├── RecentListens.tsx │ │ │ └── components │ │ │ │ └── RecentDonors.tsx │ │ ├── recommended │ │ │ └── tracks │ │ │ │ ├── Info.tsx │ │ │ │ ├── Layout.tsx │ │ │ │ ├── Recommendations.tsx │ │ │ │ └── routes │ │ │ │ └── index.tsx │ │ ├── release-group │ │ │ └── ReleaseGroup.tsx │ │ ├── release │ │ │ └── Release.tsx │ │ ├── report-user │ │ │ ├── ReportUser.tsx │ │ │ └── ReportUserModal.tsx │ │ ├── routes │ │ │ ├── EntityPages.tsx │ │ │ ├── index.tsx │ │ │ ├── redirectRoutes.tsx │ │ │ └── routes.tsx │ │ ├── search │ │ │ ├── AlbumSearch.tsx │ │ │ ├── ArtistSearch.tsx │ │ │ ├── PlaylistSearch.tsx │ │ │ ├── Search.tsx │ │ │ ├── TrackSearch.tsx │ │ │ ├── UserSearch.tsx │ │ │ └── types.d.ts │ │ ├── settings │ │ │ ├── Settings.tsx │ │ │ ├── brainzplayer │ │ │ │ └── BrainzPlayerSettings.tsx │ │ │ ├── delete-listens │ │ │ │ └── DeleteListens.tsx │ │ │ ├── delete │ │ │ │ └── DeleteAccount.tsx │ │ │ ├── export │ │ │ │ ├── ExportButtons.tsx │ │ │ │ └── ExportData.tsx │ │ │ ├── flairs │ │ │ │ └── FlairsSettings.tsx │ │ │ ├── import │ │ │ │ └── ImportListens.tsx │ │ │ ├── layout.tsx │ │ │ ├── link-listens │ │ │ │ ├── LinkListens.tsx │ │ │ │ └── MultiTrackMBIDMappingModal.tsx │ │ │ ├── music-services │ │ │ │ └── details │ │ │ │ │ ├── MusicServices.tsx │ │ │ │ │ └── components │ │ │ │ │ ├── ExternalServiceButton.tsx │ │ │ │ │ └── ImportStatus.tsx │ │ │ ├── resettoken │ │ │ │ └── ResetToken.tsx │ │ │ ├── routes │ │ │ │ ├── index.tsx │ │ │ │ └── redirectRoutes.tsx │ │ │ ├── select_timezone │ │ │ │ └── SelectTimezone.tsx │ │ │ └── troi │ │ │ │ └── SelectTroiPreferences.tsx │ │ ├── tags │ │ │ ├── TagComponent.tsx │ │ │ └── TagsComponent.tsx │ │ ├── user-feed │ │ │ ├── NetworkFeed.tsx │ │ │ ├── ThanksModal.tsx │ │ │ ├── UserFeed.tsx │ │ │ ├── UserFeedLayout.tsx │ │ │ ├── routes │ │ │ │ └── index.tsx │ │ │ └── types.ts │ │ ├── user │ │ │ ├── Dashboard.tsx │ │ │ ├── charts │ │ │ │ ├── UserEntityChart.tsx │ │ │ │ ├── components │ │ │ │ │ └── Bar.tsx │ │ │ │ └── utils.tsx │ │ │ ├── components │ │ │ │ ├── AddAlbumListens.tsx │ │ │ │ ├── AddListenModal.tsx │ │ │ │ ├── AddSingleListen.tsx │ │ │ │ ├── PinnedRecordingCard.tsx │ │ │ │ └── follow │ │ │ │ │ ├── CompatibilityCard.tsx │ │ │ │ │ ├── FollowButton.tsx │ │ │ │ │ ├── FollowerFollowingModal.tsx │ │ │ │ │ ├── SimilarUsersModal.tsx │ │ │ │ │ ├── SimilarityScore.tsx │ │ │ │ │ ├── UserListModalEntry.tsx │ │ │ │ │ └── UserSocialNetwork.tsx │ │ │ ├── layout.tsx │ │ │ ├── playlists │ │ │ │ ├── Playlists.tsx │ │ │ │ ├── components │ │ │ │ │ ├── ImportAppleMusicPlaylistModal.tsx │ │ │ │ │ ├── ImportJSPFPlaylistModal.tsx │ │ │ │ │ ├── ImportSoundCloudPlaylistModal.tsx │ │ │ │ │ ├── ImportSpotifyPlaylistModal.tsx │ │ │ │ │ ├── PlaylistCard.tsx │ │ │ │ │ └── PlaylistsList.tsx │ │ │ │ └── playlistView.d.ts │ │ │ ├── recommendations │ │ │ │ ├── RecommendationsPage.tsx │ │ │ │ └── components │ │ │ │ │ ├── RecommendationControl.tsx │ │ │ │ │ └── RecommendationPlaylistSettings.tsx │ │ │ ├── routes │ │ │ │ ├── redirectRoutes.tsx │ │ │ │ └── userRoutes.tsx │ │ │ ├── stats │ │ │ │ ├── UserReports.tsx │ │ │ │ ├── components │ │ │ │ │ ├── BarDualTone.tsx │ │ │ │ │ ├── Choropleth.tsx │ │ │ │ │ ├── HeatMap.tsx │ │ │ │ │ ├── UserArtistActivity.tsx │ │ │ │ │ ├── UserArtistMap.tsx │ │ │ │ │ ├── UserDailyActivity.tsx │ │ │ │ │ ├── UserListeningActivity.tsx │ │ │ │ │ └── UserTopEntity.tsx │ │ │ │ ├── data │ │ │ │ │ └── world_countries.json │ │ │ │ └── utils.tsx │ │ │ ├── taste │ │ │ │ ├── UserTaste.tsx │ │ │ │ └── components │ │ │ │ │ ├── UserFeedback.tsx │ │ │ │ │ └── UserPins.tsx │ │ │ └── year-in-music │ │ │ │ ├── 2021 │ │ │ │ ├── YearInMusic2021.tsx │ │ │ │ └── components │ │ │ │ │ └── ComponentToImage.tsx │ │ │ │ ├── 2022 │ │ │ │ ├── YearInMusic2022.tsx │ │ │ │ └── components │ │ │ │ │ └── MagicShareButton.tsx │ │ │ │ ├── 2023 │ │ │ │ ├── YearInMusic2023.tsx │ │ │ │ └── components │ │ │ │ │ └── ImageShareButtons.tsx │ │ │ │ ├── 2024 │ │ │ │ └── YearInMusic2024.tsx │ │ │ │ └── SEO.tsx │ │ └── utils │ │ │ ├── APIError.ts │ │ │ ├── APIService.ts │ │ │ ├── Dropdown.tsx │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── FlairLoader.tsx │ │ │ ├── GlobalAppContext.tsx │ │ │ ├── Loader.ts │ │ │ ├── Playlist.ts │ │ │ ├── ProtectedRoutes.tsx │ │ │ ├── QueryClient.ts │ │ │ ├── ReactQueryDevTools.tsx │ │ │ ├── RecordingFeedbackManager.tsx │ │ │ ├── Scrobble.ts │ │ │ ├── SearchAlbumOrMBID.tsx │ │ │ ├── SearchTrackOrMBID.tsx │ │ │ ├── appleMusicTypes.d.ts │ │ │ ├── constants.ts │ │ │ ├── coverArtCache.ts │ │ │ ├── donation.d.ts │ │ │ ├── icons.ts │ │ │ ├── musickit.d.ts │ │ │ ├── soundcloudTypes.d.ts │ │ │ ├── spotifyTypes.d.ts │ │ │ ├── types.d.ts │ │ │ └── utils.tsx │ └── tests │ │ ├── __mocks__ │ │ ├── encodeScrobbleOutput.json │ │ ├── feedProps.json │ │ ├── freshReleasesDisplaySettings.json │ │ ├── freshReleasesSitewideData.json │ │ ├── freshReleasesSitewideFilters.json │ │ ├── freshReleasesUserData.json │ │ ├── getFeedbackByMsidResponse.json │ │ ├── getInfo.json │ │ ├── getInfoNoPlayCount.json │ │ ├── getMultipleFeedbackResponse.json │ │ ├── intersection-observer.tsx │ │ ├── lastFMPrivateUser.json │ │ ├── listensTimelineProps.json │ │ ├── localforage.ts │ │ ├── lookupMBRelease.json │ │ ├── lookupMBReleaseFromTrack.json │ │ ├── matchMedia.ts │ │ ├── missingMBDataProps.json │ │ ├── page.json │ │ ├── pinProps.json │ │ ├── playlistPageProps.json │ │ ├── react-toastify.js │ │ ├── recentListensProps.json │ │ ├── recentListensPropsOneListen.json │ │ ├── recentListensPropsPlayingNow.json │ │ ├── recentListensPropsThreeListens.json │ │ ├── recommendations.json │ │ ├── soundcloudSearchResponse.json │ │ ├── spotifySearchEmptyResponse.json │ │ ├── spotifySearchResponse.json │ │ ├── timelineProps.json │ │ ├── userArtistActivity.json │ │ ├── userArtistMap.json │ │ ├── userArtistMapProcessDataArtist.json │ │ ├── userArtistMapProcessDataListen.json │ │ ├── userArtists.json │ │ ├── userArtistsProcessData.json │ │ ├── userDailyActivity.json │ │ ├── userDailyActivityProcessData.json │ │ ├── userFeedbackAPIResponse.json │ │ ├── userFeedbackProps.json │ │ ├── userListeningActivityAllTime.json │ │ ├── userListeningActivityMonth.json │ │ ├── userListeningActivityProcessDataAllTime.json │ │ ├── userListeningActivityProcessDataMonth.json │ │ ├── userListeningActivityProcessDataWeek.json │ │ ├── userListeningActivityProcessDataYear.json │ │ ├── userListeningActivityWeek.json │ │ ├── userListeningActivityYear.json │ │ ├── userPinsProps.json │ │ ├── userRecordings.json │ │ ├── userRecordingsProcessData.json │ │ ├── userReleaseGroups.json │ │ ├── userReleaseGroupsProcessData.json │ │ ├── userReleases.json │ │ ├── userReleasesProcessData.json │ │ ├── userSocialNetworkProps.json │ │ └── year-in-music-data.json │ │ ├── cb-review │ │ └── CBReviewModal.test.tsx │ │ ├── common │ │ ├── brainzplayer │ │ │ ├── BrainzPlayer.test.tsx │ │ │ ├── BrainzPlayerUI.test.tsx │ │ │ ├── MusicPlayer.test.tsx │ │ │ ├── SoundcloudPlayer.test.tsx │ │ │ ├── SpotifyPlayer.test.tsx │ │ │ └── YoutubePlayer.test.tsx │ │ └── listens │ │ │ ├── ListenCard.test.tsx │ │ │ ├── ListenControl.test.tsx │ │ │ ├── ListenCountCard.test.tsx │ │ │ ├── ListensControls.test.tsx │ │ │ └── RecommendationFeedbackComponent.test.tsx │ │ ├── components │ │ ├── Card.test.tsx │ │ ├── Loader.test.tsx │ │ └── Pill.test.tsx │ │ ├── explore │ │ ├── fresh-releases │ │ │ └── FreshReleases.test.tsx │ │ └── huesound │ │ │ └── HueSound.test.tsx │ │ ├── jest-setup.ts │ │ ├── lastfm │ │ ├── LibreFMImporter.test.tsx │ │ └── LibreFMImporterModal.test.tsx │ │ ├── personal-recommendations │ │ ├── NamePill.test.tsx │ │ ├── PersonalRecommendationsModal.test.tsx │ │ └── SearchDropDown.test.tsx │ │ ├── pins │ │ ├── PinRecordingModal.test.tsx │ │ ├── PinnedRecordingCard.test.tsx │ │ └── UserPins.test.tsx │ │ ├── playlists │ │ └── Playlist.test.tsx │ │ ├── recent │ │ └── RecentListens.test.tsx │ │ ├── recommended │ │ ├── RecommendationControl.test.tsx │ │ └── Recommendations.test.tsx │ │ ├── test-react-query.tsx │ │ ├── test-utils.ts │ │ ├── test-utils │ │ └── rtl-test-utils.tsx │ │ ├── user-feed │ │ ├── NetworkFeed.test.tsx │ │ └── UserFeed.test.tsx │ │ ├── user-settings │ │ └── SelectTimezone.test.tsx │ │ ├── user │ │ ├── Dashboard.test.tsx │ │ ├── follow │ │ │ ├── FollowButton.test.tsx │ │ │ ├── FollowerFollowingModal.test.tsx │ │ │ ├── SimilarUsersModal.test.tsx │ │ │ └── UserSocialNetwork.test.tsx │ │ ├── link-listens │ │ │ └── LinkListens.test.tsx │ │ ├── stats │ │ │ ├── BarDualTone.test.tsx │ │ │ ├── SimilarityScore.test.tsx │ │ │ ├── UserArtistActivity.test.tsx │ │ │ ├── UserArtistMap.test.tsx │ │ │ ├── UserDailyActivity.test.tsx │ │ │ ├── UserEntityChart.test.tsx │ │ │ ├── UserListeningActivity.test.tsx │ │ │ ├── UserReports.test.tsx │ │ │ └── UserTopEntity.test.tsx │ │ └── taste │ │ │ └── UserFeedback.test.tsx │ │ └── utils │ │ ├── APIService.test.ts │ │ ├── ErrorBoundary.test.tsx │ │ └── utils.test.tsx ├── robots.txt └── sound │ └── 5-seconds-of-silence.mp3 ├── jest.config.js ├── listenbrainz ├── __init__.py ├── api_compat.py ├── art │ ├── cover_art_generator.py │ └── misc │ │ └── sample_cover_art_grid_post_request.json ├── background │ ├── __init__.py │ ├── background_tasks.py │ ├── delete.py │ └── export.py ├── config.py.sample ├── db │ ├── __init__.py │ ├── artist.py │ ├── color.py │ ├── couchdb.py │ ├── cover_art.py │ ├── do_not_recommend.py │ ├── donation.py │ ├── dump_entry.py │ ├── exceptions.py │ ├── external_service_oauth.py │ ├── feedback.py │ ├── fresh_releases.py │ ├── genre.py │ ├── lastfm_session.py │ ├── lastfm_token.py │ ├── lastfm_user.py │ ├── lb_radio_artist.py │ ├── licenses │ │ ├── COPYING-PublicDomain │ │ └── README.md │ ├── listens_importer.py │ ├── mbid_manual_mapping.py │ ├── metadata.py │ ├── missing_musicbrainz_data.py │ ├── model │ │ ├── __init__.py │ │ ├── color.py │ │ ├── feedback.py │ │ ├── fresh_releases.py │ │ ├── mbid_manual_mapping.py │ │ ├── metadata.py │ │ ├── pinned_recording.py │ │ ├── playlist.py │ │ ├── recommendation_feedback.py │ │ ├── review.py │ │ └── user_timeline_event.py │ ├── msid_mbid_mapping.py │ ├── musicbrainz_entity.py │ ├── pinned_recording.py │ ├── playlist.py │ ├── popularity.py │ ├── recommendations_cf_recording.py │ ├── recommendations_cf_recording_feedback.py │ ├── recording.py │ ├── release.py │ ├── similar_users.py │ ├── similarity.py │ ├── spotify.py │ ├── stats.py │ ├── tags.py │ ├── testing.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_color.py │ │ ├── test_couchdb.py │ │ ├── test_do_not_recommend.py │ │ ├── test_dump_entry.py │ │ ├── test_external_service_oauth.py │ │ ├── test_feedback.py │ │ ├── test_lastfm_session.py │ │ ├── test_lastfm_token.py │ │ ├── test_lastfm_user.py │ │ ├── test_listens_importer.py │ │ ├── test_mbid_manual_mapping.py │ │ ├── test_missing_musicbrainz_data.py │ │ ├── test_msid_mbid_mapping.py │ │ ├── test_pinned_recording.py │ │ ├── test_playlist.py │ │ ├── test_recommendations_cf_recording.py │ │ ├── test_recommendations_cf_recording_feedback.py │ │ ├── test_similar_users.py │ │ ├── test_spotify.py │ │ ├── test_stats.py │ │ ├── test_user.py │ │ ├── test_user_relationship.py │ │ ├── test_user_setting.py │ │ ├── test_user_timeline_event.py │ │ ├── test_validators.py │ │ └── utils.py │ ├── timescale.py │ ├── user.py │ ├── user_relationship.py │ ├── user_setting.py │ ├── user_timeline_event.py │ └── year_in_music.py ├── domain │ ├── __init__.py │ ├── apple.py │ ├── brainz_service.py │ ├── critiquebrainz.py │ ├── external_service.py │ ├── importer_service.py │ ├── lastfm.py │ ├── musicbrainz.py │ ├── soundcloud.py │ ├── spotify.py │ └── tests │ │ ├── __init__.py │ │ ├── test_critiquebrainz.py │ │ ├── test_external_service.py │ │ └── test_spotify.py ├── dumps │ ├── __init__.py │ ├── check.py │ ├── cleanup.py │ ├── exceptions.py │ ├── exporter.py │ ├── importer.py │ ├── manager.py │ ├── mapping.py │ ├── models.py │ ├── sample.py │ ├── tables.py │ └── tests │ │ ├── __init__.py │ │ ├── test_dump.py │ │ └── test_dump_manager.py ├── labs_api │ ├── __init__.py │ └── labs │ │ ├── __init__.py │ │ ├── api │ │ ├── __init__.py │ │ ├── apple │ │ │ ├── __init__.py │ │ │ ├── apple_mbid_lookup.py │ │ │ └── apple_metadata_lookup.py │ │ ├── artist_country_from_artist_mbid.py │ │ ├── artist_credit_from_artist_mbid.py │ │ ├── artist_credit_recording_lookup.py │ │ ├── artist_credit_recording_release_lookup.py │ │ ├── base_mbid_mapping.py │ │ ├── bulk_tag_lookup.py │ │ ├── explain_mbid_mapping.py │ │ ├── mbid_mapping.py │ │ ├── mbid_mapping_release.py │ │ ├── metadata_index │ │ │ ├── __init__.py │ │ │ ├── metadata_index_from_mbid_lookup.py │ │ │ └── metadata_index_from_metadata_lookup.py │ │ ├── popular_tags.py │ │ ├── recording_from_recording_mbid.py │ │ ├── recording_lookup_base.py │ │ ├── recording_search.py │ │ ├── similar_artists.py │ │ ├── similar_recordings.py │ │ ├── soundcloud │ │ │ ├── __init__.py │ │ │ ├── soundcloud_from_mbid_lookup.py │ │ │ └── soundcloud_from_metadata_lookup.py │ │ ├── spotify │ │ │ ├── __init__.py │ │ │ ├── spotify_mbid_lookup.py │ │ │ └── spotify_metadata_lookup.py │ │ ├── tag_similarity.py │ │ ├── user_listen_sessions.py │ │ └── utils.py │ │ ├── main.py │ │ └── tests │ │ ├── __init__.py │ │ ├── test_artist_country_code_from_artist_mbid.py │ │ ├── test_artist_credit_from_artist_mbid_query.py │ │ ├── test_artist_credit_recording_lookup.py │ │ ├── test_explain_mbid_mapping.py │ │ ├── test_mbid_mapping.py │ │ ├── test_recording_from_recording_mbid.py │ │ ├── test_recording_search.py │ │ └── test_tag_similarity.py ├── listen.py ├── listens_importer │ ├── __init__.py │ ├── base.py │ ├── lastfm.py │ ├── spotify.py │ └── tests │ │ ├── __init__.py │ │ ├── data │ │ ├── spotify_play_no_isrc.json │ │ └── spotify_play_two_artists.json │ │ └── test_spotify_read_listens.py ├── listenstore │ ├── __init__.py │ ├── dump_listenstore.py │ ├── redis_listenstore.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_dumplistenstore.py │ │ ├── test_redislistenstore.py │ │ ├── test_timescale_utils.py │ │ ├── test_timescalelistenstore.py │ │ └── util.py │ ├── timescale_listenstore.py │ └── timescale_utils.py ├── manage.py ├── mbid_mapping_writer │ ├── __init__.py │ ├── job_queue.py │ ├── matcher.py │ ├── mbid_mapper.py │ ├── mbid_mapper_metadata_api.py │ ├── mbid_mapping_writer.py │ └── stop_words.py ├── messybrainz │ ├── README.md │ ├── __init__.py │ ├── exceptions.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_init.py │ │ └── test_update_msids_from_mapping.py │ └── update_msids_from_mapping.py ├── metadata_cache │ ├── __init__.py │ ├── album_handler.py │ ├── apple │ │ ├── __init__.py │ │ ├── client.py │ │ ├── handler.py │ │ └── runner.py │ ├── consumer.py │ ├── crawler.py │ ├── handler.py │ ├── models.py │ ├── seeder.py │ ├── soundcloud │ │ ├── __init__.py │ │ ├── client.py │ │ ├── handler.py │ │ ├── models.py │ │ └── runner.py │ ├── spotify │ │ ├── __init__.py │ │ ├── handler.py │ │ └── runner.py │ ├── store.py │ └── unique_queue.py ├── misc │ └── submit_release.py ├── model │ ├── __init__.py │ ├── external_service_oauth.py │ ├── listens_import.py │ ├── playlist.py │ ├── playlist_recording.py │ ├── reported_users.py │ ├── user.py │ └── utils.py ├── rtd_config.py ├── server.py ├── spark │ ├── __init__.py │ ├── background.py │ ├── handlers.py │ ├── request_manage.py │ ├── request_queries.json │ ├── spark_dataset.py │ ├── spark_reader.py │ └── tests │ │ ├── __init__.py │ │ ├── test_handlers.py │ │ ├── test_query_list.py │ │ └── test_request_manage.py ├── testdata │ ├── additional_info.json │ ├── artist_name_list.json │ ├── artists_listeners_db_data_for_api_test.json │ ├── empty_artist_name.json │ ├── empty_track_name.json │ ├── invalid_artist_mbid.json │ ├── invalid_duration.json │ ├── invalid_listen_missing_track_metadata.json │ ├── invalid_listen_nan_in_json.json │ ├── invalid_listen_null_listened_at.json │ ├── invalid_listen_null_track_metadata.json │ ├── invalid_mbid_listens.json │ ├── invalid_recording_mbid.json │ ├── invalid_release_mbid.json │ ├── invalid_release_name.json │ ├── lastfm_loved_tracks_1.json │ ├── lastfm_loved_tracks_2.json │ ├── listen_having_unicode_null.json │ ├── mb_artist_metadata_cache.json │ ├── mb_artist_metadata_example.json │ ├── mb_lookup_metadata_example.json │ ├── mb_metadata_cache_example.json │ ├── mb_release_group_metadata_cache_example.json │ ├── mbid_country_mapping_result.json │ ├── missing_musicbrainz_data.json │ ├── multi_duration.json │ ├── mv_artist_metadata_example.json │ ├── playing_now_more_than_one_listen.json │ ├── playing_now_ts.json │ ├── playing_now_with_duration.json │ ├── playing_now_with_duration_ms.json │ ├── playing_now_with_ts.json │ ├── release_groups_listeners_db_data_for_api_test.json │ ├── same_batch_duplicates.json │ ├── same_timestamp_diff_track_valid_single.json │ ├── same_timestamp_diff_track_valid_single_2.json │ ├── same_timestamp_diff_track_valid_single_3.json │ ├── similar_artist_db_data_for_api_test.json │ ├── single_more_than_one_listen.json │ ├── sitewide_top_artists_db.json │ ├── sitewide_top_artists_db_data_for_api_test.json │ ├── sitewide_top_artists_db_data_for_api_test_month.json │ ├── sitewide_top_artists_db_data_for_api_test_too_many.json │ ├── sitewide_top_artists_db_data_for_api_test_week.json │ ├── sitewide_top_artists_db_data_for_api_test_year.json │ ├── spotify_recently_played_expected.json │ ├── spotify_recently_played_submitted.json │ ├── timescale_listenstore_test_listens.json │ ├── timescale_listenstore_test_listens_2.json │ ├── timescale_listenstore_test_listens_over_greater_time_range.json │ ├── timestamp_before_lfm_founding.json │ ├── timestamp_in_ns.json │ ├── too_large_listen.json │ ├── too_long_tag.json │ ├── too_many_tags.json │ ├── top_recording_db_data_for_api_test.json │ ├── user_artist_map_db.json │ ├── user_artist_map_db_data_for_api_test.json │ ├── user_artist_map_db_data_for_api_test_month.json │ ├── user_artist_map_db_data_for_api_test_week.json │ ├── user_artist_map_db_data_for_api_test_year.json │ ├── user_daily_activity_api_output.json │ ├── user_daily_activity_api_output_month.json │ ├── user_daily_activity_api_output_week.json │ ├── user_daily_activity_api_output_year.json │ ├── user_daily_activity_db.json │ ├── user_daily_activity_db_data_for_api_test.json │ ├── user_daily_activity_db_data_for_api_test_month.json │ ├── user_daily_activity_db_data_for_api_test_week.json │ ├── user_daily_activity_db_data_for_api_test_year.json │ ├── user_export_test.json │ ├── user_fresh_releases.json │ ├── user_listening_activity_db.json │ ├── user_listening_activity_db_data_for_api_test.json │ ├── user_listening_activity_db_data_for_api_test_month.json │ ├── user_listening_activity_db_data_for_api_test_week.json │ ├── user_listening_activity_db_data_for_api_test_year.json │ ├── user_top_artists_db.json │ ├── user_top_artists_db_data_for_api_test.json │ ├── user_top_artists_db_data_for_api_test_month.json │ ├── user_top_artists_db_data_for_api_test_too_many.json │ ├── user_top_artists_db_data_for_api_test_week.json │ ├── user_top_artists_db_data_for_api_test_year.json │ ├── user_top_recordings_db.json │ ├── user_top_recordings_db_data_for_api_test.json │ ├── user_top_recordings_db_data_for_api_test_month.json │ ├── user_top_recordings_db_data_for_api_test_too_many.json │ ├── user_top_recordings_db_data_for_api_test_week.json │ ├── user_top_recordings_db_data_for_api_test_year.json │ ├── user_top_release_groups_db.json │ ├── user_top_release_groups_db_data_for_api_test.json │ ├── user_top_release_groups_db_data_for_api_test_month.json │ ├── user_top_release_groups_db_data_for_api_test_too_many.json │ ├── user_top_release_groups_db_data_for_api_test_week.json │ ├── user_top_release_groups_db_data_for_api_test_year.json │ ├── user_top_releases_db.json │ ├── user_top_releases_db_data_for_api_test.json │ ├── user_top_releases_db_data_for_api_test_month.json │ ├── user_top_releases_db_data_for_api_test_too_many.json │ ├── user_top_releases_db_data_for_api_test_week.json │ ├── user_top_releases_db_data_for_api_test_year.json │ ├── valid_duration.json │ ├── valid_import.json │ ├── valid_playing_now.json │ └── valid_single.json ├── tests │ ├── __init__.py │ ├── integration │ │ ├── __init__.py │ │ ├── test_api.py │ │ ├── test_api_compat.py │ │ ├── test_api_compat_deprecated.py │ │ ├── test_atom_feeds.py │ │ ├── test_do_not_recommend_api.py │ │ ├── test_export.py │ │ ├── test_feed_api.py │ │ ├── test_feedback_api.py │ │ ├── test_fresh_release_api.py │ │ ├── test_missing_musicbrainz_data_api.py │ │ ├── test_pinned_recording_api.py │ │ ├── test_playlist_api.py │ │ ├── test_recommendations_cf_api.py │ │ ├── test_recommendations_cf_recording_feedback_api.py │ │ ├── test_settings_views.py │ │ ├── test_spotify_read_listens.py │ │ ├── test_stats_api.py │ │ ├── test_timescale_writer.py │ │ ├── test_user_settings_api.py │ │ ├── test_user_timeline_event_api.py │ │ └── test_websockets.py │ ├── unit │ │ ├── test_listen.py │ │ └── test_utils.py │ └── utils.py ├── timescale_writer │ ├── __init__.py │ └── timescale_writer.py ├── troi │ ├── __init__.py │ ├── daily_jams.py │ ├── export.py │ ├── import_ms.py │ ├── spark.py │ ├── tests │ │ ├── __init__.py │ │ └── test_spark.py │ ├── utils.py │ ├── weekly_playlists.py │ └── year_in_music.py ├── utils.py ├── webserver │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── test_admin.py │ │ └── views.py │ ├── converters.py │ ├── decorators.py │ ├── errors.py │ ├── flash.py │ ├── login │ │ ├── __init__.py │ │ ├── copy_files_from_mb_to_lb.py │ │ └── provider.py │ ├── models.py │ ├── rabbitmq_connection.py │ ├── redis_connection.py │ ├── static_manager.py │ ├── templates │ │ ├── admin │ │ │ ├── home.html │ │ │ └── master.html │ │ ├── art │ │ │ ├── index.html │ │ │ └── svg-templates │ │ │ │ ├── designer-top-10-alt.svg │ │ │ │ ├── designer-top-10.svg │ │ │ │ ├── designer-top-5.svg │ │ │ │ ├── lps-on-the-floor.svg │ │ │ │ ├── macros.j2 │ │ │ │ ├── simple-grid.svg │ │ │ │ ├── year-in-music-2024 │ │ │ │ ├── yim-2024-albums.svg │ │ │ │ ├── yim-2024-artists.svg │ │ │ │ ├── yim-2024-discovery-playlist.svg │ │ │ │ ├── yim-2024-missed-tracks-playlist.svg │ │ │ │ ├── yim-2024-overview.svg │ │ │ │ ├── yim-2024-stats.svg │ │ │ │ └── yim-2024-tracks.svg │ │ │ │ ├── yim-2022-albums.svg │ │ │ │ ├── yim-2022-artists.svg │ │ │ │ ├── yim-2022-playlists.svg │ │ │ │ ├── yim-2022-tracks.svg │ │ │ │ ├── yim-2022.svg │ │ │ │ ├── yim-2023-albums.svg │ │ │ │ ├── yim-2023-artists.svg │ │ │ │ ├── yim-2023-playlist-arrows.svg │ │ │ │ ├── yim-2023-playlist-hug.svg │ │ │ │ ├── yim-2023-stats.svg │ │ │ │ ├── yim-2023-tracks.svg │ │ │ │ └── yim-2023.svg │ │ ├── atom │ │ │ ├── cb_review_event.html │ │ │ ├── follow_event.html │ │ │ ├── fresh_releases.html │ │ │ ├── listen_event.html │ │ │ ├── listens.html │ │ │ ├── notification_event.html │ │ │ ├── personal_recommendation_event.html │ │ │ ├── playlist.html │ │ │ ├── recording.html │ │ │ ├── recording_pin_event.html │ │ │ ├── recording_recommendation_event.html │ │ │ ├── top_albums.html │ │ │ ├── top_artists.html │ │ │ └── top_tracks.html │ │ ├── base.html │ │ ├── emails │ │ │ ├── artist_relation_import_notification.txt │ │ │ ├── cf_candidate_sets_upload_notification.txt │ │ │ ├── cf_recording_dataframes_upload_notification.txt │ │ │ ├── cf_recording_model_upload_notification.txt │ │ │ ├── cf_recording_recommendation_notification.txt │ │ │ ├── data_dump_created_notification.txt │ │ │ ├── data_dump_outdated.txt │ │ │ ├── dump_import_failure.txt │ │ │ ├── export_completed.txt │ │ │ ├── id_paused.txt │ │ │ ├── id_unpaused.txt │ │ │ ├── listens_importer_error.txt │ │ │ ├── mapping_import_notification.txt │ │ │ ├── similar_users_failed_notification.txt │ │ │ ├── similar_users_updated_notification.txt │ │ │ ├── user_stats_notification.txt │ │ │ ├── year_in_music.html │ │ │ └── year_in_music.txt │ │ ├── errors │ │ │ ├── 400.html │ │ │ ├── 401.html │ │ │ ├── 403.html │ │ │ ├── 404.html │ │ │ ├── 413.html │ │ │ ├── 500.html │ │ │ ├── 503.html │ │ │ └── base.html │ │ ├── index.html │ │ ├── macros.html │ │ ├── navbar.html │ │ └── widgets │ │ │ ├── pin.html │ │ │ ├── pin_card.html │ │ │ ├── playing_now.html │ │ │ └── playing_now_card.html │ ├── test │ │ ├── __init__.py │ │ ├── test_api_errors.py │ │ ├── test_routes.py │ │ └── test_utils.py │ ├── testing.py │ ├── timescale_connection.py │ ├── utils.py │ └── views │ │ ├── __init__.py │ │ ├── api.py │ │ ├── api_compat.py │ │ ├── api_compat_deprecated.py │ │ ├── api_tools.py │ │ ├── art.py │ │ ├── art_api.py │ │ ├── atom.py │ │ ├── color_api.py │ │ ├── do_not_recommend_api.py │ │ ├── donor_api.py │ │ ├── donors.py │ │ ├── entity_pages.py │ │ ├── explore.py │ │ ├── explore_api.py │ │ ├── export.py │ │ ├── feedback_api.py │ │ ├── fresh_releases.py │ │ ├── index.py │ │ ├── login.py │ │ ├── metadata_api.py │ │ ├── metadata_viewer.py │ │ ├── missing_musicbrainz_data_api.py │ │ ├── pinned_recording_api.py │ │ ├── player.py │ │ ├── playlist.py │ │ ├── playlist_api.py │ │ ├── popularity_api.py │ │ ├── recommendations_cf_recording.py │ │ ├── recommendations_cf_recording_api.py │ │ ├── recommendations_cf_recording_feedback_api.py │ │ ├── settings.py │ │ ├── social_api.py │ │ ├── stats_api.py │ │ ├── status_api.py │ │ ├── test │ │ ├── __init__.py │ │ ├── test_art.py │ │ ├── test_explore.py │ │ ├── test_index.py │ │ ├── test_login.py │ │ ├── test_player.py │ │ ├── test_recommendations_cf_recording.py │ │ ├── test_settings.py │ │ ├── test_status.py │ │ └── test_user.py │ │ ├── user.py │ │ ├── user_settings_api.py │ │ ├── user_timeline_event_api.py │ │ └── views_utils.py └── websockets │ ├── __init__.py │ ├── listens_dispatcher.py │ └── websockets.py ├── listenbrainz_spark ├── README.md ├── __init__.py ├── config.py.sample ├── constants.py ├── dump │ ├── __init__.py │ ├── ftp.py │ ├── local.py │ └── tests │ │ ├── __init__.py │ │ └── test_ftp.py ├── echo │ ├── __init__.py │ └── echo.py ├── exceptions.py ├── fresh_releases │ ├── __init__.py │ ├── fresh_releases.py │ └── tests │ │ ├── __init__.py │ │ └── test_fresh_releases.py ├── hdfs │ ├── __init__.py │ ├── upload.py │ └── utils.py ├── hdfs_connection.py ├── listens │ ├── __init__.py │ ├── cache.py │ ├── compact.py │ ├── data.py │ ├── delete.py │ ├── dump.py │ ├── metadata.py │ └── tests │ │ ├── __init__.py │ │ ├── test_dump.py │ │ └── test_utils.py ├── missing_mb_data │ ├── __init__.py │ ├── missing_mb_data.py │ └── tests │ │ ├── __init__.py │ │ └── test_missing_mb_data.py ├── mlhd │ ├── __init__.py │ └── download.py ├── path.py ├── popularity │ ├── __init__.py │ ├── common.py │ ├── listens.py │ ├── main.py │ └── mlhd.py ├── postgres │ ├── __init__.py │ ├── artist.py │ ├── artist_credit.py │ ├── feedback.py │ ├── recording.py │ ├── release.py │ ├── release_group.py │ ├── tag.py │ └── utils.py ├── query_map.py ├── recommendations │ ├── README.md │ ├── __init__.py │ ├── dataframe_utils.py │ ├── recording │ │ ├── __init__.py │ │ ├── create_dataframes.py │ │ ├── discovery.py │ │ ├── recommend.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_dataframe.py │ │ │ ├── test_models.py │ │ │ └── test_recommend.py │ │ └── train_models.py │ ├── templates │ │ ├── index.html │ │ └── model.html │ ├── tests │ │ ├── __init__.py │ │ └── test_dataframe_utils.py │ └── utils.py ├── request_consumer │ ├── __init__.py │ ├── request_consumer.py │ └── test_request_consumer.py ├── schema.py ├── similarity │ ├── __init__.py │ ├── artist.py │ ├── recording │ │ ├── __init__.py │ │ ├── common.py │ │ ├── listens.py │ │ └── mlhd.py │ └── user.py ├── stats │ ├── __init__.py │ ├── common │ │ ├── __init__.py │ │ └── listening_activity.py │ ├── incremental │ │ ├── __init__.py │ │ ├── incremental_stats_engine.py │ │ ├── listener │ │ │ ├── __init__.py │ │ │ ├── artist.py │ │ │ ├── entity.py │ │ │ └── release_group.py │ │ ├── message_creator.py │ │ ├── query_provider.py │ │ ├── range_selector.py │ │ ├── sitewide │ │ │ ├── __init__.py │ │ │ ├── artist.py │ │ │ ├── artist_map.py │ │ │ ├── entity.py │ │ │ ├── listening_activity.py │ │ │ ├── recording.py │ │ │ ├── release.py │ │ │ └── release_group.py │ │ └── user │ │ │ ├── __init__.py │ │ │ ├── artist.py │ │ │ ├── artist_map.py │ │ │ ├── daily_activity.py │ │ │ ├── entity.py │ │ │ ├── listening_activity.py │ │ │ ├── recording.py │ │ │ ├── release.py │ │ │ └── release_group.py │ ├── listener │ │ ├── __init__.py │ │ ├── entity.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ └── test_entity_stats.py │ ├── sitewide │ │ ├── __init__.py │ │ ├── entity.py │ │ ├── listening_activity.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ └── test_sitewide_artist.py │ ├── tests │ │ ├── __init__.py │ │ └── test_init.py │ └── user │ │ ├── __init__.py │ │ ├── daily_activity.py │ │ ├── entity.py │ │ ├── listening_activity.py │ │ └── tests │ │ ├── __init__.py │ │ ├── test_listening_activity_range_selector.py │ │ └── test_user_stats.py ├── tags │ ├── __init__.py │ └── tags.py ├── testdata │ ├── artist_country_code.parquet │ ├── artist_credit_mbid.parquet │ ├── full-dump-1 │ │ ├── 0.parquet │ │ ├── 1.parquet │ │ ├── 2.parquet │ │ ├── 3.parquet │ │ ├── 4.parquet │ │ ├── 5.parquet │ │ ├── 6.parquet │ │ ├── COPYING │ │ ├── END_TIMESTAMP │ │ ├── SCHEMA_SEQUENCE │ │ └── START_TIMESTAMP │ ├── import_metadata.json │ ├── incremental-dump-2 │ │ ├── 0.parquet │ │ ├── COPYING │ │ ├── END_TIMESTAMP │ │ ├── SCHEMA_SEQUENCE │ │ └── START_TIMESTAMP │ ├── incremental-dump-3 │ │ ├── 0.parquet │ │ ├── COPYING │ │ ├── END_TIMESTAMP │ │ ├── SCHEMA_SEQUENCE │ │ └── START_TIMESTAMP │ ├── incremental-dump-4 │ │ └── fresh_releases_listens.parquet │ ├── incremental-dump-5 │ │ └── rec_listens.parquet │ ├── mapped_listens.parquet │ ├── mapped_listens_candidate_sets.parquet │ ├── mapped_listens_subset.parquet │ ├── missing_musicbrainz_data.json │ ├── recording_artist.parquet │ ├── release_group_metadata_cache.parquet │ ├── release_metadata_cache.parquet │ ├── sitewide_fresh_releases.json │ ├── sitewide_top_artists_all_time.json │ ├── user_daily_activity_all_time.json │ ├── user_fresh_releases_output.json │ ├── user_listening_activity_all_time.json │ ├── user_top_artist_listeners_output.json │ ├── user_top_artists_output.json │ ├── user_top_recordings_output.json │ ├── user_top_release_group_listeners_output.json │ ├── user_top_release_groups_output.json │ └── user_top_releases_output.json ├── tests │ ├── __init__.py │ └── test_utils.py ├── troi │ ├── __init__.py │ └── periodic_jams.py ├── utils.py └── year_in_music │ ├── __init__.py │ ├── day_of_week.py │ ├── listen_count.py │ ├── listening_time.py │ ├── listens_per_day.py │ ├── most_listened_year.py │ ├── new_artists_discovered.py │ ├── new_releases_of_top_artists.py │ ├── similar_users.py │ ├── top_discoveries.py │ ├── top_genres.py │ ├── top_missed_recordings.py │ ├── top_stats.py │ └── utils.py ├── manage.py ├── mbid_mapping ├── Dockerfile ├── README.md ├── __init__.py ├── admin │ └── data_dump_files │ │ ├── COPYING │ │ ├── README │ │ └── README_data_dump_files.md ├── build.sh ├── config.py.sample ├── cron_job.py ├── docker │ ├── consul-template.conf │ ├── consul_config.py.ctmpl │ ├── crontab │ ├── mapper.service │ └── push.sh ├── manage.py ├── manage_cron.py ├── mapping │ ├── __init__.py │ ├── album_metadata_index.py │ ├── apple_metadata_index.py │ ├── bulk_table.py │ ├── canonical_musicbrainz_data.py │ ├── canonical_musicbrainz_data_base.py │ ├── canonical_musicbrainz_data_release_support.py │ ├── canonical_recording_redirect.py │ ├── canonical_recording_release_redirect.py │ ├── canonical_release.py │ ├── canonical_release_redirect.py │ ├── cube.py │ ├── custom_sorts.py │ ├── mapping_test │ │ ├── __init__.py │ │ ├── mapping_test.py │ │ └── mapping_test_cases.csv │ ├── mb_artist_metadata_cache.py │ ├── mb_cache_base.py │ ├── mb_metadata_cache.py │ ├── mb_release_group_cache.py │ ├── release_colors.py │ ├── search.py │ ├── soundcloud_metadata_index.py │ ├── spotify_metadata_index.py │ ├── typesense_index.py │ └── utils.py ├── reports │ ├── top_discoveries.py │ └── tracks_of_the_year.py ├── requirements.txt └── similar │ └── tag_similarity.py ├── mlhd_manage.py ├── package-lock.json ├── package.json ├── pytest.ini ├── pytest.spark.ini ├── requirements.txt ├── requirements_development.txt ├── requirements_spark.txt ├── spark_config.sh.sample ├── spark_manage.py ├── test.sh ├── tsconfig.json └── webpack.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | /.git* 2 | 3 | # Byte-compiled / optimized / DLL files 4 | **/__pycache__/ 5 | **/*.py[cod] 6 | 7 | # Virtual environment 8 | /env*/ 9 | /venv*/ 10 | /build*/ 11 | 12 | # Logs 13 | **/*.log 14 | **/pip-log.txt 15 | **/pip-delete-this-directory.txt 16 | 17 | # Test results 18 | **/htmlcov/ 19 | **/.coverage 20 | 21 | # Private credentials 22 | /credentials 23 | 24 | # Javascript packages 25 | /node_modules/ 26 | 27 | # ListenBrainz local dump directory 28 | /listenbrainz-export 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## What’s Changed 3 | 4 | $CHANGES -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Drafts your next Release notes as Pull Requests are merged into "master" 14 | - uses: release-drafter/release-drafter@v6 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.pep8speaks.yml: -------------------------------------------------------------------------------- 1 | scanner: 2 | diff_only: True # If False, the entire file touched by the Pull Request is scanned for errors. If True, only the diff is scanned. 3 | linter: pycodestyle # Other option is flake8 4 | 5 | pycodestyle: # Same as scanner.linter value. Other option is flake8 6 | max-line-length: 130 # Default is 79 in PEP 8 7 | 8 | no_blank_comment: True # If True, no comment is made on PR without any errors. -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-24.04 5 | tools: 6 | python: "3.13" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | formats: all 12 | 13 | python: 14 | install: 15 | - requirements: docs/requirements.txt 16 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: "stylelint-config-recommended-less", 3 | customSyntax: "postcss-less", 4 | plugins: ["stylelint-prettier"], 5 | rules: { 6 | "prettier/prettier": true, 7 | "no-descending-specificity": null, 8 | "function-calc-no-unspaced-operator": null, 9 | }, 10 | ignoreFiles: [ 11 | "**/static/css/theme/bootstrap/*", 12 | "**/static/css/theme/bootstrap/mixins/*", 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "ms-python.autopep8" 4 | }, 5 | "python.formatting.provider": "none" 6 | } -------------------------------------------------------------------------------- /.well-known/funding-manifest-urls: -------------------------------------------------------------------------------- 1 | https://metabrainz.org/funding.json 2 | -------------------------------------------------------------------------------- /Dockerfile.nginx.prod: -------------------------------------------------------------------------------- 1 | FROM nginx:1.13.5 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y --no-install-recommends 5 | 6 | WORKDIR /etc 7 | COPY ./docker/prod/nginx/nginx.conf /etc/nginx/conf.d/default.conf 8 | -------------------------------------------------------------------------------- /admin/sql/create_db.sql: -------------------------------------------------------------------------------- 1 | -- Create the user and the database. Must run as user postgres. 2 | 3 | CREATE USER listenbrainz NOCREATEDB NOSUPERUSER; 4 | ALTER USER listenbrainz WITH PASSWORD 'listenbrainz'; 5 | CREATE DATABASE listenbrainz WITH OWNER = listenbrainz TEMPLATE template0 ENCODING = 'UNICODE'; 6 | -------------------------------------------------------------------------------- /admin/sql/create_extensions.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 2 | CREATE EXTENSION IF NOT EXISTS "pg_trgm"; 3 | -- The following line is now executed by the init-db action from manage.py. If you create a DB without the init-db function 4 | -- you will need to execute the following ALTER in order to complete your DB setup. 5 | --ALTER DATABASE listenbrainz SET pg_trgm.word_similarity_threshold = 0.1; 6 | CREATE EXTENSION IF NOT EXISTS "cube"; 7 | -------------------------------------------------------------------------------- /admin/sql/create_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA api_compat; 2 | CREATE SCHEMA recommendation; 3 | -------------------------------------------------------------------------------- /admin/sql/create_test_db.sql: -------------------------------------------------------------------------------- 1 | -- Create the user and the database. Must run as user postgres. 2 | 3 | CREATE USER lb_test NOCREATEDB NOSUPERUSER; 4 | CREATE DATABASE lb_test WITH OWNER = lb_test TEMPLATE template0 ENCODING = 'UNICODE'; 5 | -------------------------------------------------------------------------------- /admin/sql/drop_db.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS listenbrainz; 2 | DROP USER IF EXISTS listenbrainz; 3 | -------------------------------------------------------------------------------- /admin/sql/updates/2017-06-05-add-last-login.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Add column for last login to user table 4 | ALTER TABLE "user" ADD COLUMN last_login TIMESTAMP WITH TIME ZONE; 5 | 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /admin/sql/updates/2017-06-23-add-latest-import.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Add latest_import timestamp column to "user" table 4 | ALTER TABLE "user" ADD COLUMN latest_import TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT TIMESTAMP 'epoch'; 5 | 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /admin/sql/updates/2017-07-03-alter-last-login.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- First update all null values with the created value for that row 4 | UPDATE "user" SET last_login = created WHERE last_login IS NULL; 5 | 6 | -- Alter the last_login column to add the NOT NULL constraint 7 | ALTER TABLE "user" ALTER COLUMN last_login SET NOT NULL; 8 | 9 | -- Alter the last_login column so that the default value becomes NOW() 10 | ALTER TABLE "user" ALTER COLUMN last_login SET DEFAULT NOW(); 11 | 12 | COMMIT; 13 | -------------------------------------------------------------------------------- /admin/sql/updates/2017-10-14-add-incremental-dump-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE data_dump ( 4 | id SERIAL, 5 | created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() 6 | ); 7 | 8 | COMMIT; 9 | -------------------------------------------------------------------------------- /admin/sql/updates/2017-10-15-remove-listen-tables.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | DROP TABLE IF EXISTS listen CASCADE; 4 | DROP TABLE IF EXISTS listen_json CASCADE; 5 | 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /admin/sql/updates/2018-05-22-add-gdpr-user-columns.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE "user" ADD COLUMN gdpr_agreed TIMESTAMP WITH TIME ZONE; 4 | 5 | COMMIT; 6 | 7 | -------------------------------------------------------------------------------- /admin/sql/updates/2018-06-13-add-musicbrainz-row-id-column.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Add musicbrainz_row_id column to the "user" table 4 | ALTER TABLE "user" ADD COLUMN musicbrainz_row_id INTEGER; 5 | ALTER TABLE "user" ADD CONSTRAINT user_musicbrainz_row_id_key UNIQUE (musicbrainz_row_id); 6 | 7 | COMMIT; 8 | -------------------------------------------------------------------------------- /admin/sql/updates/2018-06-21-make-musicbrainz-row-id-not-null.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE "user" ALTER COLUMN musicbrainz_row_id SET NOT NULL; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2018-11-17-add-on-delete-cascade-to-spotify-fk.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE spotify_auth 4 | DROP CONSTRAINT spotify_auth_user_id_foreign_key; 5 | 6 | ALTER TABLE spotify_auth 7 | ADD CONSTRAINT spotify_auth_user_id_foreign_key 8 | FOREIGN KEY (user_id) 9 | REFERENCES "user" (id) 10 | ON DELETE CASCADE; 11 | 12 | COMMIT; 13 | -------------------------------------------------------------------------------- /admin/sql/updates/2019-01-04-add-user-login-id.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Add column for alternative user id for login purpose to user table 4 | ALTER TABLE "user" ADD COLUMN user_login_id UUID NOT NULL DEFAULT uuid_generate_v4(); 5 | 6 | COMMIT; -------------------------------------------------------------------------------- /admin/sql/updates/2019-02-08-save-spotify-permissions.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- rename active to record_listens 4 | ALTER TABLE spotify_auth RENAME active TO record_listens; 5 | 6 | -- add column permission to table 7 | ALTER TABLE spotify_auth ADD COLUMN permission VARCHAR; 8 | -- set value to 'user-read-recently-played' for all current users 9 | UPDATE spotify_auth 10 | SET permission = 'user-read-recently-played'; 11 | -- set the column as not null 12 | ALTER TABLE spotify_auth ALTER COLUMN permission SET NOT NULL; 13 | 14 | COMMIT; 15 | -------------------------------------------------------------------------------- /admin/sql/updates/2019-02-13-change-login-id-type.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE "user" DROP COLUMN user_login_id; 4 | ALTER TABLE "user" ADD COLUMN login_id TEXT NOT NULL DEFAULT uuid_generate_v4()::text; 5 | 6 | UPDATE "user" 7 | SET login_id = id::text; 8 | 9 | ALTER TABLE "user" ADD CONSTRAINT user_login_id_key UNIQUE (login_id); 10 | CREATE UNIQUE INDEX login_id_ndx_user ON "user" (login_id); 11 | 12 | COMMIT; 13 | 14 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-05-16-rename-recommendation-table-col.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE recommendation.cf_recording 4 | RENAME COLUMN recording_msid TO recording_mbid; 5 | 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-05-20-change-recommendation-cf-recording-col-type.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE recommendation.cf_recording DROP recording_mbid; 4 | ALTER TABLE recommendation.cf_recording ADD COLUMN recording_mbid JSONB NOT NULL; 5 | 6 | ALTER TABLE recommendation.cf_recording ADD CONSTRAINT user_id_unique UNIQUE (user_id); 7 | 8 | COMMIT; 9 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-05-20-drop-recommendation-cf-recording-col.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE recommendation.cf_recording DROP TYPE; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-05-20-truncate-user-stats-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | TRUNCATE TABLE statistics.user; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-06-17-add-listening-activity-col.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE statistics.user ADD COLUMN listening_activity JSONB; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-07-13-add-daily-activity-col.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE statistics.user ADD COLUMN daily_activity JSONB; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-07-17-add-similar-user-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE recommendation.similar_user ( 4 | user_id INTEGER NOT NULL, -- FK to "user".id 5 | similar_users JSONB, 6 | last_updated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() 7 | ); 8 | 9 | ALTER TABLE recommendation.similar_user 10 | ADD CONSTRAINT similar_user_user_id_foreign_key 11 | FOREIGN KEY (user_id) 12 | REFERENCES "user" (id) 13 | ON DELETE CASCADE; 14 | 15 | CREATE UNIQUE INDEX user_id_ndx_similar_user ON recommendation.similar_user (user_id); 16 | 17 | COMMIT; 18 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-07-29-add-artist-map-col.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE statistics.user ADD COLUMN artist_map JSONB; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2020-08-14-add-sitewide-stats-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE statistics.sitewide ( 4 | id SERIAL, --pk 5 | stats_range TEXT, 6 | artist JSONB, 7 | release JSONB, 8 | recording JSONB, 9 | listening_activity JSONB, 10 | last_updated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() 11 | ); 12 | 13 | ALTER TABLE statistics.sitewide ADD CONSTRAINT stats_range_uniq UNIQUE (stats_range); 14 | 15 | COMMIT; 16 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-03-01-remove-follow-list.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | DROP TABLE IF EXISTS follow_list; 4 | 5 | COMMIT -------------------------------------------------------------------------------- /admin/sql/updates/2021-03-14-add-user-timeline-enum-type-notification.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TYPE user_timeline_event_type_enum ADD VALUE 'notification' AFTER 'recording_recommendation'; 4 | 5 | COMMIT; -------------------------------------------------------------------------------- /admin/sql/updates/2021-04-21-2-modify-external-service-oauth-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | TRUNCATE TABLE external_service_oauth CASCADE; 4 | 5 | ALTER TABLE external_service_oauth ADD COLUMN scopes TEXT[]; 6 | ALTER TABLE external_service_oauth DROP COLUMN service_details; 7 | ALTER TABLE external_service_oauth DROP COLUMN record_listens; 8 | 9 | COMMIT; -------------------------------------------------------------------------------- /admin/sql/updates/2021-05-03-add-email-to-user-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | ALTER TABLE "user" ADD COLUMN email TEXT; 3 | COMMIT; -------------------------------------------------------------------------------- /admin/sql/updates/2021-06-25-add-optional-mbid-to-pinned-recording-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Changes: recording_mbid column is now optional 4 | -- added a required recording_msid column, 5 | 6 | ALTER TABLE pinned_recording 7 | ALTER COLUMN recording_mbid DROP NOT NULL, 8 | ADD COLUMN recording_msid UUID NOT NULL; 9 | 10 | COMMIT; -------------------------------------------------------------------------------- /admin/sql/updates/2021-07-10-add-lastfm-external-service-type.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE external_service_oauth_type ADD VALUE 'lastfm'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-07-14-migrate-import-ts-from-users-to-listens-importer.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | INSERT INTO listens_importer(user_id, service, latest_listened_at) 3 | SELECT id, 'lastfm', latest_import 4 | FROM "user" 5 | WHERE latest_import > 'epoch'; 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-07-15-add-librefm-external-service-type.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE external_service_oauth_type ADD VALUE 'librefm'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-07-27-add-critiquebrainz-as-external-service.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE external_service_oauth_type ADD VALUE 'critiquebrainz'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-08-28-add-user-timeline-enum-type-critiquebrainz_review.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE user_timeline_event_type_enum ADD VALUE 'critiquebrainz_review' AFTER 'notification'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-09-28-add-statistics-range-quarter.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE stats_range_type ADD VALUE 'quarter' AFTER 'month'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-09-28-add-user-name-search-support.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS "pg_trgm"; 2 | ALTER DATABASE listenbrainz SET pg_trgm.word_similarity_threshold = 0.1; 3 | BEGIN; 4 | CREATE INDEX CONCURRENTLY user_name_search_trgm_idx ON "user" USING GIST (musicbrainz_id gist_trgm_ops); 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-09-29-add-statistics-range-half-yearly.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE stats_range_type ADD VALUE 'half_yearly' AFTER 'quarter'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-10-07-delete-old-statistics-user-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | DROP TABLE IF EXISTS statistics.user; 3 | 4 | ALTER TABLE statistics.user_new RENAME TO "user"; 5 | ALTER SEQUENCE statistics.user_new_id_seq RENAME TO user_id_seq; 6 | ALTER TABLE statistics.user RENAME CONSTRAINT stats_user_new_pkey TO stats_user_pkey; 7 | ALTER TABLE statistics.user RENAME CONSTRAINT user_stats_new_user_id_foreign_key TO user_stats_user_id_foreign_key; 8 | ALTER INDEX statistics.user_id_ndx__user_stats_new RENAME TO user_id_ndx__user_stats; 9 | COMMIT; 10 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-11-24-add-this-time-ranges.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE stats_range_type ADD VALUE 'this_week'; 2 | ALTER TYPE stats_range_type ADD VALUE 'this_month'; 3 | ALTER TYPE stats_range_type ADD VALUE 'this_year'; 4 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-12-19-make-msid-optional-for-pinned-recordings.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE pinned_recording ALTER COLUMN recording_msid DROP NOT NULL; 4 | 5 | ALTER TABLE pinned_recording 6 | ADD CONSTRAINT pinned_rec_recording_msid_or_recording_mbid_check 7 | CHECK ( recording_msid IS NOT NULL OR recording_mbid IS NOT NULL ); 8 | 9 | COMMIT; 10 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-12-20-add-mbid-to-recording-feedback.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE recording_feedback ALTER COLUMN recording_msid DROP NOT NULL; 4 | 5 | ALTER TABLE recording_feedback ADD COLUMN recording_mbid UUID; 6 | 7 | ALTER TABLE recording_feedback 8 | ADD CONSTRAINT feedback_recording_msid_or_recording_mbid_check 9 | CHECK ( recording_msid IS NOT NULL OR recording_mbid IS NOT NULL ); 10 | 11 | CREATE UNIQUE INDEX user_id_mbid_ndx_rec_feedback ON recording_feedback (user_id, recording_mbid); 12 | 13 | COMMIT; 14 | -------------------------------------------------------------------------------- /admin/sql/updates/2021-16-11-year-in-music.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE statistics.year_in_music ( 4 | user_id INTEGER NOT NULL, -- PK and FK to "user".id 5 | data JSONB 6 | ); 7 | 8 | ALTER TABLE statistics.year_in_music ADD CONSTRAINT stats_year_in_music_pkey PRIMARY KEY (user_id); 9 | 10 | ALTER TABLE statistics.year_in_music 11 | ADD CONSTRAINT user_stats_year_in_music_user_id_foreign_key 12 | FOREIGN KEY (user_id) 13 | REFERENCES "user" (id) 14 | ON DELETE CASCADE; 15 | 16 | COMMIT; 17 | -------------------------------------------------------------------------------- /admin/sql/updates/2022-06-26-add-user-timeline-enum-type-personal-recommendation.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE user_timeline_event_type_enum ADD VALUE 'personal_recording_recommendation' AFTER 'critiquebrainz_review'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2022-10-12-add-troi-user-setting.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE user_setting ADD COLUMN troi JSONB; 4 | 5 | COMMIT; -------------------------------------------------------------------------------- /admin/sql/updates/2022-11-21-remove-unique-index-recording-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | DROP INDEX user_id_mbid_ndx_rec_feedback; 4 | CREATE INDEX user_id_mbid_ndx_rec_feedback ON recording_feedback (user_id, recording_mbid); 5 | 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /admin/sql/updates/2023-03-23-add-external-user-id-column-to-external-services-oauth-table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE external_service_oauth ADD COLUMN external_user_id TEXT; 4 | 5 | COMMIT; -------------------------------------------------------------------------------- /admin/sql/updates/2023-04-20-add-musicbrainz-as-external-service.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE external_service_oauth_type ADD VALUE 'musicbrainz'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2023-04-20-add-soundcloud-as-external-service.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE external_service_oauth_type ADD VALUE 'soundcloud'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2023-07-06-add-apple-as-external-service.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE external_service_oauth_type ADD VALUE 'apple'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2024-01-15-add-deployments-as-external-service.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE external_service_oauth_type ADD VALUE 'musicbrainz-prod'; 2 | ALTER TYPE external_service_oauth_type ADD VALUE 'musicbrainz-beta'; 3 | ALTER TYPE external_service_oauth_type ADD VALUE 'musicbrainz-test'; 4 | -------------------------------------------------------------------------------- /admin/sql/updates/2024-04-03-add-brainzplayer-settings.sql: -------------------------------------------------------------------------------- 1 | 2 | BEGIN; 3 | 4 | ALTER TABLE user_setting 5 | ADD brainzplayer JSONB; 6 | 7 | COMMIT; -------------------------------------------------------------------------------- /admin/sql/updates/2024-07-17-hide-personal-recommendation.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE hide_user_timeline_event_type_enum ADD VALUE 'personal_recording_recommendation' BEFORE 'recording_pin'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2024-08-02-make-external-service-access-token-nullable.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE external_service_oauth ALTER COLUMN access_token DROP NOT NULL; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2024-09-09-add-user-flair.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE user_setting ADD COLUMN flair JSONB; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2025-01-27-add-thanks-event-type.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE user_timeline_event_type_enum ADD VALUE 'thanks' AFTER 'personal_recording_recommendation'; 2 | -------------------------------------------------------------------------------- /admin/sql/updates/2025-02-01-add-user-paused.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE "user" ADD COLUMN is_paused BOOLEAN NOT NULL DEFAULT FALSE; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/updates/2025-02-21-add-data-dump-type.sql: -------------------------------------------------------------------------------- 1 | CREATE TYPE data_dump_type_type AS ENUM ('incremental', 'full'); 2 | 3 | BEGIN; 4 | 5 | ALTER TABLE data_dump ADD COLUMN dump_type data_dump_type_type; 6 | 7 | COMMIT; 8 | -------------------------------------------------------------------------------- /admin/sql/updates/2025-05-05-add-import-info.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE "listens_importer" ADD COLUMN status JSONB; 4 | 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/sql/util/logout_all_users.sql: -------------------------------------------------------------------------------- 1 | -- we use this script when we need to log out all users. 2 | -- see https://flask-login.readthedocs.io/en/latest/#alternative-tokens for how/why it works 3 | BEGIN; 4 | UPDATE "user" 5 | SET login_id = uuid_generate_v4()::text; 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /admin/sql/util/restart_spotify_imports.sql: -------------------------------------------------------------------------------- 1 | -- This script resets the record listens flag for all 2 | -- users, restarting their imports. 3 | 4 | BEGIN; 5 | 6 | UPDATE listens_importer 7 | SET error_message = NULL 8 | WHERE service = 'spotify'; 9 | 10 | COMMIT; 11 | -------------------------------------------------------------------------------- /admin/timescale/create_db.sql: -------------------------------------------------------------------------------- 1 | -- Create the user and the database. Must run as user postgres. 2 | 3 | CREATE USER listenbrainz_ts NOCREATEDB NOSUPERUSER; 4 | ALTER USER listenbrainz_ts WITH PASSWORD 'listenbrainz_ts'; 5 | CREATE DATABASE listenbrainz_ts WITH OWNER = listenbrainz_ts TEMPLATE template1 ENCODING = 'UNICODE'; 6 | -------------------------------------------------------------------------------- /admin/timescale/create_extensions.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE; 2 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 3 | CREATE EXTENSION IF NOT EXISTS "pg_trgm"; 4 | -------------------------------------------------------------------------------- /admin/timescale/create_primary_keys.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE playlist.playlist ADD CONSTRAINT playlist_pkey PRIMARY KEY (id); 4 | ALTER TABLE playlist.playlist_recording ADD CONSTRAINT playlist_recording_pkey PRIMARY KEY (id); 5 | ALTER TABLE mbid_mapping_metadata ADD CONSTRAINT mbid_mapping_metadata_pkey PRIMARY KEY (recording_mbid); 6 | ALTER TABLE mapping.mb_metadata_cache ADD CONSTRAINT mb_metadata_cache_pkey PRIMARY KEY (recording_mbid); 7 | ALTER TABLE background_worker_state ADD CONSTRAINT background_worker_state_pkey PRIMARY KEY (key); 8 | 9 | COMMIT; 10 | -------------------------------------------------------------------------------- /admin/timescale/create_schemas.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA playlist; 2 | CREATE SCHEMA messybrainz; 3 | CREATE SCHEMA mapping; 4 | CREATE SCHEMA spotify_cache; 5 | CREATE SCHEMA apple_cache; 6 | CREATE SCHEMA soundcloud_cache; 7 | CREATE SCHEMA similarity; 8 | CREATE SCHEMA tags; 9 | CREATE SCHEMA popularity; 10 | CREATE SCHEMA statistics; -- this schema is used to store the YIM tables, tables are created dynamically in listenbrainz/db/year_in_music.py 11 | -------------------------------------------------------------------------------- /admin/timescale/create_test_db.sql: -------------------------------------------------------------------------------- 1 | -- Create the user and the database. Must run as user postgres. 2 | 3 | CREATE USER listenbrainz_ts_test NOCREATEDB NOSUPERUSER; 4 | CREATE DATABASE listenbrainz_ts_test WITH OWNER = listenbrainz_ts_test TEMPLATE template0 ENCODING = 'UNICODE'; 5 | -------------------------------------------------------------------------------- /admin/timescale/create_types.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TYPE mbid_mapping_match_type_enum AS ENUM('no_match', 'low_quality', 'med_quality', 'high_quality', 'exact_match'); 4 | CREATE TYPE lb_tag_radio_source_type_enum AS ENUM ('recording', 'artist', 'release-group'); 5 | CREATE TYPE listen_delete_metadata_status_enum AS ENUM ('pending', 'invalid', 'complete'); 6 | 7 | COMMIT; 8 | -------------------------------------------------------------------------------- /admin/timescale/drop_db.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS listenbrainz_ts; 2 | DROP USER IF EXISTS listenbrainz_ts; 3 | -------------------------------------------------------------------------------- /admin/timescale/reset_tables.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | DELETE FROM listen CASCADE; 4 | DELETE FROM listen_delete_metadata CASCADE; 5 | DELETE FROM listen_user_metadata CASCADE; 6 | DELETE FROM mbid_mapping CASCADE; 7 | DELETE FROM mapping.mb_metadata_cache CASCADE; 8 | DELETE FROM messybrainz.submissions CASCADE; 9 | DELETE FROM mbid_manual_mapping CASCADE; 10 | DELETE FROM playlist.playlist CASCADE; 11 | 12 | COMMIT; 13 | -------------------------------------------------------------------------------- /admin/timescale/updates/2021-10-27-add-artist-mbids-constraint.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | ALTER TABLE listen_mbid_mapping 3 | ADD CONSTRAINT listen_mbid_mapping_artist_mbids_check 4 | CHECK ( array_ndims(artist_mbids) = 1 ); 5 | COMMIT; 6 | -------------------------------------------------------------------------------- /admin/timescale/updates/2022-01-27-fixup-created-column.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- created field was not always a part of listen table. at the time it was added, it was set to 'epoch' for pre-existing 4 | -- rows and left as nullable. however, it makes more sense to set created according to listened_at field. we can also 5 | -- add the not null constraint. 6 | UPDATE listen 7 | SET created = to_timestamp(listened_at) 8 | WHERE created = 'epoch'; 9 | 10 | ALTER TABLE listen ALTER COLUMN created SET NOT NULL; 11 | COMMIT; 12 | -------------------------------------------------------------------------------- /admin/timescale/updates/2022-02-02-add-listen-table-delete-column.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | CREATE TABLE listen_delete_metadata ( 3 | id SERIAL NOT NULL, 4 | user_id INTEGER NOT NULL, 5 | listened_at BIGINT NOT NULL, 6 | recording_msid UUID NOT NULL 7 | ); 8 | COMMIT; 9 | -------------------------------------------------------------------------------- /admin/timescale/updates/2022-05-29-add-check-again-column.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | ALTER TABLE mbid_mapping ADD COLUMN check_again TIMESTAMP WITH TIME ZONE; 3 | COMMIT; 4 | 5 | -- first time adding the column need to set check_again column to a non-null 6 | -- value so that next time the msid comes in the rechecking stuff kicks in 7 | BEGIN; 8 | UPDATE mbid_mapping SET check_again = NOW() WHERE match_type = 'no_match'; 9 | COMMIT; 10 | -------------------------------------------------------------------------------- /admin/timescale/updates/2022-10-23-add-artist-similarity.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE similarity.artist ( 4 | mbid0 UUID NOT NULL, 5 | mbid1 UUID NOT NULL, 6 | metadata JSONB NOT NULL 7 | ); 8 | 9 | CREATE UNIQUE INDEX similar_artists_uniq_idx ON similarity.artist (mbid0, mbid1); 10 | -- reverse index is only needed for performance reasons 11 | CREATE UNIQUE INDEX similar_artists_reverse_uniq_idx ON similarity.artist (mbid1, mbid0); 12 | CREATE INDEX similar_artists_algorithm_idx ON similarity.artist USING gin (metadata); 13 | 14 | COMMIT; 15 | -------------------------------------------------------------------------------- /admin/timescale/updates/2022-11-08-add-additional-metadata-playlists.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE playlist.playlist RENAME COLUMN algorithm_metadata TO additional_metadata; 4 | ALTER TABLE playlist.playlist_recording ADD COLUMN additional_metadata JSONB; 5 | 6 | UPDATE playlist.playlist SET additional_metadata = jsonb_build_object('algorithm_metadata', additional_metadata); 7 | 8 | COMMIT; 9 | -------------------------------------------------------------------------------- /admin/timescale/updates/2023-06-130-fix-none-in-recording-mbid.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | UPDATE listen l 4 | SET data = jsonb_set(l.data, '{additional_info,recording_mbid}', coalesce(to_jsonb(null::int), 'null')) 5 | WHERE data->'additional_info'->>'recording_mbid' = 'None' 6 | 7 | commit; 8 | -------------------------------------------------------------------------------- /admin/timescale/updates/2023-09-05-update-column-names-for-spotify-metadata-cache.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE spotify_cache.album RENAME COLUMN spotify_id TO album_id; 4 | ALTER TABLE spotify_cache.artist RENAME COLUMN spotify_id TO artist_id; 5 | ALTER TABLE spotify_cache.track RENAME COLUMN spotify_id TO track_id; 6 | 7 | COMMIT; 8 | -------------------------------------------------------------------------------- /admin/timescale/updates/2024-05-28-add-pg_trgm-extension.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS "pg_trgm"; 2 | ALTER DATABASE listenbrainz_ts SET pg_trgm.word_similarity_threshold = 0.1; 3 | -------------------------------------------------------------------------------- /admin/timescale/updates/2024-07-05-add-soundcloud-metadata-cache-2.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | 4 | ALTER TABLE soundcloud_cache.track ADD COLUMN release_year INTEGER; 5 | ALTER TABLE soundcloud_cache.track ADD COLUMN release_month INTEGER; 6 | ALTER TABLE soundcloud_cache.track ADD COLUMN release_day INTEGER; 7 | 8 | UPDATE soundcloud_cache.track 9 | SET release_year = (data->>'release_year')::int 10 | , release_month = (data->>'release_month')::int 11 | , release_day = (data->>'release_day')::int; 12 | 13 | COMMIT; 14 | -------------------------------------------------------------------------------- /admin/timescale/updates/2025-02-18-add-listen-delete-metadata.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | ALTER TABLE listen_delete_metadata ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE; 4 | ALTER TABLE listen_delete_metadata ADD COLUMN listen_created TIMESTAMP WITH TIME ZONE; 5 | ALTER TABLE listen_delete_metadata 6 | ADD CONSTRAINT listen_delete_metadata_deleted_created_constraint 7 | CHECK ( deleted IS FALSE OR (deleted IS TRUE AND listen_created IS NOT NULL) ); 8 | 9 | COMMIT; 10 | -------------------------------------------------------------------------------- /admin/timescale/updates/2025-02-19-add-user-listen-history-delete.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE deleted_user_listen_history ( 4 | id INTEGER GENERATED ALWAYS AS IDENTITY NOT NULL, 5 | user_id INTEGER NOT NULL, 6 | max_created TIMESTAMP WITH TIME ZONE NOT NULL 7 | ); 8 | 9 | COMMIT; 10 | -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/data/__init__.py -------------------------------------------------------------------------------- /data/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/data/model/__init__.py -------------------------------------------------------------------------------- /data/model/new_releases_stat.py: -------------------------------------------------------------------------------- 1 | import pydantic 2 | from typing import List 3 | 4 | from pydantic import NonNegativeInt 5 | 6 | 7 | class NewReleasesStat(pydantic.BaseModel): 8 | type: str 9 | year: NonNegativeInt 10 | user_id: NonNegativeInt 11 | data: List 12 | -------------------------------------------------------------------------------- /data/model/sitewide_entity.py: -------------------------------------------------------------------------------- 1 | from pydantic import constr, NonNegativeInt 2 | 3 | from data.model.common_stat_spark import StatMessage 4 | from data.model.user_entity import EntityRecord 5 | 6 | 7 | class SitewideEntityStatMessage(StatMessage[EntityRecord]): 8 | """ Format of messages sent to the ListenBrainz Server """ 9 | entity: constr(min_length=1) # The entity for which stats are calculated, i.e artist, release or recording 10 | count: NonNegativeInt 11 | -------------------------------------------------------------------------------- /data/model/user_daily_activity.py: -------------------------------------------------------------------------------- 1 | """ Models for user's daily activity statistics. 2 | The daily activity shows the number of listens submitted to ListenBrainz per hour in last week/month/year. 3 | """ 4 | from pydantic import BaseModel, NonNegativeInt, constr 5 | 6 | 7 | class DailyActivityRecord(BaseModel): 8 | """ Each individual record for user's daily activity contains the time range, 9 | timestamp for start and end of the time range and listen count. 10 | """ 11 | day: constr(min_length=1) 12 | hour: NonNegativeInt 13 | listen_count: NonNegativeInt 14 | -------------------------------------------------------------------------------- /data/postgres/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/data/postgres/__init__.py -------------------------------------------------------------------------------- /docker/consul-template.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | template { 6 | source = "/code/listenbrainz/admin/config.sh.ctmpl" 7 | destination = "/code/listenbrainz/admin/config.sh" 8 | } 9 | -------------------------------------------------------------------------------- /docker/couchdb_test.ini: -------------------------------------------------------------------------------- 1 | [couchdb] 2 | single_node = true 3 | -------------------------------------------------------------------------------- /docker/prod/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name proxy.listenbrainz.org; 4 | root /usr/share/nginx/html; 5 | location / { 6 | try_files $uri @wsgi; 7 | } 8 | location @wsgi { 9 | include uwsgi_params; 10 | uwsgi_pass 10.2.2.45:13037; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/api_compat/api_compat.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="api-compat" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/api_compat/api_compat.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | UWSGI_PID_FILE=/tmp/uwsgi-api-compat.pid 6 | if [ -e "$UWSGI_PID_FILE" ]; then 7 | UWSGI_PID=`cat "$UWSGI_PID_FILE"` 8 | echo "Previous uwsgi master process pid file detected, killing process $UWSGI_PID..." 9 | kill -TERM "$UWSGI_PID" 10 | sleep 4 11 | fi 12 | 13 | # wait for syslog to start up 14 | sleep 1 15 | 16 | exec run-consul-template -config /etc/consul-template-api-compat.conf 17 | -------------------------------------------------------------------------------- /docker/services/api_compat/consul-template-api-compat.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "uwsgi", "/etc/uwsgi/uwsgi-api-compat.ini"] 8 | splay = "5s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/api_compat/uwsgi-api-compat.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | uid = www-data 3 | gid = www-data 4 | master = true 5 | socket = 0.0.0.0:3031 6 | module = listenbrainz.api_compat 7 | callable = application 8 | chdir = /code/listenbrainz 9 | enable-threads = true 10 | processes = 50 11 | log-x-forwarded-for=true 12 | disable-logging = true 13 | ; quit uwsgi if the python app fails to load 14 | need-app = true 15 | die-on-term = true 16 | safe-pidfile = /tmp/uwsgi-api-compat.pid 17 | -------------------------------------------------------------------------------- /docker/services/apple_metadata_cache/apple_metadata_cache.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="apple-metadata-cache" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/apple_metadata_cache/apple_metadata_cache.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-apple-metadata-cache.conf 5 | -------------------------------------------------------------------------------- /docker/services/apple_metadata_cache/consul-template-apple-metadata-cache.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-u", "-m", "listenbrainz.metadata_cache.apple.runner"] 8 | splay = "60s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/background_tasks/background_tasks.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="background-tasks" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/background_tasks/background_tasks.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-background-tasks.conf 5 | -------------------------------------------------------------------------------- /docker/services/background_tasks/consul-template-background-tasks.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-u", "-m", "listenbrainz.background.background_tasks"] 8 | splay = "5s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/cron/consul-template-cron-config.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | template { 7 | source = "/code/listenbrainz/admin/config.sh.ctmpl" 8 | destination = "/code/listenbrainz/admin/config.sh" 9 | } 10 | -------------------------------------------------------------------------------- /docker/services/cron/cron-config.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-cron-config.conf 5 | -------------------------------------------------------------------------------- /docker/services/labs_api/consul-template-labs-api.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "uwsgi", "/etc/uwsgi/uwsgi-labs-api.ini"] 8 | splay = "5s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/labs_api/labs_api.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="labs-api" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/labs_api/labs_api.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | UWSGI_PID_FILE=/tmp/uwsgi-labs-api.pid 6 | if [ -e "$UWSGI_PID_FILE" ]; then 7 | UWSGI_PID=`cat "$UWSGI_PID_FILE"` 8 | echo "Previous uwsgi master process pid file detected, killing process $UWSGI_PID..." 9 | kill -TERM "$UWSGI_PID" 10 | sleep 4 11 | fi 12 | 13 | # wait for syslog to start up 14 | sleep 1 15 | 16 | exec run-consul-template -config /etc/consul-template-labs-api.conf 17 | -------------------------------------------------------------------------------- /docker/services/labs_api/uwsgi-labs-api.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | uid = www-data 3 | gid = www-data 4 | master = true 5 | socket = 0.0.0.0:3031 6 | module = listenbrainz.labs_api.labs.main 7 | callable = app 8 | chdir = /code/listenbrainz 9 | enable-threads = true 10 | processes = 10 11 | log-x-forwarded-for=true 12 | disable-logging = true 13 | ; quit uwsgi if the python app fails to load 14 | need-app = true 15 | ; increase buffer size for requests that send a lot of mbids in query params 16 | buffer-size = 8192 17 | die-on-term = true 18 | safe-pidfile = /tmp/uwsgi-labs-api.pid 19 | -------------------------------------------------------------------------------- /docker/services/lastfm_importer/consul-template-lastfm-importer.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-m", "listenbrainz.listens_importer.lastfm"] 8 | splay = "5s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/lastfm_importer/lastfm_importer.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="lastfm-importer" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/lastfm_importer/lastfm_importer.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-lastfm-importer.conf 5 | -------------------------------------------------------------------------------- /docker/services/mbid_mapping_writer/consul-template-mbid-mapping-writer.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-u", "-m", "listenbrainz.mbid_mapping_writer.mbid_mapping_writer"] 8 | splay = "60s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/mbid_mapping_writer/mbid_mapping_writer.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="mbid-mapping-writer" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/mbid_mapping_writer/mbid_mapping_writer.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-mbid-mapping-writer.conf 5 | -------------------------------------------------------------------------------- /docker/services/soundcloud_metadata_cache/consul-template-soundcloud-metadata-cache.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-u", "-m", "listenbrainz.metadata_cache.soundcloud.runner"] 8 | splay = "60s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/soundcloud_metadata_cache/soundcloud_metadata_cache.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="soundcloud-metadata-cache" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/soundcloud_metadata_cache/soundcloud_metadata_cache.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-soundcloud-metadata-cache.conf 5 | -------------------------------------------------------------------------------- /docker/services/spark_reader/consul-template-spark-reader.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-m", "listenbrainz.spark.spark_reader"] 8 | splay = "5s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/spark_reader/spark_reader.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="spark-reader" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/spark_reader/spark_reader.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-spark-reader.conf 5 | -------------------------------------------------------------------------------- /docker/services/spotify_metadata_cache/consul-template-spotify-metadata-cache.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-u", "-m", "listenbrainz.metadata_cache.spotify.runner"] 8 | splay = "60s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/spotify_metadata_cache/spotify_metadata_cache.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="spotify-metadata-cache" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/spotify_metadata_cache/spotify_metadata_cache.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-spotify-metadata-cache.conf 5 | -------------------------------------------------------------------------------- /docker/services/spotify_reader/consul-template-spotify-reader.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-m", "listenbrainz.listens_importer.spotify"] 8 | splay = "5s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/spotify_reader/spotify_reader.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="spotify-reader" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/spotify_reader/spotify_reader.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-spotify-reader.conf 5 | -------------------------------------------------------------------------------- /docker/services/timescale_writer/consul-template-timescale-writer.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "-u", "-m", "listenbrainz.timescale_writer.timescale_writer"] 8 | splay = "5s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/timescale_writer/timescale_writer.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="timescale-writer" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/timescale_writer/timescale_writer.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-timescale-writer.conf 5 | -------------------------------------------------------------------------------- /docker/services/uwsgi/consul-template-uwsgi.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/etc/uwsgi/uwsgi.ini.ctmpl" 3 | destination = "/etc/uwsgi/uwsgi.ini" 4 | command = "sv hup uwsgi" 5 | } 6 | 7 | template { 8 | source = "/code/listenbrainz/consul_config.py.ctmpl" 9 | destination = "/code/listenbrainz/listenbrainz/config.py" 10 | } 11 | 12 | exec { 13 | command = ["run-lb-command", "uwsgi", "/etc/uwsgi/uwsgi.ini"] 14 | splay = "5s" 15 | reload_signal = "SIGHUP" 16 | kill_signal = "SIGTERM" 17 | kill_timeout = "30s" 18 | } 19 | -------------------------------------------------------------------------------- /docker/services/uwsgi/uwsgi.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="uwsgi" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/uwsgi/uwsgi.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | UWSGI_PID_FILE=/tmp/uwsgi.pid 6 | if [ -e "$UWSGI_PID_FILE" ]; then 7 | UWSGI_PID=`cat "$UWSGI_PID_FILE"` 8 | echo "Previous uwsgi master process pid file detected, killing process $UWSGI_PID..." 9 | kill -TERM "$UWSGI_PID" 10 | sleep 4 11 | fi 12 | 13 | # wait for syslog to start up 14 | sleep 1 15 | 16 | exec run-consul-template -config /etc/consul-template-uwsgi.conf 17 | -------------------------------------------------------------------------------- /docker/services/websockets/consul-template-websockets.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/listenbrainz/consul_config.py.ctmpl" 3 | destination = "/code/listenbrainz/listenbrainz/config.py" 4 | } 5 | 6 | exec { 7 | command = ["run-lb-command", "python3", "manage.py", "run_websockets", "-h", "0.0.0.0", "-p", "3031"] 8 | splay = "5s" 9 | reload_signal = "SIGHUP" 10 | kill_signal = "SIGTERM" 11 | kill_timeout = "30s" 12 | } 13 | -------------------------------------------------------------------------------- /docker/services/websockets/websockets.finish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export service="websockets" 4 | 5 | . /etc/lb-startup-common.sh 6 | 7 | 8 | generate_message "$service" "$@" 9 | 10 | log "$message" 11 | 12 | send_sentry_message "$message" 13 | 14 | if [ "$1" != "0" ]; then 15 | log "Exited with non-0 status, sleeping 10 seconds" 16 | sleep 10 17 | fi 18 | -------------------------------------------------------------------------------- /docker/services/websockets/websockets.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1 4 | exec run-consul-template -config /etc/consul-template-websockets.conf 5 | -------------------------------------------------------------------------------- /docker/spark-dev-push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Build base metabrainz spark image (only used in development and testing) 4 | # and push it to Docker Hub, with an optional tag (which defaults to "latest") 5 | # 6 | # Usage: 7 | # $ ./spark-dev-push.sh [tag] 8 | 9 | set -e 10 | 11 | cd "$(dirname "${BASH_SOURCE[0]}")" 12 | 13 | TAG=${1:-latest} 14 | 15 | docker build --tag metabrainz/listenbrainz-spark-base:"$TAG" -f Dockerfile.spark.base . \ 16 | && docker push metabrainz/listenbrainz-spark-base 17 | -------------------------------------------------------------------------------- /docker/start-labs-api.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker/docker-compose.labs.api.yml -p listenbrainz "$@" 4 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Sphinx stuff 2 | _build 3 | build 4 | # virtualenv for doc stuff 5 | ve 6 | _templates 7 | -------------------------------------------------------------------------------- /docs/images/auth-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/docs/images/auth-popup.png -------------------------------------------------------------------------------- /docs/images/dataflows-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/docs/images/dataflows-graph.png -------------------------------------------------------------------------------- /docs/images/release-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/docs/images/release-result.png -------------------------------------------------------------------------------- /docs/images/release-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/docs/images/release-workflow.png -------------------------------------------------------------------------------- /docs/images/request_consumer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/docs/images/request_consumer.png -------------------------------------------------------------------------------- /docs/images/user-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/docs/images/user-profile.png -------------------------------------------------------------------------------- /docs/maintainers/deploy.rst: -------------------------------------------------------------------------------- 1 | Production Deployment 2 | ===================== 3 | 4 | .. note:: 5 | 6 | This documentation is for ListenBrainz maintainers for when they deploy the website 7 | 8 | Cron 9 | ^^^^ 10 | 11 | You can cleanly shut down cron from ``docker-server-configs`` by running 12 | 13 | .. code-block:: bash 14 | 15 | ./scripts/terminate_lb_cron.sh 16 | 17 | If no cron jobs are running, this will stop and delete the cron container. If a job is running 18 | it will notify you and not stop the container. 19 | -------------------------------------------------------------------------------- /docs/maintainers/pull-requests.rst: -------------------------------------------------------------------------------- 1 | Pull Requests Policy 2 | ==================== 3 | 4 | It is recommended that maintainers (unless the change is urgently needed) do not push directly or merge pull requests 5 | without review . By default, **one** approving review is sufficient to merge a pull request. The pull request author 6 | or the reviewer can request more reviews or review from a specific person as they deem necessary. 7 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==7.2.6 2 | sphinxcontrib-httpdomain==1.8.1 3 | sphinx_rtd_theme==2.0.0 4 | docutils==0.20.1 5 | sphinx-click==5.1.0 6 | -r ../requirements.txt 7 | -------------------------------------------------------------------------------- /docs/users/api/metadata.rst: -------------------------------------------------------------------------------- 1 | .. _metadata-api: 2 | 3 | Metadata 4 | ======== 5 | 6 | The metadata API looks up MusicBrainz metadata for recordings 7 | 8 | .. autoflask:: listenbrainz.webserver:create_app_rtfd() 9 | :blueprints: metadata 10 | :include-empty-docstring: 11 | :undoc-static: 12 | -------------------------------------------------------------------------------- /docs/users/api/playlist.rst: -------------------------------------------------------------------------------- 1 | Playlists 2 | ========= 3 | 4 | The playlists API allows for the creation and editing of lists of recordings 5 | 6 | .. autoflask:: listenbrainz.webserver:create_app_rtfd() 7 | :endpoints: api_v1.get_playlists_for_user, api_v1.get_playlists_created_for_user, api_v1.get_playlists_collaborated_on_for_user 8 | :include-empty-docstring: 9 | :undoc-static: 10 | 11 | .. autoflask:: listenbrainz.webserver:create_app_rtfd() 12 | :blueprints: playlist_api_v1 13 | :include-empty-docstring: 14 | :undoc-static: 15 | -------------------------------------------------------------------------------- /docs/users/api/popularity.rst: -------------------------------------------------------------------------------- 1 | .. _popularity-api: 2 | 3 | Popularity 4 | ========== 5 | 6 | The popularity APIs return the total listen and listeners count for various entities and also a way to query top entities 7 | for a given artist. 8 | 9 | .. autoflask:: listenbrainz.webserver:create_app_rtfd() 10 | :blueprints: popularity_api_v1 11 | :include-empty-docstring: 12 | :undoc-static: 13 | -------------------------------------------------------------------------------- /docs/users/api/settings.rst: -------------------------------------------------------------------------------- 1 | Settings 2 | ======== 3 | 4 | These endpoints allow to customize a user's preferences/settings. 5 | 6 | .. autoflask:: listenbrainz.webserver:create_app_rtfd() 7 | :blueprints: user_settings_api_v1 8 | :include-empty-docstring: 9 | :undoc-static: 10 | -------------------------------------------------------------------------------- /docs/users/api/social.rst: -------------------------------------------------------------------------------- 1 | Social 2 | ====== 3 | 4 | User Timeline API 5 | ^^^^^^^^^^^^^^^^^ 6 | 7 | These api endpoints allow to create and fetch timeline events for a user. 8 | 9 | .. autoflask:: listenbrainz.webserver:create_app_rtfd() 10 | :blueprints: user_timeline_event_api_bp 11 | 12 | Follow API 13 | ^^^^^^^^^^ 14 | 15 | These apis allow to interact with follow user feature of ListenBrainz. 16 | 17 | .. autoflask:: listenbrainz.webserver:create_app_rtfd() 18 | :blueprints: social_api_v1 19 | :include-empty-docstring: 20 | :undoc-static: 21 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | !js/* 2 | dist/ -------------------------------------------------------------------------------- /frontend/css/.gitignore: -------------------------------------------------------------------------------- 1 | *.css 2 | !widgets.css -------------------------------------------------------------------------------- /frontend/css/colors.less: -------------------------------------------------------------------------------- 1 | // ListenBrainz logo colors 2 | @blue: #353070; 3 | @orange: #eb743b; 4 | @white: #ffffff; 5 | @light: #c0cfb2; 6 | @light-grey: #8d8d8d; 7 | @dark: #053b47; 8 | -------------------------------------------------------------------------------- /frontend/css/messybrainz.less: -------------------------------------------------------------------------------- 1 | .messybrainz-header { 2 | margin-top: 40px; 3 | img { 4 | display: block; 5 | margin: 0 auto; 6 | height: 80px; 7 | } 8 | margin-bottom: 40px; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/css/personal-recommendation-modal.less: -------------------------------------------------------------------------------- 1 | @white-background: #fff; 2 | 3 | #PersonalRecommendationModal { 4 | p { 5 | margin-top: 24px; 6 | margin-bottom: 24px; 7 | } 8 | 9 | .character-count { 10 | display: block; 11 | text-align: right; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/alerts.less: -------------------------------------------------------------------------------- 1 | // Alerts 2 | 3 | .alert-variant(@background; @border; @text-color) { 4 | background-color: @background; 5 | border-color: @border; 6 | color: @text-color; 7 | 8 | hr { 9 | border-top-color: darken(@border, 5%); 10 | } 11 | .alert-link { 12 | color: darken(@text-color, 10%); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/background-variant.less: -------------------------------------------------------------------------------- 1 | // Contextual backgrounds 2 | 3 | .bg-variant(@color) { 4 | background-color: @color; 5 | a&:hover, 6 | a&:focus { 7 | background-color: darken(@color, 10%); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/center-block.less: -------------------------------------------------------------------------------- 1 | // Center-align a block level element 2 | 3 | .center-block() { 4 | display: block; 5 | margin-left: auto; 6 | margin-right: auto; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/labels.less: -------------------------------------------------------------------------------- 1 | // Labels 2 | 3 | .label-variant(@color) { 4 | background-color: @color; 5 | 6 | &[href] { 7 | &:hover, 8 | &:focus { 9 | background-color: darken(@color, 10%); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/nav-divider.less: -------------------------------------------------------------------------------- 1 | // Horizontal dividers 2 | // 3 | // Dividers (basically an hr) within dropdowns and nav lists 4 | 5 | .nav-divider(@color: #e5e5e5) { 6 | height: 1px; 7 | margin: ((@line-height-computed / 2) - 1) 0; 8 | overflow: hidden; 9 | background-color: @color; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/nav-vertical-align.less: -------------------------------------------------------------------------------- 1 | // Navbar vertical align 2 | // 3 | // Vertically center elements in the navbar. 4 | // Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. 5 | 6 | .navbar-vertical-align(@element-height) { 7 | margin-top: ((@navbar-height - @element-height) / 2); 8 | margin-bottom: ((@navbar-height - @element-height) / 2); 9 | } 10 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/opacity.less: -------------------------------------------------------------------------------- 1 | // Opacity 2 | 3 | .opacity(@opacity) { 4 | opacity: @opacity; 5 | // IE8 filter 6 | @opacity-ie: (@opacity * 100); 7 | filter: ~"alpha(opacity=@{opacity-ie})"; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/progress-bar.less: -------------------------------------------------------------------------------- 1 | // Progress bars 2 | 3 | .progress-bar-variant(@color) { 4 | background-color: @color; 5 | 6 | // Deprecated parent class requirement as of v3.2.0 7 | .progress-striped & { 8 | #gradient > .striped(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/reset-filter.less: -------------------------------------------------------------------------------- 1 | // Reset filters for IE 2 | // 3 | // When you need to remove a gradient background, do not forget to use this to reset 4 | // the IE filter for IE9 and below. 5 | 6 | .reset-filter() { 7 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); 8 | } 9 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/resize.less: -------------------------------------------------------------------------------- 1 | // Resize anything 2 | 3 | .resizable(@direction) { 4 | resize: @direction; // Options: horizontal, vertical, both 5 | overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` 6 | } 7 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/responsive-visibility.less: -------------------------------------------------------------------------------- 1 | // Responsive utilities 2 | 3 | // 4 | // More easily include all the states for responsive-utilities.less. 5 | .responsive-visibility() { 6 | display: block !important; 7 | table& { display: table !important; } 8 | tr& { display: table-row !important; } 9 | th&, 10 | td& { display: table-cell !important; } 11 | } 12 | 13 | .responsive-invisibility() { 14 | display: none !important; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/size.less: -------------------------------------------------------------------------------- 1 | // Sizing shortcuts 2 | 3 | .size(@width; @height) { 4 | width: @width; 5 | height: @height; 6 | } 7 | 8 | .square(@size) { 9 | .size(@size; @size); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/tab-focus.less: -------------------------------------------------------------------------------- 1 | // WebKit-style focus 2 | 3 | .tab-focus() { 4 | // Default 5 | outline: thin dotted; 6 | // WebKit 7 | outline: 5px auto -webkit-focus-ring-color; 8 | outline-offset: -2px; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/text-emphasis.less: -------------------------------------------------------------------------------- 1 | // Typography 2 | 3 | .text-emphasis-variant(@color) { 4 | color: @color; 5 | a&:hover, 6 | a&:focus { 7 | color: darken(@color, 10%); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/css/theme/bootstrap/mixins/text-overflow.less: -------------------------------------------------------------------------------- 1 | // Text overflow 2 | // Requires inline-block or block for proper styling 3 | 4 | .text-overflow() { 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/fonts/Inter.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/fonts/Inter.woff2 -------------------------------------------------------------------------------- /frontend/img/ListenBrainz_logo_no_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/ListenBrainz_logo_no_text.png -------------------------------------------------------------------------------- /frontend/img/art/cover-art-on-floor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/art/cover-art-on-floor.png -------------------------------------------------------------------------------- /frontend/img/art/yim-2022-shareable-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/art/yim-2022-shareable-bg.png -------------------------------------------------------------------------------- /frontend/img/art/yim-2022-shareable-flames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/art/yim-2022-shareable-flames.png -------------------------------------------------------------------------------- /frontend/img/art/yim-2022-shareable-magnify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/art/yim-2022-shareable-magnify.png -------------------------------------------------------------------------------- /frontend/img/art/yim-2022-shareable-stereo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/art/yim-2022-shareable-stereo.png -------------------------------------------------------------------------------- /frontend/img/broken-cd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/broken-cd.jpg -------------------------------------------------------------------------------- /frontend/img/cover-art-placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/cover-art-placeholder.jpg -------------------------------------------------------------------------------- /frontend/img/explore/cover-art-collage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/cover-art-collage.jpg -------------------------------------------------------------------------------- /frontend/img/explore/fresh-releases.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/fresh-releases.jpg -------------------------------------------------------------------------------- /frontend/img/explore/huesound.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/huesound.jpg -------------------------------------------------------------------------------- /frontend/img/explore/lb-radio-beta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/lb-radio-beta.jpg -------------------------------------------------------------------------------- /frontend/img/explore/link-listens.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/link-listens.jpg -------------------------------------------------------------------------------- /frontend/img/explore/made-with-postgres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/made-with-postgres.png -------------------------------------------------------------------------------- /frontend/img/explore/music-neighborhood.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/music-neighborhood.jpg -------------------------------------------------------------------------------- /frontend/img/explore/similar-users.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/similar-users.jpg -------------------------------------------------------------------------------- /frontend/img/explore/stats-art-beta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/stats-art-beta.jpg -------------------------------------------------------------------------------- /frontend/img/explore/stats-art/template-designer-top-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/stats-art/template-designer-top-10.png -------------------------------------------------------------------------------- /frontend/img/explore/stats-art/template-designer-top-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/stats-art/template-designer-top-5.png -------------------------------------------------------------------------------- /frontend/img/explore/stats-art/template-grid-stats-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/stats-art/template-grid-stats-2.png -------------------------------------------------------------------------------- /frontend/img/explore/stats-art/template-grid-stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/stats-art/template-grid-stats.png -------------------------------------------------------------------------------- /frontend/img/explore/stats-art/template-lps-on-the-floor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/stats-art/template-lps-on-the-floor.png -------------------------------------------------------------------------------- /frontend/img/explore/year-in-music-2021.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/year-in-music-2021.jpg -------------------------------------------------------------------------------- /frontend/img/explore/year-in-music-2022.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/year-in-music-2022.jpg -------------------------------------------------------------------------------- /frontend/img/explore/year-in-music-2023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/year-in-music-2023.jpg -------------------------------------------------------------------------------- /frontend/img/explore/year-in-music-2024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/explore/year-in-music-2024.png -------------------------------------------------------------------------------- /frontend/img/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/favicon-16.png -------------------------------------------------------------------------------- /frontend/img/favicon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/favicon-256.png -------------------------------------------------------------------------------- /frontend/img/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/favicon-32.png -------------------------------------------------------------------------------- /frontend/img/homepage/LB-Data-Provider-Info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/homepage/LB-Data-Provider-Info.png -------------------------------------------------------------------------------- /frontend/img/homepage/LB-Data-Provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/homepage/LB-Data-Provider.png -------------------------------------------------------------------------------- /frontend/img/homepage/LB-Ethical-Info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/homepage/LB-Ethical-Info.png -------------------------------------------------------------------------------- /frontend/img/homepage/LB-Ethical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/homepage/LB-Ethical.png -------------------------------------------------------------------------------- /frontend/img/homepage/LB-Headphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/homepage/LB-Headphone.png -------------------------------------------------------------------------------- /frontend/img/homepage/LB-Open-Source-Info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/homepage/LB-Open-Source-Info.png -------------------------------------------------------------------------------- /frontend/img/homepage/LB-Open-Source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/homepage/LB-Open-Source.png -------------------------------------------------------------------------------- /frontend/img/homepage/LB-Speaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/homepage/LB-Speaker.png -------------------------------------------------------------------------------- /frontend/img/icons/angle_double_right_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/img/listenbrainz-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/listenbrainz-logo.png -------------------------------------------------------------------------------- /frontend/img/listenbrainz_logo_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/img/playlist-cover-art/cover-art_1-0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/img/recommendations/no-freshness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/recommendations/no-freshness.png -------------------------------------------------------------------------------- /frontend/img/selfie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/selfie.jpg -------------------------------------------------------------------------------- /frontend/img/share-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/share-header.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-2021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-2021.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-22/buddy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-22/buddy.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-22/magnify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-22/magnify.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-22/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-22/map.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-22/stereo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-22/stereo.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-22/yim-22-logo-small-compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-22/yim-22-logo-small-compressed.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-22/yim22-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-22/yim22-logo.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/buddies-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/buddies-square.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/cat.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/dog-tall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/dog-tall.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/dog.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/fish.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/flower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/flower.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/ghost-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/ghost-square.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/heart-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/heart-square.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/heart.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/peep-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/peep-square.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/peep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/peep.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/pick-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/pick-color.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/shareable-image-arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/shareable-image-arrows.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/shareable-image-hug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/shareable-image-hug.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/trunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/trunk.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/worm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/worm.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/yim-23-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/yim-23-header.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/yim-23-logo-small-compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/yim-23-logo-small-compressed.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-23/yim23-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-23/yim23-logo.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-05.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-06.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-07.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-08.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-09.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/buddies/yim24-buddy-10.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-01-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-01-no-bg.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-02-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-02-no-bg.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-05.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/playlists/yim24-playlist-06.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/yim24-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/yim24-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/yim24-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/yim24-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/yim24-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/yim24-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/autumn/yim24-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/autumn/yim24-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-05.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-06.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-07.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-08.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/buddies/yim24-buddy-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/buddies/yim24-buddy-09.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/playlists/yim24-playlist-01-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/playlists/yim24-playlist-01-no-bg.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/playlists/yim24-playlist-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/playlists/yim24-playlist-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/playlists/yim24-playlist-02-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/playlists/yim24-playlist-02-no-bg.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/playlists/yim24-playlist-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/playlists/yim24-playlist-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/playlists/yim24-playlist-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/playlists/yim24-playlist-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/playlists/yim24-playlist-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/playlists/yim24-playlist-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/playlists/yim24-playlist-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/playlists/yim24-playlist-05.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/playlists/yim24-playlist-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/playlists/yim24-playlist-06.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/yim24-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/yim24-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/yim24-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/yim24-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/yim24-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/yim24-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/spring/yim24-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/spring/yim24-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/buddies/yim24-buddy-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/buddies/yim24-buddy-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/buddies/yim24-buddy-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/buddies/yim24-buddy-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/buddies/yim24-buddy-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/buddies/yim24-buddy-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/buddies/yim24-buddy-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/buddies/yim24-buddy-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/buddies/yim24-buddy-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/buddies/yim24-buddy-05.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/buddies/yim24-buddy-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/buddies/yim24-buddy-06.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/buddies/yim24-buddy-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/buddies/yim24-buddy-07.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/playlists/yim24-playlist-01-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/playlists/yim24-playlist-01-no-bg.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/playlists/yim24-playlist-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/playlists/yim24-playlist-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/playlists/yim24-playlist-02-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/playlists/yim24-playlist-02-no-bg.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/playlists/yim24-playlist-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/playlists/yim24-playlist-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/playlists/yim24-playlist-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/playlists/yim24-playlist-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/playlists/yim24-playlist-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/playlists/yim24-playlist-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/playlists/yim24-playlist-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/playlists/yim24-playlist-05.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/playlists/yim24-playlist-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/playlists/yim24-playlist-06.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/yim24-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/yim24-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/yim24-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/yim24-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/yim24-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/yim24-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/summer/yim24-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/summer/yim24-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/buddies/yim24-buddy-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/buddies/yim24-buddy-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/buddies/yim24-buddy-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/buddies/yim24-buddy-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/buddies/yim24-buddy-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/buddies/yim24-buddy-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/buddies/yim24-buddy-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/buddies/yim24-buddy-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/buddies/yim24-buddy-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/buddies/yim24-buddy-05.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/playlists/yim24-playlist-01-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/playlists/yim24-playlist-01-no-bg.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/playlists/yim24-playlist-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/playlists/yim24-playlist-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/playlists/yim24-playlist-02-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/playlists/yim24-playlist-02-no-bg.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/playlists/yim24-playlist-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/playlists/yim24-playlist-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/playlists/yim24-playlist-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/playlists/yim24-playlist-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/playlists/yim24-playlist-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/playlists/yim24-playlist-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/playlists/yim24-playlist-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/playlists/yim24-playlist-05.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/playlists/yim24-playlist-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/playlists/yim24-playlist-06.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/yim24-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/yim24-01.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/yim24-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/yim24-02.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/yim24-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/yim24-03.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/winter/yim24-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/winter/yim24-04.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/yim24-header-all-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/yim24-header-all-email.png -------------------------------------------------------------------------------- /frontend/img/year-in-music-24/yim24-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/img/year-in-music-24/yim24-header.png -------------------------------------------------------------------------------- /frontend/js/src/hooks/__mocks__/useFeedbackMap.tsx: -------------------------------------------------------------------------------- 1 | // Mocking this custom hook because somehow rendering alert notifications in there is making Enzyme shit itself 2 | // because Enzyme doesn't support React functional components 3 | 4 | export default function useFeedbackMap() { 5 | return { feedbackValue: 0, update: async () => {} }; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/js/src/release-group/ReleaseGroup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Navigate, useParams } from "react-router"; 3 | 4 | export default function ReleaseGroup() { 5 | const { releaseGroupMBID } = useParams(); 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/js/src/settings/export/ExportData.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Helmet } from "react-helmet"; 4 | import GlobalAppContext from "../../utils/GlobalAppContext"; 5 | import ExportButtons from "./ExportButtons"; 6 | 7 | export default function Export() { 8 | const { currentUser } = React.useContext(GlobalAppContext); 9 | 10 | return ( 11 | <> 12 | 13 | Export for {currentUser?.name} 14 | 15 |

Export from ListenBrainz

16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/js/src/user/playlists/playlistView.d.ts: -------------------------------------------------------------------------------- 1 | enum PlaylistView { 2 | LIST = "list", 3 | GRID = "grid", 4 | } 5 | 6 | export default PlaylistView; 7 | -------------------------------------------------------------------------------- /frontend/js/src/utils/APIError.ts: -------------------------------------------------------------------------------- 1 | export default class APIError extends Error { 2 | status?: string; 3 | 4 | response?: Response; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/js/src/utils/QueryClient.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/react-query"; 2 | 3 | const queryClient = new QueryClient({ 4 | defaultOptions: { 5 | queries: { 6 | refetchOnWindowFocus: false, 7 | retry: 1, 8 | staleTime: 1000 * 60 * 5, 9 | }, 10 | }, 11 | }); 12 | 13 | export default queryClient; 14 | -------------------------------------------------------------------------------- /frontend/js/src/utils/donation.d.ts: -------------------------------------------------------------------------------- 1 | type DonationInfo = { 2 | id: number; 3 | donated_at: string; 4 | donation: number; 5 | currency: "usd" | "eur"; 6 | musicbrainz_id: string; 7 | is_listenbrainz_user: boolean; 8 | listenCount: number; 9 | playlistCount: number; 10 | }; 11 | 12 | type DonationInfoWithPinnedRecording = DonationInfo & { 13 | pinnedRecording: PinnedRecording; 14 | }; 15 | -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/freshReleasesDisplaySettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Release Title": true, 3 | "Artist": true, 4 | "Information": true, 5 | "Tags": false, 6 | "Listens": false 7 | } -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/getFeedbackByMsidResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "feedback": [ 3 | { 4 | "score": 1, 5 | "user_id": "iliekcomputers", 6 | "recording_msid": "973e5620-829d-46dd-89a8-760d87076287", 7 | "recording_mbid": null 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/getInfo.json: -------------------------------------------------------------------------------- 1 | {"user":{"playlists":"0","playcount":"1026","gender":"n","name":"ishaanshah","subscriber":"0","url":"https:\/\/www.last.fm\/user\/ishaanshah","country":"None","image":[{"size":"small","#text":""},{"size":"medium","#text":""},{"size":"large","#text":""},{"size":"extralarge","#text":""}],"registered":{"unixtime":"1513921363","#text":1513921363},"type":"user","age":"0","bootstrap":"0","realname":""}} -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/getInfoNoPlayCount.json: -------------------------------------------------------------------------------- 1 | {"user":{"playlists":"0","gender":"n","name":"ishaanshah","subscriber":"0","url":"https:\/\/www.last.fm\/user\/ishaanshah","country":"None","image":[{"size":"small","#text":""},{"size":"medium","#text":""},{"size":"large","#text":""},{"size":"extralarge","#text":""}],"registered":{"unixtime":"1513921363","#text":1513921363},"type":"user","age":"0","bootstrap":"0","realname":""}} 2 | -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/intersection-observer.tsx: -------------------------------------------------------------------------------- 1 | class IntersectionObserver { 2 | root = null; 3 | rootMargin = ""; 4 | thresholds = []; 5 | 6 | observe() { 7 | return null; 8 | } 9 | 10 | disconnect() { 11 | return null; 12 | } 13 | 14 | takeRecords() { 15 | return []; 16 | } 17 | 18 | unobserve() { 19 | return null; 20 | } 21 | } 22 | 23 | export default IntersectionObserver; 24 | -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/lastFMPrivateUser.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": 17, 3 | "message": "Login: User required to be logged in" 4 | } -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/localforage.ts: -------------------------------------------------------------------------------- 1 | const localforageMock = { 2 | createInstance: jest.fn(() => ({ 3 | setItem: jest.fn(), 4 | getItem: jest.fn(), 5 | removeItem: jest.fn(), 6 | keys: jest.fn().mockResolvedValue([]), 7 | })), 8 | }; 9 | 10 | export default localforageMock; 11 | -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/matchMedia.ts: -------------------------------------------------------------------------------- 1 | Object.defineProperty(window, "matchMedia", { 2 | writable: true, 3 | value: jest.fn().mockImplementation((query) => ({ 4 | matches: false, 5 | media: query, 6 | onchange: null, 7 | addListener: jest.fn(), // deprecated 8 | removeListener: jest.fn(), // deprecated 9 | addEventListener: jest.fn(), 10 | removeEventListener: jest.fn(), 11 | dispatchEvent: jest.fn(), 12 | })), 13 | }); 14 | -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/spotifySearchEmptyResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "tracks": { 3 | "href": "https://api.spotify.com/v1/search?query=track%3Arunning+up+that+hill+artist%3Akate+bush&type=track&locale=en-US%2Cen%3Bq%3D0.5&offset=0&limit=3", 4 | "items": [], 5 | "limit": 3, 6 | "next": "https://api.spotify.com/v1/search?query=track%3Arunning+up+that+hill+artist%3Akate+bush&type=track&locale=en-US%2Cen%3Bq%3D0.5&offset=3&limit=3", 7 | "offset": 0, 8 | "previous": null, 9 | "total": 0 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/js/tests/__mocks__/userSocialNetworkProps.json: -------------------------------------------------------------------------------- 1 | { 2 | "loggedInUser": { 3 | "id": 1, 4 | "name": "iliekcomputers" 5 | }, 6 | "user": { 7 | "id": 2, 8 | "name": "bob" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/js/tests/components/Card.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { mount } from "enzyme"; 3 | 4 | import Card from "../../src/components/Card"; 5 | 6 | describe("Card", () => { 7 | it("renders correctly", () => { 8 | const wrapper = mount(Test); 9 | expect(wrapper.find(".card")).toHaveLength(1); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/js/tests/jest-setup.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | import "@testing-library/jest-dom"; 3 | import "./__mocks__/matchMedia"; 4 | 5 | // Mocking this custom hook because somehow rendering alert notifications in there is making Enzyme shit itself 6 | // because Enzyme doesn't support React functional components 7 | jest.mock("../src/hooks/useFeedbackMap"); 8 | -------------------------------------------------------------------------------- /frontend/js/tests/test-react-query.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 3 | 4 | export const queryClient = new QueryClient({ 5 | defaultOptions: { 6 | queries: { 7 | retry: false, 8 | }, 9 | }, 10 | }); 11 | 12 | export function ReactQueryWrapper({ children }: any) { 13 | return ( 14 | {children} 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/js/tests/test-utils.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | import { ReactWrapper, ShallowWrapper } from "enzyme"; 3 | import { act } from "react-dom/test-utils"; 4 | 5 | // eslint-disable-next-line import/prefer-default-export 6 | export async function waitForComponentToPaint

( 7 | wrapper: ReactWrapper

| ShallowWrapper

, 8 | amount = 0 9 | ) { 10 | await act(async () => { 11 | await new Promise((resolve) => { 12 | setTimeout(resolve, amount); 13 | }); 14 | wrapper.update(); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /current-status 3 | Disallow: /user/ 4 | Disallow: /1/ 5 | Disallow: /api/ 6 | Disallow: /2.0/ 7 | -------------------------------------------------------------------------------- /frontend/sound/5-seconds-of-silence.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/frontend/sound/5-seconds-of-silence.mp3 -------------------------------------------------------------------------------- /listenbrainz/background/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/background/__init__.py -------------------------------------------------------------------------------- /listenbrainz/db/exceptions.py: -------------------------------------------------------------------------------- 1 | class DatabaseException(Exception): 2 | """Base exception for this package.""" 3 | pass 4 | 5 | 6 | class NoDataFoundException(DatabaseException): 7 | """Should be used when no data has been found.""" 8 | pass 9 | 10 | 11 | class BadDataException(DatabaseException): 12 | """Should be used when incorrect data is being submitted.""" 13 | pass 14 | -------------------------------------------------------------------------------- /listenbrainz/db/licenses/README.md: -------------------------------------------------------------------------------- 1 | IMPORTANT: The license file in this directory is the text file that gets included into the data dumps. 2 | 3 | The CC0 license DOES NOT APPLY to the code in this repository. 4 | -------------------------------------------------------------------------------- /listenbrainz/db/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/db/model/__init__.py -------------------------------------------------------------------------------- /listenbrainz/db/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/db/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/db/tests/test_dump_entry.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from listenbrainz.db.dump_entry import get_dump_entries, add_dump_entry 4 | from listenbrainz.db.testing import DatabaseTestCase 5 | 6 | 7 | class DumpEntryTestCase(DatabaseTestCase): 8 | 9 | def test_add_dump_entry(self): 10 | prev_dumps = get_dump_entries() 11 | add_dump_entry(datetime.today(), "incremental") 12 | now_dumps = get_dump_entries() 13 | self.assertEqual(len(now_dumps), len(prev_dumps) + 1) 14 | -------------------------------------------------------------------------------- /listenbrainz/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/domain/__init__.py -------------------------------------------------------------------------------- /listenbrainz/domain/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/domain/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/dumps/exceptions.py: -------------------------------------------------------------------------------- 1 | class SchemaMismatchException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /listenbrainz/dumps/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/dumps/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/labs_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/labs_api/__init__.py -------------------------------------------------------------------------------- /listenbrainz/labs_api/labs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/labs_api/labs/__init__.py -------------------------------------------------------------------------------- /listenbrainz/labs_api/labs/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/labs_api/labs/api/__init__.py -------------------------------------------------------------------------------- /listenbrainz/labs_api/labs/api/apple/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from listenbrainz.labs_api.labs.api.metadata_index import BaseMetadataIndexOutput 4 | 5 | 6 | class AppleMusicIdFromMBIDOutput(BaseMetadataIndexOutput): 7 | apple_music_track_ids: Optional[list[str]] 8 | -------------------------------------------------------------------------------- /listenbrainz/labs_api/labs/api/metadata_index/__init__.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class BaseMetadataIndexOutput(BaseModel): 8 | recording_mbid: Optional[uuid.UUID] 9 | artist_name: Optional[str] 10 | release_name: Optional[str] 11 | track_name: Optional[str] 12 | 13 | 14 | class AppleMusicIdFromMBIDOutput(BaseMetadataIndexOutput): 15 | apple_music_track_ids: Optional[list[str]] 16 | -------------------------------------------------------------------------------- /listenbrainz/labs_api/labs/api/soundcloud/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from listenbrainz.labs_api.labs.api.metadata_index import BaseMetadataIndexOutput 4 | 5 | 6 | class SoundCloudIdFromMBIDOutput(BaseMetadataIndexOutput): 7 | soundcloud_track_ids: Optional[list[str]] 8 | -------------------------------------------------------------------------------- /listenbrainz/labs_api/labs/api/spotify/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from listenbrainz.labs_api.labs.api.metadata_index import BaseMetadataIndexOutput 4 | 5 | 6 | class SpotifyIdFromMBIDOutput(BaseMetadataIndexOutput): 7 | spotify_track_ids: Optional[list[str]] 8 | -------------------------------------------------------------------------------- /listenbrainz/labs_api/labs/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/labs_api/labs/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/listens_importer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/listens_importer/__init__.py -------------------------------------------------------------------------------- /listenbrainz/listens_importer/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/listens_importer/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/listenstore/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/listenstore/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/mbid_mapping_writer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/mbid_mapping_writer/__init__.py -------------------------------------------------------------------------------- /listenbrainz/messybrainz/exceptions.py: -------------------------------------------------------------------------------- 1 | class MessyBrainzException(Exception): 2 | """Base exception for this package.""" 3 | pass 4 | 5 | 6 | class BadDataException(MessyBrainzException): 7 | """Should be used when incorrect data is being submitted.""" 8 | pass 9 | 10 | 11 | class ErrorAddingException(MessyBrainzException): 12 | """Should be used when incorrect data is being submitted.""" 13 | pass 14 | -------------------------------------------------------------------------------- /listenbrainz/messybrainz/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/messybrainz/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/metadata_cache/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/metadata_cache/__init__.py -------------------------------------------------------------------------------- /listenbrainz/metadata_cache/apple/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/metadata_cache/apple/__init__.py -------------------------------------------------------------------------------- /listenbrainz/metadata_cache/apple/runner.py: -------------------------------------------------------------------------------- 1 | from listenbrainz.metadata_cache.apple.handler import AppleCrawlerHandler 2 | from listenbrainz.metadata_cache.consumer import ServiceMetadataCache 3 | from listenbrainz.webserver import create_app 4 | 5 | 6 | if __name__ == "__main__": 7 | app = create_app() 8 | with app.app_context(): 9 | handler = AppleCrawlerHandler(app) 10 | smc = ServiceMetadataCache(app, handler) 11 | smc.start() 12 | -------------------------------------------------------------------------------- /listenbrainz/metadata_cache/soundcloud/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/metadata_cache/soundcloud/__init__.py -------------------------------------------------------------------------------- /listenbrainz/metadata_cache/soundcloud/models.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class SoundcloudArtist(BaseModel): 7 | id: str 8 | name: str 9 | data: dict 10 | 11 | class SoundcloudTrack(BaseModel): 12 | id: str 13 | name: str 14 | release_year: Optional[int] 15 | release_month: Optional[int] 16 | release_day: Optional[int] 17 | artist: SoundcloudArtist 18 | data: dict 19 | -------------------------------------------------------------------------------- /listenbrainz/metadata_cache/soundcloud/runner.py: -------------------------------------------------------------------------------- 1 | from listenbrainz.metadata_cache.consumer import ServiceMetadataCache 2 | from listenbrainz.metadata_cache.soundcloud.handler import SoundcloudCrawlerHandler 3 | from listenbrainz.webserver import create_app 4 | 5 | 6 | if __name__ == "__main__": 7 | app = create_app() 8 | with app.app_context(): 9 | handler = SoundcloudCrawlerHandler(app) 10 | smc = ServiceMetadataCache(app, handler) 11 | smc.start() 12 | -------------------------------------------------------------------------------- /listenbrainz/metadata_cache/spotify/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/metadata_cache/spotify/__init__.py -------------------------------------------------------------------------------- /listenbrainz/metadata_cache/spotify/runner.py: -------------------------------------------------------------------------------- 1 | from listenbrainz.metadata_cache.consumer import ServiceMetadataCache 2 | from listenbrainz.metadata_cache.spotify.handler import SpotifyCrawlerHandler 3 | from listenbrainz.webserver import create_app 4 | 5 | 6 | if __name__ == "__main__": 7 | app = create_app() 8 | with app.app_context(): 9 | handler = SpotifyCrawlerHandler(app) 10 | smc = ServiceMetadataCache(app, handler) 11 | smc.start() 12 | -------------------------------------------------------------------------------- /listenbrainz/model/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | 4 | db = SQLAlchemy() 5 | 6 | 7 | # import all models here please 8 | from listenbrainz.model.external_service_oauth import ExternalService 9 | from listenbrainz.model.user import User 10 | from listenbrainz.model.listens_import import ListensImporter 11 | from listenbrainz.model.reported_users import ReportedUsers 12 | from listenbrainz.model.playlist import Playlist 13 | from listenbrainz.model.playlist_recording import PlaylistRecording 14 | -------------------------------------------------------------------------------- /listenbrainz/model/utils.py: -------------------------------------------------------------------------------- 1 | from markupsafe import Markup 2 | 3 | 4 | def generate_username_link(user_name): 5 | return Markup(f"""{user_name}""") 6 | -------------------------------------------------------------------------------- /listenbrainz/server.py: -------------------------------------------------------------------------------- 1 | from listenbrainz.webserver import create_web_app 2 | 3 | application = create_web_app() 4 | -------------------------------------------------------------------------------- /listenbrainz/spark/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/spark/__init__.py -------------------------------------------------------------------------------- /listenbrainz/spark/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/spark/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/spark/tests/test_query_list.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import json 4 | 5 | JSON_FILE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../request_queries.json') 6 | 7 | 8 | class QueryJSONTestCase(unittest.TestCase): 9 | 10 | def test_valid_request_queries_json(self): 11 | with open(JSON_FILE_PATH) as f: 12 | query_list = json.load(f) 13 | self.assertIsNotNone(query_list) 14 | -------------------------------------------------------------------------------- /listenbrainz/testdata/artist_name_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": [ 8 | "Kanye West", 9 | "Frank Ocean" 10 | ], 11 | "track_name": "Frank's Track", 12 | "release_name": "The Life of Pablo" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /listenbrainz/testdata/empty_artist_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "", 8 | "track_name": "Duuuuuuude!", 9 | "release_name": "The Life of Pablo", 10 | "additional_info": { 11 | "music_service": "spotify.com", 12 | "recording_msid": "2cfad207-3f55-4aec-8120-86cf66e34d59" 13 | } 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /listenbrainz/testdata/empty_track_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "track_name": "", 9 | "release_name": "The Life of Pablo", 10 | "additional_info": { 11 | "music_service": "spotify.com", 12 | "recording_msid": "2cfad207-3f55-4aec-8120-86cf66e34d59" 13 | } 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_artist_mbid.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": [ 3 | { 4 | "listened_at": 1486709515, 5 | "track_metadata": { 6 | "track_name": "Every Step Every Way", 7 | "artist_name": "Majid Jordan", 8 | "additional_info": { 9 | "artist_mbids": ["this mbid is not valid"] 10 | }, 11 | "release_name": "Majid Jordan" 12 | } 13 | } 14 | ], 15 | "listen_type": "import" 16 | } 17 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_duration.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": [ 3 | { 4 | "listened_at": 1486709515, 5 | "track_metadata": { 6 | "track_name": "Every Step Every Way", 7 | "artist_name": "Majid Jordan", 8 | "additional_info": { 9 | "duration": null 10 | }, 11 | "release_name": "Majid Jordan" 12 | } 13 | } 14 | ], 15 | "listen_type": "import" 16 | } 17 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_listen_missing_track_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "foobar": { 7 | "artist_name": "Kanye West", 8 | "track_name": "Fade", 9 | "release_name": "The Life of Pablo", 10 | "artist_mbid": "5441c29d-3602-4898-b1a1-b77fa23b8e50" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_listen_nan_in_json.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": NaN, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "track_name": "Fade", 9 | "release_name": "The Life of Pablo", 10 | "artist_mbid": "5441c29d-3602-4898-b1a1-b77fa23b8e50" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_listen_null_listened_at.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": null, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "track_name": "Fade", 9 | "release_name": "The Life of Pablo", 10 | "artist_mbid": "5441c29d-3602-4898-b1a1-b77fa23b8e50" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_listen_null_track_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": null 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_recording_mbid.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": [ 3 | { 4 | "listened_at": 1486709515, 5 | "track_metadata": { 6 | "track_name": "Every Step Every Way", 7 | "artist_name": "Majid Jordan", 8 | "additional_info": { 9 | "recording_mbid": "this mbid is not valid" 10 | }, 11 | "release_name": "Majid Jordan" 12 | } 13 | } 14 | ], 15 | "listen_type": "import" 16 | } 17 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_release_mbid.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": [ 3 | { 4 | "listened_at": 1486709515, 5 | "track_metadata": { 6 | "track_name": "Every Step Every Way", 7 | "artist_name": "Majid Jordan", 8 | "additional_info": { 9 | "release_mbid": "this mbid is not valid" 10 | }, 11 | "release_name": "Majid Jordan" 12 | } 13 | } 14 | ], 15 | "listen_type": "import" 16 | } 17 | -------------------------------------------------------------------------------- /listenbrainz/testdata/invalid_release_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "track_name": "The Life of Pablo", 9 | "release_name": 20, 10 | "additional_info": { 11 | "listening_from": "spotify", 12 | "recording_msid": "2cfad207-3f55-4aec-8120-86cf66e34d59" 13 | } 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /listenbrainz/testdata/listen_having_unicode_null.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "track_name": "\u0000Fade", 9 | "release_name": "The Life of Pablo", 10 | "artist_mbid": "e1564e98-978b-4947-8698-f6fd6f8b0181\u0000\ufeff9ad10546-b081-4cc8-a487-3d2eece82d9e\u0000\ufeff5245e5cd-4408-4d9e-a037-c71a53edce83" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /listenbrainz/testdata/mb_lookup_metadata_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "artist_credit_name": "Rick Astley", 3 | "artist_mbids": [ 4 | "db92a151-1ac2-438b-bc43-b82e149ddd50" 5 | ], 6 | "recording_mbid": "8f3471b5-7e6a-48da-86a9-c1c07a0f47ae", 7 | "recording_name": "Never Gonna Give You Up", 8 | "release_mbid": "18550a84-4ede-451c-a91a-dd9b3af9f71d", 9 | "release_name": "Red Hot" 10 | } 11 | -------------------------------------------------------------------------------- /listenbrainz/testdata/multi_duration.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": [ 3 | { 4 | "listened_at": 1486709515, 5 | "track_metadata": { 6 | "track_name": "Every Step Every Way", 7 | "artist_name": "Majid Jordan", 8 | "additional_info": { 9 | "duration": 222, 10 | "duration_ms": 200000 11 | }, 12 | "release_name": "Majid Jordan" 13 | } 14 | } 15 | ], 16 | "listen_type": "import" 17 | } -------------------------------------------------------------------------------- /listenbrainz/testdata/playing_now_ts.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "playing_now", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "release_name": "The Life of Pablo", 9 | "track_name": "Fade" 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /listenbrainz/testdata/playing_now_with_duration.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "playing_now", 3 | "payload": [ 4 | { 5 | "track_metadata": { 6 | "artist_name": "Kanye West", 7 | "release_name": "The Life of Pablo", 8 | "track_name": "Fade", 9 | "additional_info": { 10 | "duration": 1 11 | } 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /listenbrainz/testdata/playing_now_with_duration_ms.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "playing_now", 3 | "payload": [ 4 | { 5 | "track_metadata": { 6 | "artist_name": "Kanye West", 7 | "release_name": "The Life of Pablo", 8 | "track_name": "Fade", 9 | "additional_info": { 10 | "duration_ms": 1001 11 | } 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /listenbrainz/testdata/playing_now_with_ts.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "playing_now", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "release_name": "The Life of Pablo", 9 | "track_name": "Fade" 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /listenbrainz/testdata/same_timestamp_diff_track_valid_single.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "Radiohead", 8 | "track_name": "I Promise" 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /listenbrainz/testdata/same_timestamp_diff_track_valid_single_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "Chance The Rapper", 8 | "track_name": "All Night" 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /listenbrainz/testdata/same_timestamp_diff_track_valid_single_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409, 6 | "track_metadata": { 7 | "artist_name": "Frank Ocean", 8 | "track_name": "Nikes" 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /listenbrainz/testdata/timescale_listenstore_test_listens_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "track_metadata": { 6 | "track_name": "Immigrant Song 0", 7 | "additional_info": { 8 | "recording_mbid": "2cfad207-3f55-4aec-8120-86cf66e34d59" 9 | }, 10 | "artist_name": "Led Zeppelin" 11 | }, 12 | "listened_at": "1400000500", 13 | "recording_msid": "4269ddbc-9241-46da-935d-4fa9e0f7f371" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /listenbrainz/testdata/timestamp_before_lfm_founding.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 100, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "track_name": "Fade", 9 | "release_name": "The Life of Pablo" 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /listenbrainz/testdata/timestamp_in_ns.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "single", 3 | "payload": [ 4 | { 5 | "listened_at": 1486449409000000000, 6 | "track_metadata": { 7 | "artist_name": "Kanye West", 8 | "track_name": "Fade", 9 | "release_name": "The Life of Pablo" 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /listenbrainz/testdata/too_long_tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": [ 3 | { 4 | "track_metadata": { 5 | "additional_info": { 6 | "tags": [ 7 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 8 | ] 9 | }, 10 | "artist_name": "Kanye West", 11 | "release_name": "The Life of Pablo", 12 | "track_name": "Fade" 13 | } 14 | } 15 | ], 16 | "listen_type": "playing_now" 17 | } 18 | -------------------------------------------------------------------------------- /listenbrainz/testdata/user_daily_activity_db.json: -------------------------------------------------------------------------------- 1 | { 2 | "from_ts": 0, 3 | "to_ts": 10, 4 | "data": [ 5 | { 6 | "day": "Friday", 7 | "hour": 1, 8 | "listen_count": 5 9 | }, 10 | { 11 | "day": "Friday", 12 | "hour": 2, 13 | "listen_count": 5 14 | }, 15 | { 16 | "day": "Thursday", 17 | "hour": 2, 18 | "listen_count": 1 19 | } 20 | ], 21 | "stats_range": "all_time" 22 | } 23 | -------------------------------------------------------------------------------- /listenbrainz/testdata/user_listening_activity_db.json: -------------------------------------------------------------------------------- 1 | { 2 | "from_ts": 0, 3 | "to_ts": 10, 4 | "data": [ 5 | { 6 | "listen_count": 230, 7 | "time_range": "2020", 8 | "from_ts": 5, 9 | "to_ts": 10 10 | }, 11 | { 12 | "listen_count": 460, 13 | "time_range": "2019", 14 | "from_ts": 0, 15 | "to_ts": 5 16 | } 17 | ], 18 | "stats_range": "all_time" 19 | } 20 | -------------------------------------------------------------------------------- /listenbrainz/testdata/user_top_artists_db.json: -------------------------------------------------------------------------------- 1 | { 2 | "from_ts": 0, 3 | "to_ts": 10, 4 | "data": [ 5 | { 6 | "artist_mbid": "ed6c388e-ea65-431f-8be5-4959239d8c65", 7 | "listen_count": 230, 8 | "artist_name": "Kanye West" 9 | }, 10 | { 11 | "artist_mbid": "80ca06c7-07fb-4b3a-a315-ad584cc8eeb0", 12 | "listen_count": 229, 13 | "artist_name": "Frank Ocean" 14 | } 15 | ], 16 | "count": 2, 17 | "stats_range": "all_time" 18 | } 19 | -------------------------------------------------------------------------------- /listenbrainz/testdata/valid_duration.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": [ 3 | { 4 | "listened_at": 1486709515, 5 | "track_metadata": { 6 | "track_name": "Every Step Every Way", 7 | "artist_name": "Majid Jordan", 8 | "additional_info": { 9 | "duration_ms": 1222.6 10 | }, 11 | "release_name": "Majid Jordan" 12 | } 13 | } 14 | ], 15 | "listen_type": "import" 16 | } -------------------------------------------------------------------------------- /listenbrainz/testdata/valid_playing_now.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen_type": "playing_now", 3 | "payload": [ 4 | { 5 | "track_metadata": { 6 | "artist_name": "Kanye West", 7 | "release_name": "The Life of Pablo", 8 | "track_name": "Fade" 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /listenbrainz/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/timescale_writer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/timescale_writer/__init__.py -------------------------------------------------------------------------------- /listenbrainz/troi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/troi/__init__.py -------------------------------------------------------------------------------- /listenbrainz/troi/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/troi/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz/webserver/admin/views.py: -------------------------------------------------------------------------------- 1 | from flask_admin import expose 2 | from listenbrainz.webserver.admin import AdminIndexView 3 | 4 | 5 | class HomeView(AdminIndexView): 6 | 7 | @expose('/') 8 | def index(self): 9 | return self.render('admin/home.html') 10 | -------------------------------------------------------------------------------- /listenbrainz/webserver/converters.py: -------------------------------------------------------------------------------- 1 | from werkzeug.routing import PathConverter, ValidationError 2 | 3 | 4 | class NotApiPathConverter(PathConverter): 5 | 6 | def to_python(self, path): 7 | if path.startswith('1/'): 8 | raise ValidationError() 9 | return path 10 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/admin/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/master.html' %} 2 | {% block body %} 3 |

Welcome!

4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/admin/master.html: -------------------------------------------------------------------------------- 1 | {% extends admin_base_template %} 2 | 3 | {% block brand %} 4 | 5 | ListenBrainz 6 | :: 7 | BDFL Zone 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/atom/cb_review_event.html: -------------------------------------------------------------------------------- 1 |

2 | {{ event.user_name }} reviewed 3 | {{ event.metadata.entity_name }} 4 | on CritiqueBrainz. 5 |

6 |

Rating: {{ event.metadata.rating }}/5

7 |

Review: {{ event.metadata.text }}

-------------------------------------------------------------------------------- /listenbrainz/webserver/templates/atom/follow_event.html: -------------------------------------------------------------------------------- 1 |

2 | {{ event.metadata.user_name_0 }} 3 | started following 4 | {{ event.metadata.user_name_1 }}. 5 |

-------------------------------------------------------------------------------- /listenbrainz/webserver/templates/atom/fresh_releases.html: -------------------------------------------------------------------------------- 1 |

2 | {% if artist_mbid -%} 3 | {{ artist_name }} 4 | {%- else -%} 5 | {{ artist_name }} 6 | {%- endif %} 7 | released 8 | {% if release_mbid -%} 9 | {{ release_name }}. 10 | {%- else -%} 11 | {{ release_name }}. 12 | {%- endif %} 13 |

14 |

15 | Explore more 16 |

-------------------------------------------------------------------------------- /listenbrainz/webserver/templates/atom/listens.html: -------------------------------------------------------------------------------- 1 |

2 | {{ user_name }} 3 | listened to 4 | {% if recording_mbid -%} 5 | {{ track_name }} 6 | {%- else -%} 7 | {{ track_name }} 8 | {%- endif %} 9 | by 10 | {% if artist_mbid -%} 11 | {{ artist_name }} 12 | {%- else -%} 13 | {{ artist_name }}. 14 | {%- endif %} 15 |

-------------------------------------------------------------------------------- /listenbrainz/webserver/templates/atom/notification_event.html: -------------------------------------------------------------------------------- 1 |

{{ event.metadata.message }}

-------------------------------------------------------------------------------- /listenbrainz/webserver/templates/atom/recording.html: -------------------------------------------------------------------------------- 1 |

2 | {% if recording_mbid -%} 3 | {{ recording_title }} 4 | {%- else -%} 5 | {{ recording_title }} 6 | {%- endif %} 7 | by 8 | {% if artist_mbid -%} 9 | {{ artist_credit }} 10 | {%- else -%} 11 | {{ artist_credit }}. 12 | {%- endif %} 13 |

-------------------------------------------------------------------------------- /listenbrainz/webserver/templates/atom/top_artists.html: -------------------------------------------------------------------------------- 1 |

2 |

14 |

15 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/artist_relation_import_notification.txt: -------------------------------------------------------------------------------- 1 | Hey! 👋 2 | 3 | Artist relation has been successfully imported into the spark cluster at {{ import_time }}. 4 | 5 | Took {{ time_taken_to_import }}s to import the artist relation. 6 | 7 | Your Friendly Neighbourhood Brainzbot 🤖 8 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/cf_candidate_sets_upload_notification.txt: -------------------------------------------------------------------------------- 1 | Hey! 👋 2 | 3 | Candidate sets have been successfully uploaded to HDFS. 4 | listens submitted from {{ from_date }} to {{ to_date }} are used to create the candidate sets. 5 | 6 | upload time = {{ time_to_upload }} 7 | time taken to create candidate sets = {{ total_time }}m 8 | 9 | Your Friendly Neighbourhood Brainzbot 🤖 10 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/cf_recording_dataframes_upload_notification.txt: -------------------------------------------------------------------------------- 1 | Hey! 👋 2 | 3 | Dataframes have been successfully uploaded to HDFS. 4 | Data relating to releases missing from MusicBrainz are being written into the DB now. 5 | listens submitted from {{ from_date }} to {{ to_date }} are used to create the dataframes. 6 | 7 | upload time = {{ time_to_upload }} 8 | time taken to create dataframes = {{ total_time }}m 9 | 10 | Your Friendly Neighbourhood Brainzbot 🤖 11 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/cf_recording_model_upload_notification.txt: -------------------------------------------------------------------------------- 1 | Hey! 👋 2 | 3 | Data has been successfully trained to create a model. 4 | 5 | upload time = {{ time_to_upload }} 6 | time taken to create model = {{ total_time }}s 7 | 8 | Your Friendly Neighbourhood Brainzbot 🤖 9 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/cf_recording_recommendation_notification.txt: -------------------------------------------------------------------------------- 1 | Hey! 👋 2 | 3 | Recommendations were received by the spark consumer 4 | and are being written into the database. 5 | 6 | It took {{ total_time }}h to generate the recommendations. 7 | Users active in the last week: {{ active_user_count }} 8 | Top artist recommendations generated for {{ top_artist_user_count }} users. 9 | Similar artist recommendations generated for {{ similar_artist_user_count }} users. 10 | 11 | Your Friendly Neighbourhood Brainzbot 🤖 12 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/data_dump_created_notification.txt: -------------------------------------------------------------------------------- 1 | Hi! 👋 2 | 3 | A new data dump has been automatically created. 4 | 5 | Name: {{ dump_name }} 6 | 7 | Please check that it was synced to FTP correctly 8 | and is available at the following link: 9 | 10 | {{ dump_link }} 11 | 12 | 13 | Your Friendly Neighbourhood Brainzbot 🤖 14 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/data_dump_outdated.txt: -------------------------------------------------------------------------------- 1 | Bon dia! 👋 2 | 3 | Sorry, but I come bearing bad news -- your shit is broken again. Surprise! 4 | 5 | Some data dumps are outdated: 6 | 7 | {{msg}} 8 | 9 | Go fix your shit! 10 | 11 | Your Grouchy Neighbourhood Brainzbot 🤖 12 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/dump_import_failure.txt: -------------------------------------------------------------------------------- 1 | Hi! 👋 2 | 3 | Sorry, but I come bearing bad news -- your shit is broken again. Surprise! 4 | 5 | No new data dump has been imported into the spark cluster. 6 | 7 | Import time: {{ time }} 8 | 9 | These are some of the errors that occurred: 10 | 11 | {% for error in errors %} 12 | {{ error }} 13 | {% endfor %} 14 | 15 | Go fix your shit! 16 | 17 | Your Friendly Neighbourhood Brainzbot 🤖 18 | 19 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/export_completed.txt: -------------------------------------------------------------------------------- 1 | Hi {{ username }}! 2 | 3 | We completed your requested data export. Please visit {{ url }} to download your data archive. 4 | 5 | Best, 6 | The ListenBrainz Team 7 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/id_paused.txt: -------------------------------------------------------------------------------- 1 | Hi {{ username }}! 2 | 3 | We have paused your ListenBrainz account {{ username }} because of a problem with your account. 4 | Our community manager will reach out to you with the details as to why we took this step. 5 | Feel free to contact Us {{ url }} if you have any questions about this. 6 | 7 | Best, 8 | The ListenBrainz Team -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/id_unpaused.txt: -------------------------------------------------------------------------------- 1 | Hi {{ username }}! 2 | 3 | Your ListenBrainz account {{ username }} has been restored and is now accepting listens again. 4 | 5 | Best, 6 | The ListenBrainz Team -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/listens_importer_error.txt: -------------------------------------------------------------------------------- 1 | Hi! 2 | 3 | We encountered an error while importing your listens from {{service}}. 4 | The error was as follows: "{{error}}" 5 | 6 | Please take a look at your {{service}} import page ({{link}}) 7 | for more information. 8 | 9 | Best, 10 | The ListenBrainz Team 11 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/mapping_import_notification.txt: -------------------------------------------------------------------------------- 1 | Hey! 👋 2 | 3 | MSID MBID mapping has been successfully imported into the spark cluster at {{ import_time }}. 4 | 5 | Took {{ time_taken_to_import }}s to import the mapping 6 | 7 | Your Friendly Neighbourhood Brainzbot 🤖 8 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/similar_users_failed_notification.txt: -------------------------------------------------------------------------------- 1 | Hey! 👋 2 | 3 | Similar users were not able to be written to the database, due to: 4 | 5 | {{ error }} 6 | 7 | Your Friendly Neighbourhood Brainzbot 🤖 8 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/similar_users_updated_notification.txt: -------------------------------------------------------------------------------- 1 | Hey! 👋 2 | 3 | Similar users were received by the spark consumer 4 | and are being written into the database. 5 | 6 | {{ user_count }} users had similar users calculated with an average 7 | of {{ avg_similar_users }} being generated per user. 8 | 9 | Your Friendly Neighbourhood Brainzbot 🤖 10 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/emails/user_stats_notification.txt: -------------------------------------------------------------------------------- 1 | Hi! 👋 2 | 3 | New user stats were received by the spark consumer 4 | and are being written into the database. 5 | 6 | Stat type: {{ stat_type }} 7 | 8 | The first stat came in at {{ now }} UTC. 9 | 10 | Your Friendly Neighbourhood Brainzbot 🤖 11 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/errors/400.html: -------------------------------------------------------------------------------- 1 | {% extends 'errors/base.html' %} 2 | {% block error_title %}Invalid request{% endblock %} 3 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/errors/401.html: -------------------------------------------------------------------------------- 1 | {% extends 'errors/base.html' %} 2 | {% block error_title %}Unauthorized{% endblock %} 3 | 4 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/errors/403.html: -------------------------------------------------------------------------------- 1 | {% extends 'errors/base.html' %} 2 | {% block error_title %}Access denied{% endblock %} 3 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/errors/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'errors/base.html' %} 2 | {% block error_title %}Page not found{% endblock %} 3 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/errors/413.html: -------------------------------------------------------------------------------- 1 | {% extends 'errors/base.html' %} 2 | {% block error_title %}Filesize limit exceeded{% endblock %} 3 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/errors/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'errors/base.html' %} 2 | {% block error_title %}Internal server error{% endblock %} 3 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/errors/503.html: -------------------------------------------------------------------------------- 1 | {% extends 'errors/base.html' %} 2 | {% block error_title %}Service unavailable{% endblock %} 3 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/errors/base.html: -------------------------------------------------------------------------------- 1 | {% extends 'index.html' %} 2 | 3 | {% block title %}{% block error_title %}{% endblock %} - ListenBrainz{% endblock %} 4 | 5 | {% block content %} 6 |

{{ self.error_title() }}

7 |

{% block error_description %}{{ error.description }}{% endblock %}

8 |

{% block error_info %}{{ error }}{% endblock %}

9 |

Back to home page

10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /listenbrainz/webserver/templates/macros.html: -------------------------------------------------------------------------------- 1 | {% macro print_message(message, category) %} 2 | {% if category == "error" %} 3 | {% set alert_class="alert-danger" %} 4 | {% elif category == "warning" %} 5 | {% set alert_class="alert-warning" %} 6 | {% elif category == "success" %} 7 | {% set alert_class="alert-success" %} 8 | {% else %} {# info & default #} 9 | {% set alert_class="alert-info" %} 10 | {% endif %} 11 | 12 |
{{ message }}
13 | {% endmacro %} 14 | -------------------------------------------------------------------------------- /listenbrainz/webserver/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/webserver/test/__init__.py -------------------------------------------------------------------------------- /listenbrainz/webserver/test/test_utils.py: -------------------------------------------------------------------------------- 1 | from listenbrainz.webserver import utils 2 | from listenbrainz.webserver.testing import ServerTestCase 3 | 4 | 5 | class UtilsTestCase(ServerTestCase): 6 | 7 | def test_generate_string(self): 8 | length = 42 9 | str_1 = utils.generate_string(length) 10 | str_2 = utils.generate_string(length) 11 | 12 | self.assertEqual(len(str_1), length) 13 | self.assertEqual(len(str_2), length) 14 | self.assertNotEqual(str_1, str_2) # Generated strings shouldn't be the same 15 | -------------------------------------------------------------------------------- /listenbrainz/webserver/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/webserver/views/__init__.py -------------------------------------------------------------------------------- /listenbrainz/webserver/views/art.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, Blueprint, current_app 2 | 3 | art_bp = Blueprint('art', __name__) 4 | 5 | 6 | @art_bp.get("/") 7 | def index(): 8 | """ This page shows of a bit of what can be done with the cover art, as a sort of showcase. """ 9 | return render_template("art/index.html", api_url=current_app.config["API_URL"]) 10 | -------------------------------------------------------------------------------- /listenbrainz/webserver/views/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/webserver/views/test/__init__.py -------------------------------------------------------------------------------- /listenbrainz/webserver/views/test/test_login.py: -------------------------------------------------------------------------------- 1 | 2 | from listenbrainz.webserver.testing import ServerTestCase 3 | 4 | 5 | class LoginViewsTestCase(ServerTestCase): 6 | 7 | def test_login_page(self): 8 | response = self.client.get(self.custom_url_for('login.index')) 9 | self.assert200(response) 10 | -------------------------------------------------------------------------------- /listenbrainz/websockets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz/websockets/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/README.md: -------------------------------------------------------------------------------- 1 | # ListenBrainz Spark 2 | 3 | In order to understand how ListenBrainz works and communicates 4 | with Spark, read the [architecture document](https://listenbrainz.readthedocs.io/en/latest/developers/spark-architecture.html). 5 | -------------------------------------------------------------------------------- /listenbrainz_spark/constants.py: -------------------------------------------------------------------------------- 1 | LAST_FM_FOUNDING_YEAR = 2002 2 | -------------------------------------------------------------------------------- /listenbrainz_spark/dump/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/dump/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/echo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/echo/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/echo/echo.py: -------------------------------------------------------------------------------- 1 | def handler(message): 2 | """ Handler that echos its message it receives back to the queue as response as-is """ 3 | return [ 4 | { 5 | "type": "echo", 6 | "message": message 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /listenbrainz_spark/fresh_releases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/fresh_releases/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/fresh_releases/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/fresh_releases/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/hdfs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/hdfs/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/hdfs_connection.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import hdfs 4 | 5 | client = None 6 | 7 | 8 | def init_hdfs(namenode_uri, user=None): 9 | if user is None: 10 | user = os.environ["USER"] 11 | global client 12 | client = hdfs.InsecureClient(namenode_uri, user=user) 13 | -------------------------------------------------------------------------------- /listenbrainz_spark/listens/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/listens/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/listens/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/listens/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/missing_mb_data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/missing_mb_data/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/missing_mb_data/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/missing_mb_data/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/mlhd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/mlhd/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/popularity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/popularity/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/postgres/artist_credit.py: -------------------------------------------------------------------------------- 1 | from data.postgres.artist_credit import get_artist_credit_cache_query 2 | from listenbrainz_spark.path import ARTIST_CREDIT_MBID_DATAFRAME 3 | from listenbrainz_spark.postgres.utils import save_pg_table_to_hdfs 4 | 5 | 6 | def create_artist_credit_cache(): 7 | """ Import artist country from postgres to HDFS for use in artist map stats calculation. """ 8 | query = get_artist_credit_cache_query() 9 | save_pg_table_to_hdfs(query, ARTIST_CREDIT_MBID_DATAFRAME) 10 | -------------------------------------------------------------------------------- /listenbrainz_spark/postgres/feedback.py: -------------------------------------------------------------------------------- 1 | from data.postgres.feedback import get_feedback_cache_query 2 | from listenbrainz_spark.path import RECORDING_FEEDBACK_DATAFRAME 3 | from listenbrainz_spark.postgres.utils import save_lb_table_to_hdfs 4 | 5 | 6 | def create_feedback_cache(): 7 | query = get_feedback_cache_query() 8 | save_lb_table_to_hdfs(query, RECORDING_FEEDBACK_DATAFRAME) 9 | -------------------------------------------------------------------------------- /listenbrainz_spark/recommendations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/recommendations/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/recommendations/recording/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/recommendations/recording/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/recommendations/templates/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/recommendations/templates/index.html -------------------------------------------------------------------------------- /listenbrainz_spark/recommendations/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/recommendations/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/request_consumer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/request_consumer/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/similarity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/similarity/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/similarity/recording/__init__.py: -------------------------------------------------------------------------------- 1 | from listenbrainz_spark.similarity.recording.listens import ListensRecordingSimilarity 2 | from listenbrainz_spark.similarity.recording.mlhd import MlhdRecordingSimilarity 3 | 4 | 5 | def main(mlhd, **kwargs): 6 | """ Generate similar recordings """ 7 | if mlhd: 8 | engine = MlhdRecordingSimilarity(**kwargs) 9 | else: 10 | engine = ListensRecordingSimilarity(**kwargs) 11 | yield from engine.run() 12 | -------------------------------------------------------------------------------- /listenbrainz_spark/stats/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/common/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/incremental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/incremental/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/incremental/listener/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/incremental/listener/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/incremental/sitewide/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/incremental/sitewide/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/incremental/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/incremental/user/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/listener/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/listener/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/listener/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/listener/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/sitewide/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/sitewide/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/sitewide/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/sitewide/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/tests/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/stats/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/stats/user/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/tags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/tags/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/artist_country_code.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/artist_country_code.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/artist_credit_mbid.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/artist_credit_mbid.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/0.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/full-dump-1/0.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/1.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/full-dump-1/1.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/2.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/full-dump-1/2.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/3.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/full-dump-1/3.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/4.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/full-dump-1/4.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/5.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/full-dump-1/5.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/6.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/full-dump-1/6.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/COPYING: -------------------------------------------------------------------------------- 1 | Sample Dump License -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/END_TIMESTAMP: -------------------------------------------------------------------------------- 1 | 2021-08-07 00:00:00.000000 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/SCHEMA_SEQUENCE: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/full-dump-1/START_TIMESTAMP: -------------------------------------------------------------------------------- 1 | 2020-01-04 15:38:30.282842 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-2/0.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/incremental-dump-2/0.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-2/COPYING: -------------------------------------------------------------------------------- 1 | Sample Dump License -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-2/END_TIMESTAMP: -------------------------------------------------------------------------------- 1 | 2021-08-09 00:00:00.000000 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-2/SCHEMA_SEQUENCE: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-2/START_TIMESTAMP: -------------------------------------------------------------------------------- 1 | 2021-08-08 00:00:00.000000 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-3/0.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/incremental-dump-3/0.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-3/COPYING: -------------------------------------------------------------------------------- 1 | Sample Dump License -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-3/END_TIMESTAMP: -------------------------------------------------------------------------------- 1 | 2021-08-10 00:00:00.000000 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-3/SCHEMA_SEQUENCE: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-3/START_TIMESTAMP: -------------------------------------------------------------------------------- 1 | 2021-08-09 00:00:00.000000 -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-4/fresh_releases_listens.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/incremental-dump-4/fresh_releases_listens.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/incremental-dump-5/rec_listens.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/incremental-dump-5/rec_listens.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/mapped_listens.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/mapped_listens.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/mapped_listens_candidate_sets.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/mapped_listens_candidate_sets.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/mapped_listens_subset.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/mapped_listens_subset.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/recording_artist.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/recording_artist.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/release_group_metadata_cache.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/release_group_metadata_cache.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/testdata/release_metadata_cache.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/testdata/release_metadata_cache.parquet -------------------------------------------------------------------------------- /listenbrainz_spark/troi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/troi/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/year_in_music/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/listenbrainz_spark/year_in_music/__init__.py -------------------------------------------------------------------------------- /listenbrainz_spark/year_in_music/utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, date, time 2 | 3 | from listenbrainz_spark.listens.data import get_listens_from_dump 4 | 5 | 6 | def setup_listens_for_year(year): 7 | start = datetime(year, 1, 1) 8 | end = datetime.combine(date(year, 12, 31), time.max) 9 | listens = get_listens_from_dump(start, end) 10 | listens.createOrReplaceTempView("listens_of_year") 11 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | import listenbrainz.spark.request_manage as spark_request_manage 2 | from listenbrainz.dumps import manager as dump_manager 3 | from listenbrainz.manage import cli 4 | 5 | # Add other commands here 6 | cli.add_command(spark_request_manage.cli, name="spark") 7 | cli.add_command(dump_manager.cli, name="dump") 8 | 9 | if __name__ == '__main__': 10 | cli() 11 | -------------------------------------------------------------------------------- /mbid_mapping/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/mbid_mapping/__init__.py -------------------------------------------------------------------------------- /mbid_mapping/admin/data_dump_files/README_data_dump_files.md: -------------------------------------------------------------------------------- 1 | Please note -- the files in this directory are here for inclusion in data dump files. They do not apply to the code in this repository! 2 | -------------------------------------------------------------------------------- /mbid_mapping/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker build -t metabrainz/mbid-mapping --target mbid-mapping-base . 4 | -------------------------------------------------------------------------------- /mbid_mapping/docker/consul-template.conf: -------------------------------------------------------------------------------- 1 | template { 2 | source = "/code/mapper/docker/consul_config.py.ctmpl" 3 | destination = "/code/mapper/config.py" 4 | } 5 | -------------------------------------------------------------------------------- /mbid_mapping/docker/mapper.service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec run-consul-template -config /etc/consul-template-mbid-mapping.conf 4 | -------------------------------------------------------------------------------- /mbid_mapping/mapping/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/mbid_mapping/mapping/__init__.py -------------------------------------------------------------------------------- /mbid_mapping/mapping/cube.py: -------------------------------------------------------------------------------- 1 | from psycopg2.extensions import adapt, AsIs 2 | 3 | 4 | class Cube(object): 5 | def __init__(self, r, g, b): 6 | self.r = r 7 | self.g = g 8 | self.b = b 9 | 10 | 11 | def adapt_cube(cube): 12 | return AsIs("'(%s, %s, %s)'" % (adapt(cube.r), adapt(cube.g), adapt(cube.b))) 13 | -------------------------------------------------------------------------------- /mbid_mapping/mapping/mapping_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metabrainz/listenbrainz-server/f9c919822105d14a82cc2f2abff7fa235752eae7/mbid_mapping/mapping/mapping_test/__init__.py -------------------------------------------------------------------------------- /mbid_mapping/requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.1.8 2 | pytest==8.3.5 3 | pytest-cov==6.1.1 4 | psycopg2-binary==2.9.10 5 | ujson==5.10.0 6 | typesense==1.0.3 7 | unidecode>=1.4.0 8 | python-dateutil==2.9.0.post0 9 | brainzutils@git+https://github.com/metabrainz/brainzutils-python.git@v3.0.0 10 | tqdm==4.67.1 11 | sentry_sdk==2.27.0 12 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs=listenbrainz_spark relations listenbrainz/mbid_mapping 3 | addopts = -W always::DeprecationWarning -W error::sqlalchemy.exc.Base20DeprecationWarning --cov=listenbrainz --no-cov-on-fail --timeout=300 4 | -------------------------------------------------------------------------------- /pytest.spark.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = listenbrainz_spark 3 | addopts = --cov=listenbrainz_spark 4 | -------------------------------------------------------------------------------- /requirements_development.txt: -------------------------------------------------------------------------------- 1 | Flask-Testing==0.8.1 2 | coverage==7.8.0 3 | flask-shell-ipython==0.5.3 4 | freezegun==1.5.1 5 | pytest==8.3.5 6 | pytest-cov==6.1.1 7 | requests-mock==1.12.1 8 | pytest-subtests==0.14.1 9 | python-socketio[client] 10 | lxml==5.4.0 11 | pytest-timeout==2.3.1 -------------------------------------------------------------------------------- /requirements_spark.txt: -------------------------------------------------------------------------------- 1 | hdfs==2.7.3 2 | Jinja2==3.1.6 3 | MarkupSafe==3.0.2 4 | itsdangerous==2.2.0 5 | kombu==5.4.2 6 | python-dateutil==2.9.0.post0 7 | numpy==2.2.3 8 | pydantic==1.10.13 9 | sentry-sdk==2.22.0 10 | more-itertools==10.6.0 11 | pycountry==24.6.1 12 | pycurl==7.45.6 13 | requests==2.32.3 14 | pandas==2.2.3 15 | zstandard==0.23.0 16 | pyarrow==19.0.1 17 | -------------------------------------------------------------------------------- /spark_config.sh.sample: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LISTENERBUS_CAPACITY="100000" 4 | MAX_CORES="20" 5 | EXECUTOR_CORES="5" 6 | EXECUTOR_MEMORY="4g" 7 | DRIVER_MEMORY="2g" 8 | DRIVER_MAX_RESULT_SIZE="4g" 9 | DRIVER_MEMORY_OVERHEAD="600m" 10 | EXECUTOR_MEMORY_OVERHEAD="600m" 11 | CONTAINER_NAME="listenbrainz-jobs-`whoami`" 12 | -------------------------------------------------------------------------------- /spark_manage.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from listenbrainz_spark.request_consumer.request_consumer import RequestConsumer 4 | 5 | 6 | if __name__ == "__main__": 7 | app_name = f"request-consumer-{int(time.time())}" 8 | rc = RequestConsumer() 9 | rc.start(app_name) 10 | --------------------------------------------------------------------------------