spotify-monitor 2.2__py3-none-any.whl → 2.3__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.dist-info → spotify_monitor-2.3.dist-info}/METADATA +17 -6
- spotify_monitor-2.3.dist-info/RECORD +7 -0
- spotify_monitor.py +163 -35
- spotify_monitor-2.2.dist-info/RECORD +0 -7
- {spotify_monitor-2.2.dist-info → spotify_monitor-2.3.dist-info}/WHEEL +0 -0
- {spotify_monitor-2.2.dist-info → spotify_monitor-2.3.dist-info}/entry_points.txt +0 -0
- {spotify_monitor-2.2.dist-info → spotify_monitor-2.3.dist-info}/licenses/LICENSE +0 -0
- {spotify_monitor-2.2.dist-info → spotify_monitor-2.3.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
|
|
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
|
|
@@ -25,7 +25,7 @@ Dynamic: license-file
|
|
|
25
25
|
|
|
26
26
|
# spotify_monitor
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Tool for real-time monitoring of Spotify friends' music activity feed.
|
|
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
|
|
|
@@ -200,12 +200,23 @@ If you store the `SP_DC_COOKIE` in a dotenv file you can update its value and se
|
|
|
200
200
|
|
|
201
201
|
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
202
|
|
|
203
|
-
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com))
|
|
203
|
+
- Run an intercepting proxy of your choice (like [Proxyman](https://proxyman.com) - the trial version is sufficient)
|
|
204
204
|
|
|
205
|
-
-
|
|
206
|
-
-
|
|
205
|
+
- Enable SSL traffic decryption for `spotify.com` domain
|
|
206
|
+
- in Proxyman: click **Tools → SSL Proxying List → + button → Add Domain → paste `*.spotify.com` → Add**
|
|
207
207
|
|
|
208
|
-
-
|
|
208
|
+
- 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
|
+
|
|
210
|
+
- If you don't see this request, try following steps (stop once it works):
|
|
211
|
+
- restart the Spotify desktop client
|
|
212
|
+
- log out from the Spotify desktop client and log back in
|
|
213
|
+
- point Spotify at the intercepting proxy directly in its settings, i.e. in **Spotify → Settings → Proxy Settings**, set:
|
|
214
|
+
- **proxy type**: `HTTP`
|
|
215
|
+
- **host**: `127.0.0.1` (IP/FQDN of your proxy, for Proxyman use the IP you see at the top bar)
|
|
216
|
+
- **port**: `9090` (port of your proxy, for Proxyman use the port you see at the top bar)
|
|
217
|
+
- 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
|
|
218
|
+
- 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
|
|
219
|
+
- try an older version of the Spotify desktop client
|
|
209
220
|
|
|
210
221
|
- Export the login request body (a binary Protobuf payload) to a file (e.g. ***login-request-body-file***)
|
|
211
222
|
- In Proxyman: **right click the request → Export → Request Body → Save File**.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
spotify_monitor.py,sha256=Nq4RTsG9bHUeVpGJLOOCX8lr5xySEniNsu4aohrpU-o,158392
|
|
2
|
+
spotify_monitor-2.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
+
spotify_monitor-2.3.dist-info/METADATA,sha256=MFBSxyNhzVr5ZCJHMWWenD4CH9EWGYvgA0UB2ZM-bgI,23411
|
|
4
|
+
spotify_monitor-2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
+
spotify_monitor-2.3.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
|
|
6
|
+
spotify_monitor-2.3.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
|
|
7
|
+
spotify_monitor-2.3.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
|
|
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 (optional, needed when the token source is set to cookie)
|
|
|
15
15
|
python-dotenv (optional)
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
VERSION = "2.
|
|
18
|
+
VERSION = "2.3"
|
|
19
19
|
|
|
20
20
|
# ---------------------------
|
|
21
21
|
# CONFIGURATION SECTION START
|
|
@@ -149,6 +149,10 @@ SP_USER_GOT_OFFLINE_TRACK_ID = ""
|
|
|
149
149
|
# Set to 0 to keep playing indefinitely until manually paused
|
|
150
150
|
SP_USER_GOT_OFFLINE_DELAY_BEFORE_PAUSE = 5 # 5 seconds
|
|
151
151
|
|
|
152
|
+
# Occasionally, the Spotify API glitches and reports that the user has disappeared from the list of friends
|
|
153
|
+
# To avoid false alarms, we delay alerts until this happens REMOVED_DISAPPEARED_COUNTER times in a row
|
|
154
|
+
REMOVED_DISAPPEARED_COUNTER = 4
|
|
155
|
+
|
|
152
156
|
# Optional: specify user agent manually
|
|
153
157
|
#
|
|
154
158
|
# When the token source is 'cookie' - set it to web browser user agent, some examples:
|
|
@@ -219,6 +223,18 @@ HORIZONTAL_LINE = 113
|
|
|
219
223
|
# Whether to clear the terminal screen after starting the tool
|
|
220
224
|
CLEAR_SCREEN = True
|
|
221
225
|
|
|
226
|
+
# Path to a file that is created when the user is active and deleted when inactive
|
|
227
|
+
# Useful for external tools to detect streaming status
|
|
228
|
+
# Can also be set via the --flag-file flag
|
|
229
|
+
FLAG_FILE = ""
|
|
230
|
+
|
|
231
|
+
# Max characters per line when printing to screen to avoid line wrapping
|
|
232
|
+
# Does not affect log file output
|
|
233
|
+
# Set to 999 to auto-detect terminal width
|
|
234
|
+
# Applies only when DISABLE_LOGGING is False
|
|
235
|
+
# Can also be set via the --truncate flag
|
|
236
|
+
TRUNCATE_CHARS = 0
|
|
237
|
+
|
|
222
238
|
# Value added/subtracted via signal handlers to adjust inactivity timeout (SPOTIFY_INACTIVITY_CHECK); in seconds
|
|
223
239
|
SPOTIFY_INACTIVITY_CHECK_SIGNAL_VALUE = 30 # 30 seconds
|
|
224
240
|
|
|
@@ -409,6 +425,7 @@ SONG_ON_LOOP_VALUE = 0
|
|
|
409
425
|
SKIPPED_SONG_THRESHOLD = 0
|
|
410
426
|
SP_USER_GOT_OFFLINE_TRACK_ID = ""
|
|
411
427
|
SP_USER_GOT_OFFLINE_DELAY_BEFORE_PAUSE = 0
|
|
428
|
+
REMOVED_DISAPPEARED_COUNTER = 0
|
|
412
429
|
USER_AGENT = ""
|
|
413
430
|
LIVENESS_CHECK_INTERVAL = 0
|
|
414
431
|
CHECK_INTERNET_URL = ""
|
|
@@ -429,6 +446,8 @@ CLEAR_SCREEN = False
|
|
|
429
446
|
SPOTIFY_INACTIVITY_CHECK_SIGNAL_VALUE = 0
|
|
430
447
|
TOKEN_MAX_RETRIES = 0
|
|
431
448
|
TOKEN_RETRY_TIMEOUT = 0.0
|
|
449
|
+
FLAG_FILE = ""
|
|
450
|
+
TRUNCATE_CHARS = 0
|
|
432
451
|
|
|
433
452
|
exec(CONFIG_BLOCK, globals())
|
|
434
453
|
|
|
@@ -458,6 +477,12 @@ SP_CACHED_CLIENT_ID = ""
|
|
|
458
477
|
# URL of the Spotify Web Player endpoint to get access token
|
|
459
478
|
TOKEN_URL = "https://open.spotify.com/api/token"
|
|
460
479
|
|
|
480
|
+
# URL of the endpoint to get server time needed to create TOTP object
|
|
481
|
+
SERVER_TIME_URL = "https://open.spotify.com/"
|
|
482
|
+
|
|
483
|
+
# Identifier used to select the appropriate encrypted secret from secret_cipher_dict when generating a TOTP token
|
|
484
|
+
TOTP_VER = 10
|
|
485
|
+
|
|
461
486
|
# Variables for caching functionality of the Spotify client token to avoid unnecessary refreshing
|
|
462
487
|
SP_CACHED_CLIENT_TOKEN = None
|
|
463
488
|
SP_CLIENT_TOKEN_EXPIRES_AT = 0
|
|
@@ -535,6 +560,19 @@ SESSION.mount("https://", adapter)
|
|
|
535
560
|
SESSION.mount("http://", adapter)
|
|
536
561
|
|
|
537
562
|
|
|
563
|
+
# Truncates each line of a string to a specified number of characters including tab expansion and multi-line support
|
|
564
|
+
def truncate_string_per_line(message, truncate_chars, tabsize=8):
|
|
565
|
+
lines = message.split('\n')
|
|
566
|
+
truncated_lines = []
|
|
567
|
+
|
|
568
|
+
for line in lines:
|
|
569
|
+
expanded_line = line.expandtabs(tabsize=tabsize)
|
|
570
|
+
truncated_line = expanded_line[:truncate_chars]
|
|
571
|
+
truncated_lines.append(truncated_line)
|
|
572
|
+
|
|
573
|
+
return '\n'.join(truncated_lines)
|
|
574
|
+
|
|
575
|
+
|
|
538
576
|
# Logger class to output messages to stdout and log file
|
|
539
577
|
class Logger(object):
|
|
540
578
|
def __init__(self, filename):
|
|
@@ -542,8 +580,10 @@ class Logger(object):
|
|
|
542
580
|
self.logfile = open(filename, "a", buffering=1, encoding="utf-8")
|
|
543
581
|
|
|
544
582
|
def write(self, message):
|
|
545
|
-
self.terminal.write(message)
|
|
546
583
|
self.logfile.write(message)
|
|
584
|
+
if (TRUNCATE_CHARS):
|
|
585
|
+
message = truncate_string_per_line(message, TRUNCATE_CHARS)
|
|
586
|
+
self.terminal.write(message)
|
|
547
587
|
self.terminal.flush()
|
|
548
588
|
self.logfile.flush()
|
|
549
589
|
|
|
@@ -551,6 +591,22 @@ class Logger(object):
|
|
|
551
591
|
pass
|
|
552
592
|
|
|
553
593
|
|
|
594
|
+
def flag_file_create():
|
|
595
|
+
try:
|
|
596
|
+
with open(FLAG_FILE, "w") as f:
|
|
597
|
+
f.write("This indicates active streaming by monitored user")
|
|
598
|
+
except Exception:
|
|
599
|
+
pass
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def flag_file_delete():
|
|
603
|
+
try:
|
|
604
|
+
if os.path.exists(FLAG_FILE):
|
|
605
|
+
os.remove(FLAG_FILE)
|
|
606
|
+
except Exception:
|
|
607
|
+
pass
|
|
608
|
+
|
|
609
|
+
|
|
554
610
|
# Class used to generate timeout exceptions
|
|
555
611
|
class TimeoutException(Exception):
|
|
556
612
|
pass
|
|
@@ -565,6 +621,8 @@ def timeout_handler(sig, frame):
|
|
|
565
621
|
def signal_handler(sig, frame):
|
|
566
622
|
sys.stdout = stdout_bck
|
|
567
623
|
print('\n* You pressed Ctrl+C, tool is terminated.')
|
|
624
|
+
if FLAG_FILE:
|
|
625
|
+
flag_file_delete()
|
|
568
626
|
sys.exit(0)
|
|
569
627
|
|
|
570
628
|
|
|
@@ -1162,7 +1220,7 @@ def fetch_server_time(session: req.Session, ua: str) -> int:
|
|
|
1162
1220
|
if platform.system() != 'Windows':
|
|
1163
1221
|
signal.signal(signal.SIGALRM, timeout_handler)
|
|
1164
1222
|
signal.alarm(FUNCTION_TIMEOUT + 2)
|
|
1165
|
-
response = session.head(
|
|
1223
|
+
response = session.head(SERVER_TIME_URL, headers=headers, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
1166
1224
|
response.raise_for_status()
|
|
1167
1225
|
except TimeoutException as e:
|
|
1168
1226
|
raise Exception(f"fetch_server_time() head network request timeout after {display_time(FUNCTION_TIMEOUT + 2)}: {e}")
|
|
@@ -1172,17 +1230,26 @@ def fetch_server_time(session: req.Session, ua: str) -> int:
|
|
|
1172
1230
|
if platform.system() != 'Windows':
|
|
1173
1231
|
signal.alarm(0)
|
|
1174
1232
|
|
|
1175
|
-
|
|
1233
|
+
date_hdr = response.headers.get("Date")
|
|
1234
|
+
if not date_hdr:
|
|
1235
|
+
raise Exception("fetch_server_time() missing 'Date' header")
|
|
1236
|
+
|
|
1237
|
+
return int(parsedate_to_datetime(date_hdr).timestamp())
|
|
1176
1238
|
|
|
1177
1239
|
|
|
1178
1240
|
# Creates a TOTP object using a secret derived from transformed cipher bytes
|
|
1179
1241
|
def generate_totp():
|
|
1180
1242
|
import pyotp
|
|
1181
1243
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1244
|
+
secret_cipher_dict = {
|
|
1245
|
+
"10": [61, 110, 58, 98, 35, 79, 117, 69, 102, 72, 92, 102, 69, 93, 41, 101, 42, 75],
|
|
1246
|
+
"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],
|
|
1247
|
+
"8": [37, 84, 32, 76, 87, 90, 87, 47, 13, 75, 48, 54, 44, 28, 19, 21, 22],
|
|
1248
|
+
"7": [59, 91, 66, 74, 30, 66, 74, 38, 46, 50, 72, 61, 44, 71, 86, 39, 89],
|
|
1249
|
+
"6": [21, 24, 85, 46, 48, 35, 33, 8, 11, 63, 76, 12, 55, 77, 14, 7, 54],
|
|
1250
|
+
"5": [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54],
|
|
1251
|
+
}
|
|
1252
|
+
secret_cipher_bytes = secret_cipher_dict[str(TOTP_VER)]
|
|
1186
1253
|
|
|
1187
1254
|
transformed = [e ^ ((t % 33) + 9) for t, e in enumerate(secret_cipher_bytes)]
|
|
1188
1255
|
joined = "".join(str(num) for num in transformed)
|
|
@@ -1197,7 +1264,6 @@ def refresh_access_token_from_sp_dc(sp_dc: str) -> dict:
|
|
|
1197
1264
|
transport = True
|
|
1198
1265
|
init = True
|
|
1199
1266
|
session = req.Session()
|
|
1200
|
-
session.cookies.set("sp_dc", sp_dc)
|
|
1201
1267
|
data: dict = {}
|
|
1202
1268
|
token = ""
|
|
1203
1269
|
|
|
@@ -1211,13 +1277,17 @@ def refresh_access_token_from_sp_dc(sp_dc: str) -> dict:
|
|
|
1211
1277
|
"productType": "web-player",
|
|
1212
1278
|
"totp": otp_value,
|
|
1213
1279
|
"totpServer": otp_value,
|
|
1214
|
-
"totpVer":
|
|
1215
|
-
"sTime": server_time,
|
|
1216
|
-
"cTime": client_time,
|
|
1217
|
-
"buildDate": time.strftime("%Y-%m-%d", time.gmtime(server_time)),
|
|
1218
|
-
"buildVer": f"web-player_{time.strftime('%Y-%m-%d', time.gmtime(server_time))}_{server_time * 1000}_{secrets.token_hex(4)}",
|
|
1280
|
+
"totpVer": TOTP_VER,
|
|
1219
1281
|
}
|
|
1220
1282
|
|
|
1283
|
+
if TOTP_VER < 10:
|
|
1284
|
+
params.update({
|
|
1285
|
+
"sTime": server_time,
|
|
1286
|
+
"cTime": client_time,
|
|
1287
|
+
"buildDate": time.strftime("%Y-%m-%d", time.gmtime(server_time)),
|
|
1288
|
+
"buildVer": f"web-player_{time.strftime('%Y-%m-%d', time.gmtime(server_time))}_{server_time * 1000}_{secrets.token_hex(4)}",
|
|
1289
|
+
})
|
|
1290
|
+
|
|
1221
1291
|
headers = {
|
|
1222
1292
|
"User-Agent": USER_AGENT,
|
|
1223
1293
|
"Accept": "application/json",
|
|
@@ -2251,7 +2321,7 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2251
2321
|
SP_CACHED_ACCESS_TOKEN = None
|
|
2252
2322
|
|
|
2253
2323
|
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']
|
|
2254
|
-
cookie_errs = ['access token', 'unauthorized']
|
|
2324
|
+
cookie_errs = ['access token', 'unauthorized', 'unsuccessful token request']
|
|
2255
2325
|
|
|
2256
2326
|
if TOKEN_SOURCE == 'client' and any(k in err for k in client_errs):
|
|
2257
2327
|
print(f"* Error: client or refresh token may be invalid or expired!")
|
|
@@ -2264,11 +2334,11 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2264
2334
|
email_sent = True
|
|
2265
2335
|
|
|
2266
2336
|
elif TOKEN_SOURCE == 'cookie' and any(k in err for k in cookie_errs):
|
|
2267
|
-
print(f"* Error: sp_dc may be invalid or
|
|
2337
|
+
print(f"* Error: sp_dc may be invalid/expired or Spotify has broken sth again!")
|
|
2268
2338
|
if ERROR_NOTIFICATION and not email_sent:
|
|
2269
|
-
m_subject = f"spotify_monitor: sp_dc may be invalid or
|
|
2270
|
-
m_body = f"sp_dc may be invalid or
|
|
2271
|
-
m_body_html = f"<html><head></head><body>sp_dc may be invalid or
|
|
2339
|
+
m_subject = f"spotify_monitor: sp_dc may be invalid/expired or Spotify has broken sth again! (uri: {user_uri_id})"
|
|
2340
|
+
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: ')}"
|
|
2341
|
+
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>"
|
|
2272
2342
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2273
2343
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
2274
2344
|
email_sent = True
|
|
@@ -2394,6 +2464,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2394
2464
|
song_on_loop = 1
|
|
2395
2465
|
print("\n*** Friend is currently ACTIVE !")
|
|
2396
2466
|
|
|
2467
|
+
if FLAG_FILE:
|
|
2468
|
+
flag_file_create()
|
|
2469
|
+
|
|
2397
2470
|
if sp_track.upper() in tracks_upper or sp_playlist.upper() in tracks_upper or sp_album.upper() in tracks_upper:
|
|
2398
2471
|
print("*** Track/playlist/album matched with the list!")
|
|
2399
2472
|
|
|
@@ -2405,8 +2478,8 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2405
2478
|
|
|
2406
2479
|
if ACTIVE_NOTIFICATION:
|
|
2407
2480
|
m_subject = f"Spotify user {sp_username} is active: '{sp_artist} - {sp_track}'"
|
|
2408
|
-
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: ')}"
|
|
2409
|
-
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>"
|
|
2481
|
+
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: ')}"
|
|
2482
|
+
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>"
|
|
2410
2483
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2411
2484
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
2412
2485
|
|
|
@@ -2423,6 +2496,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2423
2496
|
sp_active_ts_stop = sp_ts
|
|
2424
2497
|
print(f"\n*** Friend is OFFLINE for: {calculate_timespan(int(cur_ts), int(sp_ts))}")
|
|
2425
2498
|
|
|
2499
|
+
if listened_songs:
|
|
2500
|
+
print(f"\nSongs played:\t\t\t{listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})")
|
|
2501
|
+
|
|
2426
2502
|
print(f"\nTracks/playlists/albums to monitor: {tracks}")
|
|
2427
2503
|
print_cur_ts("\nTimestamp:\t\t\t")
|
|
2428
2504
|
|
|
@@ -2431,6 +2507,8 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2431
2507
|
|
|
2432
2508
|
email_sent = False
|
|
2433
2509
|
|
|
2510
|
+
disappeared_counter = 0
|
|
2511
|
+
|
|
2434
2512
|
# Primary loop
|
|
2435
2513
|
while True:
|
|
2436
2514
|
|
|
@@ -2498,7 +2576,7 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2498
2576
|
print(f"* Error, retrying in {display_time(SPOTIFY_ERROR_INTERVAL)}: '{e}'")
|
|
2499
2577
|
|
|
2500
2578
|
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']
|
|
2501
|
-
cookie_errs = ['access token', 'unauthorized']
|
|
2579
|
+
cookie_errs = ['access token', 'unauthorized', 'unsuccessful token request']
|
|
2502
2580
|
|
|
2503
2581
|
if TOKEN_SOURCE == 'client' and any(k in err for k in client_errs):
|
|
2504
2582
|
print(f"* Error: client or refresh token may be invalid or expired!")
|
|
@@ -2511,11 +2589,11 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2511
2589
|
email_sent = True
|
|
2512
2590
|
|
|
2513
2591
|
elif TOKEN_SOURCE == 'cookie' and any(k in err for k in cookie_errs):
|
|
2514
|
-
print(f"* Error: sp_dc may be invalid or
|
|
2592
|
+
print(f"* Error: sp_dc may be invalid/expired or Spotify has broken sth again!")
|
|
2515
2593
|
if ERROR_NOTIFICATION and not email_sent:
|
|
2516
|
-
m_subject = f"spotify_monitor: sp_dc may be invalid or
|
|
2517
|
-
m_body = f"sp_dc may be invalid or
|
|
2518
|
-
m_body_html = f"<html><head></head><body>sp_dc may be invalid or
|
|
2594
|
+
m_subject = f"spotify_monitor: sp_dc may be invalid/expired or Spotify has broken sth again! (uri: {user_uri_id})"
|
|
2595
|
+
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: ')}"
|
|
2596
|
+
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>"
|
|
2519
2597
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2520
2598
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
2521
2599
|
email_sent = True
|
|
@@ -2525,6 +2603,10 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2525
2603
|
|
|
2526
2604
|
if sp_found is False:
|
|
2527
2605
|
# User has disappeared from the Spotify's friend list or account has been removed
|
|
2606
|
+
disappeared_counter += 1
|
|
2607
|
+
if disappeared_counter < REMOVED_DISAPPEARED_COUNTER:
|
|
2608
|
+
time.sleep(SPOTIFY_CHECK_INTERVAL)
|
|
2609
|
+
continue
|
|
2528
2610
|
if user_not_found is False:
|
|
2529
2611
|
if is_user_removed(sp_accessToken, user_uri_id):
|
|
2530
2612
|
print(f"Spotify user '{user_uri_id}' ({sp_username}) was probably removed! Retrying in {display_time(SPOTIFY_DISAPPEARED_CHECK_INTERVAL)} intervals")
|
|
@@ -2548,6 +2630,7 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2548
2630
|
continue
|
|
2549
2631
|
else:
|
|
2550
2632
|
# User reappeared in the Spotify's friend list
|
|
2633
|
+
disappeared_counter = 0
|
|
2551
2634
|
if user_not_found is True:
|
|
2552
2635
|
print(f"Spotify user {user_uri_id} ({sp_username}) has reappeared!")
|
|
2553
2636
|
if ERROR_NOTIFICATION:
|
|
@@ -2707,6 +2790,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2707
2790
|
looped_songs = 0
|
|
2708
2791
|
song_on_loop = 1
|
|
2709
2792
|
|
|
2793
|
+
if FLAG_FILE:
|
|
2794
|
+
flag_file_create()
|
|
2795
|
+
|
|
2710
2796
|
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)})")
|
|
2711
2797
|
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)})"
|
|
2712
2798
|
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)}"
|
|
@@ -2722,8 +2808,8 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2722
2808
|
sp_active_ts_start = sp_active_ts_start_old
|
|
2723
2809
|
sp_active_ts_stop = 0
|
|
2724
2810
|
|
|
2725
|
-
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: ')}"
|
|
2726
|
-
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>"
|
|
2811
|
+
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: ')}"
|
|
2812
|
+
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>"
|
|
2727
2813
|
|
|
2728
2814
|
if ACTIVE_NOTIFICATION:
|
|
2729
2815
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2737,16 +2823,16 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2737
2823
|
|
|
2738
2824
|
if (TRACK_NOTIFICATION and on_the_list and not email_sent) or (SONG_NOTIFICATION and not email_sent):
|
|
2739
2825
|
m_subject = f"Spotify user {sp_username}: '{sp_artist} - {sp_track}'"
|
|
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}\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><br><br>Last activity: {get_date_from_ts(sp_ts)}{get_cur_ts('<br>Timestamp: ')}</body></html>"
|
|
2826
|
+
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: ')}"
|
|
2827
|
+
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>"
|
|
2742
2828
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2743
2829
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
2744
2830
|
email_sent = True
|
|
2745
2831
|
|
|
2746
2832
|
if song_on_loop == SONG_ON_LOOP_VALUE and SONG_ON_LOOP_NOTIFICATION:
|
|
2747
2833
|
m_subject = f"Spotify user {sp_username} plays song on loop: '{sp_artist} - {sp_track}'"
|
|
2748
|
-
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: ')}"
|
|
2749
|
-
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>"
|
|
2834
|
+
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: ')}"
|
|
2835
|
+
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>"
|
|
2750
2836
|
if not email_sent:
|
|
2751
2837
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2752
2838
|
send_email(m_subject, m_body, m_body_html, SMTP_SSL)
|
|
@@ -2757,6 +2843,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2757
2843
|
except Exception as e:
|
|
2758
2844
|
print(f"* Error: {e}")
|
|
2759
2845
|
|
|
2846
|
+
if listened_songs:
|
|
2847
|
+
print(f"\nSongs played:\t\t\t{listened_songs} ({calculate_timespan(int(sp_ts), int(sp_active_ts_start))})")
|
|
2848
|
+
|
|
2760
2849
|
print_cur_ts("\nTimestamp:\t\t\t")
|
|
2761
2850
|
sp_ts_old = sp_ts
|
|
2762
2851
|
# Track has not changed
|
|
@@ -2768,6 +2857,9 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2768
2857
|
print(f"*** Friend got INACTIVE after listening to music for {calculate_timespan(int(sp_active_ts_stop), int(sp_active_ts_start))}")
|
|
2769
2858
|
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 ')}")
|
|
2770
2859
|
|
|
2860
|
+
if FLAG_FILE:
|
|
2861
|
+
flag_file_delete()
|
|
2862
|
+
|
|
2771
2863
|
listened_songs_text = f"*** User played {listened_songs} songs"
|
|
2772
2864
|
listened_songs_mbody = f"\n\nUser played {listened_songs} songs"
|
|
2773
2865
|
listened_songs_mbody_html = f"<br><br>User played <b>{listened_songs}</b> songs"
|
|
@@ -2865,7 +2957,7 @@ def spotify_monitor_friend_uri(user_uri_id, tracks, csv_file_name):
|
|
|
2865
2957
|
|
|
2866
2958
|
|
|
2867
2959
|
def main():
|
|
2868
|
-
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
|
|
2960
|
+
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
|
|
2869
2961
|
|
|
2870
2962
|
if "--generate-config" in sys.argv:
|
|
2871
2963
|
print(CONFIG_BLOCK.strip("\n"))
|
|
@@ -3073,6 +3165,12 @@ def main():
|
|
|
3073
3165
|
type=str,
|
|
3074
3166
|
help="Filename with Spotify tracks/playlists/albums to alert on"
|
|
3075
3167
|
)
|
|
3168
|
+
opts.add_argument(
|
|
3169
|
+
"--flag-file",
|
|
3170
|
+
dest="flag_file",
|
|
3171
|
+
metavar="PATH",
|
|
3172
|
+
help="Path to flag file that is created when the user is active and deleted when inactive",
|
|
3173
|
+
)
|
|
3076
3174
|
opts.add_argument(
|
|
3077
3175
|
"--user-agent",
|
|
3078
3176
|
dest="user_agent",
|
|
@@ -3094,6 +3192,13 @@ def main():
|
|
|
3094
3192
|
default=None,
|
|
3095
3193
|
help="Disable logging to spotify_monitor_<user_uri_id/file_suffix>.log"
|
|
3096
3194
|
)
|
|
3195
|
+
opts.add_argument(
|
|
3196
|
+
"--truncate",
|
|
3197
|
+
dest="truncate",
|
|
3198
|
+
metavar="N",
|
|
3199
|
+
type=int,
|
|
3200
|
+
help="Max characters per screen line (not log), use 999 to auto-detect terminal width, ignored if -d is set"
|
|
3201
|
+
)
|
|
3097
3202
|
|
|
3098
3203
|
args = parser.parse_args()
|
|
3099
3204
|
|
|
@@ -3176,6 +3281,13 @@ def main():
|
|
|
3176
3281
|
if not check_internet():
|
|
3177
3282
|
sys.exit(1)
|
|
3178
3283
|
|
|
3284
|
+
if args.flag_file:
|
|
3285
|
+
FLAG_FILE = os.path.expanduser(args.flag_file)
|
|
3286
|
+
flag_file_delete()
|
|
3287
|
+
else:
|
|
3288
|
+
if FLAG_FILE:
|
|
3289
|
+
FLAG_FILE = os.path.expanduser(FLAG_FILE)
|
|
3290
|
+
|
|
3179
3291
|
if args.send_test_email:
|
|
3180
3292
|
print("* Sending test email notification ...\n")
|
|
3181
3293
|
if send_email("spotify_monitor: test email", "This is test email - your SMTP settings seems to be correct !", "", SMTP_SSL, smtp_timeout=5) == 0:
|
|
@@ -3390,6 +3502,18 @@ def main():
|
|
|
3390
3502
|
if not FILE_SUFFIX:
|
|
3391
3503
|
FILE_SUFFIX = str(args.user_id)
|
|
3392
3504
|
|
|
3505
|
+
if args.truncate:
|
|
3506
|
+
if args.truncate != 999:
|
|
3507
|
+
TRUNCATE_CHARS = args.truncate
|
|
3508
|
+
else:
|
|
3509
|
+
try:
|
|
3510
|
+
terminal_size = shutil.get_terminal_size()
|
|
3511
|
+
print(f"The detected terminal screen width is: {terminal_size.columns} characters\n")
|
|
3512
|
+
TRUNCATE_CHARS = terminal_size.columns
|
|
3513
|
+
except Exception as e:
|
|
3514
|
+
print(f"Error: Cannot determine terminal screen width: {e}")
|
|
3515
|
+
sys.exit(1)
|
|
3516
|
+
|
|
3393
3517
|
if args.disable_logging is True:
|
|
3394
3518
|
DISABLE_LOGGING = True
|
|
3395
3519
|
|
|
@@ -3436,7 +3560,7 @@ def main():
|
|
|
3436
3560
|
SONG_ON_LOOP_NOTIFICATION = False
|
|
3437
3561
|
ERROR_NOTIFICATION = False
|
|
3438
3562
|
|
|
3439
|
-
print(f"* Spotify polling intervals:\t[check: {display_time(SPOTIFY_CHECK_INTERVAL)}] [inactivity: {display_time(SPOTIFY_INACTIVITY_CHECK)}]\n
|
|
3563
|
+
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)}]")
|
|
3440
3564
|
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}]")
|
|
3441
3565
|
print(f"* Token source:\t\t\t{TOKEN_SOURCE}")
|
|
3442
3566
|
print(f"* Track listened songs:\t\t{TRACK_SONGS}")
|
|
@@ -3445,6 +3569,10 @@ def main():
|
|
|
3445
3569
|
print(f"* CSV logging enabled:\t\t{bool(CSV_FILE)}" + (f" ({CSV_FILE})" if CSV_FILE else ""))
|
|
3446
3570
|
print(f"* Alert on monitored tracks:\t{bool(MONITOR_LIST_FILE)}" + (f" ({MONITOR_LIST_FILE})" if MONITOR_LIST_FILE else ""))
|
|
3447
3571
|
print(f"* Output logging enabled:\t{not DISABLE_LOGGING}" + (f" ({FINAL_LOG_PATH})" if not DISABLE_LOGGING else ""))
|
|
3572
|
+
if not DISABLE_LOGGING and TRUNCATE_CHARS > 0:
|
|
3573
|
+
print(f"* Truncate terminal lines:\t{TRUNCATE_CHARS} chars")
|
|
3574
|
+
if FLAG_FILE:
|
|
3575
|
+
print(f"* Flag file:\t\t\t{FLAG_FILE}")
|
|
3448
3576
|
print(f"* Configuration file:\t\t{cfg_path}")
|
|
3449
3577
|
print(f"* Dotenv file:\t\t\t{env_path or 'None'}\n")
|
|
3450
3578
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
spotify_monitor.py,sha256=OGM62kDeXOa8SO0RZK1Lhuagbw_Ltchy_oWtWW3xkIA,152513
|
|
2
|
-
spotify_monitor-2.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
-
spotify_monitor-2.2.dist-info/METADATA,sha256=yXfwE_3vT7INKsIWxyBX9EgC2i3SL7-chl9gFNmH1lY,22382
|
|
4
|
-
spotify_monitor-2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
spotify_monitor-2.2.dist-info/entry_points.txt,sha256=8HzePfUcCSXrYaXOwLbNNYO8GJcnhgCSl4wcDNECht8,57
|
|
6
|
-
spotify_monitor-2.2.dist-info/top_level.txt,sha256=EP6IPD4vHT12rLM5b_jo2i3nrfOuwk3ehhr2gWdQx9Y,16
|
|
7
|
-
spotify_monitor-2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|