spotify-monitor 2.1.1__tar.gz → 2.1.2__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.

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.1.1
3
+ Version: 2.1.2
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
@@ -29,10 +29,6 @@ spotify_monitor is a tool for real-time monitoring of Spotify friends' music act
29
29
 
30
30
  NOTE: 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).
31
31
 
32
- > Spotify made multiple changes to the web endpoint on June 10th 2025, which broke the **[sp_dc cookie](#spotify-sp_dc-cookie)** method.
33
- > 📦 The issue is under investigation to determine if it can be restored.
34
- > 👉 In the meantime, use **[desktop client](#spotify-desktop-client)** method instead.
35
-
36
32
  <a id="features"></a>
37
33
  ## Features
38
34
 
@@ -4,10 +4,6 @@ spotify_monitor is a tool for real-time monitoring of Spotify friends' music act
4
4
 
5
5
  NOTE: 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).
6
6
 
7
- > Spotify made multiple changes to the web endpoint on June 10th 2025, which broke the **[sp_dc cookie](#spotify-sp_dc-cookie)** method.
8
- > 📦 The issue is under investigation to determine if it can be restored.
9
- > 👉 In the meantime, use **[desktop client](#spotify-desktop-client)** method instead.
10
-
11
7
  <a id="features"></a>
12
8
  ## Features
13
9
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spotify_monitor"
7
- version = "2.1.1"
7
+ version = "2.1.2"
8
8
  description = "Tool implementing real-time tracking of Spotify friends music activity"
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_monitor
3
- Version: 2.1.1
3
+ Version: 2.1.2
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
@@ -29,10 +29,6 @@ spotify_monitor is a tool for real-time monitoring of Spotify friends' music act
29
29
 
30
30
  NOTE: 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).
31
31
 
32
- > Spotify made multiple changes to the web endpoint on June 10th 2025, which broke the **[sp_dc cookie](#spotify-sp_dc-cookie)** method.
33
- > 📦 The issue is under investigation to determine if it can be restored.
34
- > 👉 In the meantime, use **[desktop client](#spotify-desktop-client)** method instead.
35
-
36
32
  <a id="features"></a>
37
33
  ## Features
38
34
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Author: Michal Szymanski <misiektoja-github@rm-rf.ninja>
4
- v2.1.1
4
+ v2.1.2
5
5
 
6
6
  Tool implementing real-time tracking of Spotify friends music activity:
7
7
  https://github.com/misiektoja/spotify_monitor/
@@ -15,7 +15,7 @@ pyotp (needed when the token source is set to cookie)
15
15
  python-dotenv (optional)
16
16
  """
17
17
 
18
- VERSION = "2.1.1"
18
+ VERSION = "2.1.2"
19
19
 
20
20
  # ---------------------------
21
21
  # CONFIGURATION SECTION START
@@ -396,10 +396,7 @@ SP_CACHED_CLIENT_ID = ""
396
396
  SP_CACHED_USER_AGENT = ""
397
397
 
398
398
  # URL of the Spotify Web Player endpoint to get access token
399
- TOKEN_URL = "https://open.spotify.com/get_access_token"
400
-
401
- # URL of the endpoint to get server time needed to create TOTP object
402
- SERVER_TIME_URL = "https://open.spotify.com/server-time"
399
+ TOKEN_URL = "https://open.spotify.com/api/token"
403
400
 
404
401
  # Variables for caching functionality of the Spotify client token to avoid unnecessary refreshing
405
402
  SP_CACHED_CLIENT_TOKEN = None
@@ -451,6 +448,7 @@ import shutil
451
448
  from pathlib import Path
452
449
  import secrets
453
450
  from typing import Optional
451
+ from email.utils import parsedate_to_datetime
454
452
 
455
453
  import urllib3
456
454
  if not VERIFY_SSL:
@@ -1091,30 +1089,10 @@ def get_random_user_agent() -> str:
1091
1089
  return ""
1092
1090
 
1093
1091
 
1094
- # Removes spaces from a hex string and converts it into a corresponding bytes object
1095
- def hex_to_bytes(data: str) -> bytes:
1096
- data = data.replace(" ", "")
1097
- return bytes.fromhex(data)
1098
-
1099
-
1100
- # Creates a TOTP object using a secret derived from transformed cipher bytes
1101
- def generate_totp(ua: str):
1102
- import pyotp
1103
-
1104
- secret_cipher_bytes = [
1105
- 12, 56, 76, 33, 88, 44, 88, 33,
1106
- 78, 78, 11, 66, 22, 22, 55, 69, 54,
1107
- ]
1108
-
1109
- transformed = [e ^ ((t % 33) + 9) for t, e in enumerate(secret_cipher_bytes)]
1110
- joined = "".join(str(num) for num in transformed)
1111
- utf8_bytes = joined.encode("utf-8")
1112
- hex_str = "".join(format(b, 'x') for b in utf8_bytes)
1113
- secret_bytes = hex_to_bytes(hex_str)
1114
- secret = base64.b32encode(secret_bytes).decode().rstrip('=')
1092
+ # Returns Spotify edge-server Unix time
1093
+ def fetch_server_time(session: req.Session, ua: str) -> int:
1115
1094
 
1116
1095
  headers = {
1117
- "Host": "open.spotify.com",
1118
1096
  "User-Agent": ua,
1119
1097
  "Accept": "*/*",
1120
1098
  }
@@ -1123,24 +1101,34 @@ def generate_totp(ua: str):
1123
1101
  if platform.system() != 'Windows':
1124
1102
  signal.signal(signal.SIGALRM, timeout_handler)
1125
1103
  signal.alarm(FUNCTION_TIMEOUT + 2)
1126
- resp = req.get(SERVER_TIME_URL, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
1127
- except (req.RequestException, TimeoutException) as e:
1128
- raise Exception(f"generate_totp() network request timeout after {display_time(FUNCTION_TIMEOUT + 2)}: {e}")
1104
+ response = session.head("https://open.spotify.com/", headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
1105
+ response.raise_for_status()
1106
+ except TimeoutException as e:
1107
+ raise Exception(f"fetch_server_time() head network request timeout after {display_time(FUNCTION_TIMEOUT + 2)}: {e}")
1108
+ except Exception as e:
1109
+ raise Exception(f"fetch_server_time() head network request error: {e}")
1129
1110
  finally:
1130
1111
  if platform.system() != 'Windows':
1131
1112
  signal.alarm(0)
1132
1113
 
1133
- resp.raise_for_status()
1114
+ return int(parsedate_to_datetime(response.headers["Date"]).timestamp())
1134
1115
 
1135
- json_data = resp.json()
1136
- server_time = json_data.get("serverTime")
1137
1116
 
1138
- if server_time is None:
1139
- raise Exception("Failed to get server time")
1117
+ # Creates a TOTP object using a secret derived from transformed cipher bytes
1118
+ def generate_totp():
1119
+ import pyotp
1140
1120
 
1141
- totp_obj = pyotp.TOTP(secret, digits=6, interval=30)
1121
+ secret_cipher_bytes = [
1122
+ 12, 56, 76, 33, 88, 44, 88, 33,
1123
+ 78, 78, 11, 66, 22, 22, 55, 69, 54,
1124
+ ]
1125
+
1126
+ transformed = [e ^ ((t % 33) + 9) for t, e in enumerate(secret_cipher_bytes)]
1127
+ joined = "".join(str(num) for num in transformed)
1128
+ hex_str = joined.encode().hex()
1129
+ secret = base64.b32encode(bytes.fromhex(hex_str)).decode().rstrip("=")
1142
1130
 
1143
- return totp_obj, server_time
1131
+ return pyotp.TOTP(secret, digits=6, interval=30)
1144
1132
 
1145
1133
 
1146
1134
  # Retrieves a new Spotify access token using the sp_dc cookie, tries first with mode "transport" and if needed with "init"
@@ -1153,7 +1141,8 @@ def refresh_token(sp_dc: str) -> dict:
1153
1141
  token = ""
1154
1142
 
1155
1143
  ua = get_random_user_agent()
1156
- totp_obj, server_time = generate_totp(ua)
1144
+ server_time = fetch_server_time(session, ua)
1145
+ totp_obj = generate_totp()
1157
1146
  client_time = int(time_ns() / 1000 / 1000)
1158
1147
  otp_value = totp_obj.at(server_time)
1159
1148
 
@@ -1165,11 +1154,15 @@ def refresh_token(sp_dc: str) -> dict:
1165
1154
  "totpVer": 5,
1166
1155
  "sTime": server_time,
1167
1156
  "cTime": client_time,
1157
+ "buildDate": time.strftime("%Y-%m-%d", time.gmtime(server_time)),
1158
+ "buildVer": f"web-player_{time.strftime('%Y-%m-%d', time.gmtime(server_time))}_{server_time * 1000}_{secrets.token_hex(4)}",
1168
1159
  }
1169
1160
 
1170
1161
  headers = {
1171
1162
  "User-Agent": ua,
1172
1163
  "Accept": "application/json",
1164
+ "Referer": "https://open.spotify.com/",
1165
+ "App-Platform": "WebPlayer",
1173
1166
  "Cookie": f"sp_dc={sp_dc}",
1174
1167
  }
1175
1168
 
@@ -1598,8 +1591,10 @@ def spotify_get_access_token_from_client(device_id, system_id, user_uri_id, refr
1598
1591
  signal.signal(signal.SIGALRM, timeout_handler)
1599
1592
  signal.alarm(FUNCTION_TIMEOUT + 2)
1600
1593
  response = req.post(LOGIN_URL, headers=headers, data=protobuf_body, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
1601
- except (req.RequestException, TimeoutException) as e:
1594
+ except TimeoutException as e:
1602
1595
  raise Exception(f"spotify_get_access_token_from_client() network request timeout after {display_time(FUNCTION_TIMEOUT + 2)}: {e}")
1596
+ except Exception as e:
1597
+ raise Exception(f"spotify_get_access_token_from_client() network request error: {e}")
1603
1598
  finally:
1604
1599
  if platform.system() != 'Windows':
1605
1600
  signal.alarm(0)
@@ -1670,8 +1665,10 @@ def spotify_get_client_token(app_version, device_id, system_id, **device_overrid
1670
1665
  signal.signal(signal.SIGALRM, timeout_handler)
1671
1666
  signal.alarm(FUNCTION_TIMEOUT + 2)
1672
1667
  response = req.post(CLIENTTOKEN_URL, headers=headers, data=body, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
1673
- except (req.RequestException, TimeoutException) as e:
1668
+ except TimeoutException as e:
1674
1669
  raise Exception(f"spotify_get_client_token() network request timeout after {display_time(FUNCTION_TIMEOUT + 2)}: {e}")
1670
+ except Exception as e:
1671
+ raise Exception(f"spotify_get_client_token() network request error: {e}")
1675
1672
  finally:
1676
1673
  if platform.system() != 'Windows':
1677
1674
  signal.alarm(0)
File without changes