spotify-monitor 2.3.1__py3-none-any.whl → 2.4__py3-none-any.whl

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.

Potentially problematic release.


This version of spotify-monitor might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spotify_monitor
3
- Version: 2.3.1
3
+ Version: 2.4
4
4
  Summary: Tool implementing real-time tracking of Spotify friends music activity
5
5
  Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
6
6
  License-Expression: GPL-3.0-or-later
@@ -26,7 +26,7 @@ Dynamic: license-file
26
26
 
27
27
  # spotify_monitor
28
28
 
29
- Tool for real-time monitoring of Spotify friends' music activity feed.
29
+ Tool for real-time monitoring of **Spotify friends' music activity feed**.
30
30
 
31
31
  ✨ If you're interested in tracking changes to Spotify users' profiles including their playlists, take a look at another tool I've developed: [spotify_profile_monitor](https://github.com/misiektoja/spotify_profile_monitor).
32
32
 
@@ -77,6 +77,8 @@ Tool for real-time monitoring of Spotify friends' music activity feed.
77
77
  * [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix)
78
78
  * [Coloring Log Output with GRC](#coloring-log-output-with-grc)
79
79
  6. [Debugging Tools](#debugging-tools)
80
+ * [Access Token Retrieval via sp_dc Cookie and TOTP](#access-token-retrieval-via-sp_dc-cookie-and-totp)
81
+ * [Secret Key Extraction from Spotify Web Player Bundles](#secret-key-extraction-from-spotify-web-player-bundles)
80
82
  7. [Change Log](#change-log)
81
83
  8. [License](#license)
82
84
 
@@ -112,7 +114,7 @@ Download the *[spotify_monitor.py](https://raw.githubusercontent.com/misiektoja/
112
114
  Install dependencies via pip:
113
115
 
114
116
  ```sh
115
- pip install requests python-dateutil urllib3 pyotp python-dotenv
117
+ pip install requests python-dateutil urllib3 pyotp python-dotenv wcwidth
116
118
  ```
117
119
 
118
120
  Alternatively, from the downloaded *[requirements.txt](https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/requirements.txt)*:
@@ -199,7 +201,7 @@ If your `sp_dc` cookie expires, the tool will notify you via the console and ema
199
201
 
200
202
  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).
201
203
 
202
- `Note`: encrypted byte sequences used for TOTP secret generation tend to expire every now and then; you can either check the [issues](https://github.com/misiektoja/spotify_monitor/issues) section of the project to see if there are any new secrets published or you can run the [spotify_monitor_secret_grabber.py](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [Debugging Tools](https://github.com/misiektoja/spotify_monitor#debugging-tools) for more info).
204
+ > **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every two days, that's why since v2.4 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).
203
205
 
204
206
  <a id="spotify-desktop-client"></a>
205
207
  #### Spotify Desktop Client
@@ -572,27 +574,73 @@ grc tail -F -n 100 spotify_monitor_<user_uri_id/file_suffix>.log
572
574
  <a id="debugging-tools"></a>
573
575
  ## Debugging Tools
574
576
 
575
- To help with troubleshooting and development, two debug utilities are available in the `debug` directory:
577
+ To help with troubleshooting and development, two debug utilities are available in the [debug](https://github.com/misiektoja/spotify_monitor/tree/dev/debug) directory.
576
578
 
577
- - [spotify_monitor_totp_test.py](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_totp_test.py): fetching of Spotify access token based on a Spotify Web Player `sp_dc` cookie value:
579
+ <a id="access-token-retrieval-via-sp_dc-cookie-and-totp"></a>
580
+ ### Access Token Retrieval via sp_dc Cookie and TOTP
581
+
582
+ The [spotify_monitor_totp_test](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_totp_test.py) tool retrieves a Spotify access token using a Web Player `sp_dc` cookie and TOTP parameters.
583
+
584
+ Download from [here](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_totp_test.py) or:
585
+
586
+ ```sh
587
+ wget https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/dev/debug/spotify_monitor_totp_test.py
588
+ ```
589
+
590
+ Install requirements:
578
591
 
579
592
  ```sh
580
593
  pip install requests python-dateutil pyotp
594
+ ```
595
+
596
+ Run:
597
+
598
+ ```sh
581
599
  python3 spotify_monitor_totp_test.py --sp-dc "your_sp_dc_cookie_value"
582
600
  ```
583
601
 
584
- - [spotify_monitor_secret_grabber.py](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py): automatic extractor for secret keys used for TOTP generation in Spotify Web Player JavaScript bundles:
602
+ You should get a valid Spotify access token, example output:
603
+
604
+ <p align="center">
605
+ <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%"/>
606
+ </p>
607
+
608
+ > **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](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_totp_test.py) (available since v1.6). There is also a [Thereallo1026/spotify-secrets](https://github.com/Thereallo1026/spotify-secrets) repo which offers JSON files that are automatically updated with current secrets.
609
+
610
+ <a id="secret-key-extraction-from-spotify-web-player-bundles"></a>
611
+ ### Secret Key Extraction from Spotify Web Player Bundles
612
+
613
+ The [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) tool automatically extracts secret keys used for TOTP generation in Spotify Web Player JavaScript bundles.
614
+
615
+ Download from [here](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) or:
616
+
617
+ ```sh
618
+ wget https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/dev/debug/spotify_monitor_secret_grabber.py
619
+ ```
620
+
621
+ Install requirements:
585
622
 
586
623
  ```sh
587
624
  pip install playwright
588
625
  playwright install
626
+ ```
627
+
628
+ Run:
629
+
630
+ ```sh
589
631
  python3 spotify_monitor_secret_grabber.py
590
632
  ```
591
633
 
634
+ You should get output similar to below:
635
+
592
636
  <p align="center">
593
637
  <img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_secret_grabber.png" alt="spotify_monitor_secret_grabber" width="100%"/>
594
638
  </p>
595
639
 
640
+ You can now update the secrets used for TOTP generation (for example `SECRET_CIPHER_DICT` in `spotify_monitor_totp_test`, `spotify_monitor` and `spotify_profile_monitor`).
641
+
642
+ > **NOTE:** you can also use [Thereallo1026/spotify-secrets](https://github.com/Thereallo1026/spotify-secrets) repo which offers JSON files that are automatically updated with current secrets (its secret extraction code is based on `spotify_monitor_secret_grabber`).
643
+
596
644
  <a id="change-log"></a>
597
645
  ## Change Log
598
646
 
@@ -0,0 +1,7 @@
1
+ spotify_monitor.py,sha256=TtB0dWvl5Yn1ti4y8VFk61V8rqXEZfnLY2PJZ26hVsw,161638
2
+ spotify_monitor-2.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
3
+ spotify_monitor-2.4.dist-info/METADATA,sha256=rcnYf1XgMu4k55OO0m1yfP61TXRA_CkZLLDXUzDLHLM,27747
4
+ spotify_monitor-2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
+ spotify_monitor-2.4.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
6
+ spotify_monitor-2.4.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
7
+ spotify_monitor-2.4.dist-info/RECORD,,
spotify_monitor.py CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Author: Michal Szymanski <misiektoja-github@rm-rf.ninja>
4
- v2.3.1
4
+ v2.4
5
5
 
6
6
  Tool implementing real-time tracking of Spotify friends music activity:
7
7
  https://github.com/misiektoja/spotify_monitor/
@@ -16,7 +16,7 @@ python-dotenv (optional)
16
16
  wcwidth (optional, needed by TRUNCATE_CHARS feature)
17
17
  """
18
18
 
19
- VERSION = "2.3.1"
19
+ VERSION = "2.4"
20
20
 
21
21
  # ---------------------------
22
22
  # CONFIGURATION SECTION START
@@ -244,13 +244,14 @@ SPOTIFY_INACTIVITY_CHECK_SIGNAL_VALUE = 30 # 30 seconds
244
244
  # The section below is used when the token source is set to 'cookie'
245
245
 
246
246
  # Maximum number of attempts to get a valid access token in a single run of the spotify_get_access_token_from_sp_dc() function
247
- TOKEN_MAX_RETRIES = 10
247
+ TOKEN_MAX_RETRIES = 3
248
248
 
249
249
  # Interval between access token retry attempts; in seconds
250
250
  TOKEN_RETRY_TIMEOUT = 0.5 # 0.5 second
251
251
 
252
- # Mapping of TOTP version identifiers to the encrypted byte sequence for TOTP secret generation
253
- # Newest TOTP secrets can be fetched via spotify_monitor_secret_grabber.py (see debug dir)
252
+ # Mapping of TOTP version identifiers to the secrets needed for TOTP generation
253
+ # Newest secrets are downloaded automatically from SECRET_CIPHER_DICT_URL (see below)
254
+ # Can also be fetched via spotify_monitor_secret_grabber.py utility - see debug dir
254
255
  SECRET_CIPHER_DICT = {
255
256
  "12": [107, 81, 49, 57, 67, 93, 87, 81, 69, 67, 40, 93, 48, 50, 46, 91, 94, 113, 41, 108, 77, 107, 34],
256
257
  "11": [111, 45, 40, 73, 95, 74, 35, 85, 105, 107, 60, 110, 55, 72, 69, 70, 114, 83, 63, 88, 91],
@@ -262,7 +263,11 @@ SECRET_CIPHER_DICT = {
262
263
  "5": [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54],
263
264
  }
264
265
 
265
- # Identifier used to select the appropriate encrypted secret from SECRET_CIPHER_DICT when generating a TOTP token
266
+ # Remote URL used to fetch updated secrets needed for TOTP generation
267
+ # Set to empty string to disable
268
+ SECRET_CIPHER_DICT_URL = "https://github.com/Thereallo1026/spotify-secrets/blob/main/secrets/secretDict.json?raw=true"
269
+
270
+ # Identifier used to select the appropriate secret from SECRET_CIPHER_DICT when generating a TOTP token
266
271
  # Set to 0 to auto-select the highest available version
267
272
  TOTP_VER = 0
268
273
 
@@ -467,6 +472,7 @@ SPOTIFY_INACTIVITY_CHECK_SIGNAL_VALUE = 0
467
472
  TOKEN_MAX_RETRIES = 0
468
473
  TOKEN_RETRY_TIMEOUT = 0.0
469
474
  SECRET_CIPHER_DICT = {}
475
+ SECRET_CIPHER_DICT_URL = ""
470
476
  TOTP_VER = 0
471
477
  FLAG_FILE = ""
472
478
  TRUNCATE_CHARS = 0
@@ -1276,7 +1282,10 @@ def fetch_server_time(session: req.Session, ua: str) -> int:
1276
1282
  def generate_totp():
1277
1283
  import pyotp
1278
1284
 
1279
- secret_cipher_bytes = SECRET_CIPHER_DICT[str((ver := TOTP_VER or max(map(int, SECRET_CIPHER_DICT))))]
1285
+ if str((ver := TOTP_VER or max(map(int, SECRET_CIPHER_DICT)))) not in SECRET_CIPHER_DICT:
1286
+ raise Exception(f"generate_totp(): Defined TOTP_VER ({ver}) is missing in SECRET_CIPHER_DICT")
1287
+
1288
+ secret_cipher_bytes = SECRET_CIPHER_DICT[str(ver)]
1280
1289
 
1281
1290
  transformed = [e ^ ((t % 33) + 9) for t, e in enumerate(secret_cipher_bytes)]
1282
1291
  joined = "".join(str(num) for num in transformed)
@@ -1286,6 +1295,34 @@ def generate_totp():
1286
1295
  return pyotp.TOTP(secret, digits=6, interval=30)
1287
1296
 
1288
1297
 
1298
+ def fetch_and_update_secrets():
1299
+ global SECRET_CIPHER_DICT
1300
+
1301
+ if not SECRET_CIPHER_DICT_URL:
1302
+ return False
1303
+
1304
+ try:
1305
+ response = req.get(SECRET_CIPHER_DICT_URL, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
1306
+ response.raise_for_status()
1307
+ secrets = response.json()
1308
+
1309
+ if not isinstance(secrets, dict) or not secrets:
1310
+ raise ValueError("fetch_and_update_secrets(): Fetched payload not a non‑empty dict")
1311
+
1312
+ for key, value in secrets.items():
1313
+ if not isinstance(key, str) or not key.isdigit():
1314
+ raise ValueError(f"fetch_and_update_secrets(): Invalid key format: {key}")
1315
+ if not isinstance(value, list) or not all(isinstance(x, int) for x in value):
1316
+ raise ValueError(f"fetch_and_update_secrets(): Invalid value format for key {key}")
1317
+
1318
+ SECRET_CIPHER_DICT = secrets
1319
+ return True
1320
+
1321
+ except Exception as e:
1322
+ print(f"fetch_and_update_secrets(): Failed to get new secrets: {e}")
1323
+ return False
1324
+
1325
+
1289
1326
  # Refreshes the Spotify access token using the sp_dc cookie, tries first with mode "transport" and if needed with "init"
1290
1327
  def refresh_access_token_from_sp_dc(sp_dc: str) -> dict:
1291
1328
  transport = True
@@ -1385,29 +1422,52 @@ def spotify_get_access_token_from_sp_dc(sp_dc: str):
1385
1422
  max_retries = TOKEN_MAX_RETRIES
1386
1423
  retry = 0
1387
1424
 
1388
- while retry < max_retries:
1389
- token_data = refresh_access_token_from_sp_dc(sp_dc)
1390
- token = token_data["access_token"]
1391
- client_id = token_data.get("client_id", "")
1392
- length = token_data["length"]
1425
+ last_error = ""
1393
1426
 
1394
- SP_CACHED_ACCESS_TOKEN = token
1395
- SP_ACCESS_TOKEN_EXPIRES_AT = token_data["expires_at"]
1396
- SP_CACHED_CLIENT_ID = client_id
1397
-
1398
- if SP_CACHED_ACCESS_TOKEN is None or not check_token_validity(SP_CACHED_ACCESS_TOKEN, SP_CACHED_CLIENT_ID, USER_AGENT):
1427
+ while retry < max_retries:
1428
+ try:
1429
+ token_data = refresh_access_token_from_sp_dc(sp_dc)
1430
+ token = token_data["access_token"]
1431
+ client_id = token_data.get("client_id", "")
1432
+ length = token_data["length"]
1433
+
1434
+ SP_CACHED_ACCESS_TOKEN = token
1435
+ SP_ACCESS_TOKEN_EXPIRES_AT = token_data["expires_at"]
1436
+ SP_CACHED_CLIENT_ID = client_id
1437
+
1438
+ if SP_CACHED_ACCESS_TOKEN is None or not check_token_validity(SP_CACHED_ACCESS_TOKEN, SP_CACHED_CLIENT_ID, USER_AGENT):
1439
+ retry += 1
1440
+ time.sleep(TOKEN_RETRY_TIMEOUT)
1441
+ else:
1442
+ break
1443
+ except Exception as e:
1444
+ last_error = str(e)
1399
1445
  retry += 1
1400
- time.sleep(TOKEN_RETRY_TIMEOUT)
1401
- else:
1402
- break
1446
+ if retry < max_retries:
1447
+ time.sleep(TOKEN_RETRY_TIMEOUT)
1403
1448
 
1404
1449
  if retry == max_retries:
1405
- if SP_CACHED_ACCESS_TOKEN is not None:
1406
- print(f"* Token appears to be still invalid after {max_retries} attempts, returning token anyway")
1407
- print_cur_ts("Timestamp:\t\t\t")
1408
- return SP_CACHED_ACCESS_TOKEN
1409
- else:
1410
- raise RuntimeError(f"Failed to obtain a valid Spotify access token after {max_retries} attempts")
1450
+
1451
+ if fetch_and_update_secrets():
1452
+ try:
1453
+ token_data = refresh_access_token_from_sp_dc(sp_dc)
1454
+ token = token_data["access_token"]
1455
+ client_id = token_data.get("client_id", "")
1456
+ length = token_data["length"]
1457
+
1458
+ SP_CACHED_ACCESS_TOKEN = token
1459
+ SP_ACCESS_TOKEN_EXPIRES_AT = token_data["expires_at"]
1460
+ SP_CACHED_CLIENT_ID = client_id
1461
+
1462
+ if SP_CACHED_ACCESS_TOKEN and check_token_validity(SP_CACHED_ACCESS_TOKEN, SP_CACHED_CLIENT_ID, USER_AGENT):
1463
+ return SP_CACHED_ACCESS_TOKEN
1464
+ except Exception as e:
1465
+ last_error = str(e)
1466
+
1467
+ error_msg = f"Failed to obtain a valid Spotify access token after {max_retries} attempts"
1468
+ if last_error:
1469
+ error_msg += f": {last_error}"
1470
+ raise RuntimeError(error_msg)
1411
1471
 
1412
1472
  return SP_CACHED_ACCESS_TOKEN
1413
1473
 
@@ -1,7 +0,0 @@
1
- spotify_monitor.py,sha256=msF-xUpmB_dch3C1dtA31mvU0OgtRUgUeOtXh20xDLw,159386
2
- spotify_monitor-2.3.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
3
- spotify_monitor-2.3.1.dist-info/METADATA,sha256=z37aFQ2NUa5jYzU5pF6ru5UXmu65Ctee8J_EDvjG1iI,25204
4
- spotify_monitor-2.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
- spotify_monitor-2.3.1.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
6
- spotify_monitor-2.3.1.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
7
- spotify_monitor-2.3.1.dist-info/RECORD,,