StreamingCommunity 2.9.7__py3-none-any.whl → 2.9.8__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/Upload/version.py +1 -1
- StreamingCommunity/Util/config_json.py +425 -274
- StreamingCommunity/run.py +1 -1
- {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.8.dist-info}/METADATA +1 -1
- {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.8.dist-info}/RECORD +9 -9
- {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.8.dist-info}/WHEEL +0 -0
- {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.8.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.8.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.8.dist-info}/top_level.txt +0 -0
|
@@ -20,110 +20,183 @@ validate_github_config = True
|
|
|
20
20
|
|
|
21
21
|
class ConfigManager:
|
|
22
22
|
def __init__(self, file_name: str = 'config.json') -> None:
|
|
23
|
-
"""Initialize the ConfigManager.
|
|
24
|
-
|
|
25
|
-
Parameters:
|
|
26
|
-
- file_name (str, optional): The name of the configuration file. Default is 'config.json'.
|
|
27
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
|
|
28
30
|
if getattr(sys, 'frozen', False):
|
|
29
|
-
|
|
31
|
+
# If the application is frozen (e.g., PyInstaller)
|
|
32
|
+
base_path = os.path.dirname(sys.executable)
|
|
33
|
+
|
|
30
34
|
else:
|
|
31
|
-
|
|
35
|
+
# Use the current directory where the script is executed
|
|
36
|
+
base_path = os.getcwd()
|
|
32
37
|
|
|
38
|
+
# Initialize file paths
|
|
33
39
|
self.file_path = os.path.join(base_path, file_name)
|
|
34
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
|
|
35
49
|
self.config = {}
|
|
36
50
|
self.configSite = {}
|
|
37
51
|
self.cache = {}
|
|
38
|
-
|
|
52
|
+
|
|
53
|
+
self.use_api = False
|
|
54
|
+
self.download_site_data = False
|
|
55
|
+
self.validate_github_config = False
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
self._read_initial_config()
|
|
57
|
+
console.print(f"[bold cyan]Initializing ConfigManager:[/bold cyan] [green]{self.file_path}[/green]")
|
|
42
58
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
self._validate_and_update_config()
|
|
59
|
+
# Load the configuration
|
|
60
|
+
self.load_config()
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
51
70
|
try:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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()
|
|
55
93
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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]")
|
|
60
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)")
|
|
61
144
|
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
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
|
+
|
|
67
150
|
except Exception as e:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
151
|
+
console.print(f"[bold red]Download error:[/bold red] {str(e)}")
|
|
152
|
+
raise
|
|
153
|
+
|
|
71
154
|
def _validate_and_update_config(self) -> None:
|
|
72
|
-
"""Validate local
|
|
155
|
+
"""Validate the local configuration against the reference one and update missing keys."""
|
|
73
156
|
try:
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
if os.path.exists(self.file_path):
|
|
77
|
-
with open(self.file_path, 'r') as f:
|
|
78
|
-
local_config = json.load(f)
|
|
79
|
-
console.print(f"[bold cyan]Local configuration loaded:[/bold cyan] [green]{len(local_config)} keys found[/green]")
|
|
80
|
-
|
|
81
|
-
# Download reference config
|
|
82
|
-
console.print(f"[bold cyan]Downloading reference config from:[/bold cyan] [green]{self.reference_config_url}[/green]")
|
|
157
|
+
# Download the reference configuration
|
|
158
|
+
console.print(f"[bold cyan]Validating configuration with GitHub...[/bold cyan]")
|
|
83
159
|
response = requests.get(self.reference_config_url, timeout=10)
|
|
84
|
-
|
|
160
|
+
|
|
85
161
|
if not response.ok:
|
|
86
|
-
raise Exception(f"
|
|
162
|
+
raise Exception(f"Error downloading reference configuration. Code: {response.status_code}")
|
|
87
163
|
|
|
88
164
|
reference_config = response.json()
|
|
89
|
-
console.print(f"[bold cyan]Reference
|
|
90
|
-
|
|
165
|
+
console.print(f"[bold cyan]Reference configuration downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]")
|
|
166
|
+
|
|
91
167
|
# Compare and update missing keys
|
|
92
|
-
merged_config = self._deep_merge_configs(
|
|
168
|
+
merged_config = self._deep_merge_configs(self.config, reference_config)
|
|
93
169
|
|
|
94
|
-
if merged_config !=
|
|
95
|
-
added_keys = self._get_added_keys(
|
|
96
|
-
|
|
97
|
-
# Save the merged
|
|
170
|
+
if merged_config != self.config:
|
|
171
|
+
added_keys = self._get_added_keys(self.config, merged_config)
|
|
172
|
+
|
|
173
|
+
# Save the merged configuration
|
|
98
174
|
with open(self.file_path, 'w') as f:
|
|
99
175
|
json.dump(merged_config, f, indent=4)
|
|
100
|
-
|
|
101
|
-
|
|
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()
|
|
102
186
|
else:
|
|
103
|
-
console.print("[bold green]
|
|
104
|
-
|
|
105
|
-
self.config = merged_config
|
|
106
|
-
|
|
187
|
+
console.print("[bold green]The configuration is up to date.[/bold green]")
|
|
188
|
+
|
|
107
189
|
except Exception as e:
|
|
108
|
-
console.print(f"[bold red]
|
|
109
|
-
|
|
110
|
-
if not self.config:
|
|
111
|
-
console.print("[bold yellow]Falling back to reference configuration...[/bold yellow]")
|
|
112
|
-
self.download_requirements(self.reference_config_url, self.file_path)
|
|
113
|
-
|
|
114
|
-
with open(self.file_path, 'r') as f:
|
|
115
|
-
self.config = json.load(f)
|
|
116
|
-
|
|
117
|
-
console.print(f"[bold green]Reference config loaded successfully with {len(self.config)} keys[/bold green]")
|
|
118
|
-
|
|
190
|
+
console.print(f"[bold red]Error validating configuration:[/bold red] {str(e)}")
|
|
191
|
+
|
|
119
192
|
def _get_added_keys(self, old_config: dict, new_config: dict, prefix="") -> list:
|
|
120
193
|
"""
|
|
121
|
-
Get list of keys added in the new
|
|
194
|
+
Get the list of keys added in the new configuration compared to the old one.
|
|
122
195
|
|
|
123
196
|
Args:
|
|
124
|
-
old_config (dict):
|
|
125
|
-
new_config (dict):
|
|
126
|
-
prefix (str):
|
|
197
|
+
old_config (dict): Original configuration
|
|
198
|
+
new_config (dict): New configuration
|
|
199
|
+
prefix (str): Prefix for nested keys
|
|
127
200
|
|
|
128
201
|
Returns:
|
|
129
202
|
list: List of added key names
|
|
@@ -139,14 +212,14 @@ class ConfigManager:
|
|
|
139
212
|
added_keys.extend(self._get_added_keys(old_config[key], value, full_key))
|
|
140
213
|
|
|
141
214
|
return added_keys
|
|
142
|
-
|
|
215
|
+
|
|
143
216
|
def _deep_merge_configs(self, local_config: dict, reference_config: dict) -> dict:
|
|
144
217
|
"""
|
|
145
|
-
Recursively merge reference
|
|
218
|
+
Recursively merge the reference configuration into the local one, preserving local values.
|
|
146
219
|
|
|
147
220
|
Args:
|
|
148
|
-
local_config (dict):
|
|
149
|
-
reference_config (dict):
|
|
221
|
+
local_config (dict): Local configuration
|
|
222
|
+
reference_config (dict): Reference configuration
|
|
150
223
|
|
|
151
224
|
Returns:
|
|
152
225
|
dict: Merged configuration
|
|
@@ -155,272 +228,350 @@ class ConfigManager:
|
|
|
155
228
|
|
|
156
229
|
for key, value in reference_config.items():
|
|
157
230
|
if key not in merged:
|
|
231
|
+
|
|
232
|
+
# Create the key if it doesn't exist
|
|
158
233
|
merged[key] = value
|
|
159
234
|
elif isinstance(value, dict) and isinstance(merged[key], dict):
|
|
160
|
-
merged[key] = self._deep_merge_configs(merged[key], value)
|
|
161
|
-
|
|
162
|
-
return merged
|
|
163
235
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
try:
|
|
167
|
-
logging.info(f"Reading file: {self.file_path}")
|
|
236
|
+
# Handle the DEFAULT section specially
|
|
237
|
+
if key == 'DEFAULT':
|
|
168
238
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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:
|
|
174
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]")
|
|
175
294
|
else:
|
|
176
|
-
console.print(f"[bold
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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")
|
|
187
312
|
|
|
188
|
-
# Update site configuration separately if enabled
|
|
189
|
-
if download_site_data:
|
|
190
|
-
self.update_site_config()
|
|
191
313
|
else:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
|
|
196
318
|
except Exception as e:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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]")
|
|
200
326
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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:
|
|
204
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
|
+
|
|
205
343
|
Args:
|
|
206
|
-
url (str):
|
|
207
|
-
filename (str):
|
|
344
|
+
url (str): URL to download from
|
|
345
|
+
filename (str): Local filename to save to
|
|
208
346
|
"""
|
|
209
347
|
try:
|
|
210
348
|
logging.info(f"Downloading {filename} from {url}...")
|
|
211
|
-
console.print(f"[bold cyan]
|
|
349
|
+
console.print(f"[bold cyan]File download:[/bold cyan] {os.path.basename(filename)}")
|
|
212
350
|
response = requests.get(url, timeout=10)
|
|
213
|
-
|
|
351
|
+
|
|
214
352
|
if response.status_code == 200:
|
|
215
353
|
with open(filename, 'wb') as f:
|
|
216
354
|
f.write(response.content)
|
|
217
355
|
file_size = len(response.content) / 1024
|
|
218
|
-
console.print(f"[bold green]Download
|
|
219
|
-
|
|
356
|
+
console.print(f"[bold green]Download complete:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)")
|
|
357
|
+
|
|
220
358
|
else:
|
|
221
359
|
error_msg = f"HTTP Status: {response.status_code}, Response: {response.text[:100]}"
|
|
222
360
|
console.print(f"[bold red]Download failed:[/bold red] {error_msg}")
|
|
223
|
-
logging.error(f"
|
|
224
|
-
|
|
225
|
-
|
|
361
|
+
logging.error(f"Download of {filename} failed. {error_msg}")
|
|
362
|
+
raise Exception(error_msg)
|
|
363
|
+
|
|
226
364
|
except Exception as e:
|
|
227
365
|
console.print(f"[bold red]Download error:[/bold red] {str(e)}")
|
|
228
|
-
logging.error(f"
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
try:
|
|
241
|
-
console.print("[bold cyan]Fetching site data from API...[/bold cyan]")
|
|
242
|
-
response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers, timeout=10)
|
|
243
|
-
|
|
244
|
-
if response.ok:
|
|
245
|
-
data = response.json()
|
|
246
|
-
if data and len(data) > 0:
|
|
247
|
-
self.configSite = data[0]['data']
|
|
248
|
-
|
|
249
|
-
# Display available sites and their domains
|
|
250
|
-
site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
|
|
251
|
-
console.print(f"[bold green]Site data fetched:[/bold green] {site_count} streaming services available")
|
|
252
|
-
|
|
253
|
-
# List a few sites as examples
|
|
254
|
-
if site_count > 0:
|
|
255
|
-
examples = list(self.configSite.items())[:3]
|
|
256
|
-
sites_info = []
|
|
257
|
-
for site, info in examples:
|
|
258
|
-
sites_info.append(f"[cyan]{site}[/cyan]: {info.get('full_url', 'N/A')}")
|
|
259
|
-
|
|
260
|
-
else:
|
|
261
|
-
console.print("[bold yellow]API returned empty data set[/bold yellow]")
|
|
262
|
-
else:
|
|
263
|
-
console.print(f"[bold red]API request failed:[/bold red] HTTP {response.status_code}, {response.text[:100]}")
|
|
264
|
-
|
|
265
|
-
except Exception as e:
|
|
266
|
-
console.print(f"[bold red]API connection error:[/bold red] {str(e)}")
|
|
267
|
-
else:
|
|
268
|
-
try:
|
|
269
|
-
if os.path.exists(self.domains_path):
|
|
270
|
-
console.print(f"[bold cyan]Reading domains from:[/bold cyan] {self.domains_path}")
|
|
271
|
-
with open(self.domains_path, 'r') as f:
|
|
272
|
-
self.configSite = json.load(f)
|
|
273
|
-
|
|
274
|
-
else:
|
|
275
|
-
error_msg = f"domains.json not found at {self.domains_path} and API usage is disabled"
|
|
276
|
-
console.print(f"[bold red]Configuration error:[/bold red] {error_msg}")
|
|
277
|
-
raise FileNotFoundError(error_msg)
|
|
278
|
-
|
|
279
|
-
except Exception as e:
|
|
280
|
-
console.print(f"[bold red]Domains file error:[/bold red] {str(e)}")
|
|
281
|
-
raise
|
|
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
|
|
282
378
|
|
|
283
|
-
def read_key(self, section: str, key: str, data_type: type = str, from_site: bool = False) -> Any:
|
|
284
|
-
"""Read a key from the configuration.
|
|
285
|
-
|
|
286
|
-
Parameters:
|
|
287
|
-
- section (str): The section in the configuration.
|
|
288
|
-
- key (str): The key to be read.
|
|
289
|
-
- data_type (type, optional): The expected data type of the key's value. Default is str.
|
|
290
|
-
- from_site (bool, optional): Whether to read from site config. Default is False.
|
|
291
|
-
|
|
292
379
|
Returns:
|
|
293
|
-
The value
|
|
380
|
+
Any: The key value converted to the specified data type
|
|
294
381
|
"""
|
|
295
382
|
cache_key = f"{'site' if from_site else 'config'}.{section}.{key}"
|
|
296
|
-
logging.info(f"
|
|
297
|
-
|
|
383
|
+
logging.info(f"Reading key: {cache_key}")
|
|
384
|
+
|
|
385
|
+
# Check if the value is in the cache
|
|
298
386
|
if cache_key in self.cache:
|
|
299
387
|
return self.cache[cache_key]
|
|
300
|
-
|
|
388
|
+
|
|
389
|
+
# Choose the appropriate source
|
|
301
390
|
config_source = self.configSite if from_site else self.config
|
|
302
391
|
|
|
303
|
-
if section
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
+
|
|
320
416
|
Returns:
|
|
321
|
-
|
|
417
|
+
Any: Converted value
|
|
322
418
|
"""
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
+
|
|
339
449
|
def get_int(self, section: str, key: str) -> int:
|
|
340
|
-
"""Read an integer
|
|
341
|
-
return self.
|
|
342
|
-
|
|
450
|
+
"""Read an integer from the main configuration."""
|
|
451
|
+
return self.get(section, key, int)
|
|
452
|
+
|
|
343
453
|
def get_float(self, section: str, key: str) -> float:
|
|
344
|
-
"""Read a float
|
|
345
|
-
return self.
|
|
346
|
-
|
|
454
|
+
"""Read a float from the main configuration."""
|
|
455
|
+
return self.get(section, key, float)
|
|
456
|
+
|
|
347
457
|
def get_bool(self, section: str, key: str) -> bool:
|
|
348
|
-
"""Read a boolean
|
|
349
|
-
return self.
|
|
350
|
-
|
|
458
|
+
"""Read a boolean from the main configuration."""
|
|
459
|
+
return self.get(section, key, bool)
|
|
460
|
+
|
|
351
461
|
def get_list(self, section: str, key: str) -> List[str]:
|
|
352
|
-
"""Read a list
|
|
353
|
-
return self.
|
|
354
|
-
|
|
462
|
+
"""Read a list from the main configuration."""
|
|
463
|
+
return self.get(section, key, list)
|
|
464
|
+
|
|
355
465
|
def get_dict(self, section: str, key: str) -> dict:
|
|
356
|
-
"""Read a dictionary
|
|
357
|
-
return self.
|
|
358
|
-
|
|
359
|
-
#
|
|
466
|
+
"""Read a dictionary from the main configuration."""
|
|
467
|
+
return self.get(section, key, dict)
|
|
468
|
+
|
|
469
|
+
# Getters for site configuration
|
|
360
470
|
def get_site(self, section: str, key: str) -> Any:
|
|
361
471
|
"""Read a value from the site configuration."""
|
|
362
|
-
return self.
|
|
363
|
-
|
|
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
|
+
|
|
364
478
|
def get_site_int(self, section: str, key: str) -> int:
|
|
365
|
-
"""Read an integer
|
|
366
|
-
return self.
|
|
367
|
-
|
|
479
|
+
"""Read an integer from the site configuration."""
|
|
480
|
+
return self.get(section, key, int, True)
|
|
481
|
+
|
|
368
482
|
def get_site_float(self, section: str, key: str) -> float:
|
|
369
|
-
"""Read a float
|
|
370
|
-
return self.
|
|
371
|
-
|
|
483
|
+
"""Read a float from the site configuration."""
|
|
484
|
+
return self.get(section, key, float, True)
|
|
485
|
+
|
|
372
486
|
def get_site_bool(self, section: str, key: str) -> bool:
|
|
373
|
-
"""Read a boolean
|
|
374
|
-
return self.
|
|
375
|
-
|
|
487
|
+
"""Read a boolean from the site configuration."""
|
|
488
|
+
return self.get(section, key, bool, True)
|
|
489
|
+
|
|
376
490
|
def get_site_list(self, section: str, key: str) -> List[str]:
|
|
377
|
-
"""Read a list
|
|
378
|
-
return self.
|
|
379
|
-
|
|
491
|
+
"""Read a list from the site configuration."""
|
|
492
|
+
return self.get(section, key, list, True)
|
|
493
|
+
|
|
380
494
|
def get_site_dict(self, section: str, key: str) -> dict:
|
|
381
|
-
"""Read a dictionary
|
|
382
|
-
return self.
|
|
383
|
-
|
|
495
|
+
"""Read a dictionary from the site configuration."""
|
|
496
|
+
return self.get(section, key, dict, True)
|
|
497
|
+
|
|
384
498
|
def set_key(self, section: str, key: str, value: Any, to_site: bool = False) -> None:
|
|
385
|
-
"""
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
|
392
507
|
"""
|
|
393
508
|
try:
|
|
394
509
|
config_target = self.configSite if to_site else self.config
|
|
395
510
|
|
|
396
511
|
if section not in config_target:
|
|
397
512
|
config_target[section] = {}
|
|
398
|
-
|
|
513
|
+
|
|
399
514
|
config_target[section][key] = value
|
|
515
|
+
|
|
516
|
+
# Update the cache
|
|
400
517
|
cache_key = f"{'site' if to_site else 'config'}.{section}.{key}"
|
|
401
518
|
self.cache[cache_key] = value
|
|
402
|
-
|
|
519
|
+
|
|
520
|
+
logging.info(f"Key '{key}' set in section '{section}' of {'site' if to_site else 'main'} configuration")
|
|
521
|
+
|
|
403
522
|
except Exception as e:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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."""
|
|
408
529
|
try:
|
|
409
530
|
with open(self.file_path, 'w') as f:
|
|
410
531
|
json.dump(self.config, f, indent=4)
|
|
411
|
-
except Exception as e:
|
|
412
|
-
print(f"Error writing configuration file: {e}")
|
|
413
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
|
|
414
562
|
|
|
415
|
-
config_manager = ConfigManager()
|
|
416
|
-
config_manager.read_config()
|
|
417
563
|
|
|
564
|
+
# Helper function to check the platform
|
|
418
565
|
def get_use_large_bar():
|
|
419
566
|
"""
|
|
420
|
-
|
|
421
|
-
|
|
567
|
+
Determine if the large bar feature should be enabled.
|
|
568
|
+
|
|
422
569
|
Returns:
|
|
423
|
-
bool: True if running on
|
|
570
|
+
bool: True if running on PC (Windows, macOS, Linux),
|
|
424
571
|
False if running on Android or iOS.
|
|
425
572
|
"""
|
|
426
|
-
return not any(platform in sys.platform for platform in ("android", "ios"))
|
|
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()
|
StreamingCommunity/run.py
CHANGED
|
@@ -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.
|
|
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
|
StreamingCommunity/__init__.py,sha256=Cw-N0VCg7sef1WqdtvVwrhs1zc4LoUhs5C8k7vpM1lQ,207
|
|
2
2
|
StreamingCommunity/global_search.py,sha256=1U74JtXLWDGC_r5KCsQt4kC-XRO76QIWk3QnUz-1AqY,12126
|
|
3
|
-
StreamingCommunity/run.py,sha256=
|
|
3
|
+
StreamingCommunity/run.py,sha256=G4n1n6BrJv0Y32jCs25LKgUzXaSkFe3F5DaZknnehdA,13212
|
|
4
4
|
StreamingCommunity/Api/Player/ddl.py,sha256=M_ePETCMBpIHr5K5Yb7EML5VXwqkR7vJHQcGIv4AEQw,2261
|
|
5
5
|
StreamingCommunity/Api/Player/maxstream.py,sha256=WXg8xncFXFiaUmTVXxB3NyknQtbvd0sF1eRaoDO24bU,4822
|
|
6
6
|
StreamingCommunity/Api/Player/supervideo.py,sha256=hr9QViI-XD0Dqhcx90oaH8_j0d6cxpVaf-EuCjMs6hI,5199
|
|
@@ -68,18 +68,18 @@ StreamingCommunity/Lib/TMBD/tmdb.py,sha256=byg0EFnlmd9JeLvn1N9K3QkB1KEfeMuFa7OVf
|
|
|
68
68
|
StreamingCommunity/TelegramHelp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
69
|
StreamingCommunity/TelegramHelp/telegram_bot.py,sha256=Qe1__aoK4PpDuing8JtWgdHzLee8LuYYyfeLNA7yADU,26307
|
|
70
70
|
StreamingCommunity/Upload/update.py,sha256=qViEz--kqt7Z9Zxc9z9RyvhLOl22cTfnu9VSAwqn4y0,2549
|
|
71
|
-
StreamingCommunity/Upload/version.py,sha256=
|
|
71
|
+
StreamingCommunity/Upload/version.py,sha256=nm7psKKdvMmO9ZBmcHw2ESy9cG8rStPFomHd54xe7yI,171
|
|
72
72
|
StreamingCommunity/Util/color.py,sha256=NvD0Eni-25oOOkY-szCEoc0lGvzQxyL7xhM0RE4EvUM,458
|
|
73
|
-
StreamingCommunity/Util/config_json.py,sha256=
|
|
73
|
+
StreamingCommunity/Util/config_json.py,sha256=xiE5JNw9HgbBdBqKhlywwWw1EnoyDdG47wcxFDTC8sA,24960
|
|
74
74
|
StreamingCommunity/Util/ffmpeg_installer.py,sha256=q5yb_ZXKe9PhcG7JbKLfo1AZa8DNukgHqymPbudDuAY,13585
|
|
75
75
|
StreamingCommunity/Util/headers.py,sha256=TItkaFMx1GqsVNEIS3Tr0BGU5EHyF-HkZVliHORT3P8,308
|
|
76
76
|
StreamingCommunity/Util/logger.py,sha256=9kGD6GmWj2pM8ADpJc85o7jm8DD0c5Aguqnq-9kmxos,3314
|
|
77
77
|
StreamingCommunity/Util/message.py,sha256=SJaIPLvWeQqsIODVUKw3TgYRmBChovmlbcF6OUxqMI8,1425
|
|
78
78
|
StreamingCommunity/Util/os.py,sha256=MUGJKQbNMWeoUrnJ2Ug3hoyYlrPDqU9BY94UmiUbxfA,14858
|
|
79
79
|
StreamingCommunity/Util/table.py,sha256=QDVCVewMQ2JIQV3yDxQEL7Ac_m7NyRmynFg11BDeSBQ,9621
|
|
80
|
-
streamingcommunity-2.9.
|
|
81
|
-
streamingcommunity-2.9.
|
|
82
|
-
streamingcommunity-2.9.
|
|
83
|
-
streamingcommunity-2.9.
|
|
84
|
-
streamingcommunity-2.9.
|
|
85
|
-
streamingcommunity-2.9.
|
|
80
|
+
streamingcommunity-2.9.8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
81
|
+
streamingcommunity-2.9.8.dist-info/METADATA,sha256=5K1lvxzORHEK1BBSGAqSWI2qwXH-pBazDc58BpKm19w,25012
|
|
82
|
+
streamingcommunity-2.9.8.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
83
|
+
streamingcommunity-2.9.8.dist-info/entry_points.txt,sha256=Qph9XYfDC8n4LfDLOSl6gJGlkb9eFb5f-JOr_Wb_5rk,67
|
|
84
|
+
streamingcommunity-2.9.8.dist-info/top_level.txt,sha256=YsOcxKP-WOhWpIWgBlh0coll9XUx7aqmRPT7kmt3fH0,19
|
|
85
|
+
streamingcommunity-2.9.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|