spotify-monitor 2.1__py3-none-any.whl → 2.1.1__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.1
3
+ Version: 2.1.1
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,6 +29,10 @@ 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
+
32
36
  <a id="features"></a>
33
37
  ## Features
34
38
 
@@ -200,9 +204,9 @@ To use credentials captured from the Spotify desktop client to obtain an access
200
204
 
201
205
  Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com)).
202
206
 
203
- Launch the Spotify desktop client and look for requests to `https://login{n}.spotify.com/v3/login`
207
+ Launch the Spotify desktop client and look for POST requests to `https://login{n}.spotify.com/v3/login`
204
208
 
205
- Note: The `login` part is suffixed with one or more digits (e.g. `login5.spotify.com`).
209
+ Note: The `login` part is suffixed with one or more digits (e.g. `login5`).
206
210
 
207
211
  If you don't see this request, log out from the client and log back in.
208
212
 
@@ -0,0 +1,7 @@
1
+ spotify_monitor.py,sha256=V0nSqE7yIdq2RIWhtAPsCect_1TB3eZB45mzYNgWIOg,145965
2
+ spotify_monitor-2.1.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
3
+ spotify_monitor-2.1.1.dist-info/METADATA,sha256=Iu59nenF5pT-jr4inNhdbbiuMiOLTp_72f3FW1NVIjk,21834
4
+ spotify_monitor-2.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
+ spotify_monitor-2.1.1.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
6
+ spotify_monitor-2.1.1.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
7
+ spotify_monitor-2.1.1.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.1
4
+ v2.1.1
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"
18
+ VERSION = "2.1.1"
19
19
 
20
20
  # ---------------------------
21
21
  # CONFIGURATION SECTION START
@@ -109,7 +109,7 @@ SPOTIFY_INACTIVITY_CHECK = 660 # 11 mins
109
109
  # Can also be set using the -m flag
110
110
  SPOTIFY_DISAPPEARED_CHECK_INTERVAL = 180 # 3 mins
111
111
 
112
- # Whether to autoplay each listened song in your Spotify client
112
+ # Whether to auto-play each listened song in your Spotify client
113
113
  # Can also be set using the -g flag
114
114
  TRACK_SONGS = False
115
115
 
@@ -202,7 +202,7 @@ SP_LOGFILE = "spotify_monitor"
202
202
  # Can also be disabled via the -d flag
203
203
  DISABLE_LOGGING = False
204
204
 
205
- # Width of horizontal line (─)
205
+ # Width of horizontal line
206
206
  HORIZONTAL_LINE = 113
207
207
 
208
208
  # Whether to clear the terminal screen after starting the tool
@@ -512,7 +512,7 @@ def signal_handler(sig, frame):
512
512
  # Checks internet connectivity
513
513
  def check_internet(url=CHECK_INTERNET_URL, timeout=CHECK_INTERNET_TIMEOUT, verify=VERIFY_SSL):
514
514
  try:
515
- _ = req.get(url, timeout=timeout, verify=verify)
515
+ _ = req.get(url, headers={'User-Agent': get_random_user_agent() if TOKEN_SOURCE == 'cookie' else get_random_spotify_user_agent()}, timeout=timeout, verify=verify)
516
516
  return True
517
517
  except req.RequestException as e:
518
518
  print(f"* No connectivity, please check your network:\n\n{e}")
