spotify-profile-monitor 2.5.2__tar.gz → 2.6__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.
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/PKG-INFO +30 -6
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/README.md +29 -5
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/pyproject.toml +1 -1
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/spotify_profile_monitor.egg-info/PKG-INFO +30 -6
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/spotify_profile_monitor.py +106 -24
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/LICENSE +0 -0
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/setup.cfg +0 -0
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/spotify_profile_monitor.egg-info/SOURCES.txt +0 -0
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/spotify_profile_monitor.egg-info/dependency_links.txt +0 -0
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/spotify_profile_monitor.egg-info/entry_points.txt +0 -0
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/spotify_profile_monitor.egg-info/requires.txt +0 -0
- {spotify_profile_monitor-2.5.2 → spotify_profile_monitor-2.6}/spotify_profile_monitor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spotify_profile_monitor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6
|
|
4
4
|
Summary: Tool implementing real-time tracking of Spotify users activities and profile changes including playlists
|
|
5
5
|
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -28,7 +28,7 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# spotify_profile_monitor
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
OSINT tool for real-time monitoring of Spotify users' activities and profile changes including playlists.
|
|
32
32
|
|
|
33
33
|
NOTE: If you want to track Spotify friends' music activity, check out another tool I developed: [spotify_monitor](https://github.com/misiektoja/spotify_monitor).
|
|
34
34
|
|
|
@@ -246,12 +246,23 @@ If you store the `SP_DC_COOKIE` in a dotenv file you can update its value and se
|
|
|
246
246
|
|
|
247
247
|
This is the alternative method used to obtain a Spotify access token which simulates a login from the real Spotify desktop app using credentials intercepted from a real session.
|
|
248
248
|
|
|
249
|
-
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com))
|
|
249
|
+
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com) - the trial version is sufficient)
|
|
250
|
+
|
|
251
|
+
- Enable SSL traffic decryption for `spotify.com` domain
|
|
252
|
+
- in Proxyman: click **Tools → SSL Proxying List → + button → Add Domain → paste `*.spotify.com` → Add**
|
|
250
253
|
|
|
251
|
-
- Launch the Spotify desktop client and look for POST requests to `https://
|
|
252
|
-
- Note: The `login` part is suffixed with one or more digits (e.g. `login5`).
|
|
254
|
+
- Launch the Spotify desktop client, then switch to your intercepting proxy (like Proxyman) and look for POST requests to `https://login5.spotify.com/v3/login`
|
|
253
255
|
|
|
254
|
-
- If you don't see this request,
|
|
256
|
+
- If you don't see this request, try following steps (stop once it works):
|
|
257
|
+
- restart the Spotify desktop client
|
|
258
|
+
- log out from the Spotify desktop client and log back in
|
|
259
|
+
- point Spotify at the intercepting proxy directly in its settings, i.e. in **Spotify → Settings → Proxy Settings**, set:
|
|
260
|
+
- **proxy type**: `HTTP`
|
|
261
|
+
- **host**: `127.0.0.1` (IP/FQDN of your proxy, for Proxyman use the IP you see at the top bar)
|
|
262
|
+
- **port**: `9090` (port of your proxy, for Proxyman use the port you see at the top bar)
|
|
263
|
+
- restart the app; since QUIC (HTTP/3) requires raw UDP and can't tunnel over HTTP CONNECT, Spotify will downgrade to TCP-only HTTP/2 or 1.1, which intercepting proxy can decrypt
|
|
264
|
+
- block Spotify's UDP port 443 at the OS level with a firewall of your choice - this prevents QUIC (HTTP/3), forcing TLS over TCP and letting intercepting proxy perform MITM
|
|
265
|
+
- try an older version of the Spotify desktop client
|
|
255
266
|
|
|
256
267
|
- Export the login request body (a binary Protobuf payload) to a file (e.g. ***login-request-body-file***)
|
|
257
268
|
- In Proxyman: **right click the request → Export → Request Body → Save File**.
|
|
@@ -512,6 +523,19 @@ spotify_profile_monitor <spotify_user_uri_id> -k
|
|
|
512
523
|
|
|
513
524
|
It is helpful in the case of playlists created by another user added to another user profile.
|
|
514
525
|
|
|
526
|
+
Some users don't list all their public playlists on their profile, but if you know a playlist's URI, you can still monitor it.
|
|
527
|
+
|
|
528
|
+
To do so, add entries to the `ADD_PLAYLISTS_TO_MONITOR` configuration option. Example:
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
ADD_PLAYLISTS_TO_MONITOR = [
|
|
532
|
+
{'uri': 'spotify:playlist:{playlist_id1}', 'owner_name': '{user_id}', 'owner_uri': 'spotify:user:{user_id}'},
|
|
533
|
+
{'uri': 'spotify:playlist:{playlist_id2}', 'owner_name': '{user_id}', 'owner_uri': 'spotify:user:{user_id}'}
|
|
534
|
+
]
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Replace `{playlist_id1}` and `{playlist_id2}` with the playlists URI IDs you want to monitor and `{user_id}` with the owner's URI ID (`spotify_user_uri_id`).
|
|
538
|
+
|
|
515
539
|
If you want to completely disable detection of changes in user's public playlists (like added/removed tracks in playlists, playlists name and description changes, number of likes for playlists):
|
|
516
540
|
- set `DETECT_CHANGES_IN_PLAYLISTS` to `False`
|
|
517
541
|
- or use the `-q` flag
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# spotify_profile_monitor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
OSINT tool for real-time monitoring of Spotify users' activities and profile changes including playlists.
|
|
4
4
|
|
|
5
5
|
NOTE: If you want to track Spotify friends' music activity, check out another tool I developed: [spotify_monitor](https://github.com/misiektoja/spotify_monitor).
|
|
6
6
|
|
|
@@ -218,12 +218,23 @@ If you store the `SP_DC_COOKIE` in a dotenv file you can update its value and se
|
|
|
218
218
|
|
|
219
219
|
This is the alternative method used to obtain a Spotify access token which simulates a login from the real Spotify desktop app using credentials intercepted from a real session.
|
|
220
220
|
|
|
221
|
-
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com))
|
|
221
|
+
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com) - the trial version is sufficient)
|
|
222
|
+
|
|
223
|
+
- Enable SSL traffic decryption for `spotify.com` domain
|
|
224
|
+
- in Proxyman: click **Tools → SSL Proxying List → + button → Add Domain → paste `*.spotify.com` → Add**
|
|
222
225
|
|
|
223
|
-
- Launch the Spotify desktop client and look for POST requests to `https://
|
|
224
|
-
- Note: The `login` part is suffixed with one or more digits (e.g. `login5`).
|
|
226
|
+
- Launch the Spotify desktop client, then switch to your intercepting proxy (like Proxyman) and look for POST requests to `https://login5.spotify.com/v3/login`
|
|
225
227
|
|
|
226
|
-
- If you don't see this request,
|
|
228
|
+
- If you don't see this request, try following steps (stop once it works):
|
|
229
|
+
- restart the Spotify desktop client
|
|
230
|
+
- log out from the Spotify desktop client and log back in
|
|
231
|
+
- point Spotify at the intercepting proxy directly in its settings, i.e. in **Spotify → Settings → Proxy Settings**, set:
|
|
232
|
+
- **proxy type**: `HTTP`
|
|
233
|
+
- **host**: `127.0.0.1` (IP/FQDN of your proxy, for Proxyman use the IP you see at the top bar)
|
|
234
|
+
- **port**: `9090` (port of your proxy, for Proxyman use the port you see at the top bar)
|
|
235
|
+
- restart the app; since QUIC (HTTP/3) requires raw UDP and can't tunnel over HTTP CONNECT, Spotify will downgrade to TCP-only HTTP/2 or 1.1, which intercepting proxy can decrypt
|
|
236
|
+
- block Spotify's UDP port 443 at the OS level with a firewall of your choice - this prevents QUIC (HTTP/3), forcing TLS over TCP and letting intercepting proxy perform MITM
|
|
237
|
+
- try an older version of the Spotify desktop client
|
|
227
238
|
|
|
228
239
|
- Export the login request body (a binary Protobuf payload) to a file (e.g. ***login-request-body-file***)
|
|
229
240
|
- In Proxyman: **right click the request → Export → Request Body → Save File**.
|
|
@@ -484,6 +495,19 @@ spotify_profile_monitor <spotify_user_uri_id> -k
|
|
|
484
495
|
|
|
485
496
|
It is helpful in the case of playlists created by another user added to another user profile.
|
|
486
497
|
|
|
498
|
+
Some users don't list all their public playlists on their profile, but if you know a playlist's URI, you can still monitor it.
|
|
499
|
+
|
|
500
|
+
To do so, add entries to the `ADD_PLAYLISTS_TO_MONITOR` configuration option. Example:
|
|
501
|
+
|
|
502
|
+
```python
|
|
503
|
+
ADD_PLAYLISTS_TO_MONITOR = [
|
|
504
|
+
{'uri': 'spotify:playlist:{playlist_id1}', 'owner_name': '{user_id}', 'owner_uri': 'spotify:user:{user_id}'},
|
|
505
|
+
{'uri': 'spotify:playlist:{playlist_id2}', 'owner_name': '{user_id}', 'owner_uri': 'spotify:user:{user_id}'}
|
|
506
|
+
]
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Replace `{playlist_id1}` and `{playlist_id2}` with the playlists URI IDs you want to monitor and `{user_id}` with the owner's URI ID (`spotify_user_uri_id`).
|
|
510
|
+
|
|
487
511
|
If you want to completely disable detection of changes in user's public playlists (like added/removed tracks in playlists, playlists name and description changes, number of likes for playlists):
|
|
488
512
|
- set `DETECT_CHANGES_IN_PLAYLISTS` to `False`
|
|
489
513
|
- or use the `-q` flag
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spotify_profile_monitor"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.6"
|
|
8
8
|
description = "Tool implementing real-time tracking of Spotify users activities and profile changes including playlists"
|
|
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_profile_monitor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6
|
|
4
4
|
Summary: Tool implementing real-time tracking of Spotify users activities and profile changes including playlists
|
|
5
5
|
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -28,7 +28,7 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# spotify_profile_monitor
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
OSINT tool for real-time monitoring of Spotify users' activities and profile changes including playlists.
|
|
32
32
|
|
|
33
33
|
NOTE: If you want to track Spotify friends' music activity, check out another tool I developed: [spotify_monitor](https://github.com/misiektoja/spotify_monitor).
|
|
34
34
|
|
|
@@ -246,12 +246,23 @@ If you store the `SP_DC_COOKIE` in a dotenv file you can update its value and se
|
|
|
246
246
|
|
|
247
247
|
This is the alternative method used to obtain a Spotify access token which simulates a login from the real Spotify desktop app using credentials intercepted from a real session.
|
|
248
248
|
|
|
249
|
-
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com))
|
|
249
|
+
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com) - the trial version is sufficient)
|
|
250
|
+
|
|
251
|
+
- Enable SSL traffic decryption for `spotify.com` domain
|
|
252
|
+
- in Proxyman: click **Tools → SSL Proxying List → + button → Add Domain → paste `*.spotify.com` → Add**
|
|
250
253
|
|
|
251
|
-
- Launch the Spotify desktop client and look for POST requests to `https://
|
|
252
|
-
- Note: The `login` part is suffixed with one or more digits (e.g. `login5`).
|
|
254
|
+
- Launch the Spotify desktop client, then switch to your intercepting proxy (like Proxyman) and look for POST requests to `https://login5.spotify.com/v3/login`
|
|
253
255
|
|
|
254
|
-
- If you don't see this request,
|
|
256
|
+
- If you don't see this request, try following steps (stop once it works):
|
|
257
|
+
- restart the Spotify desktop client
|
|
258
|
+
- log out from the Spotify desktop client and log back in
|
|
259
|
+
- point Spotify at the intercepting proxy directly in its settings, i.e. in **Spotify → Settings → Proxy Settings**, set:
|
|
260
|
+
- **proxy type**: `HTTP`
|
|
261
|
+
- **host**: `127.0.0.1` (IP/FQDN of your proxy, for Proxyman use the IP you see at the top bar)
|
|
262
|
+
- **port**: `9090` (port of your proxy, for Proxyman use the port you see at the top bar)
|
|
263
|
+
- restart the app; since QUIC (HTTP/3) requires raw UDP and can't tunnel over HTTP CONNECT, Spotify will downgrade to TCP-only HTTP/2 or 1.1, which intercepting proxy can decrypt
|
|
264
|
+
- block Spotify's UDP port 443 at the OS level with a firewall of your choice - this prevents QUIC (HTTP/3), forcing TLS over TCP and letting intercepting proxy perform MITM
|
|
265
|
+
- try an older version of the Spotify desktop client
|
|
255
266
|
|
|
256
267
|
- Export the login request body (a binary Protobuf payload) to a file (e.g. ***login-request-body-file***)
|
|
257
268
|
- In Proxyman: **right click the request → Export → Request Body → Save File**.
|
|
@@ -512,6 +523,19 @@ spotify_profile_monitor <spotify_user_uri_id> -k
|
|
|
512
523
|
|
|
513
524
|
It is helpful in the case of playlists created by another user added to another user profile.
|
|
514
525
|
|
|
526
|
+
Some users don't list all their public playlists on their profile, but if you know a playlist's URI, you can still monitor it.
|
|
527
|
+
|
|
528
|
+
To do so, add entries to the `ADD_PLAYLISTS_TO_MONITOR` configuration option. Example:
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
ADD_PLAYLISTS_TO_MONITOR = [
|
|
532
|
+
{'uri': 'spotify:playlist:{playlist_id1}', 'owner_name': '{user_id}', 'owner_uri': 'spotify:user:{user_id}'},
|
|
533
|
+
{'uri': 'spotify:playlist:{playlist_id2}', 'owner_name': '{user_id}', 'owner_uri': 'spotify:user:{user_id}'}
|
|
534
|
+
]
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Replace `{playlist_id1}` and `{playlist_id2}` with the playlists URI IDs you want to monitor and `{user_id}` with the owner's URI ID (`spotify_user_uri_id`).
|
|
538
|
+
|
|
515
539
|
If you want to completely disable detection of changes in user's public playlists (like added/removed tracks in playlists, playlists name and description changes, number of likes for playlists):
|
|
516
540
|
- set `DETECT_CHANGES_IN_PLAYLISTS` to `False`
|
|
517
541
|
- or use the `-q` flag
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Author: Michal Szymanski <misiektoja-github@rm-rf.ninja>
|
|
4
|
-
v2.
|
|
4
|
+
v2.6
|
|
5
5
|
|
|
6
6
|
OSINT tool implementing real-time tracking of Spotify users activities and profile changes including playlists:
|
|
7
7
|
https://github.com/misiektoja/spotify_profile_monitor/
|
|
@@ -18,7 +18,7 @@ python-dotenv (optional)
|
|
|
18
18
|
spotipy (optional, needed when the token source is set to oauth_app)
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
VERSION = "2.
|
|
21
|
+
VERSION = "2.6"
|
|
22
22
|
|
|
23
23
|
# ---------------------------
|
|
24
24
|
# CONFIGURATION SECTION START
|
|
@@ -137,6 +137,17 @@ DETECT_CHANGES_IN_PLAYLISTS = True
|
|
|
137
137
|
# Can also be enabled via the -k flag
|
|
138
138
|
GET_ALL_PLAYLISTS = False
|
|
139
139
|
|
|
140
|
+
# Some users don't list all their public playlists on their profile, but if you know a playlist's URI, you can still monitor it
|
|
141
|
+
#
|
|
142
|
+
# Example:
|
|
143
|
+
#
|
|
144
|
+
# ADD_PLAYLISTS_TO_MONITOR = [
|
|
145
|
+
# {'uri': 'spotify:playlist:{playlist_id1}', 'owner_name': '{user_id}', 'owner_uri': 'spotify:user:{user_id}'},
|
|
146
|
+
# {'uri': 'spotify:playlist:{playlist_id2}', 'owner_name': '{user_id}', 'owner_uri': 'spotify:user:{user_id}'}
|
|
147
|
+
# ]
|
|
148
|
+
# Replace {playlist_id1} and {playlist_id2} with the playlists URI IDs you want to monitor and {user_id} with the owner's URI ID
|
|
149
|
+
ADD_PLAYLISTS_TO_MONITOR = []
|
|
150
|
+
|
|
140
151
|
# Ignore Spotify-owned playlists when monitoring?
|
|
141
152
|
# Set to True to avoid tracking Spotify-generated playlists that often change frequently (likes, tracks etc.)
|
|
142
153
|
IGNORE_SPOTIFY_PLAYLISTS = True
|
|
@@ -225,6 +236,13 @@ HORIZONTAL_LINE = 113
|
|
|
225
236
|
# Whether to clear the terminal screen after starting the tool
|
|
226
237
|
CLEAR_SCREEN = True
|
|
227
238
|
|
|
239
|
+
# Max characters per line when printing to screen to avoid line wrapping
|
|
240
|
+
# Does not affect log file output
|
|
241
|
+
# Set to 999 to auto-detect terminal width
|
|
242
|
+
# Applies only when DISABLE_LOGGING is False
|
|
243
|
+
# Can also be set via the --truncate flag
|
|
244
|
+
TRUNCATE_CHARS = 0
|
|
245
|
+
|
|
228
246
|
# Value used by signal handlers to increase or decrease profile check interval (SPOTIFY_CHECK_INTERVAL); in seconds
|
|
229
247
|
SPOTIFY_CHECK_SIGNAL_VALUE = 300 # 5 minutes
|
|
230
248
|
|
|
@@ -478,6 +496,7 @@ IMGCAT_PATH = ""
|
|
|
478
496
|
SP_SHA256 = ""
|
|
479
497
|
DETECT_CHANGES_IN_PLAYLISTS = False
|
|
480
498
|
GET_ALL_PLAYLISTS = False
|
|
499
|
+
ADD_PLAYLISTS_TO_MONITOR = []
|
|
481
500
|
IGNORE_SPOTIFY_PLAYLISTS = False
|
|
482
501
|
PLAYLISTS_LIMIT = 0
|
|
483
502
|
RECENTLY_PLAYED_ARTISTS_LIMIT = 0
|
|
@@ -502,6 +521,7 @@ CLEAR_SCREEN = False
|
|
|
502
521
|
SPOTIFY_CHECK_SIGNAL_VALUE = 0
|
|
503
522
|
TOKEN_MAX_RETRIES = 0
|
|
504
523
|
TOKEN_RETRY_TIMEOUT = 0.0
|
|
524
|
+
TRUNCATE_CHARS = 0
|
|
505
525
|
|
|
506
526
|
exec(CONFIG_BLOCK, globals())
|
|
507
527
|
|
|
@@ -531,6 +551,12 @@ SP_CACHED_CLIENT_ID = ""
|
|
|
531
551
|
# URL of the Spotify Web Player endpoint to get access token
|
|
532
552
|
TOKEN_URL = "https://open.spotify.com/api/token"
|
|
533
553
|
|
|
554
|
+
# URL of the endpoint to get server time needed to create TOTP object
|
|
555
|
+
SERVER_TIME_URL = "https://open.spotify.com/"
|
|
556
|
+
|
|
557
|
+
# Identifier used to select the appropriate encrypted secret from secret_cipher_dict when generating a TOTP token
|
|
558
|
+
TOTP_VER = 10
|
|
559
|
+
|
|
534
560
|
# Variables for caching functionality of the Spotify client token to avoid unnecessary refreshing
|
|
535
561
|
SP_CACHED_CLIENT_TOKEN = None
|
|
536
562
|
SP_CLIENT_TOKEN_EXPIRES_AT = 0
|
|
@@ -634,6 +660,19 @@ SESSION.mount("https://", adapter)
|
|
|
634
660
|
SESSION.mount("http://", adapter)
|
|
635
661
|
|
|
636
662
|
|
|
663
|
+
# Truncates each line of a string to a specified number of characters including tab expansion and multi-line support
|
|
664
|
+
def truncate_string_per_line(message, truncate_chars, tabsize=8):
|
|
665
|
+
lines = message.split('\n')
|
|
666
|
+
truncated_lines = []
|
|
667
|
+
|
|
668
|
+
for line in lines:
|
|
669
|
+
expanded_line = line.expandtabs(tabsize=tabsize)
|
|
670
|
+
truncated_line = expanded_line[:truncate_chars]
|
|
671
|
+
truncated_lines.append(truncated_line)
|
|
672
|
+
|
|
673
|
+
return '\n'.join(truncated_lines)
|
|
674
|
+
|
|
675
|
+
|
|
637
676
|
# Logger class to output messages to stdout and log file
|
|
638
677
|
class Logger(object):
|
|
639
678
|
def __init__(self, filename):
|
|
@@ -641,8 +680,10 @@ class Logger(object):
|
|
|
641
680
|
self.logfile = open(filename, "a", buffering=1, encoding="utf-8")
|
|
642
681
|
|
|
643
682
|
def write(self, message):
|
|
644
|
-
self.terminal.write(message)
|
|
645
683
|
self.logfile.write(message)
|
|
684
|
+
if (TRUNCATE_CHARS):
|
|
685
|
+
message = truncate_string_per_line(message, TRUNCATE_CHARS)
|
|
686
|
+
self.terminal.write(message)
|
|
646
687
|
self.terminal.flush()
|
|
647
688
|
self.logfile.flush()
|
|
648
689
|
|
|
@@ -1375,7 +1416,7 @@ def fetch_server_time(session: req.Session, ua: str) -> int:
|
|
|
1375
1416
|
if platform.system() != 'Windows':
|
|
1376
1417
|
signal.signal(signal.SIGALRM, timeout_handler)
|
|
1377
1418
|
signal.alarm(FUNCTION_TIMEOUT + 2)
|
|
1378
|
-
response = session.head(
|
|
1419
|
+
response = session.head(SERVER_TIME_URL, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
1379
1420
|
response.raise_for_status()
|
|
1380
1421
|
except TimeoutException as e:
|
|
1381
1422
|
raise Exception(f"fetch_server_time() head network request timeout after {display_time(FUNCTION_TIMEOUT + 2)}: {e}")
|
|
@@ -1385,17 +1426,26 @@ def fetch_server_time(session: req.Session, ua: str) -> int:
|
|
|
1385
1426
|
if platform.system() != 'Windows':
|
|
1386
1427
|
signal.alarm(0)
|
|
1387
1428
|
|
|
1388
|
-
|
|
1429
|
+
date_hdr = response.headers.get("Date")
|
|
1430
|
+
if not date_hdr:
|
|
1431
|
+
raise Exception("fetch_server_time() missing 'Date' header")
|
|
1432
|
+
|
|
1433
|
+
return int(parsedate_to_datetime(date_hdr).timestamp())
|
|
1389
1434
|
|
|
1390
1435
|
|
|
1391
1436
|
# Creates a TOTP object using a secret derived from transformed cipher bytes
|
|
1392
1437
|
def generate_totp():
|
|
1393
1438
|
import pyotp
|
|
1394
1439
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1440
|
+
secret_cipher_dict = {
|
|
1441
|
+
"10": [61, 110, 58, 98, 35, 79, 117, 69, 102, 72, 92, 102, 69, 93, 41, 101, 42, 75],
|
|
1442
|
+
"9": [109, 101, 90, 99, 66, 92, 116, 108, 85, 70, 86, 49, 68, 54, 87, 50, 72, 121, 52, 64, 57, 43, 36, 81, 97, 72, 53, 41, 78, 56],
|
|
1443
|
+
"8": [37, 84, 32, 76, 87, 90, 87, 47, 13, 75, 48, 54, 44, 28, 19, 21, 22],
|
|
1444
|
+
"7": [59, 91, 66, 74, 30, 66, 74, 38, 46, 50, 72, 61, 44, 71, 86, 39, 89],
|
|
1445
|
+
"6": [21, 24, 85, 46, 48, 35, 33, 8, 11, 63, 76, 12, 55, 77, 14, 7, 54],
|
|
1446
|
+
"5": [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54],
|
|
1447
|
+
}
|
|
1448
|
+
secret_cipher_bytes = secret_cipher_dict[str(TOTP_VER)]
|
|
1399
1449
|
|
|
1400
1450
|
transformed = [e ^ ((t % 33) + 9) for t, e in enumerate(secret_cipher_bytes)]
|
|
1401
1451
|
joined = "".join(str(num) for num in transformed)
|
|
@@ -1410,7 +1460,6 @@ def refresh_access_token_from_sp_dc(sp_dc: str) -> dict:
|
|
|
1410
1460
|
transport = True
|
|
1411
1461
|
init = True
|
|
1412
1462
|
session = req.Session()
|
|
1413
|
-
session.cookies.set("sp_dc", sp_dc)
|
|
1414
1463
|
data: dict = {}
|
|
1415
1464
|
token = ""
|
|
1416
1465
|
|
|
@@ -1424,13 +1473,17 @@ def refresh_access_token_from_sp_dc(sp_dc: str) -> dict:
|
|
|
1424
1473
|
"productType": "web-player",
|
|
1425
1474
|
"totp": otp_value,
|
|
1426
1475
|
"totpServer": otp_value,
|
|
1427
|
-
"totpVer":
|
|
1428
|
-
"sTime": server_time,
|
|
1429
|
-
"cTime": client_time,
|
|
1430
|
-
"buildDate": time.strftime("%Y-%m-%d", time.gmtime(server_time)),
|
|
1431
|
-
"buildVer": f"web-player_{time.strftime('%Y-%m-%d', time.gmtime(server_time))}_{server_time * 1000}_{secrets.token_hex(4)}",
|
|
1476
|
+
"totpVer": TOTP_VER,
|
|
1432
1477
|
}
|
|
1433
1478
|
|
|
1479
|
+
if TOTP_VER < 10:
|
|
1480
|
+
params.update({
|
|
1481
|
+
"sTime": server_time,
|
|
1482
|
+
"cTime": client_time,
|
|
1483
|
+
"buildDate": time.strftime("%Y-%m-%d", time.gmtime(server_time)),
|
|
1484
|
+
"buildVer": f"web-player_{time.strftime('%Y-%m-%d', time.gmtime(server_time))}_{server_time * 1000}_{secrets.token_hex(4)}",
|
|
1485
|
+
})
|
|
1486
|
+
|
|
1434
1487
|
headers = {
|
|
1435
1488
|
"User-Agent": USER_AGENT,
|
|
1436
1489
|
"Accept": "application/json",
|
|
@@ -3775,14 +3828,14 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
3775
3828
|
SP_CACHED_ACCESS_TOKEN = None
|
|
3776
3829
|
|
|
3777
3830
|
client_errs = ['access token', 'invalid client token', 'expired client token', 'refresh token has been revoked', 'refresh token has expired', 'refresh token is invalid', 'invalid grant during refresh']
|
|
3778
|
-
cookie_errs = ['access token', 'unauthorized']
|
|
3831
|
+
cookie_errs = ['access token', 'unauthorized', 'unsuccessful token request']
|
|
3779
3832
|
oauth_app_errs = ['invalid_client', 'invalid_client_id', 'could not authenticate you', '401']
|
|
3780
3833
|
oauth_user_errs = ['invalid_client', 'invalid_grant', 'invalid_scope', 'authorization_required', 'refresh token has been revoked', 'refresh token has expired']
|
|
3781
3834
|
|
|
3782
3835
|
if TOKEN_SOURCE == 'client' and any(k in err for k in client_errs):
|
|
3783
3836
|
print(f"* Error: client or refresh token may be invalid or expired!\n{str(e)}")
|
|
3784
3837
|
elif TOKEN_SOURCE == 'cookie' and any(k in err for k in cookie_errs):
|
|
3785
|
-
print(f"* Error: sp_dc may be invalid or
|
|
3838
|
+
print(f"* Error: sp_dc may be invalid/expired or Spotify has broken sth again!\n{str(e)}")
|
|
3786
3839
|
elif TOKEN_SOURCE == 'oauth_app' and any(k in err for k in oauth_app_errs):
|
|
3787
3840
|
print(f"* Error: OAuth-app client_id/client_secret may be invalid or expired!\n{str(e)}")
|
|
3788
3841
|
elif TOKEN_SOURCE == 'oauth_user' and any(k in err for k in oauth_user_errs):
|
|
@@ -3825,6 +3878,10 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
3825
3878
|
playlists_count = sp_user_data["sp_user_public_playlists_count"]
|
|
3826
3879
|
playlists = sp_user_data["sp_user_public_playlists_uris"]
|
|
3827
3880
|
|
|
3881
|
+
if ADD_PLAYLISTS_TO_MONITOR:
|
|
3882
|
+
playlists.extend(ADD_PLAYLISTS_TO_MONITOR)
|
|
3883
|
+
playlists_count += len(ADD_PLAYLISTS_TO_MONITOR)
|
|
3884
|
+
|
|
3828
3885
|
recently_played_artists = sp_user_data["sp_user_recently_played_artists"]
|
|
3829
3886
|
|
|
3830
3887
|
print(f"Username:\t\t\t{username}")
|
|
@@ -4105,7 +4162,7 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4105
4162
|
SP_CACHED_ACCESS_TOKEN = None
|
|
4106
4163
|
|
|
4107
4164
|
client_errs = ['access token', 'invalid client token', 'expired client token', 'refresh token has been revoked', 'refresh token has expired', 'refresh token is invalid', 'invalid grant during refresh']
|
|
4108
|
-
cookie_errs = ['access token', 'unauthorized']
|
|
4165
|
+
cookie_errs = ['access token', 'unauthorized', 'unsuccessful token request']
|
|
4109
4166
|
oauth_app_errs = ['invalid_client', 'invalid_client_id', 'could not authenticate you', '401']
|
|
4110
4167
|
oauth_user_errs = ['invalid_client', 'invalid_grant', 'invalid_scope', 'authorization_required', 'refresh token has been revoked', 'refresh token has expired']
|
|
4111
4168
|
|
|
@@ -4120,11 +4177,11 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4120
4177
|
email_sent = True
|
|
4121
4178
|
|
|
4122
4179
|
elif TOKEN_SOURCE == 'cookie' and any(k in err for k in cookie_errs):
|
|
4123
|
-
print(f"* Error: sp_dc may be invalid or
|
|
4180
|
+
print(f"* Error: sp_dc may be invalid/expired or Spotify has broken sth again!")
|
|
4124
4181
|
if ERROR_NOTIFICATION and not email_sent:
|
|
4125
|
-
m_subject = f"spotify_profile_monitor: sp_dc may be invalid or
|
|
4126
|
-
m_body = f"sp_dc may be invalid or
|
|
4127
|
-
m_body_html = f"<html><head></head><body>sp_dc may be invalid or
|
|
4182
|
+
m_subject = f"spotify_profile_monitor: sp_dc may be invalid/expired or Spotify has broken sth again! (uri: {user_uri_id})"
|
|
4183
|
+
m_body = f"sp_dc may be invalid/expired or Spotify has broken sth again!\n{e}{get_cur_ts(nl_ch + nl_ch + 'Timestamp: ')}"
|
|
4184
|
+
m_body_html = f"<html><head></head><body>sp_dc may be invalid/expired or Spotify has broken sth again!<br>{escape(str(e))}{get_cur_ts('<br><br>Timestamp: ')}</body></html>"
|
|
4128
4185
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
4129
4186
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
4130
4187
|
email_sent = True
|
|
@@ -4217,6 +4274,10 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4217
4274
|
playlists_count = sp_user_data["sp_user_public_playlists_count"]
|
|
4218
4275
|
playlists = sp_user_data["sp_user_public_playlists_uris"]
|
|
4219
4276
|
|
|
4277
|
+
if ADD_PLAYLISTS_TO_MONITOR:
|
|
4278
|
+
playlists.extend(ADD_PLAYLISTS_TO_MONITOR)
|
|
4279
|
+
playlists_count += len(ADD_PLAYLISTS_TO_MONITOR)
|
|
4280
|
+
|
|
4220
4281
|
recently_played_artists = sp_user_data["sp_user_recently_played_artists"]
|
|
4221
4282
|
|
|
4222
4283
|
if followers_count != followers_old_count:
|
|
@@ -4742,7 +4803,7 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4742
4803
|
|
|
4743
4804
|
|
|
4744
4805
|
def main():
|
|
4745
|
-
global CLI_CONFIG_PATH, DOTENV_FILE, LOCAL_TIMEZONE, LIVENESS_CHECK_COUNTER, SP_DC_COOKIE, SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET, SP_USER_CLIENT_ID, SP_USER_CLIENT_SECRET, LOGIN_REQUEST_BODY_FILE, CLIENTTOKEN_REQUEST_BODY_FILE, REFRESH_TOKEN, LOGIN_URL, USER_AGENT, DEVICE_ID, SYSTEM_ID, USER_URI_ID, CSV_FILE, PLAYLISTS_TO_SKIP_FILE, FILE_SUFFIX, DISABLE_LOGGING, SP_LOGFILE, PROFILE_NOTIFICATION, SPOTIFY_CHECK_INTERVAL, SPOTIFY_ERROR_INTERVAL, FOLLOWERS_FOLLOWINGS_NOTIFICATION, ERROR_NOTIFICATION, DETECT_CHANGED_PROFILE_PIC, DETECT_CHANGES_IN_PLAYLISTS, GET_ALL_PLAYLISTS, imgcat_exe, SMTP_PASSWORD, SP_SHA256, stdout_bck, APP_VERSION, CPU_ARCH, OS_BUILD, PLATFORM, OS_MAJOR, OS_MINOR, CLIENT_MODEL, TOKEN_SOURCE, ALARM_TIMEOUT, pyotp, CLEAN_OUTPUT, USER_AGENT, SP_APP_TOKENS_FILE, SP_USER_TOKENS_FILE
|
|
4806
|
+
global CLI_CONFIG_PATH, DOTENV_FILE, LOCAL_TIMEZONE, LIVENESS_CHECK_COUNTER, SP_DC_COOKIE, SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET, SP_USER_CLIENT_ID, SP_USER_CLIENT_SECRET, LOGIN_REQUEST_BODY_FILE, CLIENTTOKEN_REQUEST_BODY_FILE, REFRESH_TOKEN, LOGIN_URL, USER_AGENT, DEVICE_ID, SYSTEM_ID, USER_URI_ID, CSV_FILE, PLAYLISTS_TO_SKIP_FILE, FILE_SUFFIX, DISABLE_LOGGING, SP_LOGFILE, PROFILE_NOTIFICATION, SPOTIFY_CHECK_INTERVAL, SPOTIFY_ERROR_INTERVAL, FOLLOWERS_FOLLOWINGS_NOTIFICATION, ERROR_NOTIFICATION, DETECT_CHANGED_PROFILE_PIC, DETECT_CHANGES_IN_PLAYLISTS, GET_ALL_PLAYLISTS, imgcat_exe, SMTP_PASSWORD, SP_SHA256, stdout_bck, APP_VERSION, CPU_ARCH, OS_BUILD, PLATFORM, OS_MAJOR, OS_MINOR, CLIENT_MODEL, TOKEN_SOURCE, ALARM_TIMEOUT, pyotp, CLEAN_OUTPUT, USER_AGENT, SP_APP_TOKENS_FILE, SP_USER_TOKENS_FILE, TRUNCATE_CHARS
|
|
4746
4807
|
|
|
4747
4808
|
if "--generate-config" in sys.argv:
|
|
4748
4809
|
print(CONFIG_BLOCK.strip("\n"))
|
|
@@ -5007,6 +5068,13 @@ def main():
|
|
|
5007
5068
|
default=None,
|
|
5008
5069
|
help="Disable logging to spotify_profile_monitor_<user_uri_id/file_suffix>.log"
|
|
5009
5070
|
)
|
|
5071
|
+
opts.add_argument(
|
|
5072
|
+
"--truncate",
|
|
5073
|
+
dest="truncate",
|
|
5074
|
+
metavar="N",
|
|
5075
|
+
type=int,
|
|
5076
|
+
help="Max characters per screen line (not log), use 999 to auto-detect terminal width, ignored if -d is set"
|
|
5077
|
+
)
|
|
5010
5078
|
|
|
5011
5079
|
args = parser.parse_args()
|
|
5012
5080
|
|
|
@@ -5519,6 +5587,18 @@ def main():
|
|
|
5519
5587
|
if not FILE_SUFFIX:
|
|
5520
5588
|
FILE_SUFFIX = str(args.user_id)
|
|
5521
5589
|
|
|
5590
|
+
if args.truncate:
|
|
5591
|
+
if args.truncate != 999:
|
|
5592
|
+
TRUNCATE_CHARS = args.truncate
|
|
5593
|
+
else:
|
|
5594
|
+
try:
|
|
5595
|
+
terminal_size = shutil.get_terminal_size()
|
|
5596
|
+
print(f"The detected terminal screen width is: {terminal_size.columns} characters\n")
|
|
5597
|
+
TRUNCATE_CHARS = terminal_size.columns
|
|
5598
|
+
except Exception as e:
|
|
5599
|
+
print(f"Error: Cannot determine terminal screen width: {e}")
|
|
5600
|
+
sys.exit(1)
|
|
5601
|
+
|
|
5522
5602
|
if args.disable_logging is True:
|
|
5523
5603
|
DISABLE_LOGGING = True
|
|
5524
5604
|
|
|
@@ -5554,7 +5634,7 @@ def main():
|
|
|
5554
5634
|
ERROR_NOTIFICATION = False
|
|
5555
5635
|
|
|
5556
5636
|
print(f"* Spotify polling intervals:\t[check: {display_time(SPOTIFY_CHECK_INTERVAL)}] [error: {display_time(SPOTIFY_ERROR_INTERVAL)}]")
|
|
5557
|
-
print(f"* Email notifications:\t\t[profile changes = {PROFILE_NOTIFICATION}] [followers/followings = {FOLLOWERS_FOLLOWINGS_NOTIFICATION}]\n
|
|
5637
|
+
print(f"* Email notifications:\t\t[profile changes = {PROFILE_NOTIFICATION}] [followers/followings = {FOLLOWERS_FOLLOWINGS_NOTIFICATION}]\n*\t\t\t\t[errors = {ERROR_NOTIFICATION}]")
|
|
5558
5638
|
print(f"* Token source:\t\t\t{TOKEN_SOURCE}")
|
|
5559
5639
|
print(f"* Profile pic changes:\t\t{DETECT_CHANGED_PROFILE_PIC}")
|
|
5560
5640
|
print(f"* Playlist changes:\t\t{DETECT_CHANGES_IN_PLAYLISTS}")
|
|
@@ -5566,6 +5646,8 @@ def main():
|
|
|
5566
5646
|
print(f"* Ignore listed playlists:\t{bool(PLAYLISTS_TO_SKIP_FILE)}" + (f" ({PLAYLISTS_TO_SKIP_FILE})" if PLAYLISTS_TO_SKIP_FILE else ""))
|
|
5567
5647
|
print(f"* Display profile pics:\t\t{bool(imgcat_exe)}" + (f" (via {imgcat_exe})" if imgcat_exe else ""))
|
|
5568
5648
|
print(f"* Output logging enabled:\t{not DISABLE_LOGGING}" + (f" ({FINAL_LOG_PATH})" if not DISABLE_LOGGING else ""))
|
|
5649
|
+
if not DISABLE_LOGGING and TRUNCATE_CHARS > 0:
|
|
5650
|
+
print(f"* Truncate terminal lines:\t{TRUNCATE_CHARS} chars")
|
|
5569
5651
|
if TOKEN_SOURCE in ('oauth_user', 'oauth_app'):
|
|
5570
5652
|
print(f"* Spotify token cache file:\t{({'oauth_app': SP_APP_TOKENS_FILE, 'oauth_user': SP_USER_TOKENS_FILE}.get(TOKEN_SOURCE) or 'None (memory only)')}")
|
|
5571
5653
|
print(f"* Configuration file:\t\t{cfg_path}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|