StreamingCommunity 2.7.0__py3-none-any.whl → 2.9.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.

Files changed (71) hide show
  1. StreamingCommunity/Api/Player/ddl.py +2 -2
  2. StreamingCommunity/Api/Player/maxstream.py +7 -13
  3. StreamingCommunity/Api/Player/supervideo.py +7 -33
  4. StreamingCommunity/Api/Player/vixcloud.py +8 -80
  5. StreamingCommunity/Api/Site/1337xx/__init__.py +8 -1
  6. StreamingCommunity/Api/Site/1337xx/site.py +10 -16
  7. StreamingCommunity/Api/Site/1337xx/title.py +4 -1
  8. StreamingCommunity/Api/Site/animeunity/__init__.py +9 -2
  9. StreamingCommunity/Api/Site/animeunity/film_serie.py +7 -1
  10. StreamingCommunity/Api/Site/animeunity/site.py +8 -10
  11. StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +1 -1
  12. StreamingCommunity/Api/Site/cb01new/__init__.py +8 -1
  13. StreamingCommunity/Api/Site/cb01new/film.py +10 -6
  14. StreamingCommunity/Api/Site/cb01new/site.py +16 -15
  15. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +9 -2
  16. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +7 -1
  17. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +10 -15
  18. StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +1 -1
  19. StreamingCommunity/Api/Site/guardaserie/__init__.py +9 -2
  20. StreamingCommunity/Api/Site/guardaserie/series.py +13 -8
  21. StreamingCommunity/Api/Site/guardaserie/site.py +12 -17
  22. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +1 -1
  23. StreamingCommunity/Api/Site/mostraguarda/__init__.py +6 -2
  24. StreamingCommunity/Api/Site/mostraguarda/film.py +10 -8
  25. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +9 -2
  26. StreamingCommunity/Api/Site/streamingcommunity/film.py +11 -6
  27. StreamingCommunity/Api/Site/streamingcommunity/series.py +17 -10
  28. StreamingCommunity/Api/Site/streamingcommunity/site.py +10 -15
  29. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +2 -2
  30. StreamingCommunity/Api/Template/Util/__init__.py +0 -1
  31. StreamingCommunity/Api/Template/Util/get_domain.py +24 -66
  32. StreamingCommunity/Api/Template/Util/manage_ep.py +10 -5
  33. StreamingCommunity/Api/Template/config_loader.py +8 -8
  34. StreamingCommunity/Api/Template/site.py +3 -6
  35. StreamingCommunity/Lib/Downloader/HLS/downloader.py +15 -14
  36. StreamingCommunity/Lib/Downloader/HLS/segments.py +11 -31
  37. StreamingCommunity/Lib/Downloader/MP4/downloader.py +12 -9
  38. StreamingCommunity/Lib/Downloader/TOR/downloader.py +109 -101
  39. StreamingCommunity/Lib/FFmpeg/__init__.py +1 -1
  40. StreamingCommunity/Lib/FFmpeg/capture.py +10 -12
  41. StreamingCommunity/Lib/FFmpeg/command.py +15 -14
  42. StreamingCommunity/Lib/FFmpeg/util.py +9 -38
  43. StreamingCommunity/Lib/M3U8/decryptor.py +72 -146
  44. StreamingCommunity/Lib/M3U8/estimator.py +8 -16
  45. StreamingCommunity/Lib/M3U8/parser.py +25 -27
  46. StreamingCommunity/Lib/M3U8/url_fixer.py +1 -4
  47. StreamingCommunity/Lib/TMBD/__init__.py +2 -0
  48. StreamingCommunity/Lib/TMBD/obj_tmbd.py +3 -17
  49. StreamingCommunity/Lib/TMBD/tmdb.py +4 -9
  50. StreamingCommunity/TelegramHelp/telegram_bot.py +50 -50
  51. StreamingCommunity/Upload/update.py +3 -2
  52. StreamingCommunity/Upload/version.py +1 -1
  53. StreamingCommunity/Util/color.py +1 -1
  54. StreamingCommunity/Util/{_jsonConfig.py → config_json.py} +148 -54
  55. StreamingCommunity/Util/headers.py +2 -38
  56. StreamingCommunity/Util/logger.py +72 -42
  57. StreamingCommunity/Util/message.py +8 -3
  58. StreamingCommunity/Util/os.py +41 -93
  59. StreamingCommunity/Util/table.py +8 -17
  60. StreamingCommunity/run.py +26 -34
  61. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/METADATA +165 -92
  62. StreamingCommunity-2.9.0.dist-info/RECORD +75 -0
  63. StreamingCommunity/Api/Template/Util/recall_search.py +0 -37
  64. StreamingCommunity/Lib/Downloader/HLS/proxyes.py +0 -110
  65. StreamingCommunity/Util/call_stack.py +0 -42
  66. StreamingCommunity/Util/console.py +0 -12
  67. StreamingCommunity-2.7.0.dist-info/RECORD +0 -79
  68. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/LICENSE +0 -0
  69. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/WHEEL +0 -0
  70. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/entry_points.txt +0 -0
  71. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.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"⚠️ save_response - errore: {e}")
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"⚠️ clear_file - errore: {e}")
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" La sessione screen con ID {script['screen_id']} è stata fermata automaticamente."
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"⚠️ Impossibile fermare la sessione screen con ID {script['screen_id']}."
237
+ f" Impossibile fermare la sessione screen con ID {script['screen_id']}."
238
238
  )
