StreamingCommunity 2.9.7__tar.gz → 2.9.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. {streamingcommunity-2.9.7/StreamingCommunity.egg-info → streamingcommunity-2.9.8}/PKG-INFO +1 -1
  2. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Upload/version.py +1 -1
  3. streamingcommunity-2.9.8/StreamingCommunity/Util/config_json.py +577 -0
  4. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/run.py +1 -1
  5. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8/StreamingCommunity.egg-info}/PKG-INFO +1 -1
  6. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/setup.py +1 -1
  7. streamingcommunity-2.9.7/StreamingCommunity/Util/config_json.py +0 -426
  8. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/LICENSE +0 -0
  9. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/MANIFEST.in +0 -0
  10. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/README.md +0 -0
  11. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Player/Helper/Vixcloud/js_parser.py +0 -0
  12. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +0 -0
  13. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Player/ddl.py +0 -0
  14. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Player/maxstream.py +0 -0
  15. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Player/supervideo.py +0 -0
  16. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Player/sweetpixel.py +0 -0
  17. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Player/vixcloud.py +0 -0
  18. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/1337xx/__init__.py +0 -0
  19. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/1337xx/site.py +0 -0
  20. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/1337xx/title.py +0 -0
  21. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/altadefinizione/__init__.py +0 -0
  22. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/altadefinizione/film.py +0 -0
  23. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/altadefinizione/series.py +0 -0
  24. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/altadefinizione/site.py +0 -0
  25. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py +0 -0
  26. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/animeunity/__init__.py +0 -0
  27. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/animeunity/film_serie.py +0 -0
  28. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/animeunity/site.py +0 -0
  29. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +0 -0
  30. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/animeworld/__init__.py +0 -0
  31. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/animeworld/serie.py +0 -0
  32. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/animeworld/site.py +0 -0
  33. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +0 -0
  34. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/cb01new/__init__.py +0 -0
  35. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/cb01new/film.py +0 -0
  36. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/cb01new/site.py +0 -0
  37. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +0 -0
  38. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/ddlstreamitaly/series.py +0 -0
  39. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/ddlstreamitaly/site.py +0 -0
  40. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +0 -0
  41. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/guardaserie/__init__.py +0 -0
  42. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/guardaserie/series.py +0 -0
  43. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/guardaserie/site.py +0 -0
  44. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +0 -0
  45. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/mostraguarda/__init__.py +0 -0
  46. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/mostraguarda/film.py +0 -0
  47. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/streamingcommunity/__init__.py +0 -0
  48. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/streamingcommunity/film.py +0 -0
  49. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/streamingcommunity/series.py +0 -0
  50. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/streamingcommunity/site.py +0 -0
  51. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +0 -0
  52. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Template/Class/SearchType.py +0 -0
  53. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Template/Util/__init__.py +0 -0
  54. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Template/Util/manage_ep.py +0 -0
  55. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Template/__init__.py +0 -0
  56. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Template/config_loader.py +0 -0
  57. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Api/Template/site.py +0 -0
  58. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/Downloader/HLS/downloader.py +0 -0
  59. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/Downloader/HLS/segments.py +0 -0
  60. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/Downloader/MP4/downloader.py +0 -0
  61. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/Downloader/TOR/downloader.py +0 -0
  62. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/Downloader/__init__.py +0 -0
  63. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/FFmpeg/__init__.py +0 -0
  64. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/FFmpeg/capture.py +0 -0
  65. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/FFmpeg/command.py +0 -0
  66. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/FFmpeg/util.py +0 -0
  67. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/M3U8/__init__.py +0 -0
  68. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/M3U8/decryptor.py +0 -0
  69. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/M3U8/estimator.py +0 -0
  70. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/M3U8/parser.py +0 -0
  71. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/M3U8/url_fixer.py +0 -0
  72. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/TMBD/__init__.py +0 -0
  73. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/TMBD/obj_tmbd.py +0 -0
  74. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Lib/TMBD/tmdb.py +0 -0
  75. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/TelegramHelp/__init__.py +0 -0
  76. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/TelegramHelp/telegram_bot.py +0 -0
  77. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Upload/update.py +0 -0
  78. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Util/color.py +0 -0
  79. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Util/ffmpeg_installer.py +0 -0
  80. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Util/headers.py +0 -0
  81. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Util/logger.py +0 -0
  82. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Util/message.py +0 -0
  83. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Util/os.py +0 -0
  84. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/Util/table.py +0 -0
  85. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/__init__.py +0 -0
  86. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity/global_search.py +0 -0
  87. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity.egg-info/SOURCES.txt +0 -0
  88. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity.egg-info/dependency_links.txt +0 -0
  89. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity.egg-info/entry_points.txt +0 -0
  90. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity.egg-info/requires.txt +0 -0
  91. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/StreamingCommunity.egg-info/top_level.txt +0 -0
  92. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/requirements.txt +0 -0
  93. {streamingcommunity-2.9.7 → streamingcommunity-2.9.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: StreamingCommunity
3
- Version: 2.9.7
3
+ Version: 2.9.8
4
4
  Home-page: https://github.com/Lovi-0/StreamingCommunity
5
5
  Author: Lovi-0
6
6
  Project-URL: Bug Reports, https://github.com/Lovi-0/StreamingCommunity/issues
@@ -1,5 +1,5 @@
1
1
  __title__ = 'StreamingCommunity'
2
- __version__ = '2.9.7'
2
+ __version__ = '2.9.8'
3
3
  __author__ = 'Arrowar'
4
4
  __description__ = 'A command-line program to download film'
5
5
  __copyright__ = 'Copyright 2024'
@@ -0,0 +1,577 @@
1
+ # 29.01.24
2
+
3
+ import os
4
+ import sys
5
+ import json
6
+ import logging
7
+ import requests
8
+ from typing import Any, List
9
+
10
+
11
+ # External library
12
+ from rich.console import Console
13
+
14
+
15
+ # Variable
16
+ console = Console()
17
+ download_site_data = True
18
+ validate_github_config = True
19
+
20
+
21
+ class ConfigManager:
22
+ def __init__(self, file_name: str = 'config.json') -> None:
23
+ """
24
+ Initialize the ConfigManager.
25
+
26
+ Args:
27
+ file_name (str, optional): Configuration file name. Default: 'config.json'.
28
+ """
29
+ # Determine the base path - use the current working directory
30
+ if getattr(sys, 'frozen', False):
31
+ # If the application is frozen (e.g., PyInstaller)
32
+ base_path = os.path.dirname(sys.executable)
33
+
34
+ else:
35
+ # Use the current directory where the script is executed
36
+ base_path = os.getcwd()
37
+
38
+ # Initialize file paths
39
+ self.file_path = os.path.join(base_path, file_name)
40
+ self.domains_path = os.path.join(base_path, 'domains.json')
41
+
42
+ # Display the actual file path for debugging
43
+ console.print(f"[bold cyan]Configuration file path:[/bold cyan] [green]{self.file_path}[/green]")
44
+
45
+ # Reference repository URL
46
+ self.reference_config_url = 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/config.json'
47
+
48
+ # Initialize data structures
49
+ self.config = {}
50
+ self.configSite = {}
51
+ self.cache = {}
52
+
53
+ self.use_api = False
54
+ self.download_site_data = False
55
+ self.validate_github_config = False
56
+
57
+ console.print(f"[bold cyan]Initializing ConfigManager:[/bold cyan] [green]{self.file_path}[/green]")
58
+
59
+ # Load the configuration
60
+ self.load_config()
61
+
62
+ def load_config(self) -> None:
63
+ """Load the configuration and initialize all settings."""
64
+ if not os.path.exists(self.file_path):
65
+ console.print(f"[bold red]WARNING: Configuration file not found:[/bold red] {self.file_path}")
66
+ console.print(f"[bold yellow]Attempting to download from reference repository...[/bold yellow]")
67
+ self._download_reference_config()
68
+
69
+ # Load the configuration file
70
+ try:
71
+ with open(self.file_path, 'r') as f:
72
+ self.config = json.load(f)
73
+ console.print(f"[bold green]Configuration loaded:[/bold green] {len(self.config)} keys")
74
+
75
+ # Update settings from the configuration
76
+ self._update_settings_from_config()
77
+
78
+ # Validate and update the configuration if requested
79
+ if self.validate_github_config:
80
+ self._validate_and_update_config()
81
+ else:
82
+ console.print("[bold yellow]GitHub validation disabled[/bold yellow]")
83
+
84
+ # Load site data if requested
85
+ if self.download_site_data:
86
+ self._load_site_data()
87
+ else:
88
+ console.print("[bold yellow]Site data download disabled[/bold yellow]")
89
+
90
+ except json.JSONDecodeError as e:
91
+ console.print(f"[bold red]Error parsing JSON:[/bold red] {str(e)}")
92
+ self._handle_config_error()
93
+
94
+ except Exception as e:
95
+ console.print(f"[bold red]Error loading configuration:[/bold red] {str(e)}")
96
+ self._handle_config_error()
97
+
98
+ def _handle_config_error(self) -> None:
99
+ """Handle configuration errors by downloading the reference version."""
100
+ console.print("[bold yellow]Attempting to retrieve reference configuration...[/bold yellow]")
101
+ self._download_reference_config()
102
+
103
+ # Reload the configuration
104
+ try:
105
+ with open(self.file_path, 'r') as f:
106
+ self.config = json.load(f)
107
+ self._update_settings_from_config()
108
+ console.print("[bold green]Reference configuration loaded successfully[/bold green]")
109
+ except Exception as e:
110
+ console.print(f"[bold red]Critical configuration error:[/bold red] {str(e)}")
111
+ console.print("[bold red]Unable to proceed. The application will terminate.[/bold red]")
112
+ sys.exit(1)
113
+
114
+ def _update_settings_from_config(self) -> None:
115
+ """Update internal settings from loaded configurations."""
116
+ default_section = self.config.get('DEFAULT', {})
117
+
118
+ # Save local values in temporary variables
119
+ temp_use_api = default_section.get('use_api', False)
120
+ temp_download_site_data = default_section.get('download_site_data', False)
121
+ temp_validate_github_config = default_section.get('validate_github_config', False)
122
+
123
+ # Update settings with found values (False by default)
124
+ self.use_api = temp_use_api
125
+ self.download_site_data = temp_download_site_data
126
+ self.validate_github_config = temp_validate_github_config
127
+
128
+ console.print(f"[bold cyan]API Usage:[/bold cyan] [{'green' if self.use_api else 'yellow'}]{self.use_api}[/{'green' if self.use_api else 'yellow'}]")
129
+ console.print(f"[bold cyan]Site data download:[/bold cyan] [{'green' if self.download_site_data else 'yellow'}]{self.download_site_data}[/{'green' if self.download_site_data else 'yellow'}]")
130
+ console.print(f"[bold cyan]GitHub configuration validation:[/bold cyan] [{'green' if self.validate_github_config else 'yellow'}]{self.validate_github_config}[/{'green' if self.validate_github_config else 'yellow'}]")
131
+
132
+ def _download_reference_config(self) -> None:
133
+ """Download the reference configuration from GitHub."""
134
+ console.print(f"[bold cyan]Downloading reference configuration:[/bold cyan] [green]{self.reference_config_url}[/green]")
135
+
136
+ try:
137
+ response = requests.get(self.reference_config_url, timeout=10)
138
+
139
+ if response.status_code == 200:
140
+ with open(self.file_path, 'wb') as f:
141
+ f.write(response.content)
142
+ file_size = len(response.content) / 1024
143
+ console.print(f"[bold green]Download complete:[/bold green] {os.path.basename(self.file_path)} ({file_size:.2f} KB)")
144
+ else:
145
+
146
+ error_msg = f"HTTP Error: {response.status_code}, Response: {response.text[:100]}"
147
+ console.print(f"[bold red]Download failed:[/bold red] {error_msg}")
148
+ raise Exception(error_msg)
149
+
150
+ except Exception as e:
151
+ console.print(f"[bold red]Download error:[/bold red] {str(e)}")
152
+ raise
153
+
154
+ def _validate_and_update_config(self) -> None:
155
+ """Validate the local configuration against the reference one and update missing keys."""
156
+ try:
157
+ # Download the reference configuration
158
+ console.print(f"[bold cyan]Validating configuration with GitHub...[/bold cyan]")
159
+ response = requests.get(self.reference_config_url, timeout=10)
160
+
161
+ if not response.ok:
162
+ raise Exception(f"Error downloading reference configuration. Code: {response.status_code}")
163
+
164
+ reference_config = response.json()
165
+ console.print(f"[bold cyan]Reference configuration downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]")
166
+
167
+ # Compare and update missing keys
168
+ merged_config = self._deep_merge_configs(self.config, reference_config)
169
+
170
+ if merged_config != self.config:
171
+ added_keys = self._get_added_keys(self.config, merged_config)
172
+
173
+ # Save the merged configuration
174
+ with open(self.file_path, 'w') as f:
175
+ json.dump(merged_config, f, indent=4)
176
+
177
+ key_examples = ', '.join(added_keys[:5])
178
+ if len(added_keys) > 5:
179
+ key_examples += ' and others...'
180
+
181
+ console.print(f"[bold green]Configuration updated with {len(added_keys)} new keys:[/bold green] {key_examples}")
182
+
183
+ # Update the configuration in memory
184
+ self.config = merged_config
185
+ self._update_settings_from_config()
186
+ else:
187
+ console.print("[bold green]The configuration is up to date.[/bold green]")
188
+
189
+ except Exception as e:
190
+ console.print(f"[bold red]Error validating configuration:[/bold red] {str(e)}")
191
+
192
+ def _get_added_keys(self, old_config: dict, new_config: dict, prefix="") -> list:
193
+ """
194
+ Get the list of keys added in the new configuration compared to the old one.
195
+
196
+ Args:
197
+ old_config (dict): Original configuration
198
+ new_config (dict): New configuration
199
+ prefix (str): Prefix for nested keys
200
+
201
+ Returns:
202
+ list: List of added key names
203
+ """
204
+ added_keys = []
205
+
206
+ for key, value in new_config.items():
207
+ full_key = f"{prefix}.{key}" if prefix else key
208
+
209
+ if key not in old_config:
210
+ added_keys.append(full_key)
211
+ elif isinstance(value, dict) and isinstance(old_config.get(key), dict):
212
+ added_keys.extend(self._get_added_keys(old_config[key], value, full_key))
213
+
214
+ return added_keys
215
+
216
+ def _deep_merge_configs(self, local_config: dict, reference_config: dict) -> dict:
217
+ """
218
+ Recursively merge the reference configuration into the local one, preserving local values.
219
+
220
+ Args:
221
+ local_config (dict): Local configuration
222
+ reference_config (dict): Reference configuration
223
+
224
+ Returns:
225
+ dict: Merged configuration
226
+ """
227
+ merged = local_config.copy()
228
+
229
+ for key, value in reference_config.items():
230
+ if key not in merged:
231
+
232
+ # Create the key if it doesn't exist
233
+ merged[key] = value
234
+ elif isinstance(value, dict) and isinstance(merged[key], dict):
235
+
236
+ # Handle the DEFAULT section specially
237
+ if key == 'DEFAULT':
238
+
239
+ # Make sure control keys maintain local values
240
+ merged_section = self._deep_merge_configs(merged[key], value)
241
+
242
+ # Preserve local values for the three critical settings
243
+ if 'use_api' in merged[key]:
244
+ merged_section['use_api'] = merged[key]['use_api']
245
+ if 'download_site_data' in merged[key]:
246
+ merged_section['download_site_data'] = merged[key]['download_site_data']
247
+ if 'validate_github_config' in merged[key]:
248
+ merged_section['validate_github_config'] = merged[key]['validate_github_config']
249
+
250
+ merged[key] = merged_section
251
+ else:
252
+
253
+ # Normal merge for other sections
254
+ merged[key] = self._deep_merge_configs(merged[key], value)
255
+
256
+ return merged
257
+
258
+ def _load_site_data(self) -> None:
259
+ """Load site data from API or local file."""
260
+ if self.use_api:
261
+ self._load_site_data_from_api()
262
+ else:
263
+ self._load_site_data_from_file()
264
+
265
+ def _load_site_data_from_api(self) -> None:
266
+ """Load site data from API."""
267
+ headers = {
268
+ "apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE",
269
+ "Authorization": f"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE",
270
+ "Content-Type": "application/json"
271
+ }
272
+
273
+ try:
274
+ console.print("[bold cyan]Retrieving site data from API...[/bold cyan]")
275
+ response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers, timeout=10)
276
+
277
+ if response.ok:
278
+ data = response.json()
279
+ if data and len(data) > 0:
280
+ self.configSite = data[0]['data']
281
+
282
+ site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
283
+ console.print(f"[bold green]Site data retrieved:[/bold green] {site_count} streaming services available")
284
+
285
+ # Show some sites as examples
286
+ if site_count > 0:
287
+ examples = list(self.configSite.items())[:3]
288
+ sites_info = []
289
+ for site, info in examples:
290
+ url = info.get('full_url', 'N/A')
291
+ console.print(f" • [cyan]{site}[/cyan]: {url}")
292
+ else:
293
+ console.print("[bold yellow]API returned an empty data set[/bold yellow]")
294
+ else:
295
+ console.print(f"[bold red]API request failed:[/bold red] HTTP {response.status_code}, {response.text[:100]}")
296
+ self._handle_site_data_fallback()
297
+
298
+ except Exception as e:
299
+ console.print(f"[bold red]API connection error:[/bold red] {str(e)}")
300
+ self._handle_site_data_fallback()
301
+
302
+ def _load_site_data_from_file(self) -> None:
303
+ """Load site data from local file."""
304
+ try:
305
+ if os.path.exists(self.domains_path):
306
+ console.print(f"[bold cyan]Reading domains from:[/bold cyan] {self.domains_path}")
307
+ with open(self.domains_path, 'r') as f:
308
+ self.configSite = json.load(f)
309
+
310
+ site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
311
+ console.print(f"[bold green]Site data loaded from file:[/bold green] {site_count} streaming services")
312
+
313
+ else:
314
+ error_msg = f"domains.json not found at {self.domains_path} and API usage is disabled"
315
+ console.print(f"[bold red]Configuration error:[/bold red] {error_msg}")
316
+ self._handle_site_data_fallback()
317
+
318
+ except Exception as e:
319
+ console.print(f"[bold red]Domain file error:[/bold red] {str(e)}")
320
+ self._handle_site_data_fallback()
321
+
322
+ def _handle_site_data_fallback(self) -> None:
323
+ """Handle site data fallback in case of error."""
324
+ if self.use_api and os.path.exists(self.domains_path):
325
+ console.print("[bold yellow]Attempting fallback to local domains.json file...[/bold yellow]")
326
+
327
+ try:
328
+ with open(self.domains_path, 'r') as f:
329
+ self.configSite = json.load(f)
330
+ console.print("[bold green]Fallback to local data successful[/bold green]")
331
+ except Exception as fallback_error:
332
+ console.print(f"[bold red]Fallback also failed:[/bold red] {str(fallback_error)}")
333
+ self.configSite = {}
334
+ else:
335
+
336
+ # Initialize with an empty dictionary if there are no alternatives
337
+ self.configSite = {}
338
+
339
+ def download_file(self, url: str, filename: str) -> None:
340
+ """
341
+ Download a file from the specified URL.
342
+
343
+ Args:
344
+ url (str): URL to download from
345
+ filename (str): Local filename to save to
346
+ """
347
+ try:
348
+ logging.info(f"Downloading {filename} from {url}...")
349
+ console.print(f"[bold cyan]File download:[/bold cyan] {os.path.basename(filename)}")
350
+ response = requests.get(url, timeout=10)
351
+
352
+ if response.status_code == 200:
353
+ with open(filename, 'wb') as f:
354
+ f.write(response.content)
355
+ file_size = len(response.content) / 1024
356
+ console.print(f"[bold green]Download complete:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)")
357
+
358
+ else:
359
+ error_msg = f"HTTP Status: {response.status_code}, Response: {response.text[:100]}"
360
+ console.print(f"[bold red]Download failed:[/bold red] {error_msg}")
361
+ logging.error(f"Download of {filename} failed. {error_msg}")
362
+ raise Exception(error_msg)
363
+
364
+ except Exception as e:
365
+ console.print(f"[bold red]Download error:[/bold red] {str(e)}")
366
+ logging.error(f"Download of {filename} failed: {e}")
367
+ raise
368
+
369
+ def get(self, section: str, key: str, data_type: type = str, from_site: bool = False) -> Any:
370
+ """
371
+ Read a value from the configuration.
372
+
373
+ Args:
374
+ section (str): Section in the configuration
375
+ key (str): Key to read
376
+ data_type (type, optional): Expected data type. Default: str
377
+ from_site (bool, optional): Whether to read from the site configuration. Default: False
378
+
379
+ Returns:
380
+ Any: The key value converted to the specified data type
381
+ """
382
+ cache_key = f"{'site' if from_site else 'config'}.{section}.{key}"
383
+ logging.info(f"Reading key: {cache_key}")
384
+
385
+ # Check if the value is in the cache
386
+ if cache_key in self.cache:
387
+ return self.cache[cache_key]
388
+
389
+ # Choose the appropriate source
390
+ config_source = self.configSite if from_site else self.config
391
+
392
+ # Check if the section and key exist
393
+ if section not in config_source:
394
+ raise ValueError(f"Section '{section}' not found in {'site' if from_site else 'main'} configuration")
395
+
396
+ if key not in config_source[section]:
397
+ raise ValueError(f"Key '{key}' not found in section '{section}' of {'site' if from_site else 'main'} configuration")
398
+
399
+ # Get and convert the value
400
+ value = config_source[section][key]
401
+ converted_value = self._convert_to_data_type(value, data_type)
402
+
403
+ # Save in cache
404
+ self.cache[cache_key] = converted_value
405
+
406
+ return converted_value
407
+
408
+ def _convert_to_data_type(self, value: Any, data_type: type) -> Any:
409
+ """
410
+ Convert the value to the specified data type.
411
+
412
+ Args:
413
+ value (Any): Value to convert
414
+ data_type (type): Target data type
415
+
416
+ Returns:
417
+ Any: Converted value
418
+ """
419
+ try:
420
+ if data_type == int:
421
+ return int(value)
422
+ elif data_type == float:
423
+ return float(value)
424
+ elif data_type == bool:
425
+ if isinstance(value, str):
426
+ return value.lower() in ("yes", "true", "t", "1")
427
+ return bool(value)
428
+ elif data_type == list:
429
+ if isinstance(value, list):
430
+ return value
431
+ if isinstance(value, str):
432
+ return [item.strip() for item in value.split(',')]
433
+ return [value]
434
+ elif data_type == dict:
435
+ if isinstance(value, dict):
436
+ return value
437
+ raise ValueError(f"Cannot convert {type(value).__name__} to dict")
438
+ else:
439
+ return value
440
+ except Exception as e:
441
+ logging.error(f"Error converting to {data_type.__name__}: {e}")
442
+ raise ValueError(f"Cannot convert '{value}' to {data_type.__name__}: {str(e)}")
443
+
444
+ # Getters for main configuration
445
+ def get_string(self, section: str, key: str) -> str:
446
+ """Read a string from the main configuration."""
447
+ return self.get(section, key, str)
448
+
449
+ def get_int(self, section: str, key: str) -> int:
450
+ """Read an integer from the main configuration."""
451
+ return self.get(section, key, int)
452
+
453
+ def get_float(self, section: str, key: str) -> float:
454
+ """Read a float from the main configuration."""
455
+ return self.get(section, key, float)
456
+
457
+ def get_bool(self, section: str, key: str) -> bool:
458
+ """Read a boolean from the main configuration."""
459
+ return self.get(section, key, bool)
460
+
461
+ def get_list(self, section: str, key: str) -> List[str]:
462
+ """Read a list from the main configuration."""
463
+ return self.get(section, key, list)
464
+
465
+ def get_dict(self, section: str, key: str) -> dict:
466
+ """Read a dictionary from the main configuration."""
467
+ return self.get(section, key, dict)
468
+
469
+ # Getters for site configuration
470
+ def get_site(self, section: str, key: str) -> Any:
471
+ """Read a value from the site configuration."""
472
+ return self.get(section, key, str, True)
473
+
474
+ def get_site_string(self, section: str, key: str) -> str:
475
+ """Read a string from the site configuration."""
476
+ return self.get(section, key, str, True)
477
+
478
+ def get_site_int(self, section: str, key: str) -> int:
479
+ """Read an integer from the site configuration."""
480
+ return self.get(section, key, int, True)
481
+
482
+ def get_site_float(self, section: str, key: str) -> float:
483
+ """Read a float from the site configuration."""
484
+ return self.get(section, key, float, True)
485
+
486
+ def get_site_bool(self, section: str, key: str) -> bool:
487
+ """Read a boolean from the site configuration."""
488
+ return self.get(section, key, bool, True)
489
+
490
+ def get_site_list(self, section: str, key: str) -> List[str]:
491
+ """Read a list from the site configuration."""
492
+ return self.get(section, key, list, True)
493
+
494
+ def get_site_dict(self, section: str, key: str) -> dict:
495
+ """Read a dictionary from the site configuration."""
496
+ return self.get(section, key, dict, True)
497
+
498
+ def set_key(self, section: str, key: str, value: Any, to_site: bool = False) -> None:
499
+ """
500
+ Set a key in the configuration.
501
+
502
+ Args:
503
+ section (str): Section in the configuration
504
+ key (str): Key to set
505
+ value (Any): Value to associate with the key
506
+ to_site (bool, optional): Whether to set in the site configuration. Default: False
507
+ """
508
+ try:
509
+ config_target = self.configSite if to_site else self.config
510
+
511
+ if section not in config_target:
512
+ config_target[section] = {}
513
+
514
+ config_target[section][key] = value
515
+
516
+ # Update the cache
517
+ cache_key = f"{'site' if to_site else 'config'}.{section}.{key}"
518
+ self.cache[cache_key] = value
519
+
520
+ logging.info(f"Key '{key}' set in section '{section}' of {'site' if to_site else 'main'} configuration")
521
+
522
+ except Exception as e:
523
+ error_msg = f"Error setting key '{key}' in section '{section}' of {'site' if to_site else 'main'} configuration: {e}"
524
+ logging.error(error_msg)
525
+ console.print(f"[bold red]{error_msg}[/bold red]")
526
+
527
+ def save_config(self) -> None:
528
+ """Save the main configuration to file."""
529
+ try:
530
+ with open(self.file_path, 'w') as f:
531
+ json.dump(self.config, f, indent=4)
532
+
533
+ logging.info(f"Configuration saved to: {self.file_path}")
534
+
535
+ except Exception as e:
536
+ error_msg = f"Error saving configuration: {e}"
537
+ console.print(f"[bold red]{error_msg}[/bold red]")
538
+ logging.error(error_msg)
539
+
540
+ def get_all_sites(self) -> List[str]:
541
+ """
542
+ Get the list of all available sites.
543
+
544
+ Returns:
545
+ List[str]: List of site names
546
+ """
547
+ return list(self.configSite.keys())
548
+
549
+ def has_section(self, section: str, in_site: bool = False) -> bool:
550
+ """
551
+ Check if a section exists in the configuration.
552
+
553
+ Args:
554
+ section (str): Section name
555
+ in_site (bool, optional): Whether to check in the site configuration. Default: False
556
+
557
+ Returns:
558
+ bool: True if the section exists, False otherwise
559
+ """
560
+ config_source = self.configSite if in_site else self.config
561
+ return section in config_source
562
+
563
+
564
+ # Helper function to check the platform
565
+ def get_use_large_bar():
566
+ """
567
+ Determine if the large bar feature should be enabled.
568
+
569
+ Returns:
570
+ bool: True if running on PC (Windows, macOS, Linux),
571
+ False if running on Android or iOS.
572
+ """
573
+ return not any(platform in sys.platform for platform in ("android", "ios"))
574
+
575
+
576
+ # Initialize the ConfigManager when the module is imported
577
+ config_manager = ConfigManager()
@@ -296,7 +296,7 @@ def main(script_id = 0):
296
296
  section, option = key.split('.')
297
297
  config_manager.set_key(section, option, value)
298
298
 
299
- config_manager.write_config()
299
+ config_manager.save_config()
300
300
 
301
301
  # Check if global search is requested
302
302
  if getattr(args, 'global'):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: StreamingCommunity
3
- Version: 2.9.7
3
+ Version: 2.9.8
4
4
  Home-page: https://github.com/Lovi-0/StreamingCommunity
5
5
  Author: Lovi-0
6
6
  Project-URL: Bug Reports, https://github.com/Lovi-0/StreamingCommunity/issues
@@ -10,7 +10,7 @@ with open(os.path.join(os.path.dirname(__file__), "requirements.txt"), "r", enco
10
10
 
11
11
  setup(
12
12
  name="StreamingCommunity",
13
- version="2.9.7",
13
+ version="2.9.8",
14
14
  long_description=read_readme(),
15
15
  long_description_content_type="text/markdown",
16
16
  author="Lovi-0",