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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spotify_profile_monitor
3
- Version: 3.0
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 **four different methods to get a Spotify access token** (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
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
- It can be configured via the `TOKEN_SOURCE` configuration option or the `--token-source` flag.
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 two 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).
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
- - Log in to Spotify Developer dashboard: https://developer.spotify.com/dashboard
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 / at the end
329
- - When copying the link via right-click, some browsers may add an extra / to the URL
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 takes care of refreshing the access token so it should remain valid indefinitely.
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 two 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).
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 **four different methods to get a Spotify access token** (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
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
- It can be configured via the `TOKEN_SOURCE` configuration option or the `--token-source` flag.
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 two 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).
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
- - Log in to Spotify Developer dashboard: https://developer.spotify.com/dashboard
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 / at the end
300
- - When copying the link via right-click, some browsers may add an extra / to the URL
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 takes care of refreshing the access token so it should remain valid indefinitely.
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 two 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).
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.0"
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.0
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 **four different methods to get a Spotify access token** (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
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
- It can be configured via the `TOKEN_SOURCE` configuration option or the `--token-source` flag.
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 two 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).
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
- - Log in to Spotify Developer dashboard: https://developer.spotify.com/dashboard
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 / at the end
329
- - When copying the link via right-click, some browsers may add an extra / to the URL
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 takes care of refreshing the access token so it should remain valid indefinitely.
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 two 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).
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.0
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 (optional, needed when the token source is set to oauth_app)
18
+ spotipy
19
19
  wcwidth (optional, needed by TRUNCATE_CHARS feature)
20
20
  """
21
21
 
22
- VERSION = "3.0"
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
- "6": [21, 24, 85, 46, 48, 35, 33, 8, 11, 63, 76, 12, 55, 77, 14, 7, 54],
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 and refresh token to avoid unnecessary refreshing
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
- url1 = "https://api.spotify.com/v1/me"
1493
- url2 = "https://api.spotify.com/v1/browse/categories?limit=1&fields=categories.items(id)"
1494
-
1495
- url = url2 if TOKEN_SOURCE == "oauth_app" else url1
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": TOTP_VER,
1741
+ "totpVer": totp_ver,
1732
1742
  }
1733
1743
 
1734
- if TOTP_VER < 10:
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 SP_CACHED_ACCESS_TOKEN
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 SP_CACHED_ACCESS_TOKEN and check_token_validity(SP_CACHED_ACCESS_TOKEN):
1879
- return SP_CACHED_ACCESS_TOKEN
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
- SP_CACHED_ACCESS_TOKEN = auth_manager.get_access_token(as_dict=False)
1904
+ SP_CACHED_OAUTH_APP_TOKEN = auth_manager.get_access_token(as_dict=False)
1892
1905
 
1893
- return SP_CACHED_ACCESS_TOKEN
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
- response = SESSION.get(url, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
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
- if TOKEN_SOURCE == "oauth_app":
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
- if TOKEN_SOURCE == "oauth_app":
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.show_user_info and not args.list_liked_tracks and login_request_body_file_param:
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.show_user_info and not args.list_liked_tracks and clienttoken_request_body_file_param:
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 {"client", "oauth_user"}:
6367
- print(f"* Error: List of liked tracks is not supported with the '{TOKEN_SOURCE}' method ! Use the 'client' or 'oauth_user' token source instead !")
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 in ('oauth_user', 'oauth_app'):
6582
- print(f"* Spotify token cache file:\t{({'oauth_app': SP_APP_TOKENS_FILE, 'oauth_user': SP_USER_TOKENS_FILE}.get(TOKEN_SOURCE) or 'None (memory only)')}")
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")