239
239
  print(
240
- f"⚠️ Lo script con ID {script['screen_id']} ha superato i 10 minuti e verrà rimosso."
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"⚠️ La sessione screen con ID {script['screen_id']} non esiste più e verrà rimossa."
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" Non sei autorizzato.")
309
- self.bot.send_message(message.chat.id, " Non sei autorizzato.")
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" Non sei autorizzato. {message.from_user.id}")
322
- self.bot.send_message(message.chat.id, " Non sei autorizzato.")
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"⚠️ Lo script con ID {screen_id} è già in esecuzione.")
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"⚠️ Lo script con ID {screen_id} è già in esecuzione.",
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" Non sei autorizzato.")
388
- self.bot.send_message(message.chat.id, " Non sei autorizzato.")
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"⚠️ Nessuno script registrato.")
399
- self.bot.send_message(message.chat.id, "⚠️ Nessuno script registrato.")
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 = ["🖥️ **Script Registrati:**\n"]
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": "🟢", "stopped": "🔴", "completed": ""}
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'], '')}\n"
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" Non sei autorizzato.")
441
- self.bot.send_message(message.chat.id, " Non sei autorizzato.")
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"⚠️ Nessuno script attivo da fermare.")
455
+ print(f" Nessuno script attivo da fermare.")
456
456
  self.bot.send_message(
457
- message.chat.id, "⚠️ Nessuno script attivo da fermare."
457
+ message.chat.id, " Nessuno script attivo da fermare."
458
458
  )
459
459
  return
460
460
 
461
- msg = "🖥️ **Script Attivi:**\n"
461
+ msg = " **Script Attivi:**\n"
462
462
  for script in running_scripts:
463
- msg += f"🔹 `/stop {script['screen_id']}` per fermarlo\n"
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"⚠️ Nessuno script attivo con ID `{screen_id}`.")
484
+ print(f" Nessuno script attivo con ID `{screen_id}`.")
485
485
  self.bot.send_message(
486
486
  message.chat.id,
487
- f"⚠️ Nessuno script attivo con ID `{screen_id}`.",
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" La sessione screen con ID {screen_id} è stata fermata.")
495
+ print(f" La sessione screen con ID {screen_id} è stata fermata.")
496
496
  except subprocess.CalledProcessError:
497
497
  print(
498
- f"⚠️ Impossibile fermare la sessione screen con ID `{screen_id}`."
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"⚠️ Impossibile fermare la sessione screen con ID `{screen_id}`.",
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" Script `{screen_id}` terminato con successo!")
511
+ print(f" Script `{screen_id}` terminato con successo!")
512
512
  self.bot.send_message(
513
513
  message.chat.id,
514
- f" Script `{screen_id}` terminato con successo!",
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"📥 Risposta salvata correttamente per il tipo {text}")
521
+ print(f" Risposta salvata correttamente per il tipo {text}")
522
522
  else:
