spotify-monitor 2.1.1__py3-none-any.whl → 2.1.2__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.
- {spotify_monitor-2.1.1.dist-info → spotify_monitor-2.1.2.dist-info}/METADATA +1 -5
- spotify_monitor-2.1.2.dist-info/RECORD +7 -0
- spotify_monitor.py +38 -41
- spotify_monitor-2.1.1.dist-info/RECORD +0 -7
- {spotify_monitor-2.1.1.dist-info → spotify_monitor-2.1.2.dist-info}/WHEEL +0 -0
- {spotify_monitor-2.1.1.dist-info → spotify_monitor-2.1.2.dist-info}/entry_points.txt +0 -0
- {spotify_monitor-2.1.1.dist-info → spotify_monitor-2.1.2.dist-info}/licenses/LICENSE +0 -0
- {spotify_monitor-2.1.1.dist-info → spotify_monitor-2.1.2.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.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
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
spotify_monitor.py,sha256=jCI1-Bi4xYlxeGLWISAHuQq0FPn4f8dEkVDrh2Pk2FE,146164
|
|
2
|
+
spotify_monitor-2.1.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
+
spotify_monitor-2.1.2.dist-info/METADATA,sha256=DVyBABHOKULe8lZ5lHPT62ak3n1jMzObpUns_Y-nW0o,21524
|
|
4
|
+
spotify_monitor-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
+
spotify_monitor-2.1.2.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
|
|
6
|
+
spotify_monitor-2.1.2.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
|
|
7
|
+
spotify_monitor-2.1.2.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.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.
|
|
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/
|
|
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
|
-
#
|
|
1095
|
-
def
|
|
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
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1139
|
-
|
|
1117
|
+
# Creates a TOTP object using a secret derived from transformed cipher bytes
|
|
1118
|
+
def generate_totp():
|
|
1119
|
+
import pyotp
|
|
1140
1120
|
|
|
1141
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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)
|
|
@@ -1,7 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|