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.
- StreamingCommunity/Api/Player/ddl.py +4 -4
- StreamingCommunity/Api/Player/maxstream.py +10 -16
- StreamingCommunity/Api/Player/supervideo.py +9 -35
- StreamingCommunity/Api/Player/vixcloud.py +18 -92
- StreamingCommunity/Api/Site/1337xx/__init__.py +8 -1
- StreamingCommunity/Api/Site/1337xx/site.py +16 -15
- StreamingCommunity/Api/Site/1337xx/title.py +7 -5
- StreamingCommunity/Api/Site/animeunity/__init__.py +9 -2
- StreamingCommunity/Api/Site/animeunity/film_serie.py +12 -5
- StreamingCommunity/Api/Site/animeunity/site.py +14 -10
- StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +9 -10
- StreamingCommunity/Api/Site/cb01new/__init__.py +8 -1
- StreamingCommunity/Api/Site/cb01new/film.py +7 -1
- StreamingCommunity/Api/Site/cb01new/site.py +24 -15
- StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +9 -2
- StreamingCommunity/Api/Site/ddlstreamitaly/series.py +7 -1
- StreamingCommunity/Api/Site/ddlstreamitaly/site.py +16 -15
- StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +3 -3
- StreamingCommunity/Api/Site/guardaserie/__init__.py +9 -2
- StreamingCommunity/Api/Site/guardaserie/series.py +9 -1
- StreamingCommunity/Api/Site/guardaserie/site.py +23 -22
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +5 -4
- StreamingCommunity/Api/Site/mostraguarda/__init__.py +6 -2
- StreamingCommunity/Api/Site/mostraguarda/film.py +10 -6
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +9 -2
- StreamingCommunity/Api/Site/streamingcommunity/film.py +9 -2
- StreamingCommunity/Api/Site/streamingcommunity/series.py +15 -6
- StreamingCommunity/Api/Site/streamingcommunity/site.py +16 -14
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +10 -11
- StreamingCommunity/Api/Template/Util/__init__.py +0 -1
- StreamingCommunity/Api/Template/Util/get_domain.py +31 -134
- StreamingCommunity/Api/Template/Util/manage_ep.py +10 -5
- StreamingCommunity/Api/Template/config_loader.py +14 -10
- StreamingCommunity/Api/Template/site.py +3 -6
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +12 -15
- StreamingCommunity/Lib/Downloader/HLS/segments.py +14 -34
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +14 -11
- StreamingCommunity/Lib/Downloader/TOR/downloader.py +109 -101
- StreamingCommunity/Lib/FFmpeg/__init__.py +1 -1
- StreamingCommunity/Lib/FFmpeg/capture.py +10 -12
- StreamingCommunity/Lib/FFmpeg/command.py +15 -14
- StreamingCommunity/Lib/FFmpeg/util.py +9 -38
- StreamingCommunity/Lib/M3U8/decryptor.py +72 -146
- StreamingCommunity/Lib/M3U8/estimator.py +8 -16
- StreamingCommunity/Lib/M3U8/parser.py +1 -17
- StreamingCommunity/Lib/M3U8/url_fixer.py +1 -4
- StreamingCommunity/Lib/TMBD/__init__.py +2 -0
- StreamingCommunity/Lib/TMBD/obj_tmbd.py +3 -17
- StreamingCommunity/Lib/TMBD/tmdb.py +4 -9
- StreamingCommunity/TelegramHelp/telegram_bot.py +50 -50
- StreamingCommunity/Upload/update.py +6 -5
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/color.py +1 -1
- StreamingCommunity/Util/config_json.py +435 -0
- StreamingCommunity/Util/headers.py +7 -36
- StreamingCommunity/Util/logger.py +72 -42
- StreamingCommunity/Util/message.py +8 -3
- StreamingCommunity/Util/os.py +41 -93
- StreamingCommunity/Util/table.py +8 -17
- StreamingCommunity/run.py +39 -43
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/METADATA +203 -114
- StreamingCommunity-2.8.0.dist-info/RECORD +75 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__init__.py +0 -53
- StreamingCommunity/Api/Site/ilcorsaronero/site.py +0 -64
- StreamingCommunity/Api/Site/ilcorsaronero/title.py +0 -42
- StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py +0 -149
- StreamingCommunity/Api/Template/Util/recall_search.py +0 -37
- StreamingCommunity/Lib/Downloader/HLS/proxyes.py +0 -110
- StreamingCommunity/Util/_jsonConfig.py +0 -241
- StreamingCommunity/Util/call_stack.py +0 -42
- StreamingCommunity/Util/console.py +0 -12
- StreamingCommunity-2.6.1.dist-info/RECORD +0 -83
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/LICENSE +0 -0
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/WHEEL +0 -0
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
#
|
|
25
|
-
if self.
|
|
26
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
self.
|
|
39
|
-
|
|
40
|
-
def
|
|
41
|
-
"""
|
|
42
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def
|
|
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
|
-
|
|
87
|
+
Get a specific logger for a module/component.
|
|
88
|
+
If name is None, returns the root logger.
|
|
60
89
|
"""
|
|
61
|
-
|
|
62
|
-
|
|
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.
|
|
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")
|