523
- print("⚠️ Nessuna richiesta attiva.")
524
- self.bot.reply_to(message, " Nessuna richiesta attiva.")
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"⚠️ ID mancante nel comando. Usa: /screen <ID>")
529
+ print(f" ID mancante nel comando. Usa: /screen <ID>")
530
530
  self.bot.send_message(
531
- message.chat.id, "⚠️ ID mancante nel comando. Usa: /screen <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"⚠️ 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.")
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" Errore durante la cattura dell'output della screen: {e}")
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" Errore durante la cattura dell'output della screen: {e}",
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" Impossibile catturare l'output della screen.")
560
+ print(f" Impossibile catturare l'output della screen.")
561
561
  self.bot.send_message(
562
- message.chat.id, f" Impossibile catturare l'output della screen."
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"📄 Output della screen {screen_id}:\n{cleaned_output}")
597
+ print(f" Output della screen {screen_id}:\n{cleaned_output}")
598
598
  self._send_long_message(
599
- message.chat.id, f"📄 Output della screen {screen_id}:\n{cleaned_output}"
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" Errore durante la lettura o l'invio dell'output della screen: {e}"
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" Errore durante la lettura o l'invio dell'output della screen: {e}",
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"⚠️ Timeout: nessuna risposta ricevuta.")
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, "⚠️ Timeout: nessuna risposta ricevuta.")
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("🚀 Avvio del bot...")
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.console import console
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():
@@ -1,5 +1,5 @@
1
1
  __title__ = 'StreamingCommunity'
2
- __version__ = '2.7.0'
2
+ __version__ = '2.9.0'
3
3
  __author__ = 'Arrowar'
4
4
  __description__ = 'A command-line program to download film'
5
5
  __copyright__ = 'Copyright 2024'
@@ -17,4 +17,4 @@ class Colors:
17
17
  LIGHT_MAGENTA = "\033[95m"
18
18
  LIGHT_CYAN = "\033[96m"
19
19
  WHITE = "\033[97m"
20
- RESET = "\033[0m"
20
+ RESET = "\033[0m"
@@ -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
- # Validate and update config before proceeding
39
- self._validate_and_update_config()
40
+ # Read initial config to get use_api setting
40
41
  self._read_initial_config()
41
42
 
42
- console.print(f"[bold cyan]📂 Configuration file path:[/bold cyan] [green]{self.file_path}[/green]")
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]📖 Local configuration found.[/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]🌍 Downloading reference configuration...[/bold cyan]")
56
- response = requests.get(self.reference_config_url)
57
- if response.status_code != 200:
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]Configuration updated with missing keys.[/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]Configuration is up to date.[/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] Error validating configuration: {e}[/bold red]")
108
+ console.print(f"[bold red]Configuration validation error:[/bold red] {str(e)}")
109
+
76
110
  if not self.config:
77
- # If validation failed and we have no config, download the reference config
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"📖 Reading file: {self.file_path}")
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]Configuration file loaded successfully.[/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]⚠️ Configuration file not found. Downloading...[/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]Configuration file downloaded and saved.[/bold green]")
183
+ console.print(f"[bold green]Configuration downloaded and saved:[/bold green] {len(self.config)} keys")
136
184
 
137
- # Update site configuration separately
138
- self.update_site_config()
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
- console.print("[bold cyan]🔧 Configuration file processing complete.[/bold cyan]")
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"Error reading configuration file: {e}")
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"🌍 Downloading {filename} from {url}...")
155
- response = requests.get(url)
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
- console.print(f"[bold green]✅ Successfully downloaded {filename}.[/bold green]")
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
- logging.error(f" Failed to download {filename}. HTTP Status code: {response.status_code}")
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
- logging.error(f" Failed to download {filename}: {e}")
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]🌍 Fetching SITE data from API...[/bold cyan]")
181
- response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers)
182
-
183
- if response.status_code == 200:
184
- self.configSite = response.json()[0]['data']
185
- console.print("[bold green]✅ SITE data successfully fetched.[/bold green]")
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] Failed to fetch SITE data. HTTP Status code: {response.status_code}[/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] Error fetching SITE data: {e}[/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]📖 Reading domains from local file...[/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
- console.print("[bold green]✅ Domains loaded successfully from local file.[/bold green]")
279
+
198
280
  else:
199
- error_msg = "domains.json not found and API usage is disabled"
200
- console.print(f"[bold red]{error_msg}[/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] Error reading domains file: {e}[/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"))