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.

@@ -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'
@@ -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
- base_path = os.path.join(".")
31
+ # If the application is frozen (e.g., PyInstaller)
32
+ base_path = os.path.dirname(sys.executable)
33
+
30
34
  else:
31
- base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
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
- self.reference_config_url = 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/config.json'
52
+
53
+ self.use_api = False
54
+ self.download_site_data = False
55
+ self.validate_github_config = False
39
56
 
40
- # Read initial config to get use_api setting
41
- self._read_initial_config()
57
+ console.print(f"[bold cyan]Initializing ConfigManager:[/bold cyan] [green]{self.file_path}[/green]")
42
58
 
43
- # Validate and update config before proceeding (if enabled)
44
- if validate_github_config:
45
- self._validate_and_update_config()
59
+ # Load the configuration
60
+ self.load_config()
46
61
 
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."""
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
- if os.path.exists(self.file_path):
53
- with open(self.file_path, 'r') as f:
54
- self.config = json.load(f)
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
- 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'}]")
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
- self.use_api = True
69
- console.print("[bold red]Error reading API setting. Using default: True[/bold red]")
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 config against reference config and update missing keys."""
155
+ """Validate the local configuration against the reference one and update missing keys."""
73
156
  try:
74
- # Load local config if exists
75
- local_config = {}
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"Failed to download reference config. Status code: {response.status_code}")
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 config downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]")
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(local_config, reference_config)
168
+ merged_config = self._deep_merge_configs(self.config, reference_config)
93
169
 
94
- if merged_config != local_config:
95
- added_keys = self._get_added_keys(local_config, merged_config)
96
-
97
- # Save the merged config
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
- 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
-
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]Configuration is up to date.[/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]Configuration validation error:[/bold red] {str(e)}")
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 config compared to old config.
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): The original configuration
125
- new_config (dict): The new configuration
126
- prefix (str): Key prefix for nested keys
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 config into local config, preserving local values.
218
+ Recursively merge the reference configuration into the local one, preserving local values.
146
219
 
147
220
  Args:
148
- local_config (dict): The local configuration
149
- reference_config (dict): The reference configuration
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
- def read_config(self) -> None:
165
- """Read the configuration file."""
166
- try:
167
- logging.info(f"Reading file: {self.file_path}")
236
+ # Handle the DEFAULT section specially
237
+ if key == 'DEFAULT':
168
238
 
