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.
- {spotify_monitor-2.1.dist-info → spotify_monitor-2.1.1.dist-info}/METADATA +7 -3
- spotify_monitor-2.1.1.dist-info/RECORD +7 -0
- spotify_monitor.py +33 -22
- spotify_monitor-2.1.dist-info/RECORD +0 -7
- {spotify_monitor-2.1.dist-info → spotify_monitor-2.1.1.dist-info}/WHEEL +0 -0
- {spotify_monitor-2.1.dist-info → spotify_monitor-2.1.1.dist-info}/entry_points.txt +0 -0
- {spotify_monitor-2.1.dist-info → spotify_monitor-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {spotify_monitor-2.1.dist-info → spotify_monitor-2.1.1.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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 auto
|
|
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
|
|
985
|
+
if user_agent is not None:
|
|
986
986
|
headers.update({
|
|
987
|
-
"
|
|
988
|
-
|
|
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
|
|
1582
|
-
origin = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
|
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
|
|
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="Auto
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|