spotify-monitor 2.2.1__py3-none-any.whl → 2.3.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.2.1.dist-info → spotify_monitor-2.3.1.dist-info}/METADATA +49 -10
- spotify_monitor-2.3.1.dist-info/RECORD +7 -0
- spotify_monitor.py +170 -30
- spotify_monitor-2.2.1.dist-info/RECORD +0 -7
- {spotify_monitor-2.2.1.dist-info → spotify_monitor-2.3.1.dist-info}/WHEEL +0 -0
- {spotify_monitor-2.2.1.dist-info → spotify_monitor-2.3.1.dist-info}/entry_points.txt +0 -0
- {spotify_monitor-2.2.1.dist-info → spotify_monitor-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {spotify_monitor-2.2.1.dist-info → spotify_monitor-2.3.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.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -21,13 +21,16 @@ Requires-Dist: python-dateutil>=2.8
|
|
|
21
21
|
Requires-Dist: urllib3>=2.0.7
|
|
22
22
|
Requires-Dist: pyotp>=2.9.0
|
|
23
23
|
Requires-Dist: python-dotenv>=0.19
|
|
24
|
+
Requires-Dist: wcwidth>=0.2.7
|
|
24
25
|
Dynamic: license-file
|
|
25
26
|
|
|
26
27
|
# spotify_monitor
|
|
27
28
|
|
|
28
29
|
Tool for real-time monitoring of Spotify friends' music activity feed.
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
✨ 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).
|
|
32
|
+
|
|
33
|
+
🛠️ If you're looking for debug tools to get Spotify Web Player access tokens and extract secret keys: [click here](#debugging-tools)
|
|
31
34
|
|
|
32
35
|
<a id="features"></a>
|
|
33
36
|
## Features
|
|
@@ -73,14 +76,15 @@ NOTE: If you're interested in tracking changes to Spotify users' profiles includ
|
|
|
73
76
|
* [Check Intervals](#check-intervals)
|
|
74
77
|
* [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix)
|
|
75
78
|
* [Coloring Log Output with GRC](#coloring-log-output-with-grc)
|
|
76
|
-
6. [
|
|
77
|
-
7. [
|
|
79
|
+
6. [Debugging Tools](#debugging-tools)
|
|
80
|
+
7. [Change Log](#change-log)
|
|
81
|
+
8. [License](#license)
|
|
78
82
|
|
|
79
83
|
<a id="requirements"></a>
|
|
80
84
|
## Requirements
|
|
81
85
|
|
|
82
86
|
* Python 3.6 or higher
|
|
83
|
-
* Libraries: `requests`, `python-dateutil`, `urllib3`, `pyotp`, `python-dotenv`
|
|
87
|
+
* Libraries: `requests`, `python-dateutil`, `urllib3`, `pyotp`, `python-dotenv`, `wcwidth`
|
|
84
88
|
|
|
85
89
|
Tested on:
|
|
86
90
|
|
|
@@ -195,19 +199,30 @@ If your `sp_dc` cookie expires, the tool will notify you via the console and ema
|
|
|
195
199
|
|
|
196
200
|
If you store the `SP_DC_COOKIE` in a dotenv file you can update its value and send a `SIGHUP` signal to reload the file with the new `sp_dc` cookie without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
197
201
|
|
|
202
|
+
`Note`: encrypted byte sequences used for TOTP secret generation tend to expire every now and then; you can either check the [issues](https://github.com/misiektoja/spotify_monitor/issues) section of the project to see if there are any new secrets published or you can run the [spotify_monitor_secret_grabber.py](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [Debugging Tools](https://github.com/misiektoja/spotify_monitor#debugging-tools) for more info).
|
|
203
|
+
|
|
198
204
|
<a id="spotify-desktop-client"></a>
|
|
199
205
|
#### Spotify Desktop Client
|
|
200
206
|
|
|
201
207
|
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.
|
|
202
208
|
|
|
203
|
-
|
|
209
|
+
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com) - the trial version is sufficient)
|
|
204
210
|
|
|
205
|
-
-
|
|
211
|
+
- Enable SSL traffic decryption for `spotify.com` domain
|
|
212
|
+
- in Proxyman: click **Tools → SSL Proxying List → + button → Add Domain → paste `*.spotify.com` → Add**
|
|
206
213
|
|
|
207
|
-
- Launch the Spotify desktop client and look for POST requests to `https://
|
|
208
|
-
- Note: The `login` part is suffixed with one or more digits (e.g. `login5`).
|
|
214
|
+
- 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`
|
|
209
215
|
|
|
210
|
-
- If you don't see this request,
|
|
216
|
+
- If you don't see this request, try following steps (stop once it works):
|
|
217
|
+
- restart the Spotify desktop client
|
|
218
|
+
- log out from the Spotify desktop client and log back in
|
|
219
|
+
- point Spotify at the intercepting proxy directly in its settings, i.e. in **Spotify → Settings → Proxy Settings**, set:
|
|
220
|
+
- **proxy type**: `HTTP`
|
|
221
|
+
- **host**: `127.0.0.1` (IP/FQDN of your proxy, for Proxyman use the IP you see at the top bar)
|
|
222
|
+
- **port**: `9090` (port of your proxy, for Proxyman use the port you see at the top bar)
|
|
223
|
+
- 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
|
|
224
|
+
- 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
|
|
225
|
+
- try an older version of the Spotify desktop client
|
|
211
226
|
|
|
212
227
|
- Export the login request body (a binary Protobuf payload) to a file (e.g. ***login-request-body-file***)
|
|
213
228
|
- In Proxyman: **right click the request → Export → Request Body → Save File**.
|
|
@@ -554,6 +569,30 @@ Example:
|
|
|
554
569
|
grc tail -F -n 100 spotify_monitor_<user_uri_id/file_suffix>.log
|
|
555
570
|
```
|
|
556
571
|
|
|
572
|
+
<a id="debugging-tools"></a>
|
|
573
|
+
## Debugging Tools
|
|
574
|
+
|
|
575
|
+
To help with troubleshooting and development, two debug utilities are available in the `debug` directory:
|
|
576
|
+
|
|
577
|
+
- [spotify_monitor_totp_test.py](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_totp_test.py): fetching of Spotify access token based on a Spotify Web Player `sp_dc` cookie value:
|
|
578
|
+
|
|
579
|
+
```sh
|
|
580
|
+
pip install requests python-dateutil pyotp
|
|
581
|
+
python3 spotify_monitor_totp_test.py --sp-dc "your_sp_dc_cookie_value"
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
- [spotify_monitor_secret_grabber.py](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py): automatic extractor for secret keys used for TOTP generation in Spotify Web Player JavaScript bundles:
|
|
585
|
+
|
|
586
|
+
```sh
|
|
587
|
+
pip install playwright
|
|
588
|
+
playwright install
|
|
589
|
+
python3 spotify_monitor_secret_grabber.py
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
<p align="center">
|
|
593
|
+
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_secret_grabber.png" alt="spotify_monitor_secret_grabber" width="100%"/>
|
|
594
|
+
</p>
|
|
595
|
+
|
|
557
596
|
<a id="change-log"></a>
|
|
558
597
|
## Change Log
|
|
559
598
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
spotify_monitor.py,sha256=msF-xUpmB_dch3C1dtA31mvU0OgtRUgUeOtXh20xDLw,159386
|
|
2
|
+
spotify_monitor-2.3.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
+
spotify_monitor-2.3.1.dist-info/METADATA,sha256=z37aFQ2NUa5jYzU5pF6ru5UXmu65Ctee8J_EDvjG1iI,25204
|
|
4
|
+
spotify_monitor-2.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
+
spotify_monitor-2.3.1.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
|
|
6
|
+
spotify_monitor-2.3.1.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
|
|
7
|
+
spotify_monitor-2.3.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.
|
|
4
|
+
v2.3.1
|
|
5
5
|
|
|
6
6
|
Tool implementing real-time tracking of Spotify friends music activity:
|
|
7
7
|
https://github.com/misiektoja/spotify_monitor/
|
|
@@ -13,9 +13,10 @@ python-dateutil
|
|
|
13
13
|
urllib3
|
|
14
14
|
pyotp (optional, needed when the token source is set to cookie)
|
|
15
15
|
python-dotenv (optional)
|
|
16
|
+
wcwidth (optional, needed by TRUNCATE_CHARS feature)
|
|
16
17
|
"""
|
|
17
18
|
|
|
18
|
-
VERSION = "2.
|
|
19
|
+
VERSION = "2.3.1"
|
|
19
20
|
|
|
20
21
|
# ---------------------------
|
|
21
22
|
# CONFIGURATION SECTION START
|
|
@@ -223,17 +224,48 @@ HORIZONTAL_LINE = 113
|
|
|
223
224
|
# Whether to clear the terminal screen after starting the tool
|
|
224
225
|
CLEAR_SCREEN = True
|
|
225
226
|
|
|
227
|
+
# Path to a file that is created when the user is active and deleted when inactive
|
|
228
|
+
# Useful for external tools to detect streaming status
|
|
229
|
+
# Can also be set via the --flag-file flag
|
|
230
|
+
FLAG_FILE = ""
|
|
231
|
+
|
|
232
|
+
# Max characters per line when printing to screen to avoid line wrapping
|
|
233
|
+
# Does not affect log file output
|
|
234
|
+
# Set to 999 to auto-detect terminal width
|
|
235
|
+
# Applies only when DISABLE_LOGGING is False
|
|
236
|
+
# Can also be set via the --truncate flag
|
|
237
|
+
TRUNCATE_CHARS = 0
|
|
238
|
+
|
|
226
239
|
# Value added/subtracted via signal handlers to adjust inactivity timeout (SPOTIFY_INACTIVITY_CHECK); in seconds
|
|
227
240
|
SPOTIFY_INACTIVITY_CHECK_SIGNAL_VALUE = 30 # 30 seconds
|
|
228
241
|
|
|
242
|
+
# ---------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
# The section below is used when the token source is set to 'cookie'
|
|
245
|
+
|
|
229
246
|
# Maximum number of attempts to get a valid access token in a single run of the spotify_get_access_token_from_sp_dc() function
|
|
230
|
-
# Used only when the token source is set to 'cookie'
|
|
231
247
|
TOKEN_MAX_RETRIES = 10
|
|
232
248
|
|
|
233
249
|
# Interval between access token retry attempts; in seconds
|
|
234
|
-
# Used only when the token source is set to 'cookie'
|
|
235
250
|
TOKEN_RETRY_TIMEOUT = 0.5 # 0.5 second
|
|
236
251
|
|
|
252
|
+
# Mapping of TOTP version identifiers to the encrypted byte sequence for TOTP secret generation
|
|
253
|
+
# Newest TOTP secrets can be fetched via spotify_monitor_secret_grabber.py (see debug dir)
|
|
254
|
+
SECRET_CIPHER_DICT = {
|
|
255
|
+
"12": [107, 81, 49, 57, 67, 93, 87, 81, 69, 67, 40, 93, 48, 50, 46, 91, 94, 113, 41, 108, 77, 107, 34],
|
|
256
|
+
"11": [111, 45, 40, 73, 95, 74, 35, 85, 105, 107, 60, 110, 55, 72, 69, 70, 114, 83, 63, 88, 91],
|
|
257
|
+
"10": [61, 110, 58, 98, 35, 79, 117, 69, 102, 72, 92, 102, 69, 93, 41, 101, 42, 75],
|
|
258
|
+
"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],
|
|
259
|
+
"8": [37, 84, 32, 76, 87, 90, 87, 47, 13, 75, 48, 54, 44, 28, 19, 21, 22],
|
|
260
|
+
"7": [59, 91, 66, 74, 30, 66, 74, 38, 46, 50, 72, 61, 44, 71, 86, 39, 89],
|
|
261
|
+
"6": [21, 24, 85, 46, 48, 35, 33, 8, 11, 63, 76, 12, 55, 77, 14, 7, 54],
|
|
262
|
+
"5": [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54],
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# Identifier used to select the appropriate encrypted secret from SECRET_CIPHER_DICT when generating a TOTP token
|
|
266
|
+
# Set to 0 to auto-select the highest available version
|
|
267
|
+
TOTP_VER = 0
|
|
268
|
+
|
|
237
269
|
# ---------------------------------------------------------------------
|
|
238
270
|
|
|
239
271
|
# The section below is used when the token source is set to 'client'
|
|
@@ -434,6 +466,10 @@ CLEAR_SCREEN = False
|
|
|
434
466
|
SPOTIFY_INACTIVITY_CHECK_SIGNAL_VALUE = 0
|
|
435
467
|
TOKEN_MAX_RETRIES = 0
|
|
436
468
|
TOKEN_RETRY_TIMEOUT = 0.0
|
|
469
|
+
SECRET_CIPHER_DICT = {}
|
|
470
|
+
TOTP_VER = 0
|
|
471
|
+
FLAG_FILE = ""
|
|
472
|
+
TRUNCATE_CHARS = 0
|
|
437
473
|
|
|
438
474
|
exec(CONFIG_BLOCK, globals())
|
|
439
475
|
|
|
@@ -463,6 +499,9 @@ SP_CACHED_CLIENT_ID = ""
|
|
|
463
499
|
# URL of the Spotify Web Player endpoint to get access token
|
|
464
500
|
TOKEN_URL = "https://open.spotify.com/api/token"
|
|
465
501
|
|
|
502
|
+
# URL of the endpoint to get server time needed to create TOTP object
|
|
503
|
+
SERVER_TIME_URL = "https://open.spotify.com/"
|
|
504
|
+
|
|
466
505
|
# Variables for caching functionality of the Spotify client token to avoid unnecessary refreshing
|
|
467
506
|
SP_CACHED_CLIENT_TOKEN = None
|
|
468
507
|
SP_CLIENT_TOKEN_EXPIRES_AT = 0
|
|
@@ -540,6 +579,35 @@ SESSION.mount("https://", adapter)
|
|
|
540
579
|
SESSION.mount("http://", adapter)
|
|
541
580
|
|
|
542
581
|
|
|
582
|
+
# Truncates each line of a string to a specified number of characters including tab expansion and multi-line support
|
|
583
|
+
def truncate_string_per_line(message, truncate_width, tabsize=8):
|
|
584
|
+
try:
|
|
585
|
+
from wcwidth import wcwidth
|
|
586
|
+
except ImportError:
|
|
587
|
+
return message
|
|
588
|
+
|
|
589
|
+
lines = message.split('\n')
|
|
590
|
+
truncated_lines = []
|
|
591
|
+
|
|
592
|
+
for line in lines:
|
|
593
|
+
expanded_line = line.expandtabs(tabsize)
|
|
594
|
+
current_width = 0
|
|
595
|
+
truncated = ''
|
|
596
|
+
|
|
597
|
+
for char in expanded_line:
|
|
598
|
+
char_width = wcwidth(char)
|
|
599
|
+
if char_width < 0:
|
|
600
|
+
char_width = 0 # Non-printable or unknown width
|
|
601
|
+
if current_width + char_width > truncate_width:
|
|
602
|
+
break
|
|
603
|
+
truncated += char
|
|
604
|
+
current_width += char_width
|
|
605
|
+
|
|
606
|
+
truncated_lines.append(truncated)
|
|
607
|
+
|
|
608
|
+
return '\n'.join(truncated_lines)
|
|
609
|
+
|
|
610
|
+
|
|
543
611
|
# Logger class to output messages to stdout and log file
|
|
544
612
|
class Logger(object):
|
|
545
613
|
def __init__(self, filename):
|
|
@@ -547,8 +615,10 @@ class Logger(object):
|
|
|
547
615
|
self.logfile = open(filename, "a", buffering=1, encoding="utf-8")
|
|
548
616
|
|
|
549
617
|
def write(self, message):
|
|
550
|
-
self.terminal.write(message)
|
|
551
618
|
self.logfile.write(message)
|
|
619
|
+
if (TRUNCATE_CHARS):
|
|
620
|
+
message = truncate_string_per_line(message, TRUNCATE_CHARS)
|
|
621
|
+
self.terminal.write(message)
|
|
552
622
|
self.terminal.flush()
|
|
553
623
|
self.logfile.flush()
|
|
554
624
|
|
|
@@ -556,6 +626,22 @@ class Logger(object):
|
|
|
556
626
|
pass
|
|
557
627
|
|
|
558
628
|
|
|
629
|
+
def flag_file_create():
|
|
630
|
+
try:
|
|
631
|
+
with open(FLAG_FILE, "w") as f:
|
|
632
|
+
f.write("This indicates active streaming by monitored user")
|
|
633
|
+
except Exception:
|
|
634
|
+
pass
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def flag_file_delete():
|
|
638
|
+
try:
|
|
639
|
+
if os.path.exists(FLAG_FILE):
|
|
640
|
+
os.remove(FLAG_FILE)
|
|
641
|
+
except Exception:
|
|
642
|
+
pass
|
|
643
|
+
|
|
644
|
+
|
|
559
645
|
# Class used to generate timeout exceptions
|
|
560
646
|
class TimeoutException(Exception):
|
|
561
647
|
pass
|
|
@@ -570,6 +656,8 @@ def timeout_handler(sig, frame):
|
|
|
570
656
|
def signal_handler(sig, frame):
|
|
571
657
|
sys.stdout = stdout_bck
|
|
572
658
|
print('\n* You pressed Ctrl+C, tool is terminated.')
|
|
659
|
+
if FLAG_FILE:
|
|
660
|
+
flag_file_delete()
|
|
573
661
|
sys.exit(0)
|
|
574
662
|
|
|
575
663
|
|
|
@@ -1167,7 +1255,7 @@ def fetch_server_time(session: req.Session, ua: str) -> int:
|
|
|
1167
1255
|
if platform.system() != 'Windows':
|
|
1168
1256
|
signal.signal(signal.SIGALRM, timeout_handler)
|
|
1169
1257
|
signal.alarm(FUNCTION_TIMEOUT + 2)
|
|
1170
|
-
response = session.head(
|
|
1258
|
+
response = session.head(SERVER_TIME_URL, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
1171
1259
|
response.raise_for_status()
|
|
1172
1260
|
except TimeoutException as e:
|
|
1173
1261
|
raise Exception(f"fetch_server_time() head network request timeout after {display_time(FUNCTION_TIMEOUT + 2)}: {e}")
|
|
@@ -1177,20 +1265,18 @@ def fetch_server_time(session: req.Session, ua: str) -> int:
|
|
|
1177
1265
|
if platform.system() != 'Windows':
|
|
1178
1266
|
signal.alarm(0)
|
|
1179
1267
|
|
|
1180
|
-
|
|
1268
|
+
date_hdr = response.headers.get("Date")
|
|
1269
|
+
if not date_hdr:
|
|
1270
|
+
raise Exception("fetch_server_time() missing 'Date' header")
|
|
1271
|
+
|
|
1272
|
+
return int(parsedate_to_datetime(date_hdr).timestamp())
|
|
1181
1273
|
|
|
1182
1274
|
|
|
1183
1275
|
# Creates a TOTP object using a secret derived from transformed cipher bytes
|
|
1184
1276
|
def generate_totp():
|
|
1185
1277
|
import pyotp
|
|
1186
1278
|
|
|
1187
|
-
|
|
1188
|
-
"8": [37, 84, 32, 76, 87, 90, 87, 47, 13, 75, 48, 54, 44, 28, 19, 21, 22],
|
|
1189
|
-
"7": [59, 91, 66, 74, 30, 66, 74, 38, 46, 50, 72, 61, 44, 71, 86, 39, 89],
|
|
1190
|
-
"6": [21, 24, 85, 46, 48, 35, 33, 8, 11, 63, 76, 12, 55, 77, 14, 7, 54],
|
|
1191
|
-
"5": [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54]
|
|
1192
|
-
}
|
|
1193
|
-
secret_cipher_bytes = secret_cipher_dict["8"]
|
|
1279
|
+
secret_cipher_bytes = SECRET_CIPHER_DICT[str((ver := TOTP_VER or max(map(int, SECRET_CIPHER_DICT))))]
|
|
1194
1280
|
|
|
1195
1281
|
transformed = [e ^ ((t % 33) + 9) for t, e in enumerate(secret_cipher_bytes)]
|
|
1196
1282
|
joined = "".join(str(num) for num in transformed)
|
|
@@ -1205,7 +1291,6 @@ def refresh_access_token_from_sp_dc(sp_dc: str) -> dict:
|
|
|
1205
1291
|
transport = True
|
|
1206
1292
|
init = True
|
|
1207
1293
|
session = req.Session()
|
|
1208
|
-
session.cookies.set("sp_dc", sp_dc)
|
|
1209
1294
|
data: dict = {}
|
|
1210
1295
|
token = ""
|
|
1211
1296
|
|
|
@@ -1219,13 +1304,17 @@ def refresh_access_token_from_sp_dc(sp_dc: str) -> dict:
|
|
|
1219
1304
|
"productType": "web-player",
|
|
1220
1305
|
"totp": otp_value,
|
|
1221
1306
|
"totpServer": otp_value,
|
|
1222
|
-
"totpVer":
|
|
1223
|
-
"sTime": server_time,
|
|
1224
|
-
"cTime": client_time,
|
|
1225
|
-
"buildDate": time.strftime("%Y-%m-%d", time.gmtime(server_time)),
|
|
1226
|
-
"buildVer": f"web-player_{time.strftime('%Y-%m-%d', time.gmtime(server_time))}_{server_time * 1000}_{secrets.token_hex(4)}",
|
|
1307
|
+
"totpVer": TOTP_VER,
|
|
1227
1308
|
}
|
|
1228
1309
|
|
|
1310
|
+
if TOTP_VER < 10:
|
|
1311
|
+
params.update({
|
|
1312
|
+
"sTime": server_time,
|
|
1313
|
+
"cTime": client_time,
|
|
1314
|
+
"buildDate": time.strftime("%Y-%m-%d", time.gmtime(server_time)),
|
|
1315
|
+
"buildVer": f"web-player_{time.strftime('%Y-%m-%d', time.gmtime(server_time))}_{server_time * 1000}_{secrets.token_hex(4)}",
|
|
1316
|
+
})
|
|
1317
|
+
|
|
1229
1318
|
headers = {
|
|
1230
1319
|
"User-Agent": USER_AGENT,
|
|
1231
1320
|
"Accept": "application/json",
|
|
@@ -2402,6 +2491,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2402
2491
|
song_on_loop = 1
|
|
2403
2492
|
print("\n*** Friend is currently ACTIVE !")
|
|
2404
2493
|
|
|
2494
|
+
if FLAG_FILE:
|
|
2495
|
+
flag_file_create()
|
|
2496
|
+
|
|
2405
2497
|
if sp_track.upper() in tracks_upper or sp_playlist.upper() in tracks_upper or sp_album.upper() in tracks_upper:
|
|
2406
2498
|
print("*** Track/playlist/album matched with the list!")
|
|
2407
2499
|
|
|
@@ -2413,8 +2505,8 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2413
2505
|
|
|
2414
2506
|
if ACTIVE_NOTIFICATION:
|
|
2415
2507
|
m_subject = f"Spotify user {sp_username} is active: '{sp_artist} - {sp_track}'"
|
|
2416
|
-
m_body = f"Last played: {sp_artist} - {sp_track}\nDuration: {display_time(sp_track_duration)}{playlist_m_body}\nAlbum: {sp_album}{context_m_body}\n\nApple Music URL: {apple_search_url}\nYouTube Music URL:{youtube_music_search_url}\nGenius lyrics URL: {genius_search_url}\n\nLast activity: {get_date_from_ts(sp_ts)}{get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2417
|
-
m_body_html = f"<html><head></head><body>Last played: <b><a href=\"{sp_artist_url}\">{escape(sp_artist)}</a> - <a href=\"{sp_track_url}\">{escape(sp_track)}</a></b><br>Duration: {display_time(sp_track_duration)}{playlist_m_body_html}<br>Album: <a href=\"{sp_album_url}\">{escape(sp_album)}</a>{context_m_body_html}<br><br>Apple Music URL: <a href=\"{apple_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>YouTube Music URL: <a href=\"{youtube_music_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>Genius lyrics URL: <a href=\"{genius_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2508
|
+
m_body = f"Last played: {sp_artist} - {sp_track}\nDuration: {display_time(sp_track_duration)}{playlist_m_body}\nAlbum: {sp_album}{context_m_body}\n\nApple Music URL: {apple_search_url}\nYouTube Music URL:{youtube_music_search_url}\nGenius lyrics URL: {genius_search_url}\n\nSongs played: {listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})\n\nLast activity: {get_date_from_ts(sp_ts)}{get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2509
|
+
m_body_html = f"<html><head></head><body>Last played: <b><a href=\"{sp_artist_url}\">{escape(sp_artist)}</a> - <a href=\"{sp_track_url}\">{escape(sp_track)}</a></b><br>Duration: {display_time(sp_track_duration)}{playlist_m_body_html}<br>Album: <a href=\"{sp_album_url}\">{escape(sp_album)}</a>{context_m_body_html}<br><br>Apple Music URL: <a href=\"{apple_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>YouTube Music URL: <a href=\"{youtube_music_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>Genius lyrics URL: <a href=\"{genius_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br><br>Songs played: {listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})<br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2418
2510
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2419
2511
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
2420
2512
|
|
|
@@ -2431,6 +2523,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2431
2523
|
sp_active_ts_stop = sp_ts
|
|
2432
2524
|
print(f"\n*** Friend is OFFLINE for: {calculate_timespan(int(cur_ts), int(sp_ts))}")
|
|
2433
2525
|
|
|
2526
|
+
if listened_songs:
|
|
2527
|
+
print(f"\nSongs played:\t\t\t{listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})")
|
|
2528
|
+
|
|
2434
2529
|
print(f"\nTracks/playlists/albums to monitor: {tracks}")
|
|
2435
2530
|
print_cur_ts("\nTimestamp:\t\t\t")
|
|
2436
2531
|
|
|
@@ -2722,6 +2817,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2722
2817
|
looped_songs = 0
|
|
2723
2818
|
song_on_loop = 1
|
|
2724
2819
|
|
|
2820
|
+
if FLAG_FILE:
|
|
2821
|
+
flag_file_create()
|
|
2822
|
+
|
|
2725
2823
|
print(f"\n*** Friend got ACTIVE after being offline for {calculate_timespan(int(sp_active_ts_start), int(sp_active_ts_stop))} ({get_date_from_ts(sp_active_ts_stop)})")
|
|
2726
2824
|
m_subject = f"Spotify user {sp_username} is active: '{sp_artist} - {sp_track}' (after {calculate_timespan(int(sp_active_ts_start), int(sp_active_ts_stop), show_seconds=False)} - {get_short_date_from_ts(sp_active_ts_stop)})"
|
|
2727
2825
|
friend_active_m_body = f"\n\nFriend got active after being offline for {calculate_timespan(int(sp_active_ts_start), int(sp_active_ts_stop))}\nLast activity (before getting offline): {get_date_from_ts(sp_active_ts_stop)}"
|
|
@@ -2737,8 +2835,8 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2737
2835
|
sp_active_ts_start = sp_active_ts_start_old
|
|
2738
2836
|
sp_active_ts_stop = 0
|
|
2739
2837
|
|
|
2740
|
-
m_body = f"Last played: {sp_artist} - {sp_track}\nDuration: {display_time(sp_track_duration)}{played_for_m_body}{playlist_m_body}\nAlbum: {sp_album}{context_m_body}\n\nApple Music URL: {apple_search_url}\nYouTube Music URL:{youtube_music_search_url}\nGenius lyrics URL: {genius_search_url}{friend_active_m_body}\n\nLast activity: {get_date_from_ts(sp_ts)}{get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2741
|
-
m_body_html = f"<html><head></head><body>Last played: <b><a href=\"{sp_artist_url}\">{escape(sp_artist)}</a> - <a href=\"{sp_track_url}\">{escape(sp_track)}</a></b><br>Duration: {display_time(sp_track_duration)}{played_for_m_body_html}{playlist_m_body_html}<br>Album: <a href=\"{sp_album_url}\">{escape(sp_album)}</a>{context_m_body_html}<br><br>Apple Music URL: <a href=\"{apple_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>YouTube Music URL: <a href=\"{youtube_music_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>Genius lyrics URL: <a href=\"{genius_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a>{friend_active_m_body_html}<br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2838
|
+
m_body = f"Last played: {sp_artist} - {sp_track}\nDuration: {display_time(sp_track_duration)}{played_for_m_body}{playlist_m_body}\nAlbum: {sp_album}{context_m_body}\n\nApple Music URL: {apple_search_url}\nYouTube Music URL:{youtube_music_search_url}\nGenius lyrics URL: {genius_search_url}{friend_active_m_body}\n\nSongs played: {listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})\n\nLast activity: {get_date_from_ts(sp_ts)}{get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2839
|
+
m_body_html = f"<html><head></head><body>Last played: <b><a href=\"{sp_artist_url}\">{escape(sp_artist)}</a> - <a href=\"{sp_track_url}\">{escape(sp_track)}</a></b><br>Duration: {display_time(sp_track_duration)}{played_for_m_body_html}{playlist_m_body_html}<br>Album: <a href=\"{sp_album_url}\">{escape(sp_album)}</a>{context_m_body_html}<br><br>Apple Music URL: <a href=\"{apple_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>YouTube Music URL: <a href=\"{youtube_music_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>Genius lyrics URL: <a href=\"{genius_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a>{friend_active_m_body_html}<br><br>Songs played: {listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})<br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2742
2840
|
|
|
2743
2841
|
if ACTIVE_NOTIFICATION:
|
|
2744
2842
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2752,16 +2850,16 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2752
2850
|
|
|
2753
2851
|
if (TRACK_NOTIFICATION and on_the_list and not email_sent) or (SONG_NOTIFICATION and not email_sent):
|
|
2754
2852
|
m_subject = f"Spotify user {sp_username}: '{sp_artist} - {sp_track}'"
|
|
2755
|
-
m_body = f"Last played: {sp_artist} - {sp_track}\nDuration: {display_time(sp_track_duration)}{played_for_m_body}{playlist_m_body}\nAlbum: {sp_album}{context_m_body}\n\nApple Music URL: {apple_search_url}\nYouTube Music URL:{youtube_music_search_url}\nGenius lyrics URL: {genius_search_url}\n\nLast activity: {get_date_from_ts(sp_ts)}{get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2756
|
-
m_body_html = f"<html><head></head><body>Last played: <b><a href=\"{sp_artist_url}\">{escape(sp_artist)}</a> - <a href=\"{sp_track_url}\">{escape(sp_track)}</a></b><br>Duration: {display_time(sp_track_duration)}{played_for_m_body_html}{playlist_m_body_html}<br>Album: <a href=\"{sp_album_url}\">{escape(sp_album)}</a>{context_m_body_html}<br><br>Apple Music URL: <a href=\"{apple_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>YouTube Music URL: <a href=\"{youtube_music_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>Genius lyrics URL: <a href=\"{genius_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2853
|
+
m_body = f"Last played: {sp_artist} - {sp_track}\nDuration: {display_time(sp_track_duration)}{played_for_m_body}{playlist_m_body}\nAlbum: {sp_album}{context_m_body}\n\nApple Music URL: {apple_search_url}\nYouTube Music URL:{youtube_music_search_url}\nGenius lyrics URL: {genius_search_url}\n\nSongs played: {listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})\n\nLast activity: {get_date_from_ts(sp_ts)}{get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2854
|
+
m_body_html = f"<html><head></head><body>Last played: <b><a href=\"{sp_artist_url}\">{escape(sp_artist)}</a> - <a href=\"{sp_track_url}\">{escape(sp_track)}</a></b><br>Duration: {display_time(sp_track_duration)}{played_for_m_body_html}{playlist_m_body_html}<br>Album: <a href=\"{sp_album_url}\">{escape(sp_album)}</a>{context_m_body_html}<br><br>Apple Music URL: <a href=\"{apple_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>YouTube Music URL: <a href=\"{youtube_music_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>Genius lyrics URL: <a href=\"{genius_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br><br>Songs played: {listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})<br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2757
2855
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2758
2856
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
2759
2857
|
email_sent = True
|
|
2760
2858
|
|
|
2761
2859
|
if song_on_loop == SONG_ON_LOOP_VALUE and SONG_ON_LOOP_NOTIFICATION:
|
|
2762
2860
|
m_subject = f"Spotify user {sp_username} plays song on loop: '{sp_artist} - {sp_track}'"
|
|
2763
|
-
m_body = f"Last played: {sp_artist} - {sp_track}\nDuration: {display_time(sp_track_duration)}{played_for_m_body}{playlist_m_body}\nAlbum: {sp_album}{context_m_body}\n\nApple Music URL: {apple_search_url}\nYouTube Music URL:{youtube_music_search_url}\nGenius lyrics URL: {genius_search_url}\n\nUser plays song on LOOP ({song_on_loop} times)\n\nLast activity: {get_date_from_ts(sp_ts)}{get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2764
|
-
m_body_html = f"<html><head></head><body>Last played: <b><a href=\"{sp_artist_url}\">{escape(sp_artist)}</a> - <a href=\"{sp_track_url}\">{escape(sp_track)}</a></b><br>Duration: {display_time(sp_track_duration)}{played_for_m_body_html}{playlist_m_body_html}<br>Album: <a href=\"{sp_album_url}\">{escape(sp_album)}</a>{context_m_body_html}<br><br>Apple Music URL: <a href=\"{apple_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>YouTube Music URL: <a href=\"{youtube_music_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>Genius lyrics URL: <a href=\"{genius_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br><br>User plays song on LOOP (<b>{song_on_loop}</b> times)<br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2861
|
+
m_body = f"Last played: {sp_artist} - {sp_track}\nDuration: {display_time(sp_track_duration)}{played_for_m_body}{playlist_m_body}\nAlbum: {sp_album}{context_m_body}\n\nApple Music URL: {apple_search_url}\nYouTube Music URL:{youtube_music_search_url}\nGenius lyrics URL: {genius_search_url}\n\nUser plays song on LOOP ({song_on_loop} times)\n\nSongs played: {listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})\n\nLast activity: {get_date_from_ts(sp_ts)}{get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2862
|
+
m_body_html = f"<html><head></head><body>Last played: <b><a href=\"{sp_artist_url}\">{escape(sp_artist)}</a> - <a href=\"{sp_track_url}\">{escape(sp_track)}</a></b><br>Duration: {display_time(sp_track_duration)}{played_for_m_body_html}{playlist_m_body_html}<br>Album: <a href=\"{sp_album_url}\">{escape(sp_album)}</a>{context_m_body_html}<br><br>Apple Music URL: <a href=\"{apple_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>YouTube Music URL: <a href=\"{youtube_music_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br>Genius lyrics URL: <a href=\"{genius_search_url}\">{escape(sp_artist)} - {escape(sp_track)}</a><br><br>User plays song on LOOP (<b>{song_on_loop}</b> times)<br><br>Songs played: {listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})<br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2765
2863
|
if not email_sent:
|
|
2766
2864
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2767
2865
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
@@ -2772,6 +2870,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2772
2870
|
except Exception as e:
|
|
2773
2871
|
print(f"* Error: {e}")
|
|
2774
2872
|
|
|
2873
|
+
if listened_songs:
|
|
2874
|
+
print(f"\nSongs played:\t\t\t{listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})")
|
|
2875
|
+
|
|
2775
2876
|
print_cur_ts("\nTimestamp:\t\t\t")
|
|
2776
2877
|
sp_ts_old = sp_ts
|
|
2777
2878
|
# Track has not changed
|
|
@@ -2783,6 +2884,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2783
2884
|
print(f"*** Friend got INACTIVE after listening to music for {calculate_timespan(int(sp_active_ts_stop), int(sp_active_ts_start))}")
|
|
2784
2885
|
print(f"*** Friend played music from {get_range_of_dates_from_tss(sp_active_ts_start, sp_active_ts_stop, short=True, between_sep=' to ')}")
|
|
2785
2886
|
|
|
2887
|
+
if FLAG_FILE:
|
|
2888
|
+
flag_file_delete()
|
|
2889
|
+
|
|
2786
2890
|
listened_songs_text = f"*** User played {listened_songs} songs"
|
|
2787
2891
|
listened_songs_mbody = f"\n\nUser played {listened_songs} songs"
|
|
2788
2892
|
listened_songs_mbody_html = f"<br><br>User played <b>{listened_songs}</b> songs"
|
|
@@ -2880,7 +2984,7 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2880
2984
|
|
|
2881
2985
|
|
|
2882
2986
|
def main():
|
|
2883
|
-
global CLI_CONFIG_PATH, DOTENV_FILE, LIVENESS_CHECK_COUNTER, LOGIN_REQUEST_BODY_FILE, CLIENTTOKEN_REQUEST_BODY_FILE, REFRESH_TOKEN, LOGIN_URL, USER_AGENT, DEVICE_ID, SYSTEM_ID, USER_URI_ID, SP_DC_COOKIE, CSV_FILE, MONITOR_LIST_FILE, FILE_SUFFIX, DISABLE_LOGGING, SP_LOGFILE, ACTIVE_NOTIFICATION, INACTIVE_NOTIFICATION, TRACK_NOTIFICATION, SONG_NOTIFICATION, SONG_ON_LOOP_NOTIFICATION, ERROR_NOTIFICATION, SPOTIFY_CHECK_INTERVAL, SPOTIFY_INACTIVITY_CHECK, SPOTIFY_ERROR_INTERVAL, SPOTIFY_DISAPPEARED_CHECK_INTERVAL, TRACK_SONGS, SMTP_PASSWORD, stdout_bck, APP_VERSION, CPU_ARCH, OS_BUILD, PLATFORM, OS_MAJOR, OS_MINOR, CLIENT_MODEL, TOKEN_SOURCE, ALARM_TIMEOUT, pyotp, USER_AGENT
|
|
2987
|
+
global CLI_CONFIG_PATH, DOTENV_FILE, LIVENESS_CHECK_COUNTER, LOGIN_REQUEST_BODY_FILE, CLIENTTOKEN_REQUEST_BODY_FILE, REFRESH_TOKEN, LOGIN_URL, USER_AGENT, DEVICE_ID, SYSTEM_ID, USER_URI_ID, SP_DC_COOKIE, CSV_FILE, MONITOR_LIST_FILE, FILE_SUFFIX, DISABLE_LOGGING, SP_LOGFILE, ACTIVE_NOTIFICATION, INACTIVE_NOTIFICATION, TRACK_NOTIFICATION, SONG_NOTIFICATION, SONG_ON_LOOP_NOTIFICATION, ERROR_NOTIFICATION, SPOTIFY_CHECK_INTERVAL, SPOTIFY_INACTIVITY_CHECK, SPOTIFY_ERROR_INTERVAL, SPOTIFY_DISAPPEARED_CHECK_INTERVAL, TRACK_SONGS, SMTP_PASSWORD, stdout_bck, APP_VERSION, CPU_ARCH, OS_BUILD, PLATFORM, OS_MAJOR, OS_MINOR, CLIENT_MODEL, TOKEN_SOURCE, ALARM_TIMEOUT, pyotp, USER_AGENT, FLAG_FILE, TRUNCATE_CHARS
|
|
2884
2988
|
|
|
2885
2989
|
if "--generate-config" in sys.argv:
|
|
2886
2990
|
print(CONFIG_BLOCK.strip("\n"))
|
|
@@ -3088,6 +3192,12 @@ def main():
|
|
|
3088
3192
|
type=str,
|
|
3089
3193
|
help="Filename with Spotify tracks/playlists/albums to alert on"
|
|
3090
3194
|
)
|
|
3195
|
+
opts.add_argument(
|
|
3196
|
+
"--flag-file",
|
|
3197
|
+
dest="flag_file",
|
|
3198
|
+
metavar="PATH",
|
|
3199
|
+
help="Path to flag file that is created when the user is active and deleted when inactive",
|
|
3200
|
+
)
|
|
3091
3201
|
opts.add_argument(
|
|
3092
3202
|
"--user-agent",
|
|
3093
3203
|
dest="user_agent",
|
|
@@ -3109,6 +3219,13 @@ def main():
|
|
|
3109
3219
|
default=None,
|
|
3110
3220
|
help="Disable logging to spotify_monitor_<user_uri_id/file_suffix>.log"
|
|
3111
3221
|
)
|
|
3222
|
+
opts.add_argument(
|
|
3223
|
+
"--truncate",
|
|
3224
|
+
dest="truncate",
|
|
3225
|
+
metavar="N",
|
|
3226
|
+
type=int,
|
|
3227
|
+
help="Max characters per screen line (not log), use 999 to auto-detect terminal width, ignored if -d is set"
|
|
3228
|
+
)
|
|
3112
3229
|
|
|
3113
3230
|
args = parser.parse_args()
|
|
3114
3231
|
|
|
@@ -3191,6 +3308,13 @@ def main():
|
|
|
3191
3308
|
if not check_internet():
|
|
3192
3309
|
sys.exit(1)
|
|
3193
3310
|
|
|
3311
|
+
if args.flag_file:
|
|
3312
|
+
FLAG_FILE = os.path.expanduser(args.flag_file)
|
|
3313
|
+
flag_file_delete()
|
|
3314
|
+
else:
|
|
3315
|
+
if FLAG_FILE:
|
|
3316
|
+
FLAG_FILE = os.path.expanduser(FLAG_FILE)
|
|
3317
|
+
|
|
3194
3318
|
if args.send_test_email:
|
|
3195
3319
|
print("* Sending test email notification ...\n")
|
|
3196
3320
|
if send_email("spotify_monitor: test email", "This is test email - your SMTP settings seems to be correct !", "", SMTP_SSL, smtp_timeout=5) == 0:
|
|
@@ -3405,6 +3529,18 @@ def main():
|
|
|
3405
3529
|
if not FILE_SUFFIX:
|
|
3406
3530
|
FILE_SUFFIX = str(args.user_id)
|
|
3407
3531
|
|
|
3532
|
+
if args.truncate:
|
|
3533
|
+
if args.truncate != 999:
|
|
3534
|
+
TRUNCATE_CHARS = args.truncate
|
|
3535
|
+
else:
|
|
3536
|
+
try:
|
|
3537
|
+
terminal_size = shutil.get_terminal_size()
|
|
3538
|
+
print(f"The detected terminal screen width is: {terminal_size.columns} characters\n")
|
|
3539
|
+
TRUNCATE_CHARS = terminal_size.columns
|
|
3540
|
+
except Exception as e:
|
|
3541
|
+
print(f"Error: Cannot determine terminal screen width: {e}")
|
|
3542
|
+
sys.exit(1)
|
|
3543
|
+
|
|
3408
3544
|
if args.disable_logging is True:
|
|
3409
3545
|
DISABLE_LOGGING = True
|
|
3410
3546
|
|
|
@@ -3451,7 +3587,7 @@ def main():
|
|
|
3451
3587
|
SONG_ON_LOOP_NOTIFICATION = False
|
|
3452
3588
|
ERROR_NOTIFICATION = False
|
|
3453
3589
|
|
|
3454
|
-
print(f"* Spotify polling intervals:\t[check: {display_time(SPOTIFY_CHECK_INTERVAL)}] [inactivity: {display_time(SPOTIFY_INACTIVITY_CHECK)}]\n
|
|
3590
|
+
print(f"* Spotify polling intervals:\t[check: {display_time(SPOTIFY_CHECK_INTERVAL)}] [inactivity: {display_time(SPOTIFY_INACTIVITY_CHECK)}]\n*\t\t\t\t[disappeared: {display_time(SPOTIFY_DISAPPEARED_CHECK_INTERVAL)}] [error: {display_time(SPOTIFY_ERROR_INTERVAL)}]")
|
|
3455
3591
|
print(f"* Email notifications:\t\t[active = {ACTIVE_NOTIFICATION}] [inactive = {INACTIVE_NOTIFICATION}] [tracked = {TRACK_NOTIFICATION}]\n*\t\t\t\t[songs on loop = {SONG_ON_LOOP_NOTIFICATION}] [every song = {SONG_NOTIFICATION}] [errors = {ERROR_NOTIFICATION}]")
|
|
3456
3592
|
print(f"* Token source:\t\t\t{TOKEN_SOURCE}")
|
|
3457
3593
|
print(f"* Track listened songs:\t\t{TRACK_SONGS}")
|
|
@@ -3460,6 +3596,10 @@ def main():
|
|
|
3460
3596
|
print(f"* CSV logging enabled:\t\t{bool(CSV_FILE)}" + (f" ({CSV_FILE})" if CSV_FILE else ""))
|
|
3461
3597
|
print(f"* Alert on monitored tracks:\t{bool(MONITOR_LIST_FILE)}" + (f" ({MONITOR_LIST_FILE})" if MONITOR_LIST_FILE else ""))
|
|
3462
3598
|
print(f"* Output logging enabled:\t{not DISABLE_LOGGING}" + (f" ({FINAL_LOG_PATH})" if not DISABLE_LOGGING else ""))
|
|
3599
|
+
if not DISABLE_LOGGING and TRUNCATE_CHARS > 0:
|
|
3600
|
+
print(f"* Truncate terminal lines:\t{TRUNCATE_CHARS} chars")
|
|
3601
|
+
if FLAG_FILE:
|
|
3602
|
+
print(f"* Flag file:\t\t\t{FLAG_FILE}")
|
|
3463
3603
|
print(f"* Configuration file:\t\t{cfg_path}")
|
|
3464
3604
|
print(f"* Dotenv file:\t\t\t{env_path or 'None'}\n")
|
|
3465
3605
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
spotify_monitor.py,sha256=-uJet8WI668Ihnqs3uArowPfrYa4MUzB1QPYEL_1EGg,153671
|
|
2
|
-
spotify_monitor-2.2.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
-
spotify_monitor-2.2.1.dist-info/METADATA,sha256=q0IT4d33nTghRZ4boNvp0KxUgmD17-URY6bQMTgPAI8,22613
|
|
4
|
-
spotify_monitor-2.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
spotify_monitor-2.2.1.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
|
|
6
|
-
spotify_monitor-2.2.1.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
|
|
7
|
-
spotify_monitor-2.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|