169
- # Check if file exists
170
- if os.path.exists(self.file_path):
171
- with open(self.file_path, 'r') as f:
172
- self.config = json.load(f)
173
- console.print(f"[bold green]Configuration loaded:[/bold green] {len(self.config)} keys, {sum(1 for _ in json.dumps(self.config))} bytes")
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 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}")
178
- self.download_requirements(self.reference_config_url, self.file_path)
179
-
180
- # Load the downloaded config.json into the config attribute
181
- with open(self.file_path, 'r') as f:
182
- self.config = json.load(f)
183
- console.print(f"[bold green]Configuration downloaded and saved:[/bold green] {len(self.config)} keys")
184
-
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)
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
- console.print("[bold yellow]Site data download is disabled[/bold yellow]")
193
-
194
- console.print("[bold cyan]Configuration processing complete[/bold cyan]")
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
- logging.error(f"Error reading configuration file: {e}")
198
- console.print(f"[bold red]Failed to read configuration:[/bold red] {str(e)}")
199
- sys.exit(0)
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
- def download_requirements(self, url: str, filename: str) -> None:
202
- """
203
- Download a file from the specified URL if not found locally using requests.
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): The URL to download the file from.
207
- filename (str): The local filename to save the file as.
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]Downloading file:[/bold cyan] {os.path.basename(filename)}")
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 successful:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)")
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"Failed to download {filename}. {error_msg}")
224
- sys.exit(0)
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"Failed to download {filename}: {e}")
229
- sys.exit(0)
230
-
231
- def update_site_config(self) -> None:
232
- """Fetch and update the site configuration with data from the API or local file."""
233
- if self.use_api:
234
- headers = {
235
- "apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE",
236
- "Authorization": f"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE",
237
- "Content-Type": "application/json"
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 of the key converted to the specified data type.
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"Read key: {cache_key}")
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 in config_source and key in config_source[section]:
304
- value = config_source[section][key]
305
- else:
306
- raise ValueError(f"Key '{key}' not found in section '{section}' of {'site' if from_site else 'main'} config")
307
-
308
- value = self._convert_to_data_type(value, data_type)
309
- self.cache[cache_key] = value
310
-
311
- return value
312
-
313
- def _convert_to_data_type(self, value: str, data_type: type) -> Any:
314
- """Convert the value to the specified data type.
315
-
316
- Parameters:
317
- - value (str): The value to be converted.
318
- - data_type (type): The expected data type.
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
- The value converted to the specified data type.
417
+ Any: Converted value
322
418
  """
323
- if data_type == int:
324
- return int(value)
325
- elif data_type == bool:
326
- return bool(value)
327
- elif data_type == list:
328
- return value if isinstance(value, list) else [item.strip() for item in value.split(',')]
329
- elif data_type == type(None):
330
- return None
331
- else:
332
- return value
333
-
334
- # Main config getters
335
- def get(self, section: str, key: str) -> Any:
336
- """Read a value from the main configuration."""
337
- return self.read_key(section, key)
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 value from the main configuration."""
341
- return self.read_key(section, key, int)
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 value from the main configuration."""
345
- return self.read_key(section, key, float)
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 value from the main configuration."""
349
- return self.read_key(section, key, bool)
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 value from the main configuration."""
353
- return self.read_key(section, key, list)
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 value from the main configuration."""
357
- return self.read_key(section, key, dict)
358
-
359
- # Site config getters
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.read_key(section, key, from_site=True)
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 value from the site configuration."""
366
- return self.read_key(section, key, int, from_site=True)
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 value from the site configuration."""
370
- return self.read_key(section, key, float, from_site=True)
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 value from the site configuration."""
374
- return self.read_key(section, key, bool, from_site=True)
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 value from the site configuration."""
378
- return self.read_key(section, key, list, from_site=True)
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 value from the site configuration."""
382
- return self.read_key(section, key, dict, from_site=True)
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
- """Set a key in the configuration.
386
-
387
- Parameters:
388
- - section (str): The section in the configuration.
389
- - key (str): The key to be set.
390
- - value (Any): The value to be associated with the key.
391
- - to_site (bool, optional): Whether to set in site config. Default is False.
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
- print(f"Error setting key '{key}' in section '{section}' of {'site' if to_site else 'main'} config: {e}")
405
-
406
- def write_config(self) -> None:
407
- """Write the main configuration to the file."""
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
- Determines whether the large bar feature should be enabled.
421
-
567
+ Determine if the large bar feature should be enabled.
568
+
422
569
  Returns:
423
- bool: True if running on a PC (Windows, macOS, Linux),
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.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
@@ -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=OgHnX1bdAFDLEou7pclpDIKXRFhIiqkZaLlYgHuhf0E,13213
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=LgWw3GKanaUE__hA3sUm7n2w8P7ROnP4GdXw9KpaH2c,171
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=ocf1wsC1o0Nq8UStHUwj_QryMi_2O0yPpLEAkaMZPGg,18978
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.7.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
81
- streamingcommunity-2.9.7.dist-info/METADATA,sha256=KJWhvPLrNBpfTP_as7jq6-L1ibdLRylY7q2FUb-wtaM,25012
82
- streamingcommunity-2.9.7.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
83
- streamingcommunity-2.9.7.dist-info/entry_points.txt,sha256=Qph9XYfDC8n4LfDLOSl6gJGlkb9eFb5f-JOr_Wb_5rk,67
84
- streamingcommunity-2.9.7.dist-info/top_level.txt,sha256=YsOcxKP-WOhWpIWgBlh0coll9XUx7aqmRPT7kmt3fH0,19
85
- streamingcommunity-2.9.7.dist-info/RECORD,,
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,,