@@ -982,10 +982,14 @@ def check_token_validity(access_token: str, client_id: Optional[str] = None, use
982
982
  url = "https://api.spotify.com/v1/me"
983
983
  headers = {"Authorization": f"Bearer {access_token}"}
984
984
 
985
- if TOKEN_SOURCE == "cookie" and client_id is not None and user_agent is not None:
985
+ if user_agent is not None:
986
986
  headers.update({
987
- "Client-Id": client_id,
988
- "User-Agent": user_agent,
987
+ "User-Agent": user_agent
988
+ })
989
+
990
+ if TOKEN_SOURCE == "cookie" and client_id is not None:
991
+ headers.update({
992
+ "Client-Id": client_id
989
993
  })
990
994
 
991
995
  if platform.system() != 'Windows':
@@ -1358,12 +1362,6 @@ def read_varint(data, index):
1358
1362
 
1359
1363
  # Parses Spotify Protobuf login response
1360
1364
  def parse_protobuf_message(data):
1361
- """
1362
- Recursively parses a Protobuf message, returns a dictionary mapping tags to values
1363
-
1364
- If a length-delimited field's first byte is a control character (i.e. < 0x20), we assume it is a nested message
1365
- and parse it recursively, otherwise we decode it as UTF-8
1366
- """
1367
1365
  index = 0
1368
1366
  result = {}
1369
1367
  while index < len(data):
@@ -1401,7 +1399,6 @@ def parse_protobuf_message(data):
1401
1399
  # (device_id, system_id, user_uri_id, refresh_token)
1402
1400
  def parse_request_body_file(file_path):
1403
1401
  """
1404
- Expected structure:
1405
1402
  {
1406
1403
  1: {
1407
1404
  1: "device_id",
@@ -1566,7 +1563,7 @@ def build_clienttoken_request_protobuf(app_version, device_id, system_id, cpu_ar
1566
1563
  def spotify_get_access_token_from_client(device_id, system_id, user_uri_id, refresh_token, client_token):
1567
1564
  global SP_CACHED_ACCESS_TOKEN, SP_CACHED_REFRESH_TOKEN, SP_ACCESS_TOKEN_EXPIRES_AT
1568
1565
 
1569
- if SP_CACHED_ACCESS_TOKEN and time.time() < SP_ACCESS_TOKEN_EXPIRES_AT and check_token_validity(SP_CACHED_ACCESS_TOKEN):
1566
+ if SP_CACHED_ACCESS_TOKEN and time.time() < SP_ACCESS_TOKEN_EXPIRES_AT and check_token_validity(SP_CACHED_ACCESS_TOKEN, user_agent=USER_AGENT):
1570
1567
  return SP_CACHED_ACCESS_TOKEN
1571
1568
 
1572
1569
  if not client_token:
@@ -1578,8 +1575,8 @@ def spotify_get_access_token_from_client(device_id, system_id, user_uri_id, refr
1578
1575
  protobuf_body = build_spotify_auth_protobuf(device_id, system_id, user_uri_id, refresh_token)
1579
1576
 
1580
1577
  parsed_url = urlparse(LOGIN_URL)
1581
- host = parsed_url.netloc # e.g., "login5.spotify.com"
1582
- origin = f"{parsed_url.scheme}://{parsed_url.netloc}" # e.g., "https://login5.spotify.com"
1578
+ host = parsed_url.netloc
1579
+ origin = f"{parsed_url.scheme}://{parsed_url.netloc}"
1583
1580
 
1584
1581
  headers = {
1585
1582
  "Host": host,
@@ -1685,7 +1682,7 @@ def spotify_get_client_token(app_version, device_id, system_id, **device_overrid
1685
1682
  parsed = parse_protobuf_message(response.content)
1686
1683
  inner = parsed.get(2, {})
1687
1684
  client_token = deep_flatten(inner.get(1)) if inner.get(1) else None
1688
- ttl = int(inner.get(3, 0)) or 1209600 # ≈ 2 weeks fallback
1685
+ ttl = int(inner.get(3, 0)) or 1209600
1689
1686
 
1690
1687
  if not client_token:
1691
1688
  raise Exception("clienttoken response did not contain a token")
@@ -1748,6 +1745,10 @@ def spotify_get_friends_json(access_token):
1748
1745
  "Client-Id": SP_CACHED_CLIENT_ID,
1749
1746
  "User-Agent": SP_CACHED_USER_AGENT,
1750
1747
  })
1748
+ elif TOKEN_SOURCE == "client":
1749
+ headers.update({
1750
+ "User-Agent": USER_AGENT
1751
+ })
1751
1752
 
1752
1753
  response = SESSION.get(url, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
1753
1754
  if response.status_code == 401:
@@ -1807,8 +1808,6 @@ def spotify_list_friends(friend_activity):
1807
1808
  sp_playlist_uri = friend["track"]["context"].get("uri")
1808
1809
  sp_track_uri = friend["track"].get("uri")
1809
1810
 
1810
- # if index > 0:
1811
- # print("─" * HORIZONTAL_LINE)
1812
1811
  print("─" * HORIZONTAL_LINE)
1813
1812
  print(f"Username:\t\t\t{sp_username}")
1814
1813
  print(f"User URI ID:\t\t\t{sp_uri}")
@@ -1877,6 +1876,10 @@ def spotify_get_track_info(access_token, track_uri):
1877
1876
  "Client-Id": SP_CACHED_CLIENT_ID,
1878
1877
  "User-Agent": SP_CACHED_USER_AGENT,
1879
1878
  })
1879
+ elif TOKEN_SOURCE == "client":
1880
+ headers.update({
1881
+ "User-Agent": USER_AGENT
1882
+ })
1880
1883
  # add si parameter so link opens in native Spotify app after clicking
1881
1884
  si = "?si=1"
1882
1885
 
@@ -1907,6 +1910,10 @@ def spotify_get_playlist_info(access_token, playlist_uri):
1907
1910
  "Client-Id": SP_CACHED_CLIENT_ID,
1908
1911
  "User-Agent": SP_CACHED_USER_AGENT,
1909
1912
  })
1913
+ elif TOKEN_SOURCE == "client":
1914
+ headers.update({
1915
+ "User-Agent": USER_AGENT
1916
+ })
1910
1917
  # add si parameter so link opens in native Spotify app after clicking
1911
1918
  si = "?si=1"
1912
1919
 
@@ -1934,6 +1941,10 @@ def spotify_get_current_user(access_token) -> dict | None:
1934
1941
  "Client-Id": SP_CACHED_CLIENT_ID,
1935
1942
  "User-Agent": SP_CACHED_USER_AGENT,
1936
1943
  })
1944
+ elif TOKEN_SOURCE == "client":
1945
+ headers.update({
1946
+ "User-Agent": USER_AGENT
1947
+ })
1937
1948
 
1938
1949
  if platform.system() != 'Windows':
1939
1950
  signal.signal(signal.SIGALRM, timeout_handler)
@@ -2916,7 +2927,7 @@ def main():
2916
2927
  dest="track_in_spotify",
2917
2928
  action="store_true",
2918
2929
  default=None,
2919
- help="Autoplay each listened song in your Spotify client"
2930
+ help="Auto-play each listened song in your Spotify client"
2920
2931
  )
2921
2932
  opts.add_argument(
2922
2933
  "-b", "--csv-file",
@@ -1,7 +0,0 @@
1
- spotify_monitor.py,sha256=g_kitqHOJr-708PeGjrosvMG3Om6AzH6d0xE8VdPbDc,145830
2
- spotify_monitor-2.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
3
- spotify_monitor-2.1.dist-info/METADATA,sha256=4OdX74oC29gletoUAq0amLvMkKcq-GqOxXVt1E_ATTQ,21529
4
- spotify_monitor-2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
- spotify_monitor-2.1.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
6
- spotify_monitor-2.1.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
7
- spotify_monitor-2.1.dist-info/RECORD,,