StreamingCommunity 2.7.0__py3-none-any.whl → 2.8.0__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 StreamingCommunity might be problematic. Click here for more details.
- StreamingCommunity/Api/Player/ddl.py +2 -2
- StreamingCommunity/Api/Player/maxstream.py +7 -13
- StreamingCommunity/Api/Player/supervideo.py +7 -33
- StreamingCommunity/Api/Player/vixcloud.py +8 -80
- StreamingCommunity/Api/Site/1337xx/__init__.py +8 -1
- StreamingCommunity/Api/Site/1337xx/site.py +10 -16
- StreamingCommunity/Api/Site/1337xx/title.py +4 -1
- StreamingCommunity/Api/Site/animeunity/__init__.py +9 -2
- StreamingCommunity/Api/Site/animeunity/film_serie.py +7 -1
- StreamingCommunity/Api/Site/animeunity/site.py +8 -10
- StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +1 -1
- StreamingCommunity/Api/Site/cb01new/__init__.py +8 -1
- StreamingCommunity/Api/Site/cb01new/film.py +7 -1
- StreamingCommunity/Api/Site/cb01new/site.py +16 -15
- StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +9 -2
- StreamingCommunity/Api/Site/ddlstreamitaly/series.py +7 -1
- StreamingCommunity/Api/Site/ddlstreamitaly/site.py +10 -15
- StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +1 -1
- StreamingCommunity/Api/Site/guardaserie/__init__.py +9 -2
- StreamingCommunity/Api/Site/guardaserie/series.py +9 -1
- StreamingCommunity/Api/Site/guardaserie/site.py +12 -17
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +1 -1
- StreamingCommunity/Api/Site/mostraguarda/__init__.py +6 -2
- StreamingCommunity/Api/Site/mostraguarda/film.py +7 -3
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +9 -2
- StreamingCommunity/Api/Site/streamingcommunity/film.py +8 -1
- StreamingCommunity/Api/Site/streamingcommunity/series.py +14 -5
- StreamingCommunity/Api/Site/streamingcommunity/site.py +10 -15
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +2 -2
- StreamingCommunity/Api/Template/Util/__init__.py +0 -1
- StreamingCommunity/Api/Template/Util/get_domain.py +24 -66
- StreamingCommunity/Api/Template/Util/manage_ep.py +10 -5
- StreamingCommunity/Api/Template/config_loader.py +8 -8
- StreamingCommunity/Api/Template/site.py +3 -6
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +10 -13
- StreamingCommunity/Lib/Downloader/HLS/segments.py +11 -31
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +12 -9
- StreamingCommunity/Lib/Downloader/TOR/downloader.py +109 -101
- StreamingCommunity/Lib/FFmpeg/__init__.py +1 -1
- StreamingCommunity/Lib/FFmpeg/capture.py +10 -12
- StreamingCommunity/Lib/FFmpeg/command.py +15 -14
- StreamingCommunity/Lib/FFmpeg/util.py +9 -38
- StreamingCommunity/Lib/M3U8/decryptor.py +72 -146
- StreamingCommunity/Lib/M3U8/estimator.py +8 -16
- StreamingCommunity/Lib/M3U8/parser.py +1 -17
- StreamingCommunity/Lib/M3U8/url_fixer.py +1 -4
- StreamingCommunity/Lib/TMBD/__init__.py +2 -0
- StreamingCommunity/Lib/TMBD/obj_tmbd.py +3 -17
- StreamingCommunity/Lib/TMBD/tmdb.py +4 -9
- StreamingCommunity/TelegramHelp/telegram_bot.py +50 -50
- StreamingCommunity/Upload/update.py +3 -2
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/color.py +1 -1
- StreamingCommunity/Util/{_jsonConfig.py → config_json.py} +148 -54
- StreamingCommunity/Util/headers.py +2 -38
- StreamingCommunity/Util/logger.py +72 -42
- StreamingCommunity/Util/message.py +8 -3
- StreamingCommunity/Util/os.py +41 -93
- StreamingCommunity/Util/table.py +8 -17
- StreamingCommunity/run.py +26 -34
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.8.0.dist-info}/METADATA +165 -92
- StreamingCommunity-2.8.0.dist-info/RECORD +75 -0
- StreamingCommunity/Api/Template/Util/recall_search.py +0 -37
- StreamingCommunity/Lib/Downloader/HLS/proxyes.py +0 -110
- StreamingCommunity/Util/call_stack.py +0 -42
- StreamingCommunity/Util/console.py +0 -12
- StreamingCommunity-2.7.0.dist-info/RECORD +0 -79
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.8.0.dist-info}/LICENSE +0 -0
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.8.0.dist-info}/WHEEL +0 -0
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.8.0.dist-info}/entry_points.txt +0 -0
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.8.0.dist-info}/top_level.txt +0 -0
|
@@ -113,7 +113,7 @@ class TelegramRequestManager:
|
|
|
113
113
|
return False
|
|
114
114
|
|
|
115
115
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
116
|
-
print(f"
|
|
116
|
+
print(f" save_response - errore: {e}")
|
|
117
117
|
return False
|
|
118
118
|
|
|
119
119
|
def get_response(self) -> Optional[str]:
|
|
@@ -142,7 +142,7 @@ class TelegramRequestManager:
|
|
|
142
142
|
return True
|
|
143
143
|
|
|
144
144
|
except Exception as e:
|
|
145
|
-
print(f"
|
|
145
|
+
print(f" clear_file - errore: {e}")
|
|
146
146
|
return False
|
|
147
147
|
|
|
148
148
|
# Funzione per caricare variabili da un file .env
|
|
@@ -230,20 +230,20 @@ class TelegramBot:
|
|
|
230
230
|
["screen", "-S", script["screen_id"], "-X", "quit"]
|
|
231
231
|
)
|
|
232
232
|
print(
|
|
233
|
-
f"
|
|
233
|
+
f" La sessione screen con ID {script['screen_id']} è stata fermata automaticamente."
|
|
234
234
|
)
|
|
235
235
|
except subprocess.CalledProcessError:
|
|
236
236
|
print(
|
|
237
|
-
f"
|
|
237
|
+
f" Impossibile fermare la sessione screen con ID {script['screen_id']}."
|
|
238
238
|
)
|
|
239
239
|
print(
|
|
240
|
-
f"
|
|
240
|
+
f" Lo script con ID {script['screen_id']} ha superato i 10 minuti e verrà rimosso."
|
|
241
241
|
)
|
|
242
242
|
else:
|
|
243
243
|
scripts_data_to_save.append(script)
|
|
244
244
|
else:
|
|
245
245
|
print(
|
|
246
|
-
f"
|
|
246
|
+
f" La sessione screen con ID {script['screen_id']} non esiste più e verrà rimossa."
|
|
247
247
|
)
|
|
248
248
|
|
|
249
249
|
# Salva la lista aggiornata, senza gli script scaduti o le screen non esistenti
|
|
@@ -305,8 +305,8 @@ class TelegramBot:
|
|
|
305
305
|
|
|
306
306
|
def handle_get_id(self, message):
|
|
307
307
|
if not self.is_authorized(message.from_user.id):
|
|
308
|
-
print(f"
|
|
309
|
-
self.bot.send_message(message.chat.id, "
|
|
308
|
+
print(f" Non sei autorizzato.")
|
|
309
|
+
self.bot.send_message(message.chat.id, " Non sei autorizzato.")
|
|
310
310
|
return
|
|
311
311
|
|
|
312
312
|
print(f"Il tuo ID utente è: `{message.from_user.id}`")
|
|
@@ -318,8 +318,8 @@ class TelegramBot:
|
|
|
318
318
|
|
|
319
319
|
def handle_start_script(self, message):
|
|
320
320
|
if not self.is_authorized(message.from_user.id):
|
|
321
|
-
print(f"
|
|
322
|
-
self.bot.send_message(message.chat.id, "
|
|
321
|
+
print(f" Non sei autorizzato. {message.from_user.id}")
|
|
322
|
+
self.bot.send_message(message.chat.id, " Non sei autorizzato.")
|
|
323
323
|
return
|
|
324
324
|
|
|
325
325
|
screen_id = str(uuid.uuid4())[:8]
|
|
@@ -335,10 +335,10 @@ class TelegramBot:
|
|
|
335
335
|
"utf-8"
|
|
336
336
|
)
|
|
337
337
|
if screen_id in existing_screens:
|
|
338
|
-
print(f"
|
|
338
|
+
print(f" Lo script con ID {screen_id} è già in esecuzione.")
|
|
339
339
|
self.bot.send_message(
|
|
340
340
|
message.chat.id,
|
|
341
|
-
f"
|
|
341
|
+
f" Lo script con ID {screen_id} è già in esecuzione.",
|
|
342
342
|
)
|
|
343
343
|
return
|
|
344
344
|
except subprocess.CalledProcessError:
|
|
@@ -384,8 +384,8 @@ class TelegramBot:
|
|
|
384
384
|
|
|
385
385
|
def handle_list_scripts(self, message):
|
|
386
386
|
if not self.is_authorized(message.from_user.id):
|
|
387
|
-
print(f"
|
|
388
|
-
self.bot.send_message(message.chat.id, "
|
|
387
|
+
print(f" Non sei autorizzato.")
|
|
388
|
+
self.bot.send_message(message.chat.id, " Non sei autorizzato.")
|
|
389
389
|
return
|
|
390
390
|
|
|
391
391
|
try:
|
|
@@ -395,12 +395,12 @@ class TelegramBot:
|
|
|
395
395
|
scripts_data = []
|
|
396
396
|
|
|
397
397
|
if not scripts_data:
|
|
398
|
-
print(f"
|
|
399
|
-
self.bot.send_message(message.chat.id, "
|
|
398
|
+
print(f" Nessuno script registrato.")
|
|
399
|
+
self.bot.send_message(message.chat.id, " Nessuno script registrato.")
|
|
400
400
|
return
|
|
401
401
|
|
|
402
402
|
current_time = time.time()
|
|
403
|
-
msg = ["
|
|
403
|
+
msg = [" **Script Registrati:**\n"]
|
|
404
404
|
|
|
405
405
|
for script in scripts_data:
|
|
406
406
|
# Calcola la durata
|
|
@@ -414,12 +414,12 @@ class TelegramBot:
|
|
|
414
414
|
duration_str = f"{int(hours)}h {int(minutes)}m {int(seconds)}s"
|
|
415
415
|
|
|
416
416
|
# Icona stato
|
|
417
|
-
status_icons = {"running": "
|
|
417
|
+
status_icons = {"running": "", "stopped": "", "completed": ""}
|
|
418
418
|
|
|
419
419
|
# Costruisci riga
|
|
420
420
|
line = (
|
|
421
421
|
f"• ID: `{script['screen_id']}`\n"
|
|
422
|
-
f"• Stato: {status_icons.get(script['status'], '
|
|
422
|
+
f"• Stato: {status_icons.get(script['status'], '')}\n"
|
|
423
423
|
f"• Stop: `/stop {script['screen_id']}`\n"
|
|
424
424
|
f"• Screen: `/screen {script['screen_id']}`\n"
|
|
425
425
|
f"• Durata: {duration_str}\n"
|
|
@@ -437,8 +437,8 @@ class TelegramBot:
|
|
|
437
437
|
|
|
438
438
|
def handle_stop_script(self, message):
|
|
439
439
|
if not self.is_authorized(message.from_user.id):
|
|
440
|
-
print(f"
|
|
441
|
-
self.bot.send_message(message.chat.id, "
|
|
440
|
+
print(f" Non sei autorizzato.")
|
|
441
|
+
self.bot.send_message(message.chat.id, " Non sei autorizzato.")
|
|
442
442
|
return
|
|
443
443
|
|
|
444
444
|
parts = message.text.split()
|
|
@@ -452,15 +452,15 @@ class TelegramBot:
|
|
|
452
452
|
running_scripts = [s for s in scripts_data if s["status"] == "running"]
|
|
453
453
|
|
|
454
454
|
if not running_scripts:
|
|
455
|
-
print(f"
|
|
455
|
+
print(f" Nessuno script attivo da fermare.")
|
|
456
456
|
self.bot.send_message(
|
|
457
|
-
message.chat.id, "
|
|
457
|
+
message.chat.id, " Nessuno script attivo da fermare."
|
|
458
458
|
)
|
|
459
459
|
return
|
|
460
460
|
|
|
461
|
-
msg = "
|
|
461
|
+
msg = " **Script Attivi:**\n"
|
|
462
462
|
for script in running_scripts:
|
|
463
|
-
msg += f"
|
|
463
|
+
msg += f" `/stop {script['screen_id']}` per fermarlo\n"
|
|
464
464
|
|
|
465
465
|
print(f"{msg}")
|
|
466
466
|
self.bot.send_message(message.chat.id, msg, parse_mode="Markdown")
|
|
@@ -481,10 +481,10 @@ class TelegramBot:
|
|
|
481
481
|
|
|
482
482
|
if len(new_scripts_data) == len(scripts_data):
|
|
483
483
|
# Nessun elemento rimosso, quindi ID non trovato
|
|
484
|
-
print(f"
|
|
484
|
+
print(f" Nessuno script attivo con ID `{screen_id}`.")
|
|
485
485
|
self.bot.send_message(
|
|
486
486
|
message.chat.id,
|
|
487
|
-
f"
|
|
487
|
+
f" Nessuno script attivo con ID `{screen_id}`.",
|
|
488
488
|
parse_mode="Markdown",
|
|
489
489
|
)
|
|
490
490
|
return
|
|
@@ -492,14 +492,14 @@ class TelegramBot:
|
|
|
492
492
|
# Terminare la sessione screen
|
|
493
493
|
try:
|
|
494
494
|
subprocess.check_output(["screen", "-S", screen_id, "-X", "quit"])
|
|
495
|
-
print(f"
|
|
495
|
+
print(f" La sessione screen con ID {screen_id} è stata fermata.")
|
|
496
496
|
except subprocess.CalledProcessError:
|
|
497
497
|
print(
|
|
498
|
-
f"
|
|
498
|
+
f" Impossibile fermare la sessione screen con ID `{screen_id}`."
|
|
499
499
|
)
|
|
500
500
|
self.bot.send_message(
|
|
501
501
|
message.chat.id,
|
|
502
|
-
f"
|
|
502
|
+
f" Impossibile fermare la sessione screen con ID `{screen_id}`.",
|
|
503
503
|
parse_mode="Markdown",
|
|
504
504
|
)
|
|
505
505
|
return
|
|
@@ -508,27 +508,27 @@ class TelegramBot:
|
|
|
508
508
|
with open("../../scripts.json", "w") as f:
|
|
509
509
|
json.dump(new_scripts_data, f, indent=4)
|
|
510
510
|
|
|
511
|
-
print(f"
|
|
511
|
+
print(f" Script `{screen_id}` terminato con successo!")
|
|
512
512
|
self.bot.send_message(
|
|
513
513
|
message.chat.id,
|
|
514
|
-
f"
|
|
514
|
+
f" Script `{screen_id}` terminato con successo!",
|
|
515
515
|
parse_mode="Markdown",
|
|
516
516
|
)
|
|
517
517
|
|
|
518
518
|
def handle_response(self, message):
|
|
519
519
|
text = message.text
|
|
520
520
|
if self.request_manager.save_response(text):
|
|
521
|
-
print(f"
|
|
521
|
+
print(f" Risposta salvata correttamente per il tipo {text}")
|
|
522
522
|
else:
|
|
523
|
-
print("
|
|
524
|
-
self.bot.reply_to(message, "
|
|
523
|
+
print(" Nessuna richiesta attiva.")
|
|
524
|
+
self.bot.reply_to(message, " Nessuna richiesta attiva.")
|
|
525
525
|
|
|
526
526
|
def handle_screen_status(self, message):
|
|
527
527
|
command_parts = message.text.split()
|
|
528
528
|
if len(command_parts) < 2:
|
|
529
|
-
print(f"
|
|
529
|
+
print(f" ID mancante nel comando. Usa: /screen <ID>")
|
|
530
530
|
self.bot.send_message(
|
|
531
|
-
message.chat.id, "
|
|
531
|
+
message.chat.id, " ID mancante nel comando. Usa: /screen <ID>"
|
|
532
532
|
)
|
|
533
533
|
return
|
|
534
534
|
|
|
@@ -539,8 +539,8 @@ class TelegramBot:
|
|
|
539
539
|
# Verifica se lo screen con l'ID specificato esiste
|
|
540
540
|
existing_screens = subprocess.check_output(["screen", "-list"]).decode('utf-8')
|
|
541
541
|
if screen_id not in existing_screens:
|
|
542
|
-
print(f"
|
|
543
|
-
self.bot.send_message(message.chat.id, f"
|
|
542
|
+
print(f" La sessione screen con ID {screen_id} non esiste.")
|
|
543
|
+
self.bot.send_message(message.chat.id, f" La sessione screen con ID {screen_id} non esiste.")
|
|
544
544
|
return
|
|
545
545
|
|
|
546
546
|
# Cattura l'output della screen
|
|
@@ -549,17 +549,17 @@ class TelegramBot:
|
|
|
549
549
|
check=True,
|
|
550
550
|
)
|
|
551
551
|
except subprocess.CalledProcessError as e:
|
|
552
|
-
print(f"
|
|
552
|
+
print(f" Errore durante la cattura dell'output della screen: {e}")
|
|
553
553
|
self.bot.send_message(
|
|
554
554
|
message.chat.id,
|
|
555
|
-
f"
|
|
555
|
+
f" Errore durante la cattura dell'output della screen: {e}",
|
|
556
556
|
)
|
|
557
557
|
return
|
|
558
558
|
|
|
559
559
|
if not os.path.exists(temp_file):
|
|
560
|
-
print(f"
|
|
560
|
+
print(f" Impossibile catturare l'output della screen.")
|
|
561
561
|
self.bot.send_message(
|
|
562
|
-
message.chat.id, f"
|
|
562
|
+
message.chat.id, f" Impossibile catturare l'output della screen."
|
|
563
563
|
)
|
|
564
564
|
return
|
|
565
565
|
|
|
@@ -594,18 +594,18 @@ class TelegramBot:
|
|
|
594
594
|
cleaned_output = re.sub(r'segments\.py:\d+', '', cleaned_output)
|
|
595
595
|
|
|
596
596
|
# Invia l'output pulito
|
|
597
|
-
print(f"
|
|
597
|
+
print(f" Output della screen {screen_id}:\n{cleaned_output}")
|
|
598
598
|
self._send_long_message(
|
|
599
|
-
message.chat.id, f"
|
|
599
|
+
message.chat.id, f" Output della screen {screen_id}:\n{cleaned_output}"
|
|
600
600
|
)
|
|
601
601
|
|
|
602
602
|
except Exception as e:
|
|
603
603
|
print(
|
|
604
|
-
f"
|
|
604
|
+
f" Errore durante la lettura o l'invio dell'output della screen: {e}"
|
|
605
605
|
)
|
|
606
606
|
self.bot.send_message(
|
|
607
607
|
message.chat.id,
|
|
608
|
-
f"
|
|
608
|
+
f" Errore durante la lettura o l'invio dell'output della screen: {e}",
|
|
609
609
|
)
|
|
610
610
|
|
|
611
611
|
# Cancella il file temporaneo
|
|
@@ -665,14 +665,14 @@ class TelegramBot:
|
|
|
665
665
|
return response
|
|
666
666
|
time.sleep(1)
|
|
667
667
|
|
|
668
|
-
print(f"
|
|
668
|
+
print(f" Timeout: nessuna risposta ricevuta.")
|
|
669
669
|
for chat_id in self.authorized_users: # Manda a tutti gli ID autorizzati
|
|
670
|
-
self.bot.send_message(chat_id, "
|
|
670
|
+
self.bot.send_message(chat_id, " Timeout: nessuna risposta ricevuta.")
|
|
671
671
|
self.request_manager.clear_file()
|
|
672
672
|
return None
|
|
673
673
|
|
|
674
674
|
def run(self):
|
|
675
|
-
print("
|
|
675
|
+
print(" Avvio del bot...")
|
|
676
676
|
with open("../../scripts.json", "w") as f:
|
|
677
677
|
json.dump([], f)
|
|
678
678
|
self.bot.infinity_polling()
|
|
@@ -7,12 +7,12 @@ import time
|
|
|
7
7
|
|
|
8
8
|
# External library
|
|
9
9
|
import httpx
|
|
10
|
+
from rich.console import Console
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
# Internal utilities
|
|
13
14
|
from .version import __version__, __author__, __title__
|
|
14
|
-
from StreamingCommunity.Util.
|
|
15
|
-
from StreamingCommunity.Util._jsonConfig import config_manager
|
|
15
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
16
16
|
from StreamingCommunity.Util.headers import get_userAgent
|
|
17
17
|
|
|
18
18
|
|
|
@@ -22,6 +22,7 @@ if getattr(sys, 'frozen', False): # Modalità PyInstaller
|
|
|
22
22
|
base_path = os.path.join(sys._MEIPASS, "StreamingCommunity")
|
|
23
23
|
else:
|
|
24
24
|
base_path = os.path.dirname(__file__)
|
|
25
|
+
console = Console()
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def update():
|
StreamingCommunity/Util/color.py
CHANGED
|
@@ -14,6 +14,8 @@ from rich.console import Console
|
|
|
14
14
|
|
|
15
15
|
# Variable
|
|
16
16
|
console = Console()
|
|
17
|
+
download_site_data = True
|
|
18
|
+
validate_github_config = True
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class ConfigManager:
|
|
@@ -35,11 +37,36 @@ class ConfigManager:
|
|
|
35
37
|
self.cache = {}
|
|
36
38
|
self.reference_config_url = 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/config.json'
|
|
37
39
|
|
|
38
|
-
#
|
|
39
|
-
self._validate_and_update_config()
|
|
40
|
+
# Read initial config to get use_api setting
|
|
40
41
|
self._read_initial_config()
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
# Validate and update config before proceeding (if enabled)
|
|
44
|
+
if validate_github_config:
|
|
45
|
+
self._validate_and_update_config()
|
|
46
|
+
|
|
47
|
+
console.print(f"[bold cyan]ConfigManager initialized:[/bold cyan] [green]{self.file_path}[/green]")
|
|
48
|
+
|
|
49
|
+
def _read_initial_config(self) -> None:
|
|
50
|
+
"""Read initial configuration to get use_api setting."""
|
|
51
|
+
try:
|
|
52
|
+
if os.path.exists(self.file_path):
|
|
53
|
+
with open(self.file_path, 'r') as f:
|
|
54
|
+
self.config = json.load(f)
|
|
55
|
+
|
|
56
|
+
self.use_api = self.config.get('DEFAULT', {}).get('use_api', True)
|
|
57
|
+
console.print(f"[bold cyan]API usage setting:[/bold cyan] [{'green' if self.use_api else 'yellow'}]{self.use_api}[/{'green' if self.use_api else 'yellow'}]")
|
|
58
|
+
console.print(f"[bold cyan]Download site data:[/bold cyan] [{'green' if download_site_data else 'yellow'}]{download_site_data}[/{'green' if download_site_data else 'yellow'}]")
|
|
59
|
+
console.print(f"[bold cyan]Validate GitHub config:[/bold cyan] [{'green' if validate_github_config else 'yellow'}]{validate_github_config}[/{'green' if validate_github_config else 'yellow'}]")
|
|
60
|
+
|
|
61
|
+
else:
|
|
62
|
+
self.use_api = True
|
|
63
|
+
console.print("[bold yellow]Configuration file not found. Using default API setting: True[/bold yellow]")
|
|
64
|
+
console.print(f"[bold yellow]Download site data: {download_site_data}[/bold yellow]")
|
|
65
|
+
console.print(f"[bold yellow]Validate GitHub config: {validate_github_config}[/bold yellow]")
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
self.use_api = True
|
|
69
|
+
console.print("[bold red]Error reading API setting. Using default: True[/bold red]")
|
|
43
70
|
|
|
44
71
|
def _validate_and_update_config(self) -> None:
|
|
45
72
|
"""Validate local config against reference config and update missing keys."""
|
|
@@ -49,36 +76,70 @@ class ConfigManager:
|
|
|
49
76
|
if os.path.exists(self.file_path):
|
|
50
77
|
with open(self.file_path, 'r') as f:
|
|
51
78
|
local_config = json.load(f)
|
|
52
|
-
console.print("[bold cyan]
|
|
79
|
+
console.print(f"[bold cyan]Local configuration loaded:[/bold cyan] [green]{len(local_config)} keys found[/green]")
|
|
53
80
|
|
|
54
81
|
# Download reference config
|
|
55
|
-
console.print("[bold cyan]
|
|
56
|
-
response = requests.get(self.reference_config_url)
|
|
57
|
-
|
|
82
|
+
console.print(f"[bold cyan]Downloading reference config from:[/bold cyan] [green]{self.reference_config_url}[/green]")
|
|
83
|
+
response = requests.get(self.reference_config_url, timeout=10)
|
|
84
|
+
|
|
85
|
+
if not response.ok:
|
|
58
86
|
raise Exception(f"Failed to download reference config. Status code: {response.status_code}")
|
|
87
|
+
|
|
59
88
|
reference_config = response.json()
|
|
89
|
+
console.print(f"[bold cyan]Reference config downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]")
|
|
60
90
|
|
|
61
91
|
# Compare and update missing keys
|
|
62
92
|
merged_config = self._deep_merge_configs(local_config, reference_config)
|
|
63
93
|
|
|
64
94
|
if merged_config != local_config:
|
|
95
|
+
added_keys = self._get_added_keys(local_config, merged_config)
|
|
96
|
+
|
|
65
97
|
# Save the merged config
|
|
66
98
|
with open(self.file_path, 'w') as f:
|
|
67
99
|
json.dump(merged_config, f, indent=4)
|
|
68
|
-
console.print("[bold green]
|
|
100
|
+
console.print(f"[bold green]Configuration updated with {len(added_keys)} new keys:[/bold green] {', '.join(added_keys[:5])}{' and more...' if len(added_keys) > 5 else ''}")
|
|
101
|
+
|
|
69
102
|
else:
|
|
70
|
-
console.print("[bold green]
|
|
103
|
+
console.print("[bold green]Configuration is up to date.[/bold green]")
|
|
71
104
|
|
|
72
105
|
self.config = merged_config
|
|
73
106
|
|
|
74
107
|
except Exception as e:
|
|
75
|
-
console.print(f"[bold red]
|
|
108
|
+
console.print(f"[bold red]Configuration validation error:[/bold red] {str(e)}")
|
|
109
|
+
|
|
76
110
|
if not self.config:
|
|
77
|
-
|
|
111
|
+
console.print("[bold yellow]Falling back to reference configuration...[/bold yellow]")
|
|
78
112
|
self.download_requirements(self.reference_config_url, self.file_path)
|
|
113
|
+
|
|
79
114
|
with open(self.file_path, 'r') as f:
|
|
80
115
|
self.config = json.load(f)
|
|
81
116
|
|
|
117
|
+
console.print(f"[bold green]Reference config loaded successfully with {len(self.config)} keys[/bold green]")
|
|
118
|
+
|
|
119
|
+
def _get_added_keys(self, old_config: dict, new_config: dict, prefix="") -> list:
|
|
120
|
+
"""
|
|
121
|
+
Get list of keys added in the new config compared to old config.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
old_config (dict): The original configuration
|
|
125
|
+
new_config (dict): The new configuration
|
|
126
|
+
prefix (str): Key prefix for nested keys
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
list: List of added key names
|
|
130
|
+
"""
|
|
131
|
+
added_keys = []
|
|
132
|
+
|
|
133
|
+
for key, value in new_config.items():
|
|
134
|
+
full_key = f"{prefix}.{key}" if prefix else key
|
|
135
|
+
|
|
136
|
+
if key not in old_config:
|
|
137
|
+
added_keys.append(full_key)
|
|
138
|
+
elif isinstance(value, dict) and isinstance(old_config.get(key), dict):
|
|
139
|
+
added_keys.extend(self._get_added_keys(old_config[key], value, full_key))
|
|
140
|
+
|
|
141
|
+
return added_keys
|
|
142
|
+
|
|
82
143
|
def _deep_merge_configs(self, local_config: dict, reference_config: dict) -> dict:
|
|
83
144
|
"""
|
|
84
145
|
Recursively merge reference config into local config, preserving local values.
|
|
@@ -100,47 +161,41 @@ class ConfigManager:
|
|
|
100
161
|
|
|
101
162
|
return merged
|
|
102
163
|
|
|
103
|
-
def _read_initial_config(self) -> None:
|
|
104
|
-
"""Read initial configuration to get use_api setting."""
|
|
105
|
-
try:
|
|
106
|
-
if os.path.exists(self.file_path):
|
|
107
|
-
with open(self.file_path, 'r') as f:
|
|
108
|
-
self.config = json.load(f)
|
|
109
|
-
self.use_api = self.config.get('DEFAULT', {}).get('use_api', True)
|
|
110
|
-
else:
|
|
111
|
-
self.use_api = True # Default to True if config file doesn't exist
|
|
112
|
-
console.print("[bold yellow]⚠️ Configuration file not found. Using default settings.[/bold yellow]")
|
|
113
|
-
|
|
114
|
-
except Exception as e:
|
|
115
|
-
self.use_api = True # Default to True in case of error
|
|
116
|
-
logging.error(f"❌ Error reading initial configuration: {e}")
|
|
117
|
-
|
|
118
164
|
def read_config(self) -> None:
|
|
119
165
|
"""Read the configuration file."""
|
|
120
166
|
try:
|
|
121
|
-
logging.info(f"
|
|
167
|
+
logging.info(f"Reading file: {self.file_path}")
|
|
122
168
|
|
|
123
169
|
# Check if file exists
|
|
124
170
|
if os.path.exists(self.file_path):
|
|
125
171
|
with open(self.file_path, 'r') as f:
|
|
126
172
|
self.config = json.load(f)
|
|
127
|
-
console.print("[bold green]
|
|
173
|
+
console.print(f"[bold green]Configuration loaded:[/bold green] {len(self.config)} keys, {sum(1 for _ in json.dumps(self.config))} bytes")
|
|
174
|
+
|
|
128
175
|
else:
|
|
129
|
-
console.print("[bold yellow]
|
|
176
|
+
console.print(f"[bold yellow]Configuration file not found at:[/bold yellow] {self.file_path}")
|
|
177
|
+
console.print(f"[bold cyan]Downloading from:[/bold cyan] {self.reference_config_url}")
|
|
130
178
|
self.download_requirements(self.reference_config_url, self.file_path)
|
|
131
179
|
|
|
132
180
|
# Load the downloaded config.json into the config attribute
|
|
133
181
|
with open(self.file_path, 'r') as f:
|
|
134
182
|
self.config = json.load(f)
|
|
135
|
-
console.print("[bold green]
|
|
183
|
+
console.print(f"[bold green]Configuration downloaded and saved:[/bold green] {len(self.config)} keys")
|
|
136
184
|
|
|
137
|
-
#
|
|
138
|
-
self.
|
|
185
|
+
# Read API setting again in case it was updated in the downloaded config
|
|
186
|
+
self.use_api = self.config.get('DEFAULT', {}).get('use_api', self.use_api)
|
|
139
187
|
|
|
140
|
-
|
|
188
|
+
# Update site configuration separately if enabled
|
|
189
|
+
if download_site_data:
|
|
190
|
+
self.update_site_config()
|
|
191
|
+
else:
|
|
192
|
+
console.print("[bold yellow]Site data download is disabled[/bold yellow]")
|
|
193
|
+
|
|
194
|
+
console.print("[bold cyan]Configuration processing complete[/bold cyan]")
|
|
141
195
|
|
|
142
196
|
except Exception as e:
|
|
143
|
-
logging.error(f"
|
|
197
|
+
logging.error(f"Error reading configuration file: {e}")
|
|
198
|
+
console.print(f"[bold red]Failed to read configuration:[/bold red] {str(e)}")
|
|
144
199
|
|
|
145
200
|
def download_requirements(self, url: str, filename: str) -> None:
|
|
146
201
|
"""
|
|
@@ -151,20 +206,25 @@ class ConfigManager:
|
|
|
151
206
|
filename (str): The local filename to save the file as.
|
|
152
207
|
"""
|
|
153
208
|
try:
|
|
154
|
-
logging.info(f"
|
|
155
|
-
|
|
209
|
+
logging.info(f"Downloading {filename} from {url}...")
|
|
210
|
+
console.print(f"[bold cyan]Downloading file:[/bold cyan] {os.path.basename(filename)}")
|
|
211
|
+
response = requests.get(url, timeout=10)
|
|
156
212
|
|
|
157
213
|
if response.status_code == 200:
|
|
158
214
|
with open(filename, 'wb') as f:
|
|
159
215
|
f.write(response.content)
|
|
160
|
-
|
|
216
|
+
file_size = len(response.content) / 1024
|
|
217
|
+
console.print(f"[bold green]Download successful:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)")
|
|
161
218
|
|
|
162
219
|
else:
|
|
163
|
-
|
|
220
|
+
error_msg = f"HTTP Status: {response.status_code}, Response: {response.text[:100]}"
|
|
221
|
+
console.print(f"[bold red]Download failed:[/bold red] {error_msg}")
|
|
222
|
+
logging.error(f"Failed to download {filename}. {error_msg}")
|
|
164
223
|
sys.exit(0)
|
|
165
224
|
|
|
166
225
|
except Exception as e:
|
|
167
|
-
|
|
226
|
+
console.print(f"[bold red]Download error:[/bold red] {str(e)}")
|
|
227
|
+
logging.error(f"Failed to download {filename}: {e}")
|
|
168
228
|
sys.exit(0)
|
|
169
229
|
|
|
170
230
|
def update_site_config(self) -> None:
|
|
@@ -177,33 +237,55 @@ class ConfigManager:
|
|
|
177
237
|
}
|
|
178
238
|
|
|
179
239
|
try:
|
|
180
|
-
console.print("[bold cyan]
|
|
181
|
-
response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers)
|
|
182
|
-
|
|
183
|
-
if response.
|
|
184
|
-
|
|
185
|
-
|
|
240
|
+
console.print("[bold cyan]Fetching site data from API...[/bold cyan]")
|
|
241
|
+
response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers, timeout=10)
|
|
242
|
+
|
|
243
|
+
if response.ok:
|
|
244
|
+
data = response.json()
|
|
245
|
+
if data and len(data) > 0:
|
|
246
|
+
self.configSite = data[0]['data']
|
|
247
|
+
|
|
248
|
+
# Display available sites and their domains
|
|
249
|
+
site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
|
|
250
|
+
console.print(f"[bold green]Site data fetched:[/bold green] {site_count} streaming services available")
|
|
251
|
+
|
|
252
|
+
# List a few sites as examples
|
|
253
|
+
if site_count > 0:
|
|
254
|
+
examples = list(self.configSite.items())[:3]
|
|
255
|
+
sites_info = []
|
|
256
|
+
for site, info in examples:
|
|
257
|
+
sites_info.append(f"[cyan]{site}[/cyan]: {info.get('full_url', 'N/A')}")
|
|
258
|
+
|
|
259
|
+
console.print("[bold cyan]Sample sites:[/bold cyan]")
|
|
260
|
+
for info in sites_info:
|
|
261
|
+
console.print(f" {info}")
|
|
262
|
+
|
|
263
|
+
if site_count > 3:
|
|
264
|
+
console.print(f" ... and {site_count - 3} more")
|
|
265
|
+
|
|
266
|
+
else:
|
|
267
|
+
console.print("[bold yellow]API returned empty data set[/bold yellow]")
|
|
186
268
|
else:
|
|
187
|
-
console.print(f"[bold red]
|
|
269
|
+
console.print(f"[bold red]API request failed:[/bold red] HTTP {response.status_code}, {response.text[:100]}")
|
|
188
270
|
|
|
189
271
|
except Exception as e:
|
|
190
|
-
console.print(f"[bold red]
|
|
272
|
+
console.print(f"[bold red]API connection error:[/bold red] {str(e)}")
|
|
191
273
|
else:
|
|
192
274
|
try:
|
|
193
275
|
if os.path.exists(self.domains_path):
|
|
194
|
-
console.print("[bold cyan]
|
|
276
|
+
console.print(f"[bold cyan]Reading domains from:[/bold cyan] {self.domains_path}")
|
|
195
277
|
with open(self.domains_path, 'r') as f:
|
|
196
278
|
self.configSite = json.load(f)
|
|
197
|
-
|
|
279
|
+
|
|
198
280
|
else:
|
|
199
|
-
error_msg = "
|
|
200
|
-
console.print(f"[bold red]
|
|
281
|
+
error_msg = f"domains.json not found at {self.domains_path} and API usage is disabled"
|
|
282
|
+
console.print(f"[bold red]Configuration error:[/bold red] {error_msg}")
|
|
201
283
|
raise FileNotFoundError(error_msg)
|
|
202
284
|
|
|
203
285
|
except Exception as e:
|
|
204
|
-
console.print(f"[bold red]
|
|
286
|
+
console.print(f"[bold red]Domains file error:[/bold red] {str(e)}")
|
|
205
287
|
raise
|
|
206
|
-
|
|
288
|
+
|
|
207
289
|
def read_key(self, section: str, key: str, data_type: type = str, from_site: bool = False) -> Any:
|
|
208
290
|
"""Read a key from the configuration.
|
|
209
291
|
|
|
@@ -336,6 +418,18 @@ class ConfigManager:
|
|
|
336
418
|
print(f"Error writing configuration file: {e}")
|
|
337
419
|
|
|
338
420
|
|
|
339
|
-
# Initialize
|
|
340
421
|
config_manager = ConfigManager()
|
|
341
422
|
config_manager.read_config()
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
import sys
|
|
426
|
+
|
|
427
|
+
def get_use_large_bar():
|
|
428
|
+
"""
|
|
429
|
+
Determines whether the large bar feature should be enabled.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
bool: True if running on a PC (Windows, macOS, Linux),
|
|
433
|
+
False if running on Android or iOS.
|
|
434
|
+
"""
|
|
435
|
+
return not any(platform in sys.platform for platform in ("android", "ios"))
|