Bump v2.9.8

This commit is contained in:
Lovi 2025-03-22 21:21:15 +01:00
parent 38188f3adf
commit 00bcdbce9b
4 changed files with 435 additions and 284 deletions

View File

@ -1,5 +1,5 @@
__title__ = 'StreamingCommunity' __title__ = 'StreamingCommunity'
__version__ = '2.9.7' __version__ = '2.9.8'
__author__ = 'Arrowar' __author__ = 'Arrowar'
__description__ = 'A command-line program to download film' __description__ = 'A command-line program to download film'
__copyright__ = 'Copyright 2024' __copyright__ = 'Copyright 2024'

View File

@ -20,110 +20,183 @@ validate_github_config = True
class ConfigManager: class ConfigManager:
def __init__(self, file_name: str = 'config.json') -> None: def __init__(self, file_name: str = 'config.json') -> None:
"""Initialize the ConfigManager.
Parameters:
- file_name (str, optional): The name of the configuration file. Default is 'config.json'.
""" """
Initialize the ConfigManager.
Args:
file_name (str, optional): Configuration file name. Default: 'config.json'.
"""
# Determine the base path - use the current working directory
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
base_path = os.path.join(".") # If the application is frozen (e.g., PyInstaller)
base_path = os.path.dirname(sys.executable)
else: else:
base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) # Use the current directory where the script is executed
base_path = os.getcwd()
# Initialize file paths
self.file_path = os.path.join(base_path, file_name) self.file_path = os.path.join(base_path, file_name)
self.domains_path = os.path.join(base_path, 'domains.json') self.domains_path = os.path.join(base_path, 'domains.json')
# Display the actual file path for debugging
console.print(f"[bold cyan]Configuration file path:[/bold cyan] [green]{self.file_path}[/green]")
# Reference repository URL
self.reference_config_url = 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/config.json'
# Initialize data structures
self.config = {} self.config = {}
self.configSite = {} self.configSite = {}
self.cache = {} self.cache = {}
self.reference_config_url = 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/config.json'
# Read initial config to get use_api setting
self._read_initial_config()
# Validate and update config before proceeding (if enabled)
if validate_github_config:
self._validate_and_update_config()
console.print(f"[bold cyan]ConfigManager initialized:[/bold cyan] [green]{self.file_path}[/green]")
def _read_initial_config(self) -> None: self.use_api = False
"""Read initial configuration to get use_api setting.""" self.download_site_data = False
self.validate_github_config = False
console.print(f"[bold cyan]Initializing ConfigManager:[/bold cyan] [green]{self.file_path}[/green]")
# Load the configuration
self.load_config()
def load_config(self) -> None:
"""Load the configuration and initialize all settings."""
if not os.path.exists(self.file_path):
console.print(f"[bold red]WARNING: Configuration file not found:[/bold red] {self.file_path}")
console.print(f"[bold yellow]Attempting to download from reference repository...[/bold yellow]")
self._download_reference_config()
# Load the configuration file
try: try:
if os.path.exists(self.file_path): with open(self.file_path, 'r') as f:
with open(self.file_path, 'r') as f: self.config = json.load(f)
self.config = json.load(f) console.print(f"[bold green]Configuration loaded:[/bold green] {len(self.config)} keys")
self.use_api = self.config.get('DEFAULT', {}).get('use_api', True) # Update settings from the configuration
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'}]") self._update_settings_from_config()
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'}]")
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'}]") # Validate and update the configuration if requested
if self.validate_github_config:
self._validate_and_update_config()
else: else:
self.use_api = True console.print("[bold yellow]GitHub validation disabled[/bold yellow]")
console.print("[bold yellow]Configuration file not found. Using default API setting: True[/bold yellow]")
console.print(f"[bold yellow]Download site data: {download_site_data}[/bold yellow]") # Load site data if requested
console.print(f"[bold yellow]Validate GitHub config: {validate_github_config}[/bold yellow]") if self.download_site_data:
self._load_site_data()
else:
console.print("[bold yellow]Site data download disabled[/bold yellow]")
except json.JSONDecodeError as e:
console.print(f"[bold red]Error parsing JSON:[/bold red] {str(e)}")
self._handle_config_error()
except Exception as e: except Exception as e:
self.use_api = True console.print(f"[bold red]Error loading configuration:[/bold red] {str(e)}")
console.print("[bold red]Error reading API setting. Using default: True[/bold red]") self._handle_config_error()
def _validate_and_update_config(self) -> None: def _handle_config_error(self) -> None:
"""Validate local config against reference config and update missing keys.""" """Handle configuration errors by downloading the reference version."""
console.print("[bold yellow]Attempting to retrieve reference configuration...[/bold yellow]")
self._download_reference_config()
# Reload the configuration
try: try:
# Load local config if exists with open(self.file_path, 'r') as f:
local_config = {} self.config = json.load(f)
if os.path.exists(self.file_path): self._update_settings_from_config()
with open(self.file_path, 'r') as f: console.print("[bold green]Reference configuration loaded successfully[/bold green]")
local_config = json.load(f) except Exception as e:
console.print(f"[bold cyan]Local configuration loaded:[/bold cyan] [green]{len(local_config)} keys found[/green]") console.print(f"[bold red]Critical configuration error:[/bold red] {str(e)}")
console.print("[bold red]Unable to proceed. The application will terminate.[/bold red]")
sys.exit(1)
def _update_settings_from_config(self) -> None:
"""Update internal settings from loaded configurations."""
default_section = self.config.get('DEFAULT', {})
# Save local values in temporary variables
temp_use_api = default_section.get('use_api', False)
temp_download_site_data = default_section.get('download_site_data', False)
temp_validate_github_config = default_section.get('validate_github_config', False)
# Update settings with found values (False by default)
self.use_api = temp_use_api
self.download_site_data = temp_download_site_data
self.validate_github_config = temp_validate_github_config
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'}]")
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'}]")
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'}]")
def _download_reference_config(self) -> None:
"""Download the reference configuration from GitHub."""
console.print(f"[bold cyan]Downloading reference configuration:[/bold cyan] [green]{self.reference_config_url}[/green]")
# Download reference config try:
console.print(f"[bold cyan]Downloading reference config from:[/bold cyan] [green]{self.reference_config_url}[/green]")
response = requests.get(self.reference_config_url, timeout=10) response = requests.get(self.reference_config_url, timeout=10)
if response.status_code == 200:
with open(self.file_path, 'wb') as f:
f.write(response.content)
file_size = len(response.content) / 1024
console.print(f"[bold green]Download complete:[/bold green] {os.path.basename(self.file_path)} ({file_size:.2f} KB)")
else:
error_msg = f"HTTP Error: {response.status_code}, Response: {response.text[:100]}"
console.print(f"[bold red]Download failed:[/bold red] {error_msg}")
raise Exception(error_msg)
except Exception as e:
console.print(f"[bold red]Download error:[/bold red] {str(e)}")
raise
def _validate_and_update_config(self) -> None:
"""Validate the local configuration against the reference one and update missing keys."""
try:
# Download the reference configuration
console.print(f"[bold cyan]Validating configuration with GitHub...[/bold cyan]")
response = requests.get(self.reference_config_url, timeout=10)
if not response.ok: if not response.ok:
raise Exception(f"Failed to download reference config. Status code: {response.status_code}") raise Exception(f"Error downloading reference configuration. Code: {response.status_code}")
reference_config = response.json() reference_config = response.json()
console.print(f"[bold cyan]Reference config downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]") console.print(f"[bold cyan]Reference configuration downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]")
# Compare and update missing keys
merged_config = self._deep_merge_configs(local_config, reference_config)
if merged_config != local_config: # Compare and update missing keys
added_keys = self._get_added_keys(local_config, merged_config) merged_config = self._deep_merge_configs(self.config, reference_config)
# Save the merged config if merged_config != self.config:
added_keys = self._get_added_keys(self.config, merged_config)
# Save the merged configuration
with open(self.file_path, 'w') as f: with open(self.file_path, 'w') as f:
json.dump(merged_config, f, indent=4) json.dump(merged_config, f, indent=4)
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 ''}")
key_examples = ', '.join(added_keys[:5])
if len(added_keys) > 5:
key_examples += ' and others...'
console.print(f"[bold green]Configuration updated with {len(added_keys)} new keys:[/bold green] {key_examples}")
# Update the configuration in memory
self.config = merged_config
self._update_settings_from_config()
else: else:
console.print("[bold green]Configuration is up to date.[/bold green]") console.print("[bold green]The configuration is up to date.[/bold green]")
self.config = merged_config
except Exception as e: except Exception as e:
console.print(f"[bold red]Configuration validation error:[/bold red] {str(e)}") console.print(f"[bold red]Error validating configuration:[/bold red] {str(e)}")
if not self.config:
console.print("[bold yellow]Falling back to reference configuration...[/bold yellow]")
self.download_requirements(self.reference_config_url, self.file_path)
with open(self.file_path, 'r') as f:
self.config = json.load(f)
console.print(f"[bold green]Reference config loaded successfully with {len(self.config)} keys[/bold green]")
def _get_added_keys(self, old_config: dict, new_config: dict, prefix="") -> list: def _get_added_keys(self, old_config: dict, new_config: dict, prefix="") -> list:
""" """
Get list of keys added in the new config compared to old config. Get the list of keys added in the new configuration compared to the old one.
Args: Args:
old_config (dict): The original configuration old_config (dict): Original configuration
new_config (dict): The new configuration new_config (dict): New configuration
prefix (str): Key prefix for nested keys prefix (str): Prefix for nested keys
Returns: Returns:
list: List of added key names list: List of added key names
@ -139,14 +212,14 @@ class ConfigManager:
added_keys.extend(self._get_added_keys(old_config[key], value, full_key)) added_keys.extend(self._get_added_keys(old_config[key], value, full_key))
return added_keys return added_keys
def _deep_merge_configs(self, local_config: dict, reference_config: dict) -> dict: def _deep_merge_configs(self, local_config: dict, reference_config: dict) -> dict:
""" """
Recursively merge reference config into local config, preserving local values. Recursively merge the reference configuration into the local one, preserving local values.
Args: Args:
local_config (dict): The local configuration local_config (dict): Local configuration
reference_config (dict): The reference configuration reference_config (dict): Reference configuration
Returns: Returns:
dict: Merged configuration dict: Merged configuration
@ -155,272 +228,350 @@ class ConfigManager:
for key, value in reference_config.items(): for key, value in reference_config.items():
if key not in merged: if key not in merged:
# Create the key if it doesn't exist
merged[key] = value merged[key] = value
elif isinstance(value, dict) and isinstance(merged[key], dict): elif isinstance(value, dict) and isinstance(merged[key], dict):
merged[key] = self._deep_merge_configs(merged[key], value)
# Handle the DEFAULT section specially
if key == 'DEFAULT':
# Make sure control keys maintain local values
merged_section = self._deep_merge_configs(merged[key], value)
# Preserve local values for the three critical settings
if 'use_api' in merged[key]:
merged_section['use_api'] = merged[key]['use_api']
if 'download_site_data' in merged[key]:
merged_section['download_site_data'] = merged[key]['download_site_data']
if 'validate_github_config' in merged[key]:
merged_section['validate_github_config'] = merged[key]['validate_github_config']
merged[key] = merged_section
else:
# Normal merge for other sections
merged[key] = self._deep_merge_configs(merged[key], value)
return merged return merged
def read_config(self) -> None: def _load_site_data(self) -> None:
"""Read the configuration file.""" """Load site data from API or local file."""
if self.use_api:
self._load_site_data_from_api()
else:
self._load_site_data_from_file()
def _load_site_data_from_api(self) -> None:
"""Load site data from API."""
headers = {
"apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE",
"Authorization": f"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE",
"Content-Type": "application/json"
}
try: try:
logging.info(f"Reading file: {self.file_path}") console.print("[bold cyan]Retrieving site data from API...[/bold cyan]")
response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers, timeout=10)
# Check if file exists
if os.path.exists(self.file_path): if response.ok:
with open(self.file_path, 'r') as f: data = response.json()
self.config = json.load(f) if data and len(data) > 0:
console.print(f"[bold green]Configuration loaded:[/bold green] {len(self.config)} keys, {sum(1 for _ in json.dumps(self.config))} bytes") self.configSite = data[0]['data']
site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
console.print(f"[bold green]Site data retrieved:[/bold green] {site_count} streaming services available")
# Show some sites as examples
if site_count > 0:
examples = list(self.configSite.items())[:3]
sites_info = []
for site, info in examples:
url = info.get('full_url', 'N/A')
console.print(f" • [cyan]{site}[/cyan]: {url}")
else:
console.print("[bold yellow]API returned an empty data set[/bold yellow]")
else: else:
console.print(f"[bold yellow]Configuration file not found at:[/bold yellow] {self.file_path}") console.print(f"[bold red]API request failed:[/bold red] HTTP {response.status_code}, {response.text[:100]}")
console.print(f"[bold cyan]Downloading from:[/bold cyan] {self.reference_config_url}") self._handle_site_data_fallback()
self.download_requirements(self.reference_config_url, self.file_path)
# Load the downloaded config.json into the config attribute
with open(self.file_path, 'r') as f:
self.config = json.load(f)
console.print(f"[bold green]Configuration downloaded and saved:[/bold green] {len(self.config)} keys")
# Read API setting again in case it was updated in the downloaded config
self.use_api = self.config.get('DEFAULT', {}).get('use_api', self.use_api)
# Update site configuration separately if enabled
if download_site_data:
self.update_site_config()
else:
console.print("[bold yellow]Site data download is disabled[/bold yellow]")
console.print("[bold cyan]Configuration processing complete[/bold cyan]")
except Exception as e: except Exception as e:
logging.error(f"Error reading configuration file: {e}") console.print(f"[bold red]API connection error:[/bold red] {str(e)}")
console.print(f"[bold red]Failed to read configuration:[/bold red] {str(e)}") self._handle_site_data_fallback()
sys.exit(0)
def _load_site_data_from_file(self) -> None:
"""Load site data from local file."""
try:
if os.path.exists(self.domains_path):
console.print(f"[bold cyan]Reading domains from:[/bold cyan] {self.domains_path}")
with open(self.domains_path, 'r') as f:
self.configSite = json.load(f)
site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
console.print(f"[bold green]Site data loaded from file:[/bold green] {site_count} streaming services")
def download_requirements(self, url: str, filename: str) -> None: else:
error_msg = f"domains.json not found at {self.domains_path} and API usage is disabled"
console.print(f"[bold red]Configuration error:[/bold red] {error_msg}")
self._handle_site_data_fallback()
except Exception as e:
console.print(f"[bold red]Domain file error:[/bold red] {str(e)}")
self._handle_site_data_fallback()
def _handle_site_data_fallback(self) -> None:
"""Handle site data fallback in case of error."""
if self.use_api and os.path.exists(self.domains_path):
console.print("[bold yellow]Attempting fallback to local domains.json file...[/bold yellow]")
try:
with open(self.domains_path, 'r') as f:
self.configSite = json.load(f)
console.print("[bold green]Fallback to local data successful[/bold green]")
except Exception as fallback_error:
console.print(f"[bold red]Fallback also failed:[/bold red] {str(fallback_error)}")
self.configSite = {}
else:
# Initialize with an empty dictionary if there are no alternatives
self.configSite = {}
def download_file(self, url: str, filename: str) -> None:
""" """
Download a file from the specified URL if not found locally using requests. Download a file from the specified URL.
Args: Args:
url (str): The URL to download the file from. url (str): URL to download from
filename (str): The local filename to save the file as. filename (str): Local filename to save to
""" """
try: try:
logging.info(f"Downloading {filename} from {url}...") logging.info(f"Downloading {filename} from {url}...")
console.print(f"[bold cyan]Downloading file:[/bold cyan] {os.path.basename(filename)}") console.print(f"[bold cyan]File download:[/bold cyan] {os.path.basename(filename)}")
response = requests.get(url, timeout=10) response = requests.get(url, timeout=10)
if response.status_code == 200: if response.status_code == 200:
with open(filename, 'wb') as f: with open(filename, 'wb') as f:
f.write(response.content) f.write(response.content)
file_size = len(response.content) / 1024 file_size = len(response.content) / 1024
console.print(f"[bold green]Download successful:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)") console.print(f"[bold green]Download complete:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)")
else: else:
error_msg = f"HTTP Status: {response.status_code}, Response: {response.text[:100]}" error_msg = f"HTTP Status: {response.status_code}, Response: {response.text[:100]}"
console.print(f"[bold red]Download failed:[/bold red] {error_msg}") console.print(f"[bold red]Download failed:[/bold red] {error_msg}")
logging.error(f"Failed to download {filename}. {error_msg}") logging.error(f"Download of {filename} failed. {error_msg}")
sys.exit(0) raise Exception(error_msg)
except Exception as e: except Exception as e:
console.print(f"[bold red]Download error:[/bold red] {str(e)}") console.print(f"[bold red]Download error:[/bold red] {str(e)}")
logging.error(f"Failed to download {filename}: {e}") logging.error(f"Download of {filename} failed: {e}")
sys.exit(0) raise
def update_site_config(self) -> None: def get(self, section: str, key: str, data_type: type = str, from_site: bool = False) -> Any:
"""Fetch and update the site configuration with data from the API or local file.""" """
if self.use_api: Read a value from the configuration.
headers = {
"apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE", Args:
"Authorization": f"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE", section (str): Section in the configuration
"Content-Type": "application/json" key (str): Key to read
} data_type (type, optional): Expected data type. Default: str
from_site (bool, optional): Whether to read from the site configuration. Default: False
try:
console.print("[bold cyan]Fetching site data from API...[/bold cyan]")
response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers, timeout=10)
if response.ok:
data = response.json()
if data and len(data) > 0:
self.configSite = data[0]['data']
# Display available sites and their domains
site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
console.print(f"[bold green]Site data fetched:[/bold green] {site_count} streaming services available")
# List a few sites as examples
if site_count > 0:
examples = list(self.configSite.items())[:3]
sites_info = []
for site, info in examples:
sites_info.append(f"[cyan]{site}[/cyan]: {info.get('full_url', 'N/A')}")
else:
console.print("[bold yellow]API returned empty data set[/bold yellow]")
else:
console.print(f"[bold red]API request failed:[/bold red] HTTP {response.status_code}, {response.text[:100]}")
except Exception as e:
console.print(f"[bold red]API connection error:[/bold red] {str(e)}")
else:
try:
if os.path.exists(self.domains_path):
console.print(f"[bold cyan]Reading domains from:[/bold cyan] {self.domains_path}")
with open(self.domains_path, 'r') as f:
self.configSite = json.load(f)
else:
error_msg = f"domains.json not found at {self.domains_path} and API usage is disabled"
console.print(f"[bold red]Configuration error:[/bold red] {error_msg}")
raise FileNotFoundError(error_msg)
except Exception as e:
console.print(f"[bold red]Domains file error:[/bold red] {str(e)}")
raise
def read_key(self, section: str, key: str, data_type: type = str, from_site: bool = False) -> Any:
"""Read a key from the configuration.
Parameters:
- section (str): The section in the configuration.
- key (str): The key to be read.
- data_type (type, optional): The expected data type of the key's value. Default is str.
- from_site (bool, optional): Whether to read from site config. Default is False.
Returns: Returns:
The value of the key converted to the specified data type. Any: The key value converted to the specified data type
""" """
cache_key = f"{'site' if from_site else 'config'}.{section}.{key}" cache_key = f"{'site' if from_site else 'config'}.{section}.{key}"
logging.info(f"Read key: {cache_key}") logging.info(f"Reading key: {cache_key}")
# Check if the value is in the cache
if cache_key in self.cache: if cache_key in self.cache:
return self.cache[cache_key] return self.cache[cache_key]
# Choose the appropriate source
config_source = self.configSite if from_site else self.config config_source = self.configSite if from_site else self.config
if section in config_source and key in config_source[section]: # Check if the section and key exist
value = config_source[section][key] if section not in config_source:
else: raise ValueError(f"Section '{section}' not found in {'site' if from_site else 'main'} configuration")
raise ValueError(f"Key '{key}' not found in section '{section}' of {'site' if from_site else 'main'} config")
if key not in config_source[section]:
value = self._convert_to_data_type(value, data_type) raise ValueError(f"Key '{key}' not found in section '{section}' of {'site' if from_site else 'main'} configuration")
self.cache[cache_key] = value
# Get and convert the value
return value value = config_source[section][key]
converted_value = self._convert_to_data_type(value, data_type)
def _convert_to_data_type(self, value: str, data_type: type) -> Any:
"""Convert the value to the specified data type. # Save in cache
self.cache[cache_key] = converted_value
Parameters:
- value (str): The value to be converted. return converted_value
- data_type (type): The expected data type.
def _convert_to_data_type(self, value: Any, data_type: type) -> Any:
Returns:
The value converted to the specified data type.
""" """
if data_type == int: Convert the value to the specified data type.
return int(value)
elif data_type == bool: Args:
return bool(value) value (Any): Value to convert
elif data_type == list: data_type (type): Target data type
return value if isinstance(value, list) else [item.strip() for item in value.split(',')]
elif data_type == type(None): Returns:
return None Any: Converted value
else: """
return value try:
if data_type == int:
# Main config getters return int(value)
def get(self, section: str, key: str) -> Any: elif data_type == float:
"""Read a value from the main configuration.""" return float(value)
return self.read_key(section, key) elif data_type == bool:
if isinstance(value, str):
return value.lower() in ("yes", "true", "t", "1")
return bool(value)
elif data_type == list:
if isinstance(value, list):
return value
if isinstance(value, str):
return [item.strip() for item in value.split(',')]
return [value]
elif data_type == dict:
if isinstance(value, dict):
return value
raise ValueError(f"Cannot convert {type(value).__name__} to dict")
else:
return value
except Exception as e:
logging.error(f"Error converting to {data_type.__name__}: {e}")
raise ValueError(f"Cannot convert '{value}' to {data_type.__name__}: {str(e)}")
# Getters for main configuration
def get_string(self, section: str, key: str) -> str:
"""Read a string from the main configuration."""
return self.get(section, key, str)
def get_int(self, section: str, key: str) -> int: def get_int(self, section: str, key: str) -> int:
"""Read an integer value from the main configuration.""" """Read an integer from the main configuration."""
return self.read_key(section, key, int) return self.get(section, key, int)
def get_float(self, section: str, key: str) -> float: def get_float(self, section: str, key: str) -> float:
"""Read a float value from the main configuration.""" """Read a float from the main configuration."""
return self.read_key(section, key, float) return self.get(section, key, float)
def get_bool(self, section: str, key: str) -> bool: def get_bool(self, section: str, key: str) -> bool:
"""Read a boolean value from the main configuration.""" """Read a boolean from the main configuration."""
return self.read_key(section, key, bool) return self.get(section, key, bool)
def get_list(self, section: str, key: str) -> List[str]: def get_list(self, section: str, key: str) -> List[str]:
"""Read a list value from the main configuration.""" """Read a list from the main configuration."""
return self.read_key(section, key, list) return self.get(section, key, list)
def get_dict(self, section: str, key: str) -> dict: def get_dict(self, section: str, key: str) -> dict:
"""Read a dictionary value from the main configuration.""" """Read a dictionary from the main configuration."""
return self.read_key(section, key, dict) return self.get(section, key, dict)
# Site config getters # Getters for site configuration
def get_site(self, section: str, key: str) -> Any: def get_site(self, section: str, key: str) -> Any:
"""Read a value from the site configuration.""" """Read a value from the site configuration."""
return self.read_key(section, key, from_site=True) return self.get(section, key, str, True)
def get_site_string(self, section: str, key: str) -> str:
"""Read a string from the site configuration."""
return self.get(section, key, str, True)
def get_site_int(self, section: str, key: str) -> int: def get_site_int(self, section: str, key: str) -> int:
"""Read an integer value from the site configuration.""" """Read an integer from the site configuration."""
return self.read_key(section, key, int, from_site=True) return self.get(section, key, int, True)
def get_site_float(self, section: str, key: str) -> float: def get_site_float(self, section: str, key: str) -> float:
"""Read a float value from the site configuration.""" """Read a float from the site configuration."""
return self.read_key(section, key, float, from_site=True) return self.get(section, key, float, True)
def get_site_bool(self, section: str, key: str) -> bool: def get_site_bool(self, section: str, key: str) -> bool:
"""Read a boolean value from the site configuration.""" """Read a boolean from the site configuration."""
return self.read_key(section, key, bool, from_site=True) return self.get(section, key, bool, True)
def get_site_list(self, section: str, key: str) -> List[str]: def get_site_list(self, section: str, key: str) -> List[str]:
"""Read a list value from the site configuration.""" """Read a list from the site configuration."""
return self.read_key(section, key, list, from_site=True) return self.get(section, key, list, True)
def get_site_dict(self, section: str, key: str) -> dict: def get_site_dict(self, section: str, key: str) -> dict:
"""Read a dictionary value from the site configuration.""" """Read a dictionary from the site configuration."""
return self.read_key(section, key, dict, from_site=True) return self.get(section, key, dict, True)
def set_key(self, section: str, key: str, value: Any, to_site: bool = False) -> None: def set_key(self, section: str, key: str, value: Any, to_site: bool = False) -> None:
"""Set a key in the configuration. """
Set a key in the configuration.
Parameters:
- section (str): The section in the configuration. Args:
- key (str): The key to be set. section (str): Section in the configuration
- value (Any): The value to be associated with the key. key (str): Key to set
- to_site (bool, optional): Whether to set in site config. Default is False. value (Any): Value to associate with the key
to_site (bool, optional): Whether to set in the site configuration. Default: False
""" """
try: try:
config_target = self.configSite if to_site else self.config config_target = self.configSite if to_site else self.config
if section not in config_target: if section not in config_target:
config_target[section] = {} config_target[section] = {}
config_target[section][key] = value config_target[section][key] = value
# Update the cache
cache_key = f"{'site' if to_site else 'config'}.{section}.{key}" cache_key = f"{'site' if to_site else 'config'}.{section}.{key}"
self.cache[cache_key] = value self.cache[cache_key] = value
logging.info(f"Key '{key}' set in section '{section}' of {'site' if to_site else 'main'} configuration")
except Exception as e: except Exception as e:
print(f"Error setting key '{key}' in section '{section}' of {'site' if to_site else 'main'} config: {e}") error_msg = f"Error setting key '{key}' in section '{section}' of {'site' if to_site else 'main'} configuration: {e}"
logging.error(error_msg)
def write_config(self) -> None: console.print(f"[bold red]{error_msg}[/bold red]")
"""Write the main configuration to the file."""
def save_config(self) -> None:
"""Save the main configuration to file."""
try: try:
with open(self.file_path, 'w') as f: with open(self.file_path, 'w') as f:
json.dump(self.config, f, indent=4) json.dump(self.config, f, indent=4)
logging.info(f"Configuration saved to: {self.file_path}")
except Exception as e: except Exception as e:
print(f"Error writing configuration file: {e}") error_msg = f"Error saving configuration: {e}"
console.print(f"[bold red]{error_msg}[/bold red]")
logging.error(error_msg)
def get_all_sites(self) -> List[str]:
"""
Get the list of all available sites.
Returns:
List[str]: List of site names
"""
return list(self.configSite.keys())
def has_section(self, section: str, in_site: bool = False) -> bool:
"""
Check if a section exists in the configuration.
Args:
section (str): Section name
in_site (bool, optional): Whether to check in the site configuration. Default: False
Returns:
bool: True if the section exists, False otherwise
"""
config_source = self.configSite if in_site else self.config
return section in config_source
config_manager = ConfigManager() # Helper function to check the platform
config_manager.read_config()
def get_use_large_bar(): def get_use_large_bar():
""" """
Determines whether the large bar feature should be enabled. Determine if the large bar feature should be enabled.
Returns: Returns:
bool: True if running on a PC (Windows, macOS, Linux), bool: True if running on PC (Windows, macOS, Linux),
False if running on Android or iOS. False if running on Android or iOS.
""" """
return not any(platform in sys.platform for platform in ("android", "ios")) return not any(platform in sys.platform for platform in ("android", "ios"))
# Initialize the ConfigManager when the module is imported
config_manager = ConfigManager()

View File

@ -296,7 +296,7 @@ def main(script_id = 0):
section, option = key.split('.') section, option = key.split('.')
config_manager.set_key(section, option, value) config_manager.set_key(section, option, value)
config_manager.write_config() config_manager.save_config()
# Check if global search is requested # Check if global search is requested
if getattr(args, 'global'): if getattr(args, 'global'):

View File

@ -10,7 +10,7 @@ with open(os.path.join(os.path.dirname(__file__), "requirements.txt"), "r", enco
setup( setup(
name="StreamingCommunity", name="StreamingCommunity",
version="2.9.7", version="2.9.8",
long_description=read_readme(), long_description=read_readme(),
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
author="Lovi-0", author="Lovi-0",