spotify-profile-monitor 3.0__tar.gz → 3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/PKG-INFO +37 -24
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/README.md +36 -23
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/pyproject.toml +1 -1
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/spotify_profile_monitor.egg-info/PKG-INFO +37 -24
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/spotify_profile_monitor.py +142 -223
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/LICENSE +0 -0
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/setup.cfg +0 -0
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/spotify_profile_monitor.egg-info/SOURCES.txt +0 -0
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/spotify_profile_monitor.egg-info/dependency_links.txt +0 -0
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/spotify_profile_monitor.egg-info/entry_points.txt +0 -0
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/spotify_profile_monitor.egg-info/requires.txt +0 -0
- {spotify_profile_monitor-3.0 → spotify_profile_monitor-3.1}/spotify_profile_monitor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spotify_profile_monitor
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1
|
|
4
4
|
Summary: Tool implementing real-time tracking of Spotify users activities and profile changes including playlists
|
|
5
5
|
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -54,7 +54,7 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
54
54
|
- Ability to **display and export** the list of tracks for a specific playlist (including **Liked Songs** for the user who owns the Spotify access token)
|
|
55
55
|
- **Saving all profile changes** (including playlists) with timestamps to the **CSV file**
|
|
56
56
|
- **Clickable** **Spotify**, **Apple Music**, **YouTube Music**, **Amazon Music**, **Deezer**, **Tidal**, **Genius Lyrics**, **AZLyrics**, **Tekstowo.pl**, **Musixmatch** and **Lyrics.com** search URLs printed in the console and included in email notifications (configurable per service)
|
|
57
|
-
- Support for **
|
|
57
|
+
- Support for **hybrid authentication approach** to get a **Spotify access token** (`sp_dc cookie`/`desktop client` + `OAuth app`, or standalone `OAuth app`/`OAuth user`)
|
|
58
58
|
- Possibility to **control the running copy** of the script via signals
|
|
59
59
|
- **Functional, procedural Python** (minimal OOP)
|
|
60
60
|
|
|
@@ -156,17 +156,16 @@ If you installed manually, download the newest *[spotify_profile_monitor.py](htt
|
|
|
156
156
|
<a id="quick-start"></a>
|
|
157
157
|
## Quick Start
|
|
158
158
|
|
|
159
|
-
- Grab your [Spotify sp_dc cookie](#spotify-sp_dc-cookie) and track the `spotify_user_uri_id` profile changes (including playlists):
|
|
160
|
-
|
|
159
|
+
- Grab your [Spotify sp_dc cookie](#spotify-sp_dc-cookie), set up [OAuth app credentials](#spotify-oauth-app) and track the `spotify_user_uri_id` profile changes (including playlists):
|
|
161
160
|
|
|
162
161
|
```sh
|
|
163
|
-
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
162
|
+
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
164
163
|
```
|
|
165
164
|
|
|
166
165
|
Or if you installed [manually](#manual-installation):
|
|
167
166
|
|
|
168
167
|
```sh
|
|
169
|
-
python3 spotify_profile_monitor.py <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
168
|
+
python3 spotify_profile_monitor.py <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
170
169
|
```
|
|
171
170
|
|
|
172
171
|
To get the list of all supported command-line arguments / flags:
|
|
@@ -192,6 +191,8 @@ spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
|
192
191
|
|
|
193
192
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
194
193
|
|
|
194
|
+
**New in v3.1:** The tool now uses a hybrid authentication approach. OAuth app credentials (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) are required for playlist and user information retrieval when using either `cookie` or `client` token source methods. See [Spotify OAuth App](#spotify-oauth-app) section for setup instructions.
|
|
195
|
+
|
|
195
196
|
**New in v2.9:** The configuration file includes options to enable/disable music service URLs (Apple Music, YouTube Music, Amazon Music, Deezer, Tidal) and lyrics service URLs (Genius, AZLyrics, Tekstowo.pl, Musixmatch, Lyrics.com) in console and email outputs.
|
|
196
197
|
|
|
197
198
|
<a id="spotify-access-token-source"></a>
|
|
@@ -199,16 +200,23 @@ Edit the `spotify_profile_monitor.conf` file and change any desired configuratio
|
|
|
199
200
|
|
|
200
201
|
The tool supports four methods for obtaining a Spotify access token.
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
When you decide to use the `cookie` or `client` token source method, the tool uses a **hybrid authentication approach** and you also need to configure **OAuth app credentials** (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) for playlist and user information retrieval as described in [Spotify OAuth App](#spotify-oauth-app).
|
|
204
|
+
|
|
205
|
+
The token source method can be configured via the `TOKEN_SOURCE` configuration option or the `--token-source` flag.
|
|
203
206
|
|
|
204
207
|
**Recommended: `cookie`**
|
|
205
208
|
|
|
206
209
|
Uses the `sp_dc` cookie to retrieve a token from the Spotify web endpoint. This method is easy to set up and supports all features except fetching the list of liked tracks for the account that owns the access token (due to recent Spotify token's scope restrictions).
|
|
207
210
|
|
|
211
|
+
Since version 3.1, due to Spotify restrictions introduced on December 22, 2025, it no longer shows other users' playlists added to a user's profile unless the user is a collaborator on a playlist owned by another user.
|
|
212
|
+
|
|
213
|
+
|
|
208
214
|
**Alternative: `client`**
|
|
209
215
|
|
|
210
216
|
Uses captured credentials from the Spotify desktop client and a Protobuf-based login flow. It's more complex to set up, but supports all features. This method is intended for advanced users who want a long-lasting token with the broadest possible access.
|
|
211
217
|
|
|
218
|
+
Since version 3.1, due to Spotify restrictions introduced on December 22, 2025, it no longer shows other users' playlists added to a user's profile unless the user is a collaborator on a playlist owned by another user.
|
|
219
|
+
|
|
212
220
|
**Safe fallback: `oauth_app`**
|
|
213
221
|
|
|
214
222
|
Relies on the official Spotify Web API (Client Credentials OAuth flow). This method is easy to set up and safe to use, but has several limitations. The following features are **not** supported:
|
|
@@ -263,7 +271,7 @@ If your `sp_dc` cookie expires, the tool will notify you via the console and ema
|
|
|
263
271
|
|
|
264
272
|
If you store the `SP_DC_COOKIE` in a dotenv file you can update its value and send a `SIGHUP` signal to reload the file with the new `sp_dc` cookie without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
265
273
|
|
|
266
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every
|
|
274
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every few days, that's why since v2.7 the tool fetches it from remote URL (see `SECRET_CIPHER_DICT_URL`); you can also run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [Secret Key Extraction from Spotify Web Player Bundles](#secret-key-extraction-from-spotify-web-player-bundles) for more info).
|
|
267
275
|
|
|
268
276
|
<a id="spotify-desktop-client"></a>
|
|
269
277
|
#### Spotify Desktop Client
|
|
@@ -318,39 +326,42 @@ Advanced options are available for further customization - refer to the configur
|
|
|
318
326
|
<a id="spotify-oauth-app"></a>
|
|
319
327
|
#### Spotify OAuth App
|
|
320
328
|
|
|
329
|
+
Can be used as a standalone mode, but also as a secondary mode to either a `cookie` or `client` token source method to retrieve playlist and user information (required due to restrictions introduced by Spotify on December 22, 2025).
|
|
330
|
+
|
|
321
331
|
This method uses an official Spotify Web API (Client Credentials OAuth flow).
|
|
322
332
|
|
|
323
|
-
|
|
333
|
+
To obtain the credentials:
|
|
334
|
+
|
|
335
|
+
- Log in to [Spotify Developer dashboard](https://developer.spotify.com/dashboard)
|
|
324
336
|
|
|
325
337
|
- Create a new app
|
|
326
338
|
|
|
327
|
-
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
328
|
-
- The URL must match exactly as shown, including not having a
|
|
329
|
-
- When copying the link via right-click, some browsers may add an extra
|
|
339
|
+
- For **Redirect URL**, use: `http://127.0.0.1:1234`
|
|
340
|
+
- The URL must match exactly as shown, including not having a `/` at the end
|
|
341
|
+
- When copying the link via right-click, some browsers may add an extra `/` to the URL
|
|
330
342
|
|
|
331
343
|
- Select **Web API** as the intended API
|
|
332
344
|
|
|
333
345
|
- Copy the **Client ID** and **Client Secret**
|
|
334
346
|
|
|
335
347
|
- Provide the `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` secrets using one of the following methods:
|
|
336
|
-
- Pass it at runtime with `-r` / `--oauth-app-creds`
|
|
337
|
-
- Use `SP_APP_CLIENT_ID`:`SP_APP_CLIENT_SECRET` format - note the colon separator
|
|
348
|
+
- Pass it at runtime with `-r` / `--oauth-app-creds` (use `SP_APP_CLIENT_ID:SP_APP_CLIENT_SECRET` format - note the colon separator)
|
|
338
349
|
- Set it as an [environment variable](#storing-secrets) (e.g. `export SP_APP_CLIENT_ID=...; export SP_APP_CLIENT_SECRET=...`)
|
|
339
350
|
- Add it to [.env file](#storing-secrets) (`SP_APP_CLIENT_ID=...` and `SP_APP_CLIENT_SECRET=...`) for persistent use
|
|
340
351
|
- Fallback: hard-code it in the code or config file
|
|
341
352
|
|
|
342
|
-
You can use the same client ID and secret values as those used for the [Spotify OAuth User](#spotify-oauth-user).
|
|
343
|
-
|
|
344
353
|
Example:
|
|
345
354
|
|
|
346
355
|
```sh
|
|
347
356
|
spotify_profile_monitor --token-source oauth_app -r "your_spotify_app_client_id:your_spotify_app_client_secret" <spotify_user_uri_id>
|
|
348
357
|
```
|
|
349
358
|
|
|
350
|
-
The tool
|
|
359
|
+
The tool automatically refreshes the OAuth app access token, so it remains valid indefinitely. Tokens are cached in the file specified by `SP_APP_TOKENS_FILE` configuration option (default: `.spotify-profile-monitor-oauth-app.json`).
|
|
351
360
|
|
|
352
361
|
If you store the `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` in a dotenv file you can update their values and send a `SIGHUP` signal to reload the file with the new secret values without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
353
362
|
|
|
363
|
+
You can also use this method as a standalone token source (without `cookie` or `client`) by setting `TOKEN_SOURCE` to `oauth_app`, but this has several limitations as described in the [Spotify access token source](#spotify-access-token-source) section.
|
|
364
|
+
|
|
354
365
|
<a id="spotify-oauth-user"></a>
|
|
355
366
|
#### Spotify OAuth User
|
|
356
367
|
|
|
@@ -529,6 +540,14 @@ If you use the default method to obtain a Spotify access token (`cookie`) and ha
|
|
|
529
540
|
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
530
541
|
```
|
|
531
542
|
|
|
543
|
+
**Note:** OAuth app credentials are now required for playlist and user information retrieval. If you haven't set `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` via environment variables or `.env` file, you can use the `-r` flag:
|
|
544
|
+
|
|
545
|
+
```sh
|
|
546
|
+
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
See [Spotify OAuth App](#spotify-oauth-app) for detailed setup instructions.
|
|
550
|
+
|
|
532
551
|
By default, the tool looks for a configuration file named `spotify_profile_monitor.conf` in:
|
|
533
552
|
- current directory
|
|
534
553
|
- home directory (`~`)
|
|
@@ -661,12 +680,6 @@ If you want to display a list of recently played artists (this feature only work
|
|
|
661
680
|
spotify_profile_monitor <spotify_user_uri_id> -a
|
|
662
681
|
```
|
|
663
682
|
|
|
664
|
-
To get basic information about the Spotify access token owner (`-v` flag):
|
|
665
|
-
|
|
666
|
-
```sh
|
|
667
|
-
spotify_profile_monitor -v
|
|
668
|
-
```
|
|
669
|
-
|
|
670
683
|
If you want to search the Spotify catalog for users with a specific name to obtain their Spotify user URI ID (`-s` flag):
|
|
671
684
|
|
|
672
685
|
```sh
|
|
@@ -873,7 +886,7 @@ You should get a valid Spotify access token, example output:
|
|
|
873
886
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_totp_test.png" alt="spotify_monitor_totp_test" width="100%"/>
|
|
874
887
|
</p>
|
|
875
888
|
|
|
876
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every
|
|
889
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every few days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in `spotify_monitor_totp_test` (available since v1.6). There is also a [xyloflake/spot-secrets-go/](https://github.com/xyloflake/spot-secrets-go/) repo which offers JSON files that are automatically updated with current secrets (you can pass `--download-secrets` flag in `spotify_monitor_totp_test` to get it automatically from remote URL, available since v1.8).
|
|
877
890
|
|
|
878
891
|
<a id="secret-key-extraction-from-spotify-web-player-bundles"></a>
|
|
879
892
|
### Secret Key Extraction from Spotify Web Player Bundles
|
|
@@ -25,7 +25,7 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
25
25
|
- Ability to **display and export** the list of tracks for a specific playlist (including **Liked Songs** for the user who owns the Spotify access token)
|
|
26
26
|
- **Saving all profile changes** (including playlists) with timestamps to the **CSV file**
|
|
27
27
|
- **Clickable** **Spotify**, **Apple Music**, **YouTube Music**, **Amazon Music**, **Deezer**, **Tidal**, **Genius Lyrics**, **AZLyrics**, **Tekstowo.pl**, **Musixmatch** and **Lyrics.com** search URLs printed in the console and included in email notifications (configurable per service)
|
|
28
|
-
- Support for **
|
|
28
|
+
- Support for **hybrid authentication approach** to get a **Spotify access token** (`sp_dc cookie`/`desktop client` + `OAuth app`, or standalone `OAuth app`/`OAuth user`)
|
|
29
29
|
- Possibility to **control the running copy** of the script via signals
|
|
30
30
|
- **Functional, procedural Python** (minimal OOP)
|
|
31
31
|
|
|
@@ -127,17 +127,16 @@ If you installed manually, download the newest *[spotify_profile_monitor.py](htt
|
|
|
127
127
|
<a id="quick-start"></a>
|
|
128
128
|
## Quick Start
|
|
129
129
|
|
|
130
|
-
- Grab your [Spotify sp_dc cookie](#spotify-sp_dc-cookie) and track the `spotify_user_uri_id` profile changes (including playlists):
|
|
131
|
-
|
|
130
|
+
- Grab your [Spotify sp_dc cookie](#spotify-sp_dc-cookie), set up [OAuth app credentials](#spotify-oauth-app) and track the `spotify_user_uri_id` profile changes (including playlists):
|
|
132
131
|
|
|
133
132
|
```sh
|
|
134
|
-
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
133
|
+
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
135
134
|
```
|
|
136
135
|
|
|
137
136
|
Or if you installed [manually](#manual-installation):
|
|
138
137
|
|
|
139
138
|
```sh
|
|
140
|
-
python3 spotify_profile_monitor.py <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
139
|
+
python3 spotify_profile_monitor.py <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
141
140
|
```
|
|
142
141
|
|
|
143
142
|
To get the list of all supported command-line arguments / flags:
|
|
@@ -163,6 +162,8 @@ spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
|
163
162
|
|
|
164
163
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
165
164
|
|
|
165
|
+
**New in v3.1:** The tool now uses a hybrid authentication approach. OAuth app credentials (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) are required for playlist and user information retrieval when using either `cookie` or `client` token source methods. See [Spotify OAuth App](#spotify-oauth-app) section for setup instructions.
|
|
166
|
+
|
|
166
167
|
**New in v2.9:** The configuration file includes options to enable/disable music service URLs (Apple Music, YouTube Music, Amazon Music, Deezer, Tidal) and lyrics service URLs (Genius, AZLyrics, Tekstowo.pl, Musixmatch, Lyrics.com) in console and email outputs.
|
|
167
168
|
|
|
168
169
|
<a id="spotify-access-token-source"></a>
|
|
@@ -170,16 +171,23 @@ Edit the `spotify_profile_monitor.conf` file and change any desired configuratio
|
|
|
170
171
|
|
|
171
172
|
The tool supports four methods for obtaining a Spotify access token.
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
When you decide to use the `cookie` or `client` token source method, the tool uses a **hybrid authentication approach** and you also need to configure **OAuth app credentials** (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) for playlist and user information retrieval as described in [Spotify OAuth App](#spotify-oauth-app).
|
|
175
|
+
|
|
176
|
+
The token source method can be configured via the `TOKEN_SOURCE` configuration option or the `--token-source` flag.
|
|
174
177
|
|
|
175
178
|
**Recommended: `cookie`**
|
|
176
179
|
|
|
177
180
|
Uses the `sp_dc` cookie to retrieve a token from the Spotify web endpoint. This method is easy to set up and supports all features except fetching the list of liked tracks for the account that owns the access token (due to recent Spotify token's scope restrictions).
|
|
178
181
|
|
|
182
|
+
Since version 3.1, due to Spotify restrictions introduced on December 22, 2025, it no longer shows other users' playlists added to a user's profile unless the user is a collaborator on a playlist owned by another user.
|
|
183
|
+
|
|
184
|
+
|
|
179
185
|
**Alternative: `client`**
|
|
180
186
|
|
|
181
187
|
Uses captured credentials from the Spotify desktop client and a Protobuf-based login flow. It's more complex to set up, but supports all features. This method is intended for advanced users who want a long-lasting token with the broadest possible access.
|
|
182
188
|
|
|
189
|
+
Since version 3.1, due to Spotify restrictions introduced on December 22, 2025, it no longer shows other users' playlists added to a user's profile unless the user is a collaborator on a playlist owned by another user.
|
|
190
|
+
|
|
183
191
|
**Safe fallback: `oauth_app`**
|
|
184
192
|
|
|
185
193
|
Relies on the official Spotify Web API (Client Credentials OAuth flow). This method is easy to set up and safe to use, but has several limitations. The following features are **not** supported:
|
|
@@ -234,7 +242,7 @@ If your `sp_dc` cookie expires, the tool will notify you via the console and ema
|
|
|
234
242
|
|
|
235
243
|
If you store the `SP_DC_COOKIE` in a dotenv file you can update its value and send a `SIGHUP` signal to reload the file with the new `sp_dc` cookie without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
236
244
|
|
|
237
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every
|
|
245
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every few days, that's why since v2.7 the tool fetches it from remote URL (see `SECRET_CIPHER_DICT_URL`); you can also run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [Secret Key Extraction from Spotify Web Player Bundles](#secret-key-extraction-from-spotify-web-player-bundles) for more info).
|
|
238
246
|
|
|
239
247
|
<a id="spotify-desktop-client"></a>
|
|
240
248
|
#### Spotify Desktop Client
|
|
@@ -289,39 +297,42 @@ Advanced options are available for further customization - refer to the configur
|
|
|
289
297
|
<a id="spotify-oauth-app"></a>
|
|
290
298
|
#### Spotify OAuth App
|
|
291
299
|
|
|
300
|
+
Can be used as a standalone mode, but also as a secondary mode to either a `cookie` or `client` token source method to retrieve playlist and user information (required due to restrictions introduced by Spotify on December 22, 2025).
|
|
301
|
+
|
|
292
302
|
This method uses an official Spotify Web API (Client Credentials OAuth flow).
|
|
293
303
|
|
|
294
|
-
|
|
304
|
+
To obtain the credentials:
|
|
305
|
+
|
|
306
|
+
- Log in to [Spotify Developer dashboard](https://developer.spotify.com/dashboard)
|
|
295
307
|
|
|
296
308
|
- Create a new app
|
|
297
309
|
|
|
298
|
-
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
299
|
-
- The URL must match exactly as shown, including not having a
|
|
300
|
-
- When copying the link via right-click, some browsers may add an extra
|
|
310
|
+
- For **Redirect URL**, use: `http://127.0.0.1:1234`
|
|
311
|
+
- The URL must match exactly as shown, including not having a `/` at the end
|
|
312
|
+
- When copying the link via right-click, some browsers may add an extra `/` to the URL
|
|
301
313
|
|
|
302
314
|
- Select **Web API** as the intended API
|
|
303
315
|
|
|
304
316
|
- Copy the **Client ID** and **Client Secret**
|
|
305
317
|
|
|
306
318
|
- Provide the `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` secrets using one of the following methods:
|
|
307
|
-
- Pass it at runtime with `-r` / `--oauth-app-creds`
|
|
308
|
-
- Use `SP_APP_CLIENT_ID`:`SP_APP_CLIENT_SECRET` format - note the colon separator
|
|
319
|
+
- Pass it at runtime with `-r` / `--oauth-app-creds` (use `SP_APP_CLIENT_ID:SP_APP_CLIENT_SECRET` format - note the colon separator)
|
|
309
320
|
- Set it as an [environment variable](#storing-secrets) (e.g. `export SP_APP_CLIENT_ID=...; export SP_APP_CLIENT_SECRET=...`)
|
|
310
321
|
- Add it to [.env file](#storing-secrets) (`SP_APP_CLIENT_ID=...` and `SP_APP_CLIENT_SECRET=...`) for persistent use
|
|
311
322
|
- Fallback: hard-code it in the code or config file
|
|
312
323
|
|
|
313
|
-
You can use the same client ID and secret values as those used for the [Spotify OAuth User](#spotify-oauth-user).
|
|
314
|
-
|
|
315
324
|
Example:
|
|
316
325
|
|
|
317
326
|
```sh
|
|
318
327
|
spotify_profile_monitor --token-source oauth_app -r "your_spotify_app_client_id:your_spotify_app_client_secret" <spotify_user_uri_id>
|
|
319
328
|
```
|
|
320
329
|
|
|
321
|
-
The tool
|
|
330
|
+
The tool automatically refreshes the OAuth app access token, so it remains valid indefinitely. Tokens are cached in the file specified by `SP_APP_TOKENS_FILE` configuration option (default: `.spotify-profile-monitor-oauth-app.json`).
|
|
322
331
|
|
|
323
332
|
If you store the `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` in a dotenv file you can update their values and send a `SIGHUP` signal to reload the file with the new secret values without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
324
333
|
|
|
334
|
+
You can also use this method as a standalone token source (without `cookie` or `client`) by setting `TOKEN_SOURCE` to `oauth_app`, but this has several limitations as described in the [Spotify access token source](#spotify-access-token-source) section.
|
|
335
|
+
|
|
325
336
|
<a id="spotify-oauth-user"></a>
|
|
326
337
|
#### Spotify OAuth User
|
|
327
338
|
|
|
@@ -500,6 +511,14 @@ If you use the default method to obtain a Spotify access token (`cookie`) and ha
|
|
|
500
511
|
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
501
512
|
```
|
|
502
513
|
|
|
514
|
+
**Note:** OAuth app credentials are now required for playlist and user information retrieval. If you haven't set `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` via environment variables or `.env` file, you can use the `-r` flag:
|
|
515
|
+
|
|
516
|
+
```sh
|
|
517
|
+
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
See [Spotify OAuth App](#spotify-oauth-app) for detailed setup instructions.
|
|
521
|
+
|
|
503
522
|
By default, the tool looks for a configuration file named `spotify_profile_monitor.conf` in:
|
|
504
523
|
- current directory
|
|
505
524
|
- home directory (`~`)
|
|
@@ -632,12 +651,6 @@ If you want to display a list of recently played artists (this feature only work
|
|
|
632
651
|
spotify_profile_monitor <spotify_user_uri_id> -a
|
|
633
652
|
```
|
|
634
653
|
|
|
635
|
-
To get basic information about the Spotify access token owner (`-v` flag):
|
|
636
|
-
|
|
637
|
-
```sh
|
|
638
|
-
spotify_profile_monitor -v
|
|
639
|
-
```
|
|
640
|
-
|
|
641
654
|
If you want to search the Spotify catalog for users with a specific name to obtain their Spotify user URI ID (`-s` flag):
|
|
642
655
|
|
|
643
656
|
```sh
|
|
@@ -844,7 +857,7 @@ You should get a valid Spotify access token, example output:
|
|
|
844
857
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_totp_test.png" alt="spotify_monitor_totp_test" width="100%"/>
|
|
845
858
|
</p>
|
|
846
859
|
|
|
847
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every
|
|
860
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every few days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in `spotify_monitor_totp_test` (available since v1.6). There is also a [xyloflake/spot-secrets-go/](https://github.com/xyloflake/spot-secrets-go/) repo which offers JSON files that are automatically updated with current secrets (you can pass `--download-secrets` flag in `spotify_monitor_totp_test` to get it automatically from remote URL, available since v1.8).
|
|
848
861
|
|
|
849
862
|
<a id="secret-key-extraction-from-spotify-web-player-bundles"></a>
|
|
850
863
|
### Secret Key Extraction from Spotify Web Player Bundles
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spotify_profile_monitor"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.1"
|
|
8
8
|
description = "Tool implementing real-time tracking of Spotify users activities and profile changes including playlists"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spotify_profile_monitor
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1
|
|
4
4
|
Summary: Tool implementing real-time tracking of Spotify users activities and profile changes including playlists
|
|
5
5
|
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -54,7 +54,7 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
54
54
|
- Ability to **display and export** the list of tracks for a specific playlist (including **Liked Songs** for the user who owns the Spotify access token)
|
|
55
55
|
- **Saving all profile changes** (including playlists) with timestamps to the **CSV file**
|
|
56
56
|
- **Clickable** **Spotify**, **Apple Music**, **YouTube Music**, **Amazon Music**, **Deezer**, **Tidal**, **Genius Lyrics**, **AZLyrics**, **Tekstowo.pl**, **Musixmatch** and **Lyrics.com** search URLs printed in the console and included in email notifications (configurable per service)
|
|
57
|
-
- Support for **
|
|
57
|
+
- Support for **hybrid authentication approach** to get a **Spotify access token** (`sp_dc cookie`/`desktop client` + `OAuth app`, or standalone `OAuth app`/`OAuth user`)
|
|
58
58
|
- Possibility to **control the running copy** of the script via signals
|
|
59
59
|
- **Functional, procedural Python** (minimal OOP)
|
|
60
60
|
|
|
@@ -156,17 +156,16 @@ If you installed manually, download the newest *[spotify_profile_monitor.py](htt
|
|
|
156
156
|
<a id="quick-start"></a>
|
|
157
157
|
## Quick Start
|
|
158
158
|
|
|
159
|
-
- Grab your [Spotify sp_dc cookie](#spotify-sp_dc-cookie) and track the `spotify_user_uri_id` profile changes (including playlists):
|
|
160
|
-
|
|
159
|
+
- Grab your [Spotify sp_dc cookie](#spotify-sp_dc-cookie), set up [OAuth app credentials](#spotify-oauth-app) and track the `spotify_user_uri_id` profile changes (including playlists):
|
|
161
160
|
|
|
162
161
|
```sh
|
|
163
|
-
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
162
|
+
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
164
163
|
```
|
|
165
164
|
|
|
166
165
|
Or if you installed [manually](#manual-installation):
|
|
167
166
|
|
|
168
167
|
```sh
|
|
169
|
-
python3 spotify_profile_monitor.py <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
168
|
+
python3 spotify_profile_monitor.py <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
170
169
|
```
|
|
171
170
|
|
|
172
171
|
To get the list of all supported command-line arguments / flags:
|
|
@@ -192,6 +191,8 @@ spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
|
192
191
|
|
|
193
192
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
194
193
|
|
|
194
|
+
**New in v3.1:** The tool now uses a hybrid authentication approach. OAuth app credentials (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) are required for playlist and user information retrieval when using either `cookie` or `client` token source methods. See [Spotify OAuth App](#spotify-oauth-app) section for setup instructions.
|
|
195
|
+
|
|
195
196
|
**New in v2.9:** The configuration file includes options to enable/disable music service URLs (Apple Music, YouTube Music, Amazon Music, Deezer, Tidal) and lyrics service URLs (Genius, AZLyrics, Tekstowo.pl, Musixmatch, Lyrics.com) in console and email outputs.
|
|
196
197
|
|
|
197
198
|
<a id="spotify-access-token-source"></a>
|
|
@@ -199,16 +200,23 @@ Edit the `spotify_profile_monitor.conf` file and change any desired configuratio
|
|
|
199
200
|
|
|
200
201
|
The tool supports four methods for obtaining a Spotify access token.
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
When you decide to use the `cookie` or `client` token source method, the tool uses a **hybrid authentication approach** and you also need to configure **OAuth app credentials** (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) for playlist and user information retrieval as described in [Spotify OAuth App](#spotify-oauth-app).
|
|
204
|
+
|
|
205
|
+
The token source method can be configured via the `TOKEN_SOURCE` configuration option or the `--token-source` flag.
|
|
203
206
|
|
|
204
207
|
**Recommended: `cookie`**
|
|
205
208
|
|
|
206
209
|
Uses the `sp_dc` cookie to retrieve a token from the Spotify web endpoint. This method is easy to set up and supports all features except fetching the list of liked tracks for the account that owns the access token (due to recent Spotify token's scope restrictions).
|
|
207
210
|
|
|
211
|
+
Since version 3.1, due to Spotify restrictions introduced on December 22, 2025, it no longer shows other users' playlists added to a user's profile unless the user is a collaborator on a playlist owned by another user.
|
|
212
|
+
|
|
213
|
+
|
|
208
214
|
**Alternative: `client`**
|
|
209
215
|
|
|
210
216
|
Uses captured credentials from the Spotify desktop client and a Protobuf-based login flow. It's more complex to set up, but supports all features. This method is intended for advanced users who want a long-lasting token with the broadest possible access.
|
|
211
217
|
|
|
218
|
+
Since version 3.1, due to Spotify restrictions introduced on December 22, 2025, it no longer shows other users' playlists added to a user's profile unless the user is a collaborator on a playlist owned by another user.
|
|
219
|
+
|
|
212
220
|
**Safe fallback: `oauth_app`**
|
|
213
221
|
|
|
214
222
|
Relies on the official Spotify Web API (Client Credentials OAuth flow). This method is easy to set up and safe to use, but has several limitations. The following features are **not** supported:
|
|
@@ -263,7 +271,7 @@ If your `sp_dc` cookie expires, the tool will notify you via the console and ema
|
|
|
263
271
|
|
|
264
272
|
If you store the `SP_DC_COOKIE` in a dotenv file you can update its value and send a `SIGHUP` signal to reload the file with the new `sp_dc` cookie without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
265
273
|
|
|
266
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every
|
|
274
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every few days, that's why since v2.7 the tool fetches it from remote URL (see `SECRET_CIPHER_DICT_URL`); you can also run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [Secret Key Extraction from Spotify Web Player Bundles](#secret-key-extraction-from-spotify-web-player-bundles) for more info).
|
|
267
275
|
|
|
268
276
|
<a id="spotify-desktop-client"></a>
|
|
269
277
|
#### Spotify Desktop Client
|
|
@@ -318,39 +326,42 @@ Advanced options are available for further customization - refer to the configur
|
|
|
318
326
|
<a id="spotify-oauth-app"></a>
|
|
319
327
|
#### Spotify OAuth App
|
|
320
328
|
|
|
329
|
+
Can be used as a standalone mode, but also as a secondary mode to either a `cookie` or `client` token source method to retrieve playlist and user information (required due to restrictions introduced by Spotify on December 22, 2025).
|
|
330
|
+
|
|
321
331
|
This method uses an official Spotify Web API (Client Credentials OAuth flow).
|
|
322
332
|
|
|
323
|
-
|
|
333
|
+
To obtain the credentials:
|
|
334
|
+
|
|
335
|
+
- Log in to [Spotify Developer dashboard](https://developer.spotify.com/dashboard)
|
|
324
336
|
|
|
325
337
|
- Create a new app
|
|
326
338
|
|
|
327
|
-
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
328
|
-
- The URL must match exactly as shown, including not having a
|
|
329
|
-
- When copying the link via right-click, some browsers may add an extra
|
|
339
|
+
- For **Redirect URL**, use: `http://127.0.0.1:1234`
|
|
340
|
+
- The URL must match exactly as shown, including not having a `/` at the end
|
|
341
|
+
- When copying the link via right-click, some browsers may add an extra `/` to the URL
|
|
330
342
|
|
|
331
343
|
- Select **Web API** as the intended API
|
|
332
344
|
|
|
333
345
|
- Copy the **Client ID** and **Client Secret**
|
|
334
346
|
|
|
335
347
|
- Provide the `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` secrets using one of the following methods:
|
|
336
|
-
- Pass it at runtime with `-r` / `--oauth-app-creds`
|
|
337
|
-
- Use `SP_APP_CLIENT_ID`:`SP_APP_CLIENT_SECRET` format - note the colon separator
|
|
348
|
+
- Pass it at runtime with `-r` / `--oauth-app-creds` (use `SP_APP_CLIENT_ID:SP_APP_CLIENT_SECRET` format - note the colon separator)
|
|
338
349
|
- Set it as an [environment variable](#storing-secrets) (e.g. `export SP_APP_CLIENT_ID=...; export SP_APP_CLIENT_SECRET=...`)
|
|
339
350
|
- Add it to [.env file](#storing-secrets) (`SP_APP_CLIENT_ID=...` and `SP_APP_CLIENT_SECRET=...`) for persistent use
|
|
340
351
|
- Fallback: hard-code it in the code or config file
|
|
341
352
|
|
|
342
|
-
You can use the same client ID and secret values as those used for the [Spotify OAuth User](#spotify-oauth-user).
|
|
343
|
-
|
|
344
353
|
Example:
|
|
345
354
|
|
|
346
355
|
```sh
|
|
347
356
|
spotify_profile_monitor --token-source oauth_app -r "your_spotify_app_client_id:your_spotify_app_client_secret" <spotify_user_uri_id>
|
|
348
357
|
```
|
|
349
358
|
|
|
350
|
-
The tool
|
|
359
|
+
The tool automatically refreshes the OAuth app access token, so it remains valid indefinitely. Tokens are cached in the file specified by `SP_APP_TOKENS_FILE` configuration option (default: `.spotify-profile-monitor-oauth-app.json`).
|
|
351
360
|
|
|
352
361
|
If you store the `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` in a dotenv file you can update their values and send a `SIGHUP` signal to reload the file with the new secret values without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
353
362
|
|
|
363
|
+
You can also use this method as a standalone token source (without `cookie` or `client`) by setting `TOKEN_SOURCE` to `oauth_app`, but this has several limitations as described in the [Spotify access token source](#spotify-access-token-source) section.
|
|
364
|
+
|
|
354
365
|
<a id="spotify-oauth-user"></a>
|
|
355
366
|
#### Spotify OAuth User
|
|
356
367
|
|
|
@@ -529,6 +540,14 @@ If you use the default method to obtain a Spotify access token (`cookie`) and ha
|
|
|
529
540
|
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value"
|
|
530
541
|
```
|
|
531
542
|
|
|
543
|
+
**Note:** OAuth app credentials are now required for playlist and user information retrieval. If you haven't set `SP_APP_CLIENT_ID` and `SP_APP_CLIENT_SECRET` via environment variables or `.env` file, you can use the `-r` flag:
|
|
544
|
+
|
|
545
|
+
```sh
|
|
546
|
+
spotify_profile_monitor <spotify_user_uri_id> -u "your_sp_dc_cookie_value" -r "your_spotify_app_client_id:your_spotify_app_client_secret"
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
See [Spotify OAuth App](#spotify-oauth-app) for detailed setup instructions.
|
|
550
|
+
|
|
532
551
|
By default, the tool looks for a configuration file named `spotify_profile_monitor.conf` in:
|
|
533
552
|
- current directory
|
|
534
553
|
- home directory (`~`)
|
|
@@ -661,12 +680,6 @@ If you want to display a list of recently played artists (this feature only work
|
|
|
661
680
|
spotify_profile_monitor <spotify_user_uri_id> -a
|
|
662
681
|
```
|
|
663
682
|
|
|
664
|
-
To get basic information about the Spotify access token owner (`-v` flag):
|
|
665
|
-
|
|
666
|
-
```sh
|
|
667
|
-
spotify_profile_monitor -v
|
|
668
|
-
```
|
|
669
|
-
|
|
670
683
|
If you want to search the Spotify catalog for users with a specific name to obtain their Spotify user URI ID (`-s` flag):
|
|
671
684
|
|
|
672
685
|
```sh
|
|
@@ -873,7 +886,7 @@ You should get a valid Spotify access token, example output:
|
|
|
873
886
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_totp_test.png" alt="spotify_monitor_totp_test" width="100%"/>
|
|
874
887
|
</p>
|
|
875
888
|
|
|
876
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every
|
|
889
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every few days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in `spotify_monitor_totp_test` (available since v1.6). There is also a [xyloflake/spot-secrets-go/](https://github.com/xyloflake/spot-secrets-go/) repo which offers JSON files that are automatically updated with current secrets (you can pass `--download-secrets` flag in `spotify_monitor_totp_test` to get it automatically from remote URL, available since v1.8).
|
|
877
890
|
|
|
878
891
|
<a id="secret-key-extraction-from-spotify-web-player-bundles"></a>
|
|
879
892
|
### Secret Key Extraction from Spotify Web Player Bundles
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Author: Michal Szymanski <misiektoja-github@rm-rf.ninja>
|
|
4
|
-
v3.
|
|
4
|
+
v3.1
|
|
5
5
|
|
|
6
6
|
OSINT tool implementing real-time tracking of Spotify users activities and profile changes including playlists:
|
|
7
7
|
https://github.com/misiektoja/spotify_profile_monitor/
|
|
@@ -15,11 +15,11 @@ pyotp (optional, needed when the token source is set to cookie)
|
|
|
15
15
|
pytz
|
|
16
16
|
tzlocal (optional)
|
|
17
17
|
python-dotenv (optional)
|
|
18
|
-
spotipy
|
|
18
|
+
spotipy
|
|
19
19
|
wcwidth (optional, needed by TRUNCATE_CHARS feature)
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
VERSION = "3.
|
|
22
|
+
VERSION = "3.1"
|
|
23
23
|
|
|
24
24
|
# ---------------------------
|
|
25
25
|
# CONFIGURATION SECTION START
|
|
@@ -50,6 +50,32 @@ SP_DC_COOKIE = "your_sp_dc_cookie_value"
|
|
|
50
50
|
|
|
51
51
|
# ---------------------------------------------------------------------
|
|
52
52
|
|
|
53
|
+
# The section below is used when the token source is set to 'oauth_app' (Client Credentials OAuth Flow)
|
|
54
|
+
# It is also used to get playlists and users info in 'cookie' and 'client' modes
|
|
55
|
+
#
|
|
56
|
+
# To obtain the credentials:
|
|
57
|
+
# - Log in to Spotify Developer dashboard: https://developer.spotify.com/dashboard
|
|
58
|
+
# - Create a new app
|
|
59
|
+
# - For 'Redirect URL', use: http://127.0.0.1:1234
|
|
60
|
+
# - Select 'Web API' as the intended API
|
|
61
|
+
# - Copy the 'Client ID' and 'Client Secret'
|
|
62
|
+
#
|
|
63
|
+
# Provide the SP_APP_CLIENT_ID and SP_APP_CLIENT_SECRET secrets using one of the following methods:
|
|
64
|
+
# - Pass it at runtime with -r / --oauth-app-creds (use SP_APP_CLIENT_ID:SP_APP_CLIENT_SECRET format - note the colon separator)
|
|
65
|
+
# - Set it as an environment variable (e.g. export SP_APP_CLIENT_ID=...; export SP_APP_CLIENT_SECRET=...)
|
|
66
|
+
# - Add it to ".env" file (SP_APP_CLIENT_ID=... and SP_APP_CLIENT_SECRET=...) for persistent use
|
|
67
|
+
# - Fallback: hard-code it in the code or config file
|
|
68
|
+
#
|
|
69
|
+
# The tool automatically refreshes the access token, so it remains valid indefinitely
|
|
70
|
+
SP_APP_CLIENT_ID = "your_spotify_app_client_id"
|
|
71
|
+
SP_APP_CLIENT_SECRET = "your_spotify_app_client_secret"
|
|
72
|
+
|
|
73
|
+
# Path to cache file used to store OAuth app access tokens across tool restarts
|
|
74
|
+
# Set to empty to use in-memory cache only
|
|
75
|
+
SP_APP_TOKENS_FILE = ".spotify-profile-monitor-oauth-app.json"
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------
|
|
78
|
+
|
|
53
79
|
# SMTP settings for sending email notifications
|
|
54
80
|
# If left as-is, no notifications will be sent
|
|
55
81
|
#
|
|
@@ -299,8 +325,7 @@ TOKEN_RETRY_TIMEOUT = 0.5 # 0.5 second
|
|
|
299
325
|
# Newest secrets are downloaded automatically from SECRET_CIPHER_DICT_URL (see below)
|
|
300
326
|
# Can also be fetched via spotify_monitor_secret_grabber.py utility - see debug dir
|
|
301
327
|
SECRET_CIPHER_DICT = {
|
|
302
|
-
"
|
|
303
|
-
"5": [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54],
|
|
328
|
+
# "61": [44, 55, 47, 42, 70, 40, 34, 114, 76, 74, 50, 111, 120, 97, 75, 76, 94, 102, 43, 69, 49, 120, 118, 80, 64, 78],
|
|
304
329
|
}
|
|
305
330
|
|
|
306
331
|
# Remote or local URL used to fetch updated secrets needed for TOTP generation
|
|
@@ -317,32 +342,6 @@ TOTP_VER = 0
|
|
|
317
342
|
|
|
318
343
|
# ---------------------------------------------------------------------
|
|
319
344
|
|
|
320
|
-
# The section below is used when the token source is set to 'oauth_app'
|
|
321
|
-
# (Client Credentials OAuth Flow)
|
|
322
|
-
#
|
|
323
|
-
# To obtain the credentials:
|
|
324
|
-
# - Log in to Spotify Developer dashboard: https://developer.spotify.com/dashboard
|
|
325
|
-
# - Create a new app
|
|
326
|
-
# - For 'Redirect URL', use: http://127.0.0.1:1234
|
|
327
|
-
# - Select 'Web API' as the intended API
|
|
328
|
-
# - Copy the 'Client ID' and 'Client Secret'
|
|
329
|
-
#
|
|
330
|
-
# Provide the SP_APP_CLIENT_ID and SP_APP_CLIENT_SECRET secrets using one of the following methods:
|
|
331
|
-
# - Pass it at runtime with -r / --oauth-app-creds (use SP_APP_CLIENT_ID:SP_APP_CLIENT_SECRET format - note the colon separator)
|
|
332
|
-
# - Set it as an environment variable (e.g. export SP_APP_CLIENT_ID=...; export SP_APP_CLIENT_SECRET=...)
|
|
333
|
-
# - Add it to ".env" file (SP_APP_CLIENT_ID=... and SP_APP_CLIENT_SECRET=...) for persistent use
|
|
334
|
-
# - Fallback: hard-code it in the code or config file
|
|
335
|
-
#
|
|
336
|
-
# The tool automatically refreshes the access token, so it remains valid indefinitely
|
|
337
|
-
SP_APP_CLIENT_ID = "your_spotify_app_client_id"
|
|
338
|
-
SP_APP_CLIENT_SECRET = "your_spotify_app_client_secret"
|
|
339
|
-
|
|
340
|
-
# Path to cache file used to store OAuth app access tokens across tool restarts
|
|
341
|
-
# Set to empty to use in-memory cache only
|
|
342
|
-
SP_APP_TOKENS_FILE = ".spotify-profile-monitor-oauth-app.json"
|
|
343
|
-
|
|
344
|
-
# ---------------------------------------------------------------------
|
|
345
|
-
|
|
346
345
|
# The section below is used when the token source is set to 'oauth_user'
|
|
347
346
|
# (Authorization Code OAuth Flow)
|
|
348
347
|
#
|
|
@@ -617,12 +616,15 @@ FUNCTION_TIMEOUT = 15
|
|
|
617
616
|
ALARM_TIMEOUT = 15
|
|
618
617
|
ALARM_RETRY = 10
|
|
619
618
|
|
|
620
|
-
# Variables for caching functionality of the Spotify access
|
|
619
|
+
# Variables for caching functionality of the Spotify 'cookie' access token / 'client' refresh token to avoid unnecessary refreshing
|
|
621
620
|
SP_CACHED_ACCESS_TOKEN = None
|
|
622
621
|
SP_CACHED_REFRESH_TOKEN = None
|
|
623
622
|
SP_ACCESS_TOKEN_EXPIRES_AT = 0
|
|
624
623
|
SP_CACHED_CLIENT_ID = ""
|
|
625
624
|
|
|
625
|
+
# Separate cache for OAuth app access token (Client Credentials Flow) used in hybrid mode
|
|
626
|
+
SP_CACHED_OAUTH_APP_TOKEN = None
|
|
627
|
+
|
|
626
628
|
# URL of the Spotify Web Player endpoint to get access token
|
|
627
629
|
TOKEN_URL = "https://open.spotify.com/api/token"
|
|
628
630
|
|
|
@@ -1488,11 +1490,17 @@ def spotify_extract_id_or_name(s):
|
|
|
1488
1490
|
|
|
1489
1491
|
|
|
1490
1492
|
# Sends a lightweight request to check Spotify token validity
|
|
1491
|
-
def check_token_validity(access_token: str, client_id: Optional[str] = None, user_agent: Optional[str] = None) -> bool:
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1493
|
+
def check_token_validity(access_token: str, client_id: Optional[str] = None, user_agent: Optional[str] = None, oauth_app: bool = False) -> bool:
|
|
1494
|
+
url_cookie_client = "https://guc-spclient.spotify.com/presence-view/v1/buddylist"
|
|
1495
|
+
url_oauth_app = "https://api.spotify.com/v1/browse/categories?limit=1&fields=categories.items(id)"
|
|
1496
|
+
url_oauth_user = "https://api.spotify.com/v1/me"
|
|
1497
|
+
|
|
1498
|
+
if oauth_app or TOKEN_SOURCE == "oauth_app":
|
|
1499
|
+
url = url_oauth_app
|
|
1500
|
+
elif TOKEN_SOURCE in {"cookie", "client"}:
|
|
1501
|
+
url = url_cookie_client
|
|
1502
|
+
else:
|
|
1503
|
+
url = url_oauth_user
|
|
1496
1504
|
|
|
1497
1505
|
headers = {"Authorization": f"Bearer {access_token}"}
|
|
1498
1506
|
|
|
@@ -1501,7 +1509,7 @@ def check_token_validity(access_token: str, client_id: Optional[str] = None, use
|
|
|
1501
1509
|
"User-Agent": user_agent
|
|
1502
1510
|
})
|
|
1503
1511
|
|
|
1504
|
-
if TOKEN_SOURCE == "cookie" and client_id is not None:
|
|
1512
|
+
if not oauth_app and TOKEN_SOURCE == "cookie" and client_id is not None:
|
|
1505
1513
|
headers.update({
|
|
1506
1514
|
"Client-Id": client_id
|
|
1507
1515
|
})
|
|
@@ -1723,15 +1731,17 @@ def refresh_access_token_from_sp_dc(sp_dc: str) -> dict:
|
|
|
1723
1731
|
client_time = int(time_ns() / 1000 / 1000)
|
|
1724
1732
|
otp_value = totp_obj.at(server_time)
|
|
1725
1733
|
|
|
1734
|
+
totp_ver = TOTP_VER or max(map(int, SECRET_CIPHER_DICT))
|
|
1735
|
+
|
|
1726
1736
|
params = {
|
|
1727
1737
|
"reason": "transport",
|
|
1728
1738
|
"productType": "web-player",
|
|
1729
1739
|
"totp": otp_value,
|
|
1730
1740
|
"totpServer": otp_value,
|
|
1731
|
-
"totpVer":
|
|
1741
|
+
"totpVer": totp_ver,
|
|
1732
1742
|
}
|
|
1733
1743
|
|
|
1734
|
-
if
|
|
1744
|
+
if totp_ver < 10:
|
|
1735
1745
|
params.update({
|
|
1736
1746
|
"sTime": server_time,
|
|
1737
1747
|
"cTime": client_time,
|
|
@@ -1866,7 +1876,10 @@ def spotify_get_access_token_from_sp_dc(sp_dc: str):
|
|
|
1866
1876
|
|
|
1867
1877
|
# Fetches Spotify access token based on provided sp_client_id & sp_client_secret values (Client Credentials OAuth Flow)
|
|
1868
1878
|
def spotify_get_access_token_from_oauth_app(sp_client_id, sp_client_secret):
|
|
1869
|
-
global
|
|
1879
|
+
global SP_CACHED_OAUTH_APP_TOKEN
|
|
1880
|
+
|
|
1881
|
+
if not sp_client_id or not sp_client_secret:
|
|
1882
|
+
return None
|
|
1870
1883
|
|
|
1871
1884
|
try:
|
|
1872
1885
|
from spotipy.oauth2 import SpotifyClientCredentials
|
|
@@ -1875,8 +1888,8 @@ def spotify_get_access_token_from_oauth_app(sp_client_id, sp_client_secret):
|
|
|
1875
1888
|
print("* Warning: the 'spotipy' package is required for 'oauth_app' token source, install it with `pip install spotipy`")
|
|
1876
1889
|
return None
|
|
1877
1890
|
|
|
1878
|
-
if
|
|
1879
|
-
return
|
|
1891
|
+
if SP_CACHED_OAUTH_APP_TOKEN and check_token_validity(SP_CACHED_OAUTH_APP_TOKEN, oauth_app=True):
|
|
1892
|
+
return SP_CACHED_OAUTH_APP_TOKEN
|
|
1880
1893
|
|
|
1881
1894
|
if SP_APP_TOKENS_FILE:
|
|
1882
1895
|
cache_handler = CacheFileHandler(cache_path=SP_APP_TOKENS_FILE)
|
|
@@ -1888,9 +1901,9 @@ def spotify_get_access_token_from_oauth_app(sp_client_id, sp_client_secret):
|
|
|
1888
1901
|
|
|
1889
1902
|
auth_manager = SpotifyClientCredentials(client_id=sp_client_id, client_secret=sp_client_secret, cache_handler=cache_handler, requests_session=session) # type: ignore[arg-type]
|
|
1890
1903
|
|
|
1891
|
-
|
|
1904
|
+
SP_CACHED_OAUTH_APP_TOKEN = auth_manager.get_access_token(as_dict=False)
|
|
1892
1905
|
|
|
1893
|
-
return
|
|
1906
|
+
return SP_CACHED_OAUTH_APP_TOKEN
|
|
1894
1907
|
|
|
1895
1908
|
|
|
1896
1909
|
# -----------------------------------------------------------
|
|
@@ -2589,56 +2602,14 @@ def spotify_convert_url_to_uri(url):
|
|
|
2589
2602
|
return uri
|
|
2590
2603
|
|
|
2591
2604
|
|
|
2592
|
-
# Gets basic information about access token owner
|
|
2593
|
-
def spotify_get_current_user_or_app(access_token) -> dict | None:
|
|
2594
|
-
|
|
2595
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
2596
|
-
app_info = {
|
|
2597
|
-
"type": "app_token",
|
|
2598
|
-
"client_id": SP_APP_CLIENT_ID
|
|
2599
|
-
}
|
|
2600
|
-
return app_info
|
|
2601
|
-
|
|
2602
|
-
url = "https://api.spotify.com/v1/me"
|
|
2603
|
-
|
|
2604
|
-
headers = {
|
|
2605
|
-
"Authorization": f"Bearer {access_token}",
|
|
2606
|
-
"User-Agent": USER_AGENT
|
|
2607
|
-
}
|
|
2608
|
-
|
|
2609
|
-
if TOKEN_SOURCE == "cookie":
|
|
2610
|
-
headers.update({
|
|
2611
|
-
"Client-Id": SP_CACHED_CLIENT_ID
|
|
2612
|
-
})
|
|
2613
|
-
|
|
2614
|
-
if platform.system() != 'Windows':
|
|
2615
|
-
signal.signal(signal.SIGALRM, timeout_handler)
|
|
2616
|
-
signal.alarm(FUNCTION_TIMEOUT + 2)
|
|
2617
|
-
try:
|
|
2618
|
-
response = SESSION.get(url, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
2619
|
-
response.raise_for_status()
|
|
2620
|
-
data = response.json()
|
|
2621
|
-
|
|
2622
|
-
user_info = {
|
|
2623
|
-
"display_name": data.get("display_name"),
|
|
2624
|
-
"uri": data.get("uri"),
|
|
2625
|
-
"is_premium": data.get("product") == "premium",
|
|
2626
|
-
"country": data.get("country"),
|
|
2627
|
-
"email": data.get("email"),
|
|
2628
|
-
"spotify_url": data.get("external_urls", {}).get("spotify") + "?si=1" if data.get("external_urls", {}).get("spotify") else None
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
return user_info
|
|
2632
|
-
except Exception as e:
|
|
2633
|
-
print(f"* Error: {e}")
|
|
2634
|
-
return None
|
|
2635
|
-
finally:
|
|
2636
|
-
if platform.system() != 'Windows':
|
|
2637
|
-
signal.alarm(0)
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
2605
|
# Checks if a playlist has been completely removed and/or set as private
|
|
2641
|
-
def is_playlist_private(access_token, playlist_uri):
|
|
2606
|
+
def is_playlist_private(access_token, playlist_uri, oauth_app: bool = False):
|
|
2607
|
+
if TOKEN_SOURCE in {"cookie", "client"} and not oauth_app:
|
|
2608
|
+
access_token = spotify_get_access_token_from_oauth_app(SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET)
|
|
2609
|
+
oauth_app = True
|
|
2610
|
+
if not access_token:
|
|
2611
|
+
return False
|
|
2612
|
+
|
|
2642
2613
|
playlist_id = playlist_uri.split(':', 2)[2]
|
|
2643
2614
|
url = f"https://api.spotify.com/v1/playlists/{playlist_id}?fields=id"
|
|
2644
2615
|
|
|
@@ -2647,7 +2618,7 @@ def is_playlist_private(access_token, playlist_uri):
|
|
|
2647
2618
|
"User-Agent": USER_AGENT
|
|
2648
2619
|
}
|
|
2649
2620
|
|
|
2650
|
-
if TOKEN_SOURCE == "cookie":
|
|
2621
|
+
if TOKEN_SOURCE == "cookie" and not oauth_app:
|
|
2651
2622
|
headers.update({
|
|
2652
2623
|
"Client-Id": SP_CACHED_CLIENT_ID
|
|
2653
2624
|
})
|
|
@@ -2662,7 +2633,13 @@ def is_playlist_private(access_token, playlist_uri):
|
|
|
2662
2633
|
|
|
2663
2634
|
|
|
2664
2635
|
# Checks if a Spotify user URI ID has been deleted
|
|
2665
|
-
def is_user_removed(access_token, user_uri_id):
|
|
2636
|
+
def is_user_removed(access_token, user_uri_id, oauth_app: bool = False):
|
|
2637
|
+
if TOKEN_SOURCE in {"cookie", "client"} and not oauth_app:
|
|
2638
|
+
access_token = spotify_get_access_token_from_oauth_app(SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET)
|
|
2639
|
+
oauth_app = True
|
|
2640
|
+
if not access_token:
|
|
2641
|
+
return False
|
|
2642
|
+
|
|
2666
2643
|
url = f"https://api.spotify.com/v1/users/{user_uri_id}"
|
|
2667
2644
|
|
|
2668
2645
|
headers = {
|
|
@@ -2670,23 +2647,46 @@ def is_user_removed(access_token, user_uri_id):
|
|
|
2670
2647
|
"User-Agent": USER_AGENT
|
|
2671
2648
|
}
|
|
2672
2649
|
|
|
2673
|
-
if TOKEN_SOURCE == "cookie":
|
|
2650
|
+
if TOKEN_SOURCE == "cookie" and not oauth_app:
|
|
2674
2651
|
headers.update({
|
|
2675
2652
|
"Client-Id": SP_CACHED_CLIENT_ID
|
|
2676
2653
|
})
|
|
2677
2654
|
|
|
2655
|
+
if platform.system() != 'Windows':
|
|
2656
|
+
signal.signal(signal.SIGALRM, timeout_handler)
|
|
2657
|
+
signal.alarm(FUNCTION_TIMEOUT + 2)
|
|
2658
|
+
|
|
2678
2659
|
try:
|
|
2679
|
-
|
|
2660
|
+
temp_session = req.Session()
|
|
2661
|
+
temp_session.headers.update(headers)
|
|
2662
|
+
|
|
2663
|
+
response = temp_session.get(url, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
2664
|
+
|
|
2665
|
+
if response.status_code == 429:
|
|
2666
|
+
return False
|
|
2667
|
+
|
|
2680
2668
|
if response.status_code == 404:
|
|
2681
2669
|
return True
|
|
2682
2670
|
return False
|
|
2671
|
+
except TimeoutException:
|
|
2672
|
+
return False
|
|
2673
|
+
except req.HTTPError as e:
|
|
2674
|
+
if e.response is not None and e.response.status_code == 429:
|
|
2675
|
+
return False
|
|
2676
|
+
elif e.response is not None and e.response.status_code == 404:
|
|
2677
|
+
return True
|
|
2678
|
+
return False
|
|
2683
2679
|
except Exception:
|
|
2684
2680
|
return False
|
|
2681
|
+
finally:
|
|
2682
|
+
if platform.system() != 'Windows':
|
|
2683
|
+
signal.alarm(0)
|
|
2685
2684
|
|
|
2686
2685
|
|
|
2687
2686
|
# Returns True if the access token owner's user ID matches the provided user_uri_id, False otherwise
|
|
2688
2687
|
def is_token_owner(access_token, user_uri_id) -> bool:
|
|
2689
|
-
|
|
2688
|
+
# /v1/me is only reliable/usable for oauth_user now
|
|
2689
|
+
if TOKEN_SOURCE != "oauth_user":
|
|
2690
2690
|
return False
|
|
2691
2691
|
|
|
2692
2692
|
url = "https://api.spotify.com/v1/me"
|
|
@@ -2696,11 +2696,6 @@ def is_token_owner(access_token, user_uri_id) -> bool:
|
|
|
2696
2696
|
"User-Agent": USER_AGENT
|
|
2697
2697
|
}
|
|
2698
2698
|
|
|
2699
|
-
if TOKEN_SOURCE == "cookie":
|
|
2700
|
-
headers.update({
|
|
2701
|
-
"Client-Id": SP_CACHED_CLIENT_ID
|
|
2702
|
-
})
|
|
2703
|
-
|
|
2704
2699
|
try:
|
|
2705
2700
|
response = SESSION.get(url, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
2706
2701
|
response.raise_for_status()
|
|
@@ -2710,7 +2705,13 @@ def is_token_owner(access_token, user_uri_id) -> bool:
|
|
|
2710
2705
|
|
|
2711
2706
|
|
|
2712
2707
|
# Returns detailed info about playlist with specified URI (with possibility to get its tracks as well)
|
|
2713
|
-
def spotify_get_playlist_info(access_token, playlist_uri, get_tracks):
|
|
2708
|
+
def spotify_get_playlist_info(access_token, playlist_uri, get_tracks, oauth_app: bool = False):
|
|
2709
|
+
if TOKEN_SOURCE in {"cookie", "client"} and not oauth_app:
|
|
2710
|
+
access_token = spotify_get_access_token_from_oauth_app(SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET)
|
|
2711
|
+
oauth_app = True
|
|
2712
|
+
if not access_token:
|
|
2713
|
+
raise Exception("spotify_get_playlist_info(): oauth_app token is missing - set SP_APP_CLIENT_ID/SP_APP_CLIENT_SECRET (or pass -r / --oauth-app-creds)")
|
|
2714
|
+
|
|
2714
2715
|
parts = playlist_uri.split(':')
|
|
2715
2716
|
if len(parts) == 3:
|
|
2716
2717
|
playlist_id = parts[2]
|
|
@@ -2730,7 +2731,7 @@ def spotify_get_playlist_info(access_token, playlist_uri, get_tracks):
|
|
|
2730
2731
|
"User-Agent": USER_AGENT
|
|
2731
2732
|
}
|
|
2732
2733
|
|
|
2733
|
-
if TOKEN_SOURCE == "cookie":
|
|
2734
|
+
if TOKEN_SOURCE == "cookie" and not oauth_app:
|
|
2734
2735
|
headers.update({
|
|
2735
2736
|
"Client-Id": SP_CACHED_CLIENT_ID
|
|
2736
2737
|
})
|
|
@@ -3094,16 +3095,6 @@ def spotify_list_tracks_for_playlist(sp_accessToken, playlist_url, csv_file_name
|
|
|
3094
3095
|
list_operation = "* Listing & saving" if csv_file_name else "* Listing"
|
|
3095
3096
|
print(f"{list_operation} tracks for playlist '{playlist_url}' ...\n")
|
|
3096
3097
|
|
|
3097
|
-
user_info = spotify_get_current_user_or_app(sp_accessToken)
|
|
3098
|
-
if user_info and not CLEAN_OUTPUT:
|
|
3099
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
3100
|
-
print(f"Token belongs to:\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})\n")
|
|
3101
|
-
else:
|
|
3102
|
-
print(f"Token belongs to:\t{user_info.get('display_name', '')} (via {TOKEN_SOURCE})\n\t\t\t[ {user_info.get('spotify_url')} ]\n")
|
|
3103
|
-
|
|
3104
|
-
if not CLEAN_OUTPUT:
|
|
3105
|
-
print("─" * HORIZONTAL_LINE)
|
|
3106
|
-
|
|
3107
3098
|
user_id_name_mapping = {}
|
|
3108
3099
|
user_track_counts = Counter()
|
|
3109
3100
|
unknown_added_by_tracks = 0
|
|
@@ -3322,18 +3313,6 @@ def spotify_list_liked_tracks(sp_accessToken, csv_file_name, format_type=2):
|
|
|
3322
3313
|
list_operation = "* Listing & saving" if csv_file_name else "* Listing"
|
|
3323
3314
|
print(f"{list_operation} liked tracks for the user owning the token ...\n")
|
|
3324
3315
|
|
|
3325
|
-
user_info = spotify_get_current_user_or_app(sp_accessToken)
|
|
3326
|
-
if user_info:
|
|
3327
|
-
username = user_info.get("display_name", "")
|
|
3328
|
-
if not CLEAN_OUTPUT:
|
|
3329
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
3330
|
-
print(f"Token belongs to:\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})\n")
|
|
3331
|
-
else:
|
|
3332
|
-
print(f"Token belongs to:\t{username} (via {TOKEN_SOURCE})\n\t\t\t[ {user_info.get('spotify_url')} ]\n")
|
|
3333
|
-
|
|
3334
|
-
if not CLEAN_OUTPUT:
|
|
3335
|
-
print("─" * HORIZONTAL_LINE)
|
|
3336
|
-
|
|
3337
3316
|
sp_playlist_data = spotify_get_user_liked_tracks(sp_accessToken)
|
|
3338
3317
|
|
|
3339
3318
|
p_tracks = sp_playlist_data.get("sp_playlist_tracks_count", 0)
|
|
@@ -3433,15 +3412,6 @@ def spotify_search_users(access_token, username):
|
|
|
3433
3412
|
|
|
3434
3413
|
print(f"* Searching for users with '{username}' string ...\n")
|
|
3435
3414
|
|
|
3436
|
-
user_info = spotify_get_current_user_or_app(access_token)
|
|
3437
|
-
if user_info:
|
|
3438
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
3439
|
-
print(f"Token belongs to:\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})\n")
|
|
3440
|
-
else:
|
|
3441
|
-
print(f"Token belongs to:\t{user_info.get('display_name', '')} (via {TOKEN_SOURCE})\n\t\t\t[ {user_info.get('spotify_url')} ]\n")
|
|
3442
|
-
|
|
3443
|
-
print("─" * HORIZONTAL_LINE)
|
|
3444
|
-
|
|
3445
3415
|
try:
|
|
3446
3416
|
response = SESSION.get(url, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
3447
3417
|
response.raise_for_status()
|
|
@@ -3843,13 +3813,6 @@ def spotify_get_user_details(sp_accessToken, user_uri_id):
|
|
|
3843
3813
|
|
|
3844
3814
|
print(f"* Getting detailed info for user with URI ID '{user_uri_id}' ...\n")
|
|
3845
3815
|
|
|
3846
|
-
user_info = spotify_get_current_user_or_app(sp_accessToken)
|
|
3847
|
-
if user_info:
|
|
3848
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
3849
|
-
print(f"Token belongs to:\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})\n")
|
|
3850
|
-
else:
|
|
3851
|
-
print(f"Token belongs to:\t{user_info.get('display_name', '')} (via {TOKEN_SOURCE})\n\t\t\t[ {user_info.get('spotify_url')} ]\n")
|
|
3852
|
-
|
|
3853
3816
|
sp_user_data = spotify_get_user_info(sp_accessToken, user_uri_id, DETECT_CHANGES_IN_PLAYLISTS, RECENTLY_PLAYED_ARTISTS_LIMIT_INFO)
|
|
3854
3817
|
sp_user_followers_data = spotify_get_user_followers(sp_accessToken, user_uri_id)
|
|
3855
3818
|
sp_user_followings_data = spotify_get_user_followings(sp_accessToken, user_uri_id)
|
|
@@ -3928,13 +3891,6 @@ def spotify_get_user_details(sp_accessToken, user_uri_id):
|
|
|
3928
3891
|
def spotify_get_recently_played_artists(sp_accessToken, user_uri_id):
|
|
3929
3892
|
print(f"* Getting list of recently played artists for user with URI ID '{user_uri_id}' ...\n")
|
|
3930
3893
|
|
|
3931
|
-
user_info = spotify_get_current_user_or_app(sp_accessToken)
|
|
3932
|
-
if user_info:
|
|
3933
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
3934
|
-
print(f"Token belongs to:\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})\n")
|
|
3935
|
-
else:
|
|
3936
|
-
print(f"Token belongs to:\t{user_info.get('display_name', '')} (via {TOKEN_SOURCE})\n\t\t\t[ {user_info.get('spotify_url')} ]\n")
|
|
3937
|
-
|
|
3938
3894
|
sp_user_data = spotify_get_user_info(sp_accessToken, user_uri_id, False, RECENTLY_PLAYED_ARTISTS_LIMIT)
|
|
3939
3895
|
|
|
3940
3896
|
username = sp_user_data["sp_username"]
|
|
@@ -3961,13 +3917,6 @@ def spotify_get_recently_played_artists(sp_accessToken, user_uri_id):
|
|
|
3961
3917
|
def spotify_get_followers_and_followings(sp_accessToken, user_uri_id):
|
|
3962
3918
|
print(f"* Getting followers & followings for user with URI ID '{user_uri_id}' ...\n")
|
|
3963
3919
|
|
|
3964
|
-
user_info = spotify_get_current_user_or_app(sp_accessToken)
|
|
3965
|
-
if user_info:
|
|
3966
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
3967
|
-
print(f"Token belongs to:\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})\n")
|
|
3968
|
-
else:
|
|
3969
|
-
print(f"Token belongs to:\t{user_info.get('display_name', '')} (via {TOKEN_SOURCE})\n\t\t\t[ {user_info.get('spotify_url')} ]\n")
|
|
3970
|
-
|
|
3971
3920
|
sp_user_data = spotify_get_user_info(sp_accessToken, user_uri_id, False, 0)
|
|
3972
3921
|
image_url = sp_user_data["sp_user_image_url"]
|
|
3973
3922
|
sp_user_followers_data = spotify_get_user_followers(sp_accessToken, user_uri_id)
|
|
@@ -4556,7 +4505,7 @@ def resolve_executable(path):
|
|
|
4556
4505
|
|
|
4557
4506
|
# Monitors profile changes of the specified Spotify user URI ID
|
|
4558
4507
|
def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
4559
|
-
global SP_CACHED_ACCESS_TOKEN
|
|
4508
|
+
global SP_CACHED_ACCESS_TOKEN, SP_CACHED_OAUTH_APP_TOKEN
|
|
4560
4509
|
playlists_count = 0
|
|
4561
4510
|
playlists_old_count = 0
|
|
4562
4511
|
playlists = None
|
|
@@ -4589,7 +4538,6 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4589
4538
|
else:
|
|
4590
4539
|
sp_accessToken = spotify_get_access_token_from_sp_dc(SP_DC_COOKIE)
|
|
4591
4540
|
sp_user_data = spotify_get_user_info(sp_accessToken, user_uri_id, DETECT_CHANGES_IN_PLAYLISTS, 0)
|
|
4592
|
-
user_info = spotify_get_current_user_or_app(sp_accessToken)
|
|
4593
4541
|
sp_user_followers_data = spotify_get_user_followers(sp_accessToken, user_uri_id)
|
|
4594
4542
|
sp_user_followings_data = spotify_get_user_followings(sp_accessToken, user_uri_id)
|
|
4595
4543
|
except Exception as e:
|
|
@@ -4597,6 +4545,7 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4597
4545
|
|
|
4598
4546
|
if TOKEN_SOURCE == 'cookie' and '401' in err:
|
|
4599
4547
|
SP_CACHED_ACCESS_TOKEN = None
|
|
4548
|
+
SP_CACHED_OAUTH_APP_TOKEN = None
|
|
4600
4549
|
|
|
4601
4550
|
client_errs = ['access token', 'invalid client token', 'expired client token', 'refresh token has been revoked', 'refresh token has expired', 'refresh token is invalid', 'invalid grant during refresh']
|
|
4602
4551
|
cookie_errs = ['access token', 'unauthorized', 'unsuccessful token request']
|
|
@@ -4621,13 +4570,6 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4621
4570
|
|
|
4622
4571
|
sys.exit(1)
|
|
4623
4572
|
|
|
4624
|
-
if user_info:
|
|
4625
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
4626
|
-
print(f"Token belongs to:\t\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})")
|
|
4627
|
-
else:
|
|
4628
|
-
print(f"Token belongs to:\t\t{user_info.get('display_name', '')} (via {TOKEN_SOURCE})\n\t\t\t\t[ {user_info.get('spotify_url')} ]")
|
|
4629
|
-
print("─" * HORIZONTAL_LINE)
|
|
4630
|
-
|
|
4631
4573
|
username = sp_user_data["sp_username"]
|
|
4632
4574
|
image_url = sp_user_data["sp_user_image_url"]
|
|
4633
4575
|
|
|
@@ -5896,7 +5838,7 @@ def main():
|
|
|
5896
5838
|
"-x", "--list-liked-tracks",
|
|
5897
5839
|
dest="list_liked_tracks",
|
|
5898
5840
|
action="store_true",
|
|
5899
|
-
help="List all liked tracks for the user owning the Spotify access token"
|
|
5841
|
+
help="List all liked tracks for the user owning the Spotify access token (works only with oauth_user)"
|
|
5900
5842
|
)
|
|
5901
5843
|
listing.add_argument(
|
|
5902
5844
|
"-i", "--show-user-profile",
|
|
@@ -5916,12 +5858,6 @@ def main():
|
|
|
5916
5858
|
action="store_true",
|
|
5917
5859
|
help="List followers & followings for a user"
|
|
5918
5860
|
)
|
|
5919
|
-
listing.add_argument(
|
|
5920
|
-
"-v", "--show-user-info",
|
|
5921
|
-
dest="show_user_info",
|
|
5922
|
-
action="store_true",
|
|
5923
|
-
help="Get basic information about the Spotify access token owner"
|
|
5924
|
-
)
|
|
5925
5861
|
listing.add_argument(
|
|
5926
5862
|
"-s", "--search-username",
|
|
5927
5863
|
dest="search_username",
|
|
@@ -6102,7 +6038,8 @@ def main():
|
|
|
6102
6038
|
except ModuleNotFoundError:
|
|
6103
6039
|
raise SystemExit("Error: Couldn't find the pyotp library !\n\nTo install it, run:\n pip install pyotp\n\nOnce installed, re-run this tool")
|
|
6104
6040
|
|
|
6105
|
-
|
|
6041
|
+
# spotipy is required for oauth_app tokens (also used as a secondary token in cookie/client hybrid mode)
|
|
6042
|
+
if TOKEN_SOURCE in {"oauth_app", "cookie", "client"}:
|
|
6106
6043
|
try:
|
|
6107
6044
|
from spotipy.oauth2 import SpotifyClientCredentials
|
|
6108
6045
|
except ModuleNotFoundError:
|
|
@@ -6150,6 +6087,14 @@ def main():
|
|
|
6150
6087
|
if args.error_interval:
|
|
6151
6088
|
SPOTIFY_ERROR_INTERVAL = args.error_interval
|
|
6152
6089
|
|
|
6090
|
+
# Allow providing oauth_app creds via CLI for both oauth_app token source and cookie/client hybrid mode
|
|
6091
|
+
if args.oauth_app_creds:
|
|
6092
|
+
try:
|
|
6093
|
+
SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET = args.oauth_app_creds.split(":")
|
|
6094
|
+
except ValueError:
|
|
6095
|
+
print("* Error: -r / --oauth-app-creds has invalid format - use SP_APP_CLIENT_ID:SP_APP_CLIENT_SECRET")
|
|
6096
|
+
sys.exit(1)
|
|
6097
|
+
|
|
6153
6098
|
if TOKEN_SOURCE == "client":
|
|
6154
6099
|
login_request_body_file_param = False
|
|
6155
6100
|
if args.login_request_body_file:
|
|
@@ -6167,7 +6112,7 @@ def main():
|
|
|
6167
6112
|
print(f"* Error: Protobuf file ({LOGIN_REQUEST_BODY_FILE}) cannot be processed: {e}")
|
|
6168
6113
|
sys.exit(1)
|
|
6169
6114
|
else:
|
|
6170
|
-
if not args.user_id and not args.list_tracks_for_playlist and not args.search_username and not args.user_profile_details and not args.recently_played_artists and not args.followers_and_followings and not args.
|
|
6115
|
+
if not args.user_id and not args.list_tracks_for_playlist and not args.search_username and not args.user_profile_details and not args.recently_played_artists and not args.followers_and_followings and not args.list_liked_tracks and login_request_body_file_param:
|
|
6171
6116
|
print(f"* Login data correctly read from Protobuf file ({LOGIN_REQUEST_BODY_FILE}):")
|
|
6172
6117
|
print(" - Device ID:\t\t", DEVICE_ID)
|
|
6173
6118
|
print(" - System ID:\t\t", SYSTEM_ID)
|
|
@@ -6219,7 +6164,7 @@ def main():
|
|
|
6219
6164
|
print(f"* Error: Protobuf file ({CLIENTTOKEN_REQUEST_BODY_FILE}) cannot be processed: {e}")
|
|
6220
6165
|
sys.exit(1)
|
|
6221
6166
|
else:
|
|
6222
|
-
if not args.user_id and not args.list_tracks_for_playlist and not args.search_username and not args.user_profile_details and not args.recently_played_artists and not args.followers_and_followings and not args.
|
|
6167
|
+
if not args.user_id and not args.list_tracks_for_playlist and not args.search_username and not args.user_profile_details and not args.recently_played_artists and not args.followers_and_followings and not args.list_liked_tracks and clienttoken_request_body_file_param:
|
|
6223
6168
|
print(f"* Client token data correctly read from Protobuf file ({CLIENTTOKEN_REQUEST_BODY_FILE}):")
|
|
6224
6169
|
print(" - App version:\t\t", APP_VERSION)
|
|
6225
6170
|
print(" - CPU arch:\t\t", CPU_ARCH)
|
|
@@ -6244,13 +6189,6 @@ def main():
|
|
|
6244
6189
|
APP_VERSION = app_version_default
|
|
6245
6190
|
|
|
6246
6191
|
elif TOKEN_SOURCE == "oauth_app":
|
|
6247
|
-
if args.oauth_app_creds:
|
|
6248
|
-
try:
|
|
6249
|
-
SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET = args.oauth_app_creds.split(":")
|
|
6250
|
-
except ValueError:
|
|
6251
|
-
print("* Error: -r / --oauth-app-creds has invalid format - use SP_APP_CLIENT_ID:SP_APP_CLIENT_SECRET")
|
|
6252
|
-
sys.exit(1)
|
|
6253
|
-
|
|
6254
6192
|
if any([
|
|
6255
6193
|
not SP_APP_CLIENT_ID,
|
|
6256
6194
|
SP_APP_CLIENT_ID == "your_spotify_app_client_id",
|
|
@@ -6283,6 +6221,17 @@ def main():
|
|
|
6283
6221
|
print("* Error: SP_DC_COOKIE (-u / --spotify_dc_cookie) value is empty or incorrect")
|
|
6284
6222
|
sys.exit(1)
|
|
6285
6223
|
|
|
6224
|
+
# Hybrid mode requirement: cookie/client now needs oauth_app creds for /v1/users and /v1/playlists calls
|
|
6225
|
+
if TOKEN_SOURCE in {"cookie", "client"}:
|
|
6226
|
+
if any([
|
|
6227
|
+
not SP_APP_CLIENT_ID,
|
|
6228
|
+
SP_APP_CLIENT_ID == "your_spotify_app_client_id",
|
|
6229
|
+
not SP_APP_CLIENT_SECRET,
|
|
6230
|
+
SP_APP_CLIENT_SECRET == "your_spotify_app_client_secret",
|
|
6231
|
+
]):
|
|
6232
|
+
print("* Error: SP_APP_CLIENT_ID or SP_APP_CLIENT_SECRET (-r / --oauth-app-creds) value is empty or incorrect (required for cookie/client hybrid mode since 22 Dec 2025)")
|
|
6233
|
+
sys.exit(1)
|
|
6234
|
+
|
|
6286
6235
|
if IMGCAT_PATH:
|
|
6287
6236
|
try:
|
|
6288
6237
|
imgcat_exe = resolve_executable(IMGCAT_PATH)
|
|
@@ -6295,40 +6244,6 @@ def main():
|
|
|
6295
6244
|
if SP_USER_TOKENS_FILE:
|
|
6296
6245
|
SP_USER_TOKENS_FILE = os.path.expanduser(SP_USER_TOKENS_FILE)
|
|
6297
6246
|
|
|
6298
|
-
if args.show_user_info:
|
|
6299
|
-
print("* Getting basic information about access token owner ...\n")
|
|
6300
|
-
try:
|
|
6301
|
-
if TOKEN_SOURCE == "client":
|
|
6302
|
-
sp_accessToken = spotify_get_access_token_from_client_auto(DEVICE_ID, SYSTEM_ID, USER_URI_ID, REFRESH_TOKEN)
|
|
6303
|
-
elif TOKEN_SOURCE == "oauth_app":
|
|
6304
|
-
sp_accessToken = spotify_get_access_token_from_oauth_app(SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET)
|
|
6305
|
-
elif TOKEN_SOURCE == "oauth_user":
|
|
6306
|
-
sp_accessToken = spotify_get_access_token_from_oauth_user(SP_USER_CLIENT_ID, SP_USER_CLIENT_SECRET, SP_USER_REDIRECT_URI, SP_USER_SCOPE, init=True)
|
|
6307
|
-
else:
|
|
6308
|
-
sp_accessToken = spotify_get_access_token_from_sp_dc(SP_DC_COOKIE)
|
|
6309
|
-
user_info = spotify_get_current_user_or_app(sp_accessToken)
|
|
6310
|
-
|
|
6311
|
-
if user_info:
|
|
6312
|
-
print(f"Token fetched via '{TOKEN_SOURCE}' belongs to:\n")
|
|
6313
|
-
|
|
6314
|
-
if TOKEN_SOURCE == "oauth_app":
|
|
6315
|
-
print(f"App client ID:\t\t{user_info.get('client_id', '')}")
|
|
6316
|
-
else:
|
|
6317
|
-
print(f"Username:\t\t{user_info.get('display_name', '')}")
|
|
6318
|
-
print(f"User URI ID:\t\t{user_info.get('uri', '').split('spotify:user:', 1)[1]}")
|
|
6319
|
-
print(f"User URL:\t\t{user_info.get('spotify_url', '')}")
|
|
6320
|
-
print(f"User e-mail:\t\t{user_info.get('email', '')}")
|
|
6321
|
-
print(f"User country:\t\t{user_info.get('country', '')}")
|
|
6322
|
-
print(f"Is Premium?:\t\t{user_info.get('is_premium', '')}")
|
|
6323
|
-
else:
|
|
6324
|
-
print("Failed to retrieve user info.")
|
|
6325
|
-
|
|
6326
|
-
print("─" * HORIZONTAL_LINE)
|
|
6327
|
-
except Exception as e:
|
|
6328
|
-
print(f"* Error: {e}")
|
|
6329
|
-
sys.exit(1)
|
|
6330
|
-
sys.exit(0)
|
|
6331
|
-
|
|
6332
6247
|
if args.csv_file:
|
|
6333
6248
|
CSV_FILE = os.path.expanduser(args.csv_file)
|
|
6334
6249
|
else:
|
|
@@ -6363,8 +6278,8 @@ def main():
|
|
|
6363
6278
|
sys.exit(0)
|
|
6364
6279
|
|
|
6365
6280
|
if args.list_liked_tracks:
|
|
6366
|
-
if TOKEN_SOURCE not in {"
|
|
6367
|
-
print(f"* Error: List of liked tracks is not supported with the '{TOKEN_SOURCE}' method ! Use the '
|
|
6281
|
+
if TOKEN_SOURCE not in {"oauth_user"}:
|
|
6282
|
+
print(f"* Error: List of liked tracks is not supported with the '{TOKEN_SOURCE}' method ! Use the 'oauth_user' token source instead !")
|
|
6368
6283
|
sys.exit(2)
|
|
6369
6284
|
try:
|
|
6370
6285
|
if TOKEN_SOURCE == "client":
|
|
@@ -6565,7 +6480,7 @@ def main():
|
|
|
6565
6480
|
|
|
6566
6481
|
print(f"* Spotify polling intervals:\t[check: {display_time(SPOTIFY_CHECK_INTERVAL)}] [error: {display_time(SPOTIFY_ERROR_INTERVAL)}]")
|
|
6567
6482
|
print(f"* Email notifications:\t\t[profile changes = {PROFILE_NOTIFICATION}] [followers/followings = {FOLLOWERS_FOLLOWINGS_NOTIFICATION}]\n*\t\t\t\t[errors = {ERROR_NOTIFICATION}]")
|
|
6568
|
-
print(f"* Token source:\t\t\t{TOKEN_SOURCE}")
|
|
6483
|
+
print(f"* Token source:\t\t\t{TOKEN_SOURCE}" + (" + oauth_app" if TOKEN_SOURCE in {"cookie", "client"} else ""))
|
|
6569
6484
|
print(f"* Profile pic changes:\t\t{DETECT_CHANGED_PROFILE_PIC}")
|
|
6570
6485
|
print(f"* Playlist changes:\t\t{DETECT_CHANGES_IN_PLAYLISTS}")
|
|
6571
6486
|
print(f"* All public playlists:\t\t{GET_ALL_PLAYLISTS}")
|
|
@@ -6578,8 +6493,12 @@ def main():
|
|
|
6578
6493
|
print(f"* Output logging enabled:\t{not DISABLE_LOGGING}" + (f" ({FINAL_LOG_PATH})" if not DISABLE_LOGGING else ""))
|
|
6579
6494
|
if not DISABLE_LOGGING and TRUNCATE_CHARS > 0:
|
|
6580
6495
|
print(f"* Truncate terminal lines:\t{TRUNCATE_CHARS} chars")
|
|
6581
|
-
if TOKEN_SOURCE
|
|
6582
|
-
print(f"* Spotify token cache file:\t{
|
|
6496
|
+
if TOKEN_SOURCE == 'oauth_user':
|
|
6497
|
+
print(f"* Spotify token cache file:\t{SP_USER_TOKENS_FILE if SP_USER_TOKENS_FILE else 'None (memory only)'}")
|
|
6498
|
+
elif TOKEN_SOURCE == 'oauth_app':
|
|
6499
|
+
print(f"* Spotify token cache file:\t{SP_APP_TOKENS_FILE if SP_APP_TOKENS_FILE else 'None (memory only)'}")
|
|
6500
|
+
elif TOKEN_SOURCE in {'cookie', 'client'}:
|
|
6501
|
+
print(f"* Spotify OAuth cache file:\t{SP_APP_TOKENS_FILE if SP_APP_TOKENS_FILE else 'None (memory only)'}")
|
|
6583
6502
|
print(f"* Configuration file:\t\t{cfg_path}")
|
|
6584
6503
|
print(f"* Dotenv file:\t\t\t{env_path or 'None'}")
|
|
6585
6504
|
print(f"* Local timezone:\t\t{LOCAL_TIMEZONE}\n")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|