File: README.md
Your Spotify
YourSpotify is a self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it!
It's composed of a web server which polls the Spotify API every now and then and a web application on which you can explore your statistics.
Table of contents
- Prerequisites
- Installation
- Creating the Spotify application
- Importing past history
- FAQ
- External guides
- Contributing
- Sponsoring
Prerequisites
- You have to own a Spotify application ID that you can create through their dashboard.
- You need to provide the Server environment the public AND secret key of the application (cf. Installation).
- You need to provide an authorized redirect URI to the
docker-composefile.
A tutorial is available at the end of this readme.
Installation
Using `docker-compose`
Follow the docker-compose-example.yml to host your application through docker.
services:
server:
image: yooooomi/your_spotify_server
restart: always
ports:
- "8080:8080"
links:
- mongo
depends_on:
- mongo
environment:
API_ENDPOINT: http://localhost:8080 # This MUST be included as a valid URL in the spotify dashboard (see below)
CLIENT_ENDPOINT: http://localhost:3000
SPOTIFY_PUBLIC: __your_spotify_client_id__
SPOTIFY_SECRET: __your_spotify_secret__
web:
image: yooooomi/your_spotify_client
restart: always
ports:
- "3000:3000"
environment:
API_ENDPOINT: http://localhost:8080
mongo:
container_name: mongo
image: mongo:8
volumes:
- ./your_spotify_db:/data/dbInstalling locally (not recommended)
You can follow the instructions here. Note that you will still have to do the steps below.
Environment
| Key | Default value (if any) | Description |
|---|---|---|
| CLIENT_ENDPOINT | REQUIRED | The endpoint of your web application |
| API_ENDPOINT | REQUIRED | The endpoint of your server |
| SPOTIFY_PUBLIC | REQUIRED | The public key of your Spotify application (cf Creating the Spotify Application) |
| SPOTIFY_SECRET | REQUIRED | The secret key of your Spotify application (cf Creating the Spotify Application) |
| TIMEZONE | Europe/Paris | The timezone of your stats, only affects read requests since data is saved with UTC time |
| MONGO_ENDPOINT | mongodb://mongo:27017/your_spotify | The endpoint of the Mongo database, where mongo is the name of your service in the compose file |
| PROMETHEUS_USERNAME | not defined | Prometheus basic auth username (see here) |
| PROMETHEUS_PASSWORD | not defined | Prometheus basic auth password |
| LOG_LEVEL | info | The log level, debug is useful if you encouter any bugs |
| CORS | not defined | List of comma-separated origin allowed (not required; defaults to CLIENT_ENDPOINT) |
| COOKIE_VALIDITY_MS | 1h | Validity time of the authentication cookie, following this pattern |
| MAX_IMPORT_CACHE_SIZE | Infinite | The maximum element in the cache when importing data from an outside source, more cache means less requests to Spotify, resulting in faster imports |
| MONGO_NO_ADMIN_RIGHTS | false | Do not ask for admin right on the Mongo database |
| PORT | 8080 | The port of the server, do not modify if you're using docker |
| FRAME_ANCESTORS | not defined | Sites allowed to frame the website, comma separated list of URLs (i-want-a-security-vulnerability-and-want-to-allow-all-frame-ancestors to allow every website) |
Advanced CORS settings
Manually specifying CORS configuration is not required for typical deployments.
99.9% of users do not need to worry about this, it is handled automatically.
If your use case requires the backend to be used from multiple frontend origins, you can manually adjust the CORS variable.
For example, a value of origin1,origin2 will allow origin1 and origin2.
Creating the Spotify Application
For YourSpotify to work you need to provide a Spotify application public AND secret to the server environment.
To do so, you need to create a Spotify application here.
- Click on Create app.
- Fill out all the information.
- Set the redirect URI, corresponding to your server location on the internet (or your local network) adding the suffix /oauth/spotify/callback (/api/oauth/spotify/callback if using the linuxserver image).
- i.e:
http://localhost:8080/oauth/spotify/callbackorhttp://home.mydomain.com/your_spotify_backend/oauth/spotify/callback
- Check Web API
- Check I understand and agree
- Hit Settings at the top right corner
- Copy the public and the secret key into your
docker-composefile under the name ofSPOTIFY_PUBLICandSPOTIFY_SECRET
respectively. - Once you have created your application, Spotify wants you to register the users that will be able to access the application. (You don't need to do that for the account that created the application)
- Click the User Management button
- Enter the required information, a name and the email the user's Spotify account has been created with.
- (Optional) You can Request extension if you do not want to register the users by hand.
Importing past history
By default, YourSpotify will only retrieve data for the past 24 hours once registered. This is a technical limitation. However, you can import previous data by two ways.
The import process uses cache to limit requests to the Spotify API. By default, the cache size is unlimited, but you can limit is with the MAX_IMPORT_CACHE_SIZE env variable in the server.
Supported import methods
Privacy data
Takes a maximum of 5 days.
Only gets you the last year of history.
- Request your privacy data at Spotify to have access to your history for the past year here.
- Head to the Settings page and choose the Account data method.
- Input your files starting with
StreamingHistoryX.json. - Start your import.
Full privacy data (recommended)
Takes a maximum of 30 days.
Gets you the whole history since the creation of your account.
- Request your Full privacy data to have access to your history data since the creation of the account here.
- Head to the Settings page and choose the Extended streaming history method.
- Input your files starting with
Streaming_History_Audio_YYYY-YYYY_X.json. - Start your import.
Troubleshoot
An import can fail:
- If the server reboots.
- If a request fails 10 times in a row.
A failed import can be retried in the Settings page. Be sure to clean your failed imports if you do not want to retry it as it will remove the files used for it.
It is safer to import data at account creation. Though YourSpotify detects duplicates, some may still be inserted.
FAQ
How can I block new registrations?
From an admin account, go to the Settings page and hit the Disable new registrations button.
Songs don't seem to synchronize anymore.
This can happen if you revoked access on your Spotify account. To re-sync the songs, go to settings and hit the Relog to Spotify button.
The web application is telling me it cannot retrieve global preferences.
This means that your web application can't connect to the backend. Check that your API_ENDPOINT env variable is reachable from the device you're using the platform from.
A specific user does not use the application in the same timezone as the server, how can I set a specific timezone for him?
Any user can set his proper timezone in the settings, it will be used for any computed statistics. The timezone of the device will be used for everything else, such as song history.
External guides
- BreadNet installation tutorial
Contributing
If you have any issue or any idea that could make the project better, feel free to open an issue. I'd love to hear about new ideas or bugs you are encountering.
Sponsoring
I work on this project on my spare time and try to fix issues as soon as I can. If you feel generous and think this project and my investment are worth a few cents, you can consider sponsoring it with the button on the right, many thanks.
File: docker-compose-dev.yml
services:
app:
container_name: server
restart: always
build:
context: .
dockerfile: Dockerfile.server
args:
NODE_ENV: development
volumes:
- ./apps/server/src/:/app/apps/server/src/
links:
- mongo
depends_on:
- mongo
ports:
- "8080:8080"
environment:
LOG_LEVEL: debug
API_ENDPOINT: http://127.0.0.1:8080 # This MUST be included as a valid URL in the spotify dashboard
CLIENT_ENDPOINT: http://127.0.0.1:3000
# OFFLINE_DEV_ID: true
mongo:
image: mongo:8
volumes:
- ./db_data:/data/db
ports:
- "27017:27017"
web:
container_name: web
restart: always
build:
context: .
dockerfile: Dockerfile.client
args:
NODE_ENV: development
volumes:
- ./apps/client/src:/app/apps/client/src/
- ./apps/client/public:/app/apps/client/public/
ports:
- "3000:3000"
environment:
NODE_ENV: development
API_ENDPOINT: http://127.0.0.1:8080
File: docker-compose-example.yml
services:
server:
image: yooooomi/your_spotify_server
restart: always
ports:
- "8080:8080"
links:
- mongo
depends_on:
- mongo
environment:
API_ENDPOINT: http://127.0.0.1:8080 # This MUST be included as a valid URL in the spotify dashboard
CLIENT_ENDPOINT: http://127.0.0.1:3000
SPOTIFY_PUBLIC: your_spotify_client_id
SPOTIFY_SECRET: your_spotify_secret
mongo:
container_name: mongo
image: mongo:8
restart: always
volumes:
- ./your_spotify_db:/data/db
web:
image: yooooomi/your_spotify_client
restart: always
ports:
- "3000:3000"
environment:
API_ENDPOINT: http://127.0.0.1:8080
File: docker-compose-prod.yml
services:
app:
container_name: server
restart: always
build:
context: .
dockerfile: Dockerfile.server.production
args:
NODE_ENV: production
links:
- mongo
depends_on:
- mongo
ports:
- "8080:8080"
environment:
LOG_LEVEL: debug
API_ENDPOINT: http://127.0.0.1:8080 # This MUST be included as a valid URL in the spotify dashboard
CLIENT_ENDPOINT: http://127.0.0.1:3000
# OFFLINE_DEV_ID: true
mongo:
image: mongo:8
# logging:
# driver: none
volumes:
- ./db_data:/data/db
ports:
- "27017:27017"
web:
container_name: web
restart: always
build:
context: .
dockerfile: Dockerfile.client.production
args:
NODE_ENV: production
ports:
- "3000:3000"
environment:
NODE_ENV: production
API_ENDPOINT: http://127.0.0.1:8080
File: LOCAL_INSTALL.md
Local installation (no docker)
Prerequisites
You will need to have
- Git clone of this repo (you can
git clone [email protected]:Yooooomi/your_spotify.git --depth=1) - A mongo database
- NodeJS
Hosting backend
- Go to the server directory and
yarnin it - Run
yarn build - Run
yarn migratewith the right environment variables - Run
node lib/bin/www.jsthe right environment variables
The environment variables are the same as in the docker-compose for server, with an additional PORT variable you can set.
Hosting client
- Go to the client directory and
yarnin it - Run
yarn build - In the build directory produced, copy
variables-template.jsintovariables.jsand replace the endpoint with the API_ENDPOINT, that is the endpoint of your backend - Host the static website in the build directory the way you want, I personally use
serve
Note that you need to redirect all 404 errors to index.html, serve does this by default.
You can find an.htaccessfile for Apache2.
Using systemd user unit
You can have the Mongo and backend running through systemd user units.
- Create a systemd unit file in your home directory at
.config/systemd/user/(create the folders if they don't exist) and name it something likeyour_spotify.service.
File content should be something like (seperate one for MongoDB if needed):
[Unit]
Description=Your Spotify Backend
Wants=network-online.target mongodb.service
After=syslog.target network.target nss-lookup.target network-online.target
[Service]
EnvironmentFile=/home/YOUR_USER/sites/applications/spotify/your_spotify/server/.env
ExecStart=/home/YOUR_USER/.nvm/versions/node/v16.14.0/bin/node lib/bin/www
StandardOutput=journal
Restart=on-failure
WorkingDirectory=/home/YOUR_USER/sites/applications/spotify/your_spotify/server
[Install]
WantedBy=multi-user.targetRefrenced env file only has the ENV variables in it.
Example MongoDB version of the systemd unit file:
[Unit]
Description=MongoDB
Wants=network-online.target
After=syslog.target network.target nss-lookup.target network-online.target
[Service]
Type=forking
ExecStart=/home/YOUR_USER/filesystem/bin/mongod --dbpath /home/YOUR_USER/filesystem/var/lib/mongo --logpath /home/YOUR_USER/filesystem/var/log/mongodb/mongod.log --fork --bind_ip 127.0.0.1 --port 40097
StandardOutput=journal
Restart=on-failure
WorkingDirectory=/home/YOUR_USER/filesystem/
[Install]
WantedBy=multi-user.target- Enable the services and start them with
systemctl --user --now enable mongodbandsystemctl --user --now enable your_spotify. - The backend should have started up properly.
File: package.json
{
"name": "@your_spotify/root",
"version": "1.20.1",
"repository": "[email protected]:Yooooomi/your_spotify.git",
"author": "Timothee [email protected]",
"private": true
}
File: pnpm-workspace.yaml
packages:
- apps/*
allowBuilds:
core-js-pure: true
catalog:
'@eslint/json': 1.2.0
'@rsbuild/core': 2.0.15
globals: 17.6.0
typescript: 6.0.3
typescript-eslint: 8.59.3
oxfmt: 0.55.0
oxlint: 1.70.0
minimumReleaseAgeExclude:
onlyBuiltDependencies:
- core-js
- unrs-resolver
File: apps/client/package.json
{
"name": "@your_spotify/client",
"version": "1.20.1",
"private": true,
"scripts": {
"start": "rsbuild dev --host",
"build": "rsbuild build",
"preview": "rsbuild preview",
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "oxlint -c ../dev/.oxlintrc.json . && oxfmt -c ../dev/.oxfmtrc.json --check .",
"lint:fix": "oxlint -c ../dev/.oxlintrc.json --fix . && oxfmt -c ../dev/.oxfmtrc.json .",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^9.1.1",
"@mui/material": "^9.1.1",
"@mui/system": "^9.1.1",
"@mui/x-date-pickers": "^9.6.0",
"@reduxjs/toolkit": "^2.12.0",
"axios": "^1.18.0",
"babel-plugin-react-compiler": "^1.0.0",
"clsx": "^2.1.1",
"date-fns": "^4.4.0",
"react": "^19.2.7",
"react-copy-to-clipboard": "^5.1.1",
"react-dom": "^19.2.7",
"react-infinite-scroll-component": "^7.2.1",
"react-redux": "^9.3.0",
"react-router-dom": "^7.18.0",
"recharts": "^3.8.1",
"redux": "^5.0.1"
},
"devDependencies": {
"@babel/preset-react": "^8.0.1",
"@rsbuild/core": "catalog:",
"@rsbuild/plugin-babel": "^2.0.0",
"@rsbuild/plugin-react": "^2.1.0",
"@types/node": "^26.0.0",
"@types/react": "^19.2.17",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-date-range": "^1.4.10",
"@types/react-dom": "^19.2.3",
"@your_spotify/dev": "workspace:*",
"oxfmt": "catalog:",
"oxlint": "catalog:",
"typescript": "catalog:"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
File: apps/client/tsconfig.json
{
"extends": ["@your_spotify/dev/tsconfig.json"],
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx"
}
}
File: apps/client/public/manifest.json
{
"short_name": "Your Spotify",
"name": "Your Spotify",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
File: apps/client/public/robots.txt
https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
File: apps/client/scripts/run/serve.json
{
"headers": [
{
"source": "*",
"headers": [
{
"key": "Content-Security-Policy",
"value": "frame-ancestors 'none';"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
}
]
}
]
}
File: apps/dev/package.json
{
"name": "@your_spotify/dev",
"version": "0.0.1",
"private": true,
"dependencies": {
"oxfmt": "^0.55.0",
"oxlint": "^1.70.0"
}
}
File: apps/dev/tsconfig.json
{
"ts-node": {
"files": true
},
"compilerOptions": {
"module": "esnext",
"target": "esnext",
// Rules
"noImplicitReturns": true,
"noImplicitAny": true,
"noUncheckedIndexedAccess": true,
"strict": true,
"useUnknownInCatchVariables": false,
// build
"sourceMap": false,
"esModuleInterop": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"skipLibCheck": true,
"declaration": false
},
}
File: apps/dev/.oxfmtrc.json
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": [
"../client/build/",
"../server/build/"
],
"arrowParens": "always",
"bracketSameLine": true,
"bracketSpacing": true,
"endOfLine": "lf",
"printWidth": 80,
"semi": true,
"singleQuote": false,
"sortImports": true,
"trailingComma": "all",
"useTabs": false,
"tabWidth": 2,
"objectWrap": "collapse"
}
File: apps/dev/.oxlintrc.json
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["import", "react", "typescript"],
"rules": {
"no-shadow": "error",
"eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^",
"varsIgnorePattern": "^"
}
],
"react/exhaustive-deps": "error",
"typescript/no-explicit-any": "off",
"typescript/no-empty-object-type": "off"
}
}
File: apps/server/README.md
Your Spotify server
Table of Contents
Prometheus
You can access various prometheus metrics through /metrics:
- http_requests_total: Total number of HTTP requests
- http_request_duration_nanoseconds: Duration of HTTP requests in nanoseconds
- imports_total: Total number of imports
- ingested_tracks_total: Total number of ingested tracks from Spotify API
- ingested_albums_total: Total number of ingested albums from Spotify API
- ingested_artists_total: Total number of ingested artists from Spotify API
You will need to define to environment variable in the server:
- PROMETHEUS_USERNAME: the basic auth username expected by the server to authorize the request
- PROMETHEUS_PASSWORD: the expected password
Those values are to be referenced in the configuration of your Prometheus instance:
scrape_configs:
- job_name: "your_spotify"
basic_auth:
username: "myuser" # PROMETHEUS_USERNAME
password: "mypassword" # PROMETHEUS_PASSWORD
static_configs:
- targets: ["example.com:443"]API
Authentication
OAuth
`GET /oauth/spotify`
Initiates Spotify OAuth flow.
Response:
- Redirects to Spotify login page
- For development: Returns 204 if offline mode enabled
`GET /oauth/spotify/callback`
Handles Spotify OAuth callback.
Query Parameters:
code: string - OAuth code from Spotifystate: string - State parameter for security
Response:
- Redirects to client application
`GET /oauth/spotify/me`
Gets current user's Spotify profile.
Response:
200: Spotify user profile500: Error with code "SPOTIFY_ERROR"
User Management
Account
`GET /me`
Get current user information.
Response:
{
"status": boolean,
"user": UserObject // Only if status is true
}`GET /accounts`
Get list of all users (requires authentication).
Response:
[{
"id": string,
"username": string,
"admin": boolean,
"firstListenedAt": Date
}]`PUT /rename`
Change username.
Body:
{
"newName": string // 2-64 characters
}Response:
204: Success500: Error
Admin
`PUT /admin/:id`
Set user admin status (requires admin).
Parameters:
id: User ID
Body:
{
"status": boolean
}Response:
204: Success400: Error if trying to remove last admin500: Error
`DELETE /account/:id`
Delete user account (requires admin).
Parameters:
id: User ID
Response:
204: Success404: User not found500: Error
Spotify Integration
Playback
`POST /spotify/play`
Start playback of a track.
Body:
{
"id": string // Spotify track ID
}Response:
200: Success400: Invalid track or playback error500: Server error
Playlist Management
`GET /spotify/playlists`
Get user's Spotify playlists.
Response:
200: Array of user's Spotify playlists
`POST /spotify/playlist/create`
Create a new Spotify playlist or add tracks to existing playlist.
Body:
{
"playlistId": string?, // Existing playlist ID (optional)
"name": string?, // New playlist name (optional)
"sortKey": string?, // Sort key for tracks (default "count")
"type": "top" | "affinity" | "single", // Playlist type
// For type "top"
"interval": {
"start": date,
"end": date
},
"nb": number, // Number of tracks to include
// For type "affinity"
"interval": {
"start": date,
"end": date
},
"nb": number, // Number of tracks to include
"userIds": string[], // User IDs for collaboration
"mode": "intersection" | "union", // Collaboration mode
// For type "single"
"songId": string // Single track ID to add
}Response:
204: Successfully created or updated playlist400: Invalid request500: Server error
History & Statistics
`GET /spotify/gethistory`
Get user's listening history.
Query Parameters:
number: number - Maximum number of items to return (max 20)offset: number - Number of items to skipstart: date (optional) - Start dateend: date (optional) - End date
Response:
200: Array of listening history items
`GET /spotify/listened_to`
Get count of songs listened to in a time period.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)
Response:
200:{ count: number }
`GET /spotify/most_listened`
Get most listened songs in a time period.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)
Response:
200: Array of most listened songs with counts
`GET /spotify/most_listened_artist`
Get most listened artists in a time period.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)
Response:
200: Array of most listened artists with counts
`GET /spotify/songs_per`
Get song count per time unit.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)
Response:
200: Array of time periods with song counts
`GET /spotify/time_per`
Get listening time per time unit.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)
Response:
200: Array of time periods with listening durations
`GET /spotify/album_date_ratio`
Get album release date distribution for listened tracks.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)
Response:
200: Distribution of tracks by album release date
`GET /spotify/feat_ratio`
Get ratio of songs featuring multiple artists.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)
Response:
200: Ratio data for featured vs. non-featured tracks
`GET /spotify/different_artists_per`
Get count of unique artists listened to per time unit.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)
Response:
200: Count of unique artists per time unit
`GET /spotify/time_per_hour_of_day`
Get listening distribution by hour of day.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)
Response:
200: Distribution of listening time by hour
`GET /spotify/best_artists_per`
Get top artists per time unit.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)
Response:
200: Top artists for each time unit
Top Items
`GET /spotify/top/songs`
Get top songs in a time period.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)nb: number - Number of items to return (1-30)offset: number - Number of items to skip (default 0)sortKey: string - Sort criteria (default "count")
Response:
200: Array of top song objects
`GET /spotify/top/artists`
Get top artists in a time period.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)nb: number - Number of items to return (1-30)offset: number - Number of items to skip (default 0)sortKey: string - Sort criteria (default "count")
Response:
200: Array of top artist objects
`GET /spotify/top/albums`
Get top albums in a time period.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)nb: number - Number of items to return (1-30)offset: number - Number of items to skip (default 0)sortKey: string - Sort criteria (default "count")
Response:
200: Array of top album objects
`GET /spotify/top/hour-repartition/songs`
Get top songs by hour of day.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)
Response:
200: Top songs for each hour of the day
`GET /spotify/top/hour-repartition/albums`
Get top albums by hour of day.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)
Response:
200: Top albums for each hour of the day
`GET /spotify/top/hour-repartition/artists`
Get top artists by hour of day.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)
Response:
200: Top artists for each hour of the day
`GET /spotify/top/sessions`
Get longest listening sessions.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)
Response:
200: Array of listening session data
Collaborative Features
`GET /spotify/collaborative/top/songs`
Get shared top songs between multiple users.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)otherIds: string[] - Array of other user IDs to compare withmode: string - Collaboration mode (intersection, union)
Response:
200: Array of shared top songs
`GET /spotify/collaborative/top/albums`
Get shared top albums between multiple users.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)otherIds: string[] - Array of other user IDs to compare withmode: string - Collaboration mode (intersection, union)
Response:
200: Array of shared top albums
`GET /spotify/collaborative/top/artists`
Get shared top artists between multiple users.
Query Parameters:
start: date - Start dateend: date - End date (defaults to current time)timeSplit: string - Time unit for grouping (day, week, month, year)otherIds: string[] - Array of other user IDs to compare withmode: string - Collaboration mode (intersection, union)
Response:
200: Array of shared top artists
Search
`GET /search/:query`
Search across tracks, artists and albums.
Parameters:
query: Search term (3-64 characters)
Response:
{
"artists": Artist[],
"tracks": Track[],
"albums": Album[]
}Artist
`GET /artist/:ids`
Get artist details.
Parameters:
ids: Comma-separated artist IDs
`GET /artist/:id/stats`
Get artist statistics.
Parameters:
id: Artist ID
Response:
{
"artist": ArtistObject,
"firstLast": FirstLastObject,
"mostListened": TrackObject,
"albumMostListened": AlbumObject,
"bestPeriod": PeriodStats,
"total": TotalStats,
"dayRepartition": DayStats
}`GET /artist/search/:query`
Search for artists by name.
Parameters:
query: Search term (3-64 characters)
Response:
200: Array of matching artists
`POST /artist/blacklist/:id`
Blacklist an artist.
`POST /artist/unblacklist/:id`
Remove artist from blacklist.
`GET /artist/:id/rank`
Get artist's ranking among all artists.
Parameters:
id: Artist ID
Response:
- Artist ranking information
Album
`GET /album/:ids`
Get album details.
Parameters:
ids: Comma-separated album IDs
`GET /album/:id/stats`
Get album statistics.
Parameters:
id: Album ID
Response:
{
"album": AlbumObject,
"artists": Artist[],
"firstLast": FirstLastObject,
"tracks": Track[]
}`GET /album/:id/rank`
Get album's ranking among all albums.
Parameters:
id: Album ID
Response:
- Album ranking information
Track
`GET /track/:ids`
Get track details.
Parameters:
ids: Comma-separated track IDs
`GET /track/:id/stats`
Get track statistics.
Parameters:
id: Track ID
Response:
{
"track": TrackObject,
"artist": ArtistObject,
"album": AlbumObject,
"bestPeriod": PeriodStats,
"firstLast": FirstLastObject,
"recentHistory": HistoryObject,
"total": TotalStats
}`GET /track/:id/rank`
Get track's ranking among all tracks.
Parameters:
id: Track ID
Response:
- Track ranking information
Import
`POST /import/privacy`
Import privacy data.
Body:
- Multipart form with files named "imports"
Response:
{
"code": "IMPORT_STARTED" | "IMPORT_INIT_FAILED"
}`POST /import/retry`
Retry failed import.
Body:
{
"existingStateId": string
}`DELETE /import/clean/:id`
Clean up import.
Parameters:
id: Import ID
`GET /imports`
Get import status.
Global Preferences
`GET /global/preferences`
Get global application preferences.
`POST /global/preferences`
Update global preferences (admin only).
Body:
{
"allowRegistrations": boolean
}Account Settings
`POST /settings`
Update user settings.
Body:
{
"historyLine": boolean?,
"preferredStatsPeriod": "day" | "week" | "month" | "year"?,
"nbElements": number?, // 5-50
"metricUsed": "number" | "duration"?,
"darkMode": "follow" | "dark" | "light"?,
"timezone": string?,
"dateFormat": string?
}`POST /generate-public-token`
Generate new public access token.
`POST /delete-public-token`
Delete public access token.
File: apps/server/package.json
{
"name": "@your_spotify/server",
"version": "1.20.1",
"private": true,
"scripts": {
"build": "rsbuild build",
"start": "node build/index.js",
"typecheck": "tsc --noEmit",
"dev": "concurrently "rsbuild build -w" "nodemon build/index.js"",
"migrate": "node ./build/index.js --migrate",
"lint": "oxlint -c ../dev/.oxlintrc.json . && oxfmt -c ../dev/.oxfmtrc.json --check .",
"lint:fix": "oxlint -c ../dev/.oxlintrc.json --fix . && oxfmt -c ../dev/.oxfmtrc.json ."
},
"dependencies": {
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"express": "^5.2.1",
"express-basic-auth": "^1.2.1",
"jsonwebtoken": "^9.0.3",
"migrate": "^2.1.0",
"mongodb": "^7.3.0",
"mongoose": "^9.7.1",
"morgan": "^1.11.0",
"multer": "^2.2.0",
"prom-client": "^15.1.3",
"uuid": "^14.0.1",
"zod": "^4.4.3"
},
"devDependencies": {
"@rsbuild/core": "catalog:",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.10",
"@types/morgan": "^1.9.10",
"@types/multer": "^2.1.0",
"@types/node": "^26.0.0",
"@your_spotify/dev": "workspace:*",
"concurrently": "^10.0.3",
"oxfmt": "catalog:",
"oxlint": "catalog:",
"ts-node": "^10.9.2",
"typescript": "catalog:"
}
}
File: apps/server/tsconfig.json
{
"extends": "@your_spotify/dev/tsconfig.json",
"compilerOptions": { "outDir": "lib" }
}
File: .github/FUNDING.yml
github: ['Yooooomi']
custom: ['https://www.paypal.com/donate/?hosted_button_id=BLAPT49PK9A8G']
File: .github/ISSUE_TEMPLATE/bug_report.md
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
Describe the bug
A clear and concise description of what the bug is.
Expected behavior
A clear and concise description of what you expected to happen.
Additional context
Please paste the docker-compose file you're using to host YourSpotify and their logs (you can skip mongo logs). If your issue have anything related to other tools, please paste their config too.
Screenshots
If applicable, add screenshots to help explain your problem.
Shared link
If you don't mind, you can add a Shared link to this issue by clicking Share this page on your YourSpotify application so that I can identify the issue quicker.
File: .github/workflows/nightly.yml
name: Nightly build CI
on:
push:
paths:
- .github/workflows/nightly.yml
- Dockerfile.client.production
- Dockerfile.server.production
- apps/dev/**
- apps/client/**
- apps/server/**
- yarn.lock
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker metadata
id: docker-meta
uses: docker/metadata-action@v5
with:
images: yooooomi/your_spotify # required dummy value, will not be used
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push client nightly
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.client.production
platforms: linux/amd64,linux/arm64
push: true
labels: ${{ steps.docker-meta.outputs.labels }}
tags: |
yooooomi/your_spotify_client:nightly
yooooomi/your_spotify_client:${{ github.sha }}
ghcr.io/yooooomi/your_spotify_client:nightly
ghcr.io/yooooomi/your_spotify_client:${{ github.sha }}
- name: Build and push server nightly
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.server.production
platforms: linux/amd64,linux/arm64
push: true
labels: ${{ steps.docker-meta.outputs.labels }}
tags: |
yooooomi/your_spotify_server:nightly
yooooomi/your_spotify_server:${{ github.sha }}
ghcr.io/yooooomi/your_spotify_server:nightly
ghcr.io/yooooomi/your_spotify_server:${{ github.sha }}File: .github/workflows/release.yml
name: Release CI
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker metadata
id: docker-meta
uses: docker/metadata-action@v5
with:
images: yooooomi/your_spotify # required dummy value, will not be used
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get current tag
id: tag
uses: "WyriHaximus/github-action-get-previous-tag@master"
- name: Verify tag matches package.json before pushing
run: |
for PKG_PATH in "./package.json" "./apps/client/package.json" "./apps/server/package.json"; do
if [ -f "$PKG_PATH" ] && [ "$(node -p "require('$PKG_PATH').version")" != "${{ steps.tag.outputs.tag }}" ]; then
echo "Error: $PKG_PATH version doesn't match tag ${{ steps.tag.outputs.tag }}"
exit 1
fi
done
- name: Build and push client release
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.client.production
platforms: linux/amd64,linux/arm64
push: true
labels: ${{ steps.docker-meta.outputs.labels }}
tags: |
yooooomi/your_spotify_client:latest
yooooomi/your_spotify_client:${{ steps.tag.outputs.tag }}
ghcr.io/yooooomi/your_spotify_client:latest
ghcr.io/yooooomi/your_spotify_client:${{ steps.tag.outputs.tag }}
- name: Build and push server release
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.server.production
platforms: linux/amd64,linux/arm64
push: true
labels: ${{ steps.docker-meta.outputs.labels }}
tags: |
yooooomi/your_spotify_server:latest
yooooomi/your_spotify_server:${{ steps.tag.outputs.tag }}
ghcr.io/yooooomi/your_spotify_server:latest
ghcr.io/yooooomi/your_spotify_server:${{ steps.tag.outputs.tag }}