StreamingCommunity 2.6.1__py3-none-any.whl → 2.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of StreamingCommunity might be problematic. Click here for more details.

Files changed (76) hide show
  1. StreamingCommunity/Api/Player/ddl.py +4 -4
  2. StreamingCommunity/Api/Player/maxstream.py +10 -16
  3. StreamingCommunity/Api/Player/supervideo.py +9 -35
  4. StreamingCommunity/Api/Player/vixcloud.py +18 -92
  5. StreamingCommunity/Api/Site/1337xx/__init__.py +8 -1
  6. StreamingCommunity/Api/Site/1337xx/site.py +16 -15
  7. StreamingCommunity/Api/Site/1337xx/title.py +7 -5
  8. StreamingCommunity/Api/Site/animeunity/__init__.py +9 -2
  9. StreamingCommunity/Api/Site/animeunity/film_serie.py +12 -5
  10. StreamingCommunity/Api/Site/animeunity/site.py +14 -10
  11. StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +9 -10
  12. StreamingCommunity/Api/Site/cb01new/__init__.py +8 -1
  13. StreamingCommunity/Api/Site/cb01new/film.py +7 -1
  14. StreamingCommunity/Api/Site/cb01new/site.py +24 -15
  15. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +9 -2
  16. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +7 -1
  17. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +16 -15
  18. StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +3 -3
  19. StreamingCommunity/Api/Site/guardaserie/__init__.py +9 -2
  20. StreamingCommunity/Api/Site/guardaserie/series.py +9 -1
  21. StreamingCommunity/Api/Site/guardaserie/site.py +23 -22
  22. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +5 -4
  23. StreamingCommunity/Api/Site/mostraguarda/__init__.py +6 -2
  24. StreamingCommunity/Api/Site/mostraguarda/film.py +10 -6
  25. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +9 -2
  26. StreamingCommunity/Api/Site/streamingcommunity/film.py +9 -2
  27. StreamingCommunity/Api/Site/streamingcommunity/series.py +15 -6
  28. StreamingCommunity/Api/Site/streamingcommunity/site.py +16 -14
  29. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +10 -11
  30. StreamingCommunity/Api/Template/Util/__init__.py +0 -1
  31. StreamingCommunity/Api/Template/Util/get_domain.py +31 -134
  32. StreamingCommunity/Api/Template/Util/manage_ep.py +10 -5
  33. StreamingCommunity/Api/Template/config_loader.py +14 -10
  34. StreamingCommunity/Api/Template/site.py +3 -6
  35. StreamingCommunity/Lib/Downloader/HLS/downloader.py +12 -15
  36. StreamingCommunity/Lib/Downloader/HLS/segments.py +14 -34
  37. StreamingCommunity/Lib/Downloader/MP4/downloader.py +14 -11
  38. StreamingCommunity/Lib/Downloader/TOR/downloader.py +109 -101
  39. StreamingCommunity/Lib/FFmpeg/__init__.py +1 -1
  40. StreamingCommunity/Lib/FFmpeg/capture.py +10 -12
  41. StreamingCommunity/Lib/FFmpeg/command.py +15 -14
  42. StreamingCommunity/Lib/FFmpeg/util.py +9 -38
  43. StreamingCommunity/Lib/M3U8/decryptor.py +72 -146
  44. StreamingCommunity/Lib/M3U8/estimator.py +8 -16
  45. StreamingCommunity/Lib/M3U8/parser.py +1 -17
  46. StreamingCommunity/Lib/M3U8/url_fixer.py +1 -4
  47. StreamingCommunity/Lib/TMBD/__init__.py +2 -0
  48. StreamingCommunity/Lib/TMBD/obj_tmbd.py +3 -17
  49. StreamingCommunity/Lib/TMBD/tmdb.py +4 -9
  50. StreamingCommunity/TelegramHelp/telegram_bot.py +50 -50
  51. StreamingCommunity/Upload/update.py +6 -5
  52. StreamingCommunity/Upload/version.py +1 -1
  53. StreamingCommunity/Util/color.py +1 -1
  54. StreamingCommunity/Util/config_json.py +435 -0
  55. StreamingCommunity/Util/headers.py +7 -36
  56. StreamingCommunity/Util/logger.py +72 -42
  57. StreamingCommunity/Util/message.py +8 -3
  58. StreamingCommunity/Util/os.py +41 -93
  59. StreamingCommunity/Util/table.py +8 -17
  60. StreamingCommunity/run.py +39 -43
  61. {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/METADATA +203 -114
  62. StreamingCommunity-2.8.0.dist-info/RECORD +75 -0
  63. StreamingCommunity/Api/Site/ilcorsaronero/__init__.py +0 -53
  64. StreamingCommunity/Api/Site/ilcorsaronero/site.py +0 -64
  65. StreamingCommunity/Api/Site/ilcorsaronero/title.py +0 -42
  66. StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py +0 -149
  67. StreamingCommunity/Api/Template/Util/recall_search.py +0 -37
  68. StreamingCommunity/Lib/Downloader/HLS/proxyes.py +0 -110
  69. StreamingCommunity/Util/_jsonConfig.py +0 -241
  70. StreamingCommunity/Util/call_stack.py +0 -42
  71. StreamingCommunity/Util/console.py +0 -12
  72. StreamingCommunity-2.6.1.dist-info/RECORD +0 -83
  73. {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/LICENSE +0 -0
  74. {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/WHEEL +0 -0
  75. {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/entry_points.txt +0 -0
  76. {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,435 @@
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
+ """Initialize the ConfigManager.
24
+
25
+ Parameters:
26
+ - file_name (str, optional): The name of the configuration file. Default is 'config.json'.
27
+ """
28
+ if getattr(sys, 'frozen', False):
29
+ base_path = os.path.join(".")
30
+ else:
31
+ base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
32
+
33
+ self.file_path = os.path.join(base_path, file_name)
34
+ self.domains_path = os.path.join(base_path, 'domains.json')
35
+ self.config = {}
36
+ self.configSite = {}
37
+ self.cache = {}
38
+ self.reference_config_url = 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/config.json'
39
+
40
+ # Read initial config to get use_api setting
41
+ self._read_initial_config()
42
+
43
+ # Validate and update config before proceeding (if enabled)
44
+ if validate_github_config:
45
+ self._validate_and_update_config()
46
+
47
+ console.print(f"[bold cyan]ConfigManager initialized:[/bold cyan] [green]{self.file_path}[/green]")
48
+
49
+ def _read_initial_config(self) -> None:
50
+ """Read initial configuration to get use_api setting."""
51
+ try:
52
+ if os.path.exists(self.file_path):
53
+ with open(self.file_path, 'r') as f:
54
+ self.config = json.load(f)
55
+
56
+ self.use_api = self.config.get('DEFAULT', {}).get('use_api', True)
57
+ console.print(f"[bold cyan]API usage setting:[/bold cyan] [{'green' if self.use_api else 'yellow'}]{self.use_api}[/{'green' if self.use_api else 'yellow'}]")
58
+ console.print(f"[bold cyan]Download site data:[/bold cyan] [{'green' if download_site_data else 'yellow'}]{download_site_data}[/{'green' if download_site_data else 'yellow'}]")
59
+ console.print(f"[bold cyan]Validate GitHub config:[/bold cyan] [{'green' if validate_github_config else 'yellow'}]{validate_github_config}[/{'green' if validate_github_config else 'yellow'}]")
60
+
61
+ else:
62
+ self.use_api = True
63
+ console.print("[bold yellow]Configuration file not found. Using default API setting: True[/bold yellow]")
64
+ console.print(f"[bold yellow]Download site data: {download_site_data}[/bold yellow]")
65
+ console.print(f"[bold yellow]Validate GitHub config: {validate_github_config}[/bold yellow]")
66
+
67
+ except Exception as e:
68
+ self.use_api = True
69
+ console.print("[bold red]Error reading API setting. Using default: True[/bold red]")
70
+
71
+ def _validate_and_update_config(self) -> None:
72
+ """Validate local config against reference config and update missing keys."""
73
+ 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]")
83
+ response = requests.get(self.reference_config_url, timeout=10)
84
+
85
+ if not response.ok:
86
+ raise Exception(f"Failed to download reference config. Status code: {response.status_code}")
87
+
88
+ reference_config = response.json()
89
+ console.print(f"[bold cyan]Reference config downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]")
90
+
91
+ # Compare and update missing keys
92
+ merged_config = self._deep_merge_configs(local_config, reference_config)
93
+
94
+ if merged_config != local_config:
95
+ added_keys = self._get_added_keys(local_config, merged_config)
96
+
97
+ # Save the merged config
98
+ with open(self.file_path, 'w') as f:
99
+ 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
+
102
+ else:
103
+ console.print("[bold green]Configuration is up to date.[/bold green]")
104
+
105
+ self.config = merged_config
106
+
107
+ 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
+
119
+ def _get_added_keys(self, old_config: dict, new_config: dict, prefix="") -> list:
120
+ """
121
+ Get list of keys added in the new config compared to old config.
122
+
123
+ Args:
124
+ old_config (dict): The original configuration
125
+ new_config (dict): The new configuration
126
+ prefix (str): Key prefix for nested keys
127
+
128
+ Returns:
129
+ list: List of added key names
130
+ """
131
+ added_keys = []
132
+
133
+ for key, value in new_config.items():
134
+ full_key = f"{prefix}.{key}" if prefix else key
135
+
136
+ if key not in old_config:
137
+ added_keys.append(full_key)
138
+ elif isinstance(value, dict) and isinstance(old_config.get(key), dict):
139
+ added_keys.extend(self._get_added_keys(old_config[key], value, full_key))
140
+
141
+ return added_keys
142
+
143
+ def _deep_merge_configs(self, local_config: dict, reference_config: dict) -> dict:
144
+ """
145
+ Recursively merge reference config into local config, preserving local values.
146
+
147
+ Args:
148
+ local_config (dict): The local configuration
149
+ reference_config (dict): The reference configuration
150
+
151
+ Returns:
152
+ dict: Merged configuration
153
+ """
154
+ merged = local_config.copy()
155
+
156
+ for key, value in reference_config.items():
157
+ if key not in merged:
158
+ merged[key] = value
159
+ elif isinstance(value, dict) and isinstance(merged[key], dict):
160
+ merged[key] = self._deep_merge_configs(merged[key], value)
161
+
162
+ return merged
163
+
164
+ def read_config(self) -> None:
165
+ """Read the configuration file."""
166
+ try:
167
+ logging.info(f"Reading file: {self.file_path}")
168
+
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")
174
+
175
+ 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)
187
+
188
+ # Update site configuration separately if enabled
189
+ if download_site_data:
190
+ self.update_site_config()
191
+ else:
192
+ console.print("[bold yellow]Site data download is disabled[/bold yellow]")
193
+
194
+ console.print("[bold cyan]Configuration processing complete[/bold cyan]")
195
+
196
+ 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
+
200
+ def download_requirements(self, url: str, filename: str) -> None:
201
+ """
202
+ Download a file from the specified URL if not found locally using requests.
203
+
204
+ Args:
205
+ url (str): The URL to download the file from.
206
+ filename (str): The local filename to save the file as.
207
+ """
208
+ try:
209
+ logging.info(f"Downloading {filename} from {url}...")
210
+ console.print(f"[bold cyan]Downloading file:[/bold cyan] {os.path.basename(filename)}")
211
+ response = requests.get(url, timeout=10)
212
+
213
+ if response.status_code == 200:
214
+ with open(filename, 'wb') as f:
215
+ f.write(response.content)
216
+ file_size = len(response.content) / 1024
217
+ console.print(f"[bold green]Download successful:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)")
218
+
219
+ else:
220
+ error_msg = f"HTTP Status: {response.status_code}, Response: {response.text[:100]}"
221
+ console.print(f"[bold red]Download failed:[/bold red] {error_msg}")
222
+ logging.error(f"Failed to download {filename}. {error_msg}")
223
+ sys.exit(0)
224
+
225
+ except Exception as e:
226
+ console.print(f"[bold red]Download error:[/bold red] {str(e)}")
227
+ logging.error(f"Failed to download {filename}: {e}")
228
+ sys.exit(0)
229
+
230
+ def update_site_config(self) -> None:
231
+ """Fetch and update the site configuration with data from the API or local file."""
232
+ if self.use_api:
233
+ headers = {
234
+ "apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE",
235
+ "Authorization": f"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE",
236
+ "Content-Type": "application/json"
237
+ }
238
+
239
+ try:
240
+ console.print("[bold cyan]Fetching site data from API...[/bold cyan]")
241
+ response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers, timeout=10)
242
+
243
+ if response.ok:
244
+ data = response.json()
245
+ if data and len(data) > 0:
246
+ self.configSite = data[0]['data']
247
+
248
+ # Display available sites and their domains
249
+ site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
250
+ console.print(f"[bold green]Site data fetched:[/bold green] {site_count} streaming services available")
251
+
252
+ # List a few sites as examples
253
+ if site_count > 0:
254
+ examples = list(self.configSite.items())[:3]
255
+ sites_info = []
256
+ for site, info in examples:
257
+ sites_info.append(f"[cyan]{site}[/cyan]: {info.get('full_url', 'N/A')}")
258
+
259
+ console.print("[bold cyan]Sample sites:[/bold cyan]")
260
+ for info in sites_info:
261
+ console.print(f" {info}")
262
+
263
+ if site_count > 3:
264
+ console.print(f" ... and {site_count - 3} more")
265
+
266
+ else:
267
+ console.print("[bold yellow]API returned empty data set[/bold yellow]")
268
+ else:
269
+ console.print(f"[bold red]API request failed:[/bold red] HTTP {response.status_code}, {response.text[:100]}")
270
+
271
+ except Exception as e:
272
+ console.print(f"[bold red]API connection error:[/bold red] {str(e)}")
273
+ else:
274
+ try:
275
+ if os.path.exists(self.domains_path):
276
+ console.print(f"[bold cyan]Reading domains from:[/bold cyan] {self.domains_path}")
277
+ with open(self.domains_path, 'r') as f:
278
+ self.configSite = json.load(f)
279
+
280
+ else:
281
+ error_msg = f"domains.json not found at {self.domains_path} and API usage is disabled"
282
+ console.print(f"[bold red]Configuration error:[/bold red] {error_msg}")
283
+ raise FileNotFoundError(error_msg)
284
+
285
+ except Exception as e:
286
+ console.print(f"[bold red]Domains file error:[/bold red] {str(e)}")
287
+ raise
288
+
289
+ def read_key(self, section: str, key: str, data_type: type = str, from_site: bool = False) -> Any:
290
+ """Read a key from the configuration.
291
+
292
+ Parameters:
293
+ - section (str): The section in the configuration.
294
+ - key (str): The key to be read.
295
+ - data_type (type, optional): The expected data type of the key's value. Default is str.
296
+ - from_site (bool, optional): Whether to read from site config. Default is False.
297
+
298
+ Returns:
299
+ The value of the key converted to the specified data type.
300
+ """
301
+ cache_key = f"{'site' if from_site else 'config'}.{section}.{key}"
302
+ logging.info(f"Read key: {cache_key}")
303
+
304
+ if cache_key in self.cache:
305
+ return self.cache[cache_key]
306
+
307
+ config_source = self.configSite if from_site else self.config
308
+
309
+ if section in config_source and key in config_source[section]:
310
+ value = config_source[section][key]
311
+ else:
312
+ raise ValueError(f"Key '{key}' not found in section '{section}' of {'site' if from_site else 'main'} config")
313
+
314
+ value = self._convert_to_data_type(value, data_type)
315
+ self.cache[cache_key] = value
316
+
317
+ return value
318
+
319
+ def _convert_to_data_type(self, value: str, data_type: type) -> Any:
320
+ """Convert the value to the specified data type.
321
+
322
+ Parameters:
323
+ - value (str): The value to be converted.
324
+ - data_type (type): The expected data type.
325
+
326
+ Returns:
327
+ The value converted to the specified data type.
328
+ """
329
+ if data_type == int:
330
+ return int(value)
331
+ elif data_type == bool:
332
+ return bool(value)
333
+ elif data_type == list:
334
+ return value if isinstance(value, list) else [item.strip() for item in value.split(',')]
335
+ elif data_type == type(None):
336
+ return None
337
+ else:
338
+ return value
339
+
340
+ # Main config getters
341
+ def get(self, section: str, key: str) -> Any:
342
+ """Read a value from the main configuration."""
343
+ return self.read_key(section, key)
344
+
345
+ def get_int(self, section: str, key: str) -> int:
346
+ """Read an integer value from the main configuration."""
347
+ return self.read_key(section, key, int)
348
+
349
+ def get_float(self, section: str, key: str) -> float:
350
+ """Read a float value from the main configuration."""
351
+ return self.read_key(section, key, float)
352
+
353
+ def get_bool(self, section: str, key: str) -> bool:
354
+ """Read a boolean value from the main configuration."""
355
+ return self.read_key(section, key, bool)
356
+
357
+ def get_list(self, section: str, key: str) -> List[str]:
358
+ """Read a list value from the main configuration."""
359
+ return self.read_key(section, key, list)
360
+
361
+ def get_dict(self, section: str, key: str) -> dict:
362
+ """Read a dictionary value from the main configuration."""
363
+ return self.read_key(section, key, dict)
364
+
365
+ # Site config getters
366
+ def get_site(self, section: str, key: str) -> Any:
367
+ """Read a value from the site configuration."""
368
+ return self.read_key(section, key, from_site=True)
369
+
370
+ def get_site_int(self, section: str, key: str) -> int:
371
+ """Read an integer value from the site configuration."""
372
+ return self.read_key(section, key, int, from_site=True)
373
+
374
+ def get_site_float(self, section: str, key: str) -> float:
375
+ """Read a float value from the site configuration."""
376
+ return self.read_key(section, key, float, from_site=True)
377
+
378
+ def get_site_bool(self, section: str, key: str) -> bool:
379
+ """Read a boolean value from the site configuration."""
380
+ return self.read_key(section, key, bool, from_site=True)
381
+
382
+ def get_site_list(self, section: str, key: str) -> List[str]:
383
+ """Read a list value from the site configuration."""
384
+ return self.read_key(section, key, list, from_site=True)
385
+
386
+ def get_site_dict(self, section: str, key: str) -> dict:
387
+ """Read a dictionary value from the site configuration."""
388
+ return self.read_key(section, key, dict, from_site=True)
389
+
390
+ def set_key(self, section: str, key: str, value: Any, to_site: bool = False) -> None:
391
+ """Set a key in the configuration.
392
+
393
+ Parameters:
394
+ - section (str): The section in the configuration.
395
+ - key (str): The key to be set.
396
+ - value (Any): The value to be associated with the key.
397
+ - to_site (bool, optional): Whether to set in site config. Default is False.
398
+ """
399
+ try:
400
+ config_target = self.configSite if to_site else self.config
401
+
402
+ if section not in config_target:
403
+ config_target[section] = {}
404
+
405
+ config_target[section][key] = value
406
+ cache_key = f"{'site' if to_site else 'config'}.{section}.{key}"
407
+ self.cache[cache_key] = value
408
+
409
+ except Exception as e:
410
+ print(f"Error setting key '{key}' in section '{section}' of {'site' if to_site else 'main'} config: {e}")
411
+
412
+ def write_config(self) -> None:
413
+ """Write the main configuration to the file."""
414
+ try:
415
+ with open(self.file_path, 'w') as f:
416
+ json.dump(self.config, f, indent=4)
417
+ except Exception as e:
418
+ print(f"Error writing configuration file: {e}")
419
+
420
+
421
+ config_manager = ConfigManager()
422
+ config_manager.read_config()
423
+
424
+
425
+ import sys
426
+
427
+ def get_use_large_bar():
428
+ """
429
+ Determines whether the large bar feature should be enabled.
430
+
431
+ Returns:
432
+ bool: True if running on a PC (Windows, macOS, Linux),
433
+ False if running on Android or iOS.
434
+ """
435
+ return not any(platform in sys.platform for platform in ("android", "ios"))
@@ -7,43 +7,14 @@ import random
7
7
  import ua_generator
8
8
 
9
9
 
10
- def get_headers() -> str:
11
- """
12
- Generate a random user agent to use in HTTP requests.
13
-
14
- Returns:
15
- - str: A random user agent string.
16
- """
17
-
18
- # Get a random user agent string from the user agent rotator
10
+ # Variable
11
+ ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'))
12
+
13
+
14
+ def get_userAgent() -> str:
19
15
  user_agent = ua_generator.generate().text
20
16
  return user_agent
21
17
 
22
18
 
23
- def random_headers(referer: str = None):
24
- """
25
- Generate random HTTP headers to simulate human-like behavior.
26
-
27
- Returns:
28
- dict: Generated HTTP headers.
29
- """
30
- ua = ua_generator.generate()
31
-
32
- headers = {
33
- 'User-Agent': ua.text,
34
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
35
- 'Accept-Language': random.choice(['en-US', 'en-GB', 'fr-FR', 'es-ES', 'de-DE']),
36
- 'Accept-Encoding': 'gzip, deflate, br',
37
- 'Connection': 'keep-alive',
38
- 'Upgrade-Insecure-Requests': '1',
39
- 'Sec-Fetch-Dest': 'document',
40
- 'Sec-Fetch-Mode': 'navigate',
41
- 'Sec-Fetch-Site': 'none',
42
- 'Sec-Fetch-User': '?1',
43
- }
44
-
45
- if referer:
46
- headers['Origin'] = referer
47
- headers['Referer'] = referer
48
-
49
- return headers
19
+ def get_headers() -> dict:
20
+ return ua.headers.get()
@@ -6,57 +6,87 @@ from logging.handlers import RotatingFileHandler
6
6
 
7
7
 
8
8
  # Internal utilities
9
- from StreamingCommunity.Util._jsonConfig import config_manager
9
+ from StreamingCommunity.Util.config_json import config_manager
10
10
 
11
11
 
12
12
  class Logger:
13
+ _instance = None
14
+
15
+ def __new__(cls):
16
+ # Singleton pattern to avoid multiple logger instances
17
+ if cls._instance is None:
18
+ cls._instance = super(Logger, cls).__new__(cls)
19
+ cls._instance._initialized = False
20
+ return cls._instance
21
+
13
22
  def __init__(self):
14
-
15
- # Fetching configuration values
16
- self.DEBUG_MODE = config_manager.get_bool("DEFAULT", "debug")
17
- self.log_to_file = config_manager.get_bool("DEFAULT", "log_to_file")
18
- self.log_file = config_manager.get("DEFAULT", "log_file") if self.log_to_file else None
19
-
23
+ # Initialize only once
24
+ if getattr(self, '_initialized', False):
25
+ return
26
+
27
+ # Fetch only the debug setting from config
28
+ self.debug_mode = config_manager.get_bool("DEFAULT", "debug")
29
+
30
+ # Configure root logger
31
+ self.logger = logging.getLogger('')
32
+
33
+ # Remove any existing handlers to avoid duplication
34
+ for handler in self.logger.handlers[:]:
35
+ self.logger.removeHandler(handler)
36
+
37
+ # Reduce logging level for external libraries
20
38
  logging.getLogger("httpx").setLevel(logging.WARNING)
21
39
  logging.getLogger("httpcore").setLevel(logging.WARNING)
22
-
23
40
 
24
- # Setting logging level based on DEBUG_MODE
25
- if self.DEBUG_MODE:
26
- logging.getLogger('root').setLevel(logging.DEBUG)
41
+ # Set logging level based on debug_mode
42
+ if self.debug_mode:
43
+ self.logger.setLevel(logging.DEBUG)
44
+ self._configure_console_log_file()
27
45
 
28
- # Configure file logging if debug mode and logging to file are both enabled
29
- if self.log_to_file:
30
- self.remove_existing_log_file()
31
- self.configure_file_logging()
32
46
  else:
33
-
34
- # If DEBUG_MODE is False, set logging level to ERROR
35
- logging.getLogger('root').setLevel(logging.ERROR)
36
-
37
- # Configure console logging
38
- self.configure_logging()
39
-
40
- def configure_logging(self):
41
- """
42
- Configure console logging.
43
- """
44
- logging.basicConfig(level=logging.DEBUG, format='[%(filename)s:%(lineno)s - %(funcName)20s() ] %(asctime)s - %(levelname)s - %(message)s')
45
-
46
- def configure_file_logging(self):
47
- """
48
- Configure file logging if enabled.
49
- """
50
-
51
- file_handler = RotatingFileHandler(self.log_file, maxBytes=10*1024*1024, backupCount=5)
52
- file_handler.setLevel(logging.DEBUG)
47
+ self.logger.setLevel(logging.ERROR)
48
+
49
+ # Configure console logging (terminal output) regardless of debug mode
50
+ self._configure_console_logging()
51
+
52
+ self._initialized = True
53
+
54
+ def _configure_console_logging(self):
55
+ """Configure console logging output to terminal."""
56
+ console_handler = logging.StreamHandler()
57
+ console_handler.setLevel(logging.DEBUG if self.debug_mode else logging.ERROR)
53
58
  formatter = logging.Formatter('[%(filename)s:%(lineno)s - %(funcName)20s() ] %(asctime)s - %(levelname)s - %(message)s')
54
- file_handler.setFormatter(formatter)
55
- logging.getLogger('').addHandler(file_handler)
56
-
57
- def remove_existing_log_file(self):
59
+ console_handler.setFormatter(formatter)
60
+ self.logger.addHandler(console_handler)
61
+
62
+ def _configure_console_log_file(self):
63
+ """Create a console.log file only when debug mode is enabled."""
64
+ console_log_path = "console.log"
65
+ try:
66
+ # Remove existing file if present
67
+ if os.path.exists(console_log_path):
68
+ os.remove(console_log_path)
69
+
70
+ # Create handler for console.log
71
+ console_file_handler = RotatingFileHandler(
72
+ console_log_path,
73
+ maxBytes=5*1024*1024, # 5 MB
74
+ backupCount=3
75
+ )
76
+ console_file_handler.setLevel(logging.DEBUG)
77
+ formatter = logging.Formatter('[%(filename)s:%(lineno)s - %(funcName)20s() ] %(asctime)s - %(levelname)s - %(message)s')
78
+ console_file_handler.setFormatter(formatter)
79
+ self.logger.addHandler(console_file_handler)
80
+
81
+ except Exception as e:
82
+ print(f"Error creating console.log: {e}")
83
+
84
+ @staticmethod
85
+ def get_logger(name=None):
58
86
  """
59
- Remove the log file if it already exists.
87
+ Get a specific logger for a module/component.
88
+ If name is None, returns the root logger.
60
89
  """
61
- if os.path.exists(self.log_file):
62
- os.remove(self.log_file)
90
+ # Ensure Logger instance is initialized
91
+ Logger()
92
+ return logging.getLogger(name)
@@ -3,12 +3,17 @@
3
3
  import os
4
4
  import platform
5
5
 
6
+
7
+ # External library
8
+ from rich.console import Console
9
+
10
+
6
11
  # Internal utilities
7
- from StreamingCommunity.Util.console import console
8
- from StreamingCommunity.Util._jsonConfig import config_manager
12
+ from StreamingCommunity.Util.config_json import config_manager
9
13
 
10
14
 
11
15
  # Variable
16
+ console = Console()
12
17
  CLEAN = config_manager.get_bool('DEFAULT', 'clean_console')
13
18
  SHOW = config_manager.get_bool('DEFAULT', 'show_message')
14
19
 
@@ -33,4 +38,4 @@ def start_message():
33
38
 
34
39
  # Print a decorative separator line using asterisks
35
40
  separator = "_" * (console.width - 2) # Ridotto di 2 per il padding
36
- console.print(f"[cyan]{separator}[/cyan]\n")
41
+ console.print(f"[cyan]{separator}[/cyan]\n")