This commit is contained in:
Lovi 2025-01-23 10:50:43 +01:00
parent fd0add424d
commit 3b8af89c35
20 changed files with 367 additions and 193 deletions

3
.gitignore vendored
View File

@ -47,5 +47,4 @@ venv.bak/
Video Video
note.txt note.txt
list_proxy.txt list_proxy.txt
cmd.txt cmd.txt
downloaded_files

View File

@ -9,9 +9,6 @@
<a href="https://www.paypal.com/donate/?hosted_button_id=UXTWMT8P6HE2C"> <a href="https://www.paypal.com/donate/?hosted_button_id=UXTWMT8P6HE2C">
<img src="https://img.shields.io/badge/_-Donate-red.svg?logo=githubsponsors&labelColor=555555&style=for-the-badge" alt="Donate"/> <img src="https://img.shields.io/badge/_-Donate-red.svg?logo=githubsponsors&labelColor=555555&style=for-the-badge" alt="Donate"/>
</a> </a>
<a href="https://github.com/Lovi-0/StreamingCommunity/blob/main/LICENSE">
<img src="https://img.shields.io/badge/License-GPL_3.0-blue.svg?style=for-the-badge" alt="License"/>
</a>
<a href="https://github.com/Lovi-0/StreamingCommunity/commits"> <a href="https://github.com/Lovi-0/StreamingCommunity/commits">
<img src="https://img.shields.io/github/commit-activity/m/Lovi-0/StreamingCommunity?label=commits&style=for-the-badge" alt="Commits"/> <img src="https://img.shields.io/github/commit-activity/m/Lovi-0/StreamingCommunity?label=commits&style=for-the-badge" alt="Commits"/>
</a> </a>
@ -21,17 +18,11 @@
</p> </p>
<p align="center"> <p align="center">
<a href="https://github.com/Lovi-0/StreamingCommunity/blob/main/LICENSE">
<img src="https://img.shields.io/badge/License-GPL_3.0-blue.svg?style=for-the-badge" alt="License"/>
</a>
<a href="https://pypi.org/project/streamingcommunity"> <a href="https://pypi.org/project/streamingcommunity">
<img src="https://img.shields.io/pypi/dm/streamingcommunity?style=for-the-badge" alt="PyPI Downloads"/> <img src="https://img.shields.io/pypi/dw/streamingcommunity?style=for-the-badge" alt="PyPI Downloads"/>
</a>
<a href="https://github.com/Lovi-0/StreamingCommunity/network/members">
<img src="https://img.shields.io/github/forks/Lovi-0/StreamingCommunity?style=for-the-badge" alt="Forks"/>
</a>
<a href="https://github.com/Lovi-0/StreamingCommunity">
<img src="https://img.shields.io/github/languages/code-size/Lovi-0/StreamingCommunity?style=for-the-badge" alt="Code Size"/>
</a>
<a href="https://github.com/Lovi-0/StreamingCommunity">
<img src="https://img.shields.io/github/repo-size/Lovi-0/StreamingCommunity?style=for-the-badge" alt="Repo Size"/>
</a> </a>
</p> </p>
@ -477,10 +468,10 @@ The `run-container` command mounts also the `config.json` file, so any change to
| Website | Status | | Website | Status |
|:-------------------|:------:| |:-------------------|:------:|
| [1337xx](https://1337xx.to/) | ✅ | | [1337xx](https://1337xx.to/) | ✅ |
| [Altadefinizione](https://altadefinizione.florist/) | ✅ | | [AltadefinizioneGratis](https://altadefinizionegratis.info/) | ✅ |
| [AnimeUnity](https://animeunity.so/) | ✅ | | [AnimeUnity](https://animeunity.so/) | ✅ |
| [Ilcorsaronero](https://ilcorsaronero.link/) | ✅ | | [Ilcorsaronero](https://ilcorsaronero.link/) | ✅ |
| [CB01New](https://cb01new.lol/) | ✅ | | [CB01New](https://cb01new.video/) | ✅ |
| [DDLStreamItaly](https://ddlstreamitaly.co/) | ✅ | | [DDLStreamItaly](https://ddlstreamitaly.co/) | ✅ |
| [GuardaSerie](https://guardaserie.academy/) | ✅ | | [GuardaSerie](https://guardaserie.academy/) | ✅ |
| [MostraGuarda](https://mostraguarda.stream/) | ✅ | | [MostraGuarda](https://mostraguarda.stream/) | ✅ |

View File

@ -199,7 +199,7 @@ def display_episodes_list(scrape_serie) -> str:
# Run the table and handle user input # Run the table and handle user input
last_command = table_show_manager.run() last_command = table_show_manager.run()
if last_command == "q": if last_command == "q" or last_command == "quit":
console.print("\n[red]Quit [white]...") console.print("\n[red]Quit [white]...")
sys.exit(0) sys.exit(0)

View File

@ -1,6 +1,5 @@
# 10.12.23 # 10.12.23
import sys
import json import json
import logging import logging
import secrets import secrets
@ -83,8 +82,12 @@ def get_version_and_domain():
if not disable_searchDomain: if not disable_searchDomain:
domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}.{DOMAIN_NOW}") domain_to_use, base_url = search_domain(SITE_NAME, f"https://{SITE_NAME}.{DOMAIN_NOW}")
version = get_version(domain_to_use) try:
version = get_version(domain_to_use)
except:
console.print("[green]Auto generate version ...")
version = secrets.token_hex(32 // 2)
return version, domain_to_use return version, domain_to_use

View File

@ -2,7 +2,6 @@
import ssl import ssl
import time import time
import certifi
from urllib.parse import urlparse, unquote from urllib.parse import urlparse, unquote
@ -20,14 +19,14 @@ from StreamingCommunity.Util._jsonConfig import config_manager
base_headers = { base_headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7', 'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
'cache-control': 'max-age=0',
'dnt': '1', 'dnt': '1',
'priority': 'u=0, i', 'priority': 'u=0, i',
'referer': '',
'sec-ch-ua-mobile': '?0', 'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"', 'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'document', 'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate', 'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none', 'sec-fetch-site': 'same-origin',
'sec-fetch-user': '?1', 'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1', 'upgrade-insecure-requests': '1',
'user-agent': '' 'user-agent': ''
@ -84,6 +83,8 @@ def validate_url(url, base_url, max_timeout, max_retries=3, sleep=3):
# Verify URL structure matches base_url structure # Verify URL structure matches base_url structure
base_domain = get_base_domain(base_url) base_domain = get_base_domain(base_url)
url_domain = get_base_domain(url) url_domain = get_base_domain(url)
base_headers['referer'] = url
base_headers['user-agent'] = get_headers() base_headers['user-agent'] = get_headers()
if base_domain != url_domain: if base_domain != url_domain:
@ -98,7 +99,7 @@ def validate_url(url, base_url, max_timeout, max_retries=3, sleep=3):
return False, None return False, None
client = httpx.Client( client = httpx.Client(
verify=certifi.where(), verify=False,
headers=base_headers, headers=base_headers,
timeout=max_timeout timeout=max_timeout
) )

View File

@ -74,7 +74,7 @@ def get_select_title(table_show_manager, media_search_manager):
table_show_manager.clear() table_show_manager.clear()
# Handle user's quit command # Handle user's quit command
if last_command == "q": if last_command == "q" or last_command == "quit":
console.print("\n[red]Quit [white]...") console.print("\n[red]Quit [white]...")
sys.exit(0) sys.exit(0)

View File

@ -75,7 +75,7 @@ def get_select_title(table_show_manager, generic_obj):
table_show_manager.clear() table_show_manager.clear()
# Handle user's quit command # Handle user's quit command
if last_command == "q": if last_command == "q" or last_command == "quit":
Console.print("\n[red]Quit [white]...") Console.print("\n[red]Quit [white]...")
sys.exit(0) sys.exit(0)

View File

@ -1,5 +1,5 @@
__title__ = 'StreamingCommunity' __title__ = 'StreamingCommunity'
__version__ = '2.3.0' __version__ = '2.4.0'
__author__ = 'Lovi-0' __author__ = 'Lovi-0'
__description__ = 'A command-line program to download film' __description__ = 'A command-line program to download film'
__copyright__ = 'Copyright 2024' __copyright__ = 'Copyright 2024'

View File

@ -19,6 +19,8 @@ from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeRe
# Variable # Variable
console = Console() console = Console()
# https://github.com/eugeneware/ffmpeg-static/releases
FFMPEG_CONFIGURATION = { FFMPEG_CONFIGURATION = {
'windows': { 'windows': {
'base_dir': lambda home: os.path.join(os.path.splitdrive(home)[0] + os.path.sep, 'binary'), 'base_dir': lambda home: os.path.join(os.path.splitdrive(home)[0] + os.path.sep, 'binary'),
@ -28,13 +30,13 @@ FFMPEG_CONFIGURATION = {
}, },
'darwin': { 'darwin': {
'base_dir': lambda home: os.path.join(home, 'Applications', 'binary'), 'base_dir': lambda home: os.path.join(home, 'Applications', 'binary'),
'download_url': 'https://evermeet.cx/ffmpeg/ffmpeg-{version}.zip', 'download_url': 'https://github.com/eugeneware/ffmpeg-static/releases/download/b{version}/ffmpeg-macOS-{arch}.zip',
'file_extension': '.zip', 'file_extension': '.zip',
'executables': ['ffmpeg', 'ffprobe', 'ffplay'] 'executables': ['ffmpeg', 'ffprobe', 'ffplay']
}, },
'linux': { 'linux': {
'base_dir': lambda home: os.path.join(home, '.local', 'bin', 'binary'), 'base_dir': lambda home: os.path.join(home, '.local', 'bin', 'binary'),
'download_url': 'https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-{arch}-static.tar.xz', 'download_url': 'https://github.com/eugeneware/ffmpeg-static/releases/download/b{version}/ffmpeg-linux-{arch}.tar.xz',
'file_extension': '.tar.xz', 'file_extension': '.tar.xz',
'executables': ['ffmpeg', 'ffprobe', 'ffplay'] 'executables': ['ffmpeg', 'ffprobe', 'ffplay']
} }
@ -150,19 +152,26 @@ class FFMPEGDownloader:
def _get_latest_version(self) -> Optional[str]: def _get_latest_version(self) -> Optional[str]:
""" """
Get the latest FFmpeg version from the official website. Get the latest FFmpeg version from the GitHub releases page.
Returns: Returns:
Optional[str]: The latest version string, or None if retrieval fails Optional[str]: The latest version string, or None if retrieval fails.
Raises: Raises:
requests.exceptions.RequestException: If there are network-related errors requests.exceptions.RequestException: If there are network-related errors.
""" """
try: try:
version_url = 'https://www.gyan.dev/ffmpeg/builds/release-version' # Use GitHub API to fetch the latest release
return requests.get(version_url).text.strip() response = requests.get(
'https://api.github.com/repos/eugeneware/ffmpeg-static/releases/latest'
)
response.raise_for_status()
latest_release = response.json()
# Extract the tag name or version from the release
return latest_release.get('tag_name')
except Exception as e: except Exception as e:
logging.error(f"Unable to get version: {e}") logging.error(f"Unable to get version from GitHub: {e}")
return None return None
def _download_file(self, url: str, destination: str) -> bool: def _download_file(self, url: str, destination: str) -> bool:

View File

@ -1,5 +1,6 @@
# 26.03.24 # 26.03.24
import os
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
@ -26,6 +27,7 @@ class Logger:
# Configure file logging if debug mode and logging to file are both enabled # Configure file logging if debug mode and logging to file are both enabled
if self.log_to_file: if self.log_to_file:
self.remove_existing_log_file()
self.configure_file_logging() self.configure_file_logging()
else: else:
@ -51,3 +53,10 @@ class Logger:
formatter = logging.Formatter('[%(filename)s:%(lineno)s - %(funcName)20s() ] %(asctime)s - %(levelname)s - %(message)s') formatter = logging.Formatter('[%(filename)s:%(lineno)s - %(funcName)20s() ] %(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
logging.getLogger('').addHandler(file_handler) logging.getLogger('').addHandler(file_handler)
def remove_existing_log_file(self):
"""
Remove the log file if it already exists.
"""
if os.path.exists(self.log_file):
os.remove(self.log_file)

View File

@ -8,16 +8,16 @@ import shutil
import hashlib import hashlib
import logging import logging
import platform import platform
import unidecode
import subprocess import subprocess
import contextlib import contextlib
import pathvalidate
import urllib.request import urllib.request
import importlib.metadata import importlib.metadata
# External library # External library
import httpx import httpx
from unidecode import unidecode
from pathvalidate import sanitize_filename, sanitize_filepath
# Internal utilities # Internal utilities
@ -25,172 +25,133 @@ from .ffmpeg_installer import check_ffmpeg
from StreamingCommunity.Util.console import console, msg from StreamingCommunity.Util.console import console, msg
# Variable
OS_CONFIGURATIONS = {
'windows': {
'max_length': 255,
'invalid_chars': '<>:"/\\|?*',
'reserved_names': [
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
],
'max_path': 255
},
'darwin': {
'max_length': 4096,
'invalid_chars': '/:',
'reserved_names': [],
'hidden_file_restriction': True
},
'linux': {
'max_length': 4096,
'invalid_chars': '/\0',
'reserved_names': []
}
}
class OsManager: class OsManager:
def __init__(self): def __init__(self):
self.system = self._detect_system() self.system = self._detect_system()
self.config = OS_CONFIGURATIONS.get(self.system, {}) self.max_length = self._get_max_length()
def _detect_system(self) -> str: def _detect_system(self) -> str:
"""Detect and normalize operating system name.""" """Detect and normalize operating system name."""
system = platform.system().lower() system = platform.system().lower()
if system not in ['windows', 'darwin', 'linux']:
raise ValueError(f"Unsupported operating system: {system}")
return system
if system in OS_CONFIGURATIONS: def _get_max_length(self) -> int:
return system """Get max filename length based on OS."""
return 255 if self.system == 'windows' else 4096
raise ValueError(f"Unsupported operating system: {system}")
def _normalize_windows_path(self, path: str) -> str: def _normalize_windows_path(self, path: str) -> str:
""" """Normalize Windows paths."""
Normalize Windows paths to handle drive letters correctly. if not path or self.system != 'windows':
Args:
path (str): Original path that might contain a drive letter.
Returns:
str: Properly normalized absolute path.
"""
if self.system != 'windows':
return path return path
# Check if path starts with a drive letter # Preserve network paths (UNC and IP-based)
if path.startswith('\\\\') or path.startswith('//'):
return path.replace('/', '\\')
# Handle drive letters
if len(path) >= 2 and path[1] == ':': if len(path) >= 2 and path[1] == ':':
drive = path[0:2] drive = path[0:2]
rest = path[2:].lstrip(os.sep) rest = path[2:].replace('/', '\\').lstrip('\\')
# Ensure proper absolute path format return f"{drive}\\{rest}"
return os.path.join(drive + os.sep, rest)
return path
def _process_filename(self, filename: str) -> str:
"""
Comprehensively process filename with cross-platform considerations.
Args:
filename (str): Original filename.
Returns:
str: Processed filename.
"""
name, ext = os.path.splitext(filename)
# Handle length restrictions
if len(name) > self.config['max_length']:
name = self._truncate_filename(name)
# Reconstruct filename
processed_filename = name + ext
return processed_filename
def _truncate_filename(self, name: str) -> str: return path.replace('/', '\\')
"""
Truncate filename based on OS-specific rules.
Args:
name (str): Original filename.
Returns:
str: Truncated filename.
"""
logging.info("_truncate_filename: ", name)
if self.system == 'windows': def _normalize_mac_path(self, path: str) -> str:
return name[:self.config['max_length'] - 3] + '___' """Normalize macOS paths."""
elif self.system == 'darwin': if not path or self.system != 'darwin':
return name[:self.config['max_length']] return path
elif self.system == 'linux':
return name[:self.config['max_length'] - 2] + '___' # Convert Windows separators to Unix
normalized = path.replace('\\', '/')
# Ensure absolute paths start with /
if normalized.startswith('/'):
return os.path.normpath(normalized)
return normalized
def get_sanitize_file(self, filename: str) -> str: def get_sanitize_file(self, filename: str) -> str:
""" """Sanitize filename."""
Sanitize filename using pathvalidate with unidecode. if not filename:
return filename
Args:
filename (str): Original filename.
Returns:
str: Sanitized filename.
"""
# Decode unicode characters and sanitize # Decode and sanitize
decoded_filename = unidecode.unidecode(filename) decoded = unidecode(filename)
sanitized_filename = pathvalidate.sanitize_filename(decoded_filename) sanitized = sanitize_filename(decoded)
# Truncate if necessary based on OS configuration # Split name and extension
name, ext = os.path.splitext(sanitized_filename) name, ext = os.path.splitext(sanitized)
if len(name) > self.config['max_length']:
name = self._truncate_filename(name)
result = name + ext # Calculate available length for name considering the '...' and extension
return result max_name_length = self.max_length - len('...') - len(ext)
# Truncate name if it exceeds the max name length
if len(name) > max_name_length:
name = name[:max_name_length] + '...'
# Ensure the final file name includes the extension
return name + ext
def get_sanitize_path(self, path: str) -> str: def get_sanitize_path(self, path: str) -> str:
""" """Sanitize complete path."""
Sanitize folder path using pathvalidate with unidecode. if not path:
return path
Args:
path (str): Original folder path.
Returns:
str: Sanitized folder path.
"""
# Normalize path for Windows drive letters first # Decode unicode characters
path = self._normalize_windows_path(path) decoded = unidecode(path)
# Decode unicode characters and sanitize
decoded_path = unidecode.unidecode(path)
sanitized_path = pathvalidate.sanitize_filepath(decoded_path)
# Split path and process each component # Basic path sanitization
path_components = os.path.normpath(sanitized_path).split(os.sep) sanitized = sanitize_filepath(decoded)
# Handle Windows drive letter specially
if self.system == 'windows' and len(path_components[0]) == 2 and path_components[0][1] == ':':
drive = path_components.pop(0)
processed_components = [drive + os.sep]
if self.system == 'windows':
# Handle network paths (UNC or IP-based)
if path.startswith('\\\\') or path.startswith('//'):
parts = path.replace('/', '\\').split('\\')
# Keep server/IP and share name as is
sanitized_parts = parts[:4]
# Sanitize remaining parts
if len(parts) > 4:
sanitized_parts.extend([
self.get_sanitize_file(part)
for part in parts[4:]
if part
])
return '\\'.join(sanitized_parts)
# Handle drive letters
elif len(path) >= 2 and path[1] == ':':
drive = path[:2]
rest = path[2:].lstrip('\\').lstrip('/')
path_parts = [drive] + [
self.get_sanitize_file(part)
for part in rest.replace('/', '\\').split('\\')
if part
]
return '\\'.join(path_parts)
# Regular path
else:
parts = path.replace('/', '\\').split('\\')
return '\\'.join(p for p in parts if p)
else: else:
processed_components = [] # Handle Unix-like paths (Linux and macOS)
is_absolute = path.startswith('/')
parts = path.replace('\\', '/').split('/')
sanitized_parts = [
self.get_sanitize_file(part)
for part in parts
if part
]
result = '/'.join(sanitized_parts)
if is_absolute:
result = '/' + result
return result
# Process remaining components
for component in path_components:
if component: # Skip empty components
if len(component) > self.config['max_length']:
component = self._truncate_filename(component)
processed_components.append(component)
# Join with proper separator and normalize
result = os.path.normpath(os.path.join(*processed_components))
return result
def create_path(self, path: str, mode: int = 0o755) -> bool: def create_path(self, path: str, mode: int = 0o755) -> bool:
""" """
Create directory path with specified permissions. Create directory path with specified permissions.
@ -272,6 +233,7 @@ class OsManager:
logging.error(f"An error occurred while checking file existence: {e}") logging.error(f"An error occurred while checking file existence: {e}")
return False return False
class InternManager(): class InternManager():
def format_file_size(self, size_bytes: float) -> str: def format_file_size(self, size_bytes: float) -> str:

View File

@ -174,12 +174,12 @@ class TVShowManager:
else: else:
choices = [str(i) for i in range(0, max_int_input)] choices = [str(i) for i in range(0, max_int_input)]
choices.extend(["q", "", "back"]) choices.extend(["q", "quit", "b", "back"])
key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False) key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
last_command = key last_command = key
if key.lower() == "q": if key.lower() == "q" or key.lower() == "quit":
break break
elif key == "": elif key == "":
@ -188,7 +188,7 @@ class TVShowManager:
if self.slice_end > total_items: if self.slice_end > total_items:
self.slice_end = total_items self.slice_end = total_items
elif key.lower() == "back" and research_func: elif (key.lower() == "b" or key.lower() == "back") and research_func:
self.run_back_command(research_func) self.run_back_command(research_func)
else: else:
@ -205,19 +205,19 @@ class TVShowManager:
else: else:
choices = [str(i) for i in range(0, max_int_input)] choices = [str(i) for i in range(0, max_int_input)]
choices.extend(["q", "", "back"]) choices.extend(["q", "quit", "b", "back"])
key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False) key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
last_command = key last_command = key
if key.lower() == "q": if key.lower() == "q" or key.lower() == "quit":
break break
elif key == "": elif key == "":
self.slice_start = 0 self.slice_start = 0
self.slice_end = self.step self.slice_end = self.step
elif key.lower() == "back" and research_func: elif (key.lower() == "b" or key.lower() == "back") and research_func:
self.run_back_command(research_func) self.run_back_command(research_func)
else: else:

View File

@ -125,7 +125,6 @@ def initialize():
def main(): def main():
start = time.time() start = time.time()
# Create logger # Create logger
@ -136,9 +135,39 @@ def main():
search_functions = load_search_functions() search_functions = load_search_functions()
logging.info(f"Load module in: {time.time() - start} s") logging.info(f"Load module in: {time.time() - start} s")
# Create dynamic argument parser # Create argument parser
parser = argparse.ArgumentParser(description='Script to download film and series from the internet.') parser = argparse.ArgumentParser(
description='Script to download movies and series from the internet. Use these commands to configure the script and control its behavior.'
)
# Add arguments for the main configuration parameters
parser.add_argument(
'--add_siteName', type=bool, help='Enable or disable adding the site name to the file name (e.g., true/false).'
)
parser.add_argument(
'--disable_searchDomain', type=bool, help='Enable or disable searching in configured domains (e.g., true/false).'
)
parser.add_argument(
'--not_close', type=bool, help='If set to true, the script will not close the console after execution (e.g., true/false).'
)
# Add arguments for M3U8 configuration
parser.add_argument(
'--default_video_worker', type=int, help='Number of workers for video during M3U8 download (default: 12).'
)
parser.add_argument(
'--default_audio_worker', type=int, help='Number of workers for audio during M3U8 download (default: 12).'
)
# Add options for audio and subtitles
parser.add_argument(
'--specific_list_audio', type=str, help='Comma-separated list of specific audio languages to download (e.g., ita,eng).'
)
parser.add_argument(
'--specific_list_subtitles', type=str, help='Comma-separated list of specific subtitle languages to download (e.g., eng,spa).'
)
# Add arguments for search functions
color_map = { color_map = {
"anime": "red", "anime": "red",
"film_serie": "yellow", "film_serie": "yellow",
@ -153,10 +182,35 @@ def main():
long_option = alias long_option = alias
parser.add_argument(f'-{short_option}', f'--{long_option}', action='store_true', help=f'Search for {alias.split("_")[0]} on streaming platforms.') parser.add_argument(f'-{short_option}', f'--{long_option}', action='store_true', help=f'Search for {alias.split("_")[0]} on streaming platforms.')
# Parse command line arguments # Parse command-line arguments
args = parser.parse_args() args = parser.parse_args()
# Mapping command-line arguments to functions # Map command-line arguments to the config values
config_updates = {}
if args.add_siteName is not None:
config_updates['DEFAULT.add_siteName'] = args.add_siteName
if args.disable_searchDomain is not None:
config_updates['DEFAULT.disable_searchDomain'] = args.disable_searchDomain
if args.not_close is not None:
config_updates['DEFAULT.not_close'] = args.not_close
if args.default_video_worker is not None:
config_updates['M3U8_DOWNLOAD.default_video_worker'] = args.default_video_worker
if args.default_audio_worker is not None:
config_updates['M3U8_DOWNLOAD.default_audio_worker'] = args.default_audio_worker
if args.specific_list_audio is not None:
config_updates['M3U8_DOWNLOAD.specific_list_audio'] = args.specific_list_audio.split(',')
if args.specific_list_subtitles is not None:
config_updates['M3U8_DOWNLOAD.specific_list_subtitles'] = args.specific_list_subtitles.split(',')
# Apply the updates to the config file
for key, value in config_updates.items():
section, option = key.split('.')
config_manager.set_key(section, option, value)
config_manager.write_config()
# Map command-line arguments to functions
arg_to_function = {alias: func for alias, (func, _) in search_functions.items()} arg_to_function = {alias: func for alias, (func, _) in search_functions.items()}
# Check which argument is provided and run the corresponding function # Check which argument is provided and run the corresponding function
@ -188,4 +242,4 @@ def main():
run_function(input_to_function[category]) run_function(input_to_function[category])
else: else:
console.print("[red]Invalid category.") console.print("[red]Invalid category.")
sys.exit(0) sys.exit(0)

146
Test/Util/oss.py Normal file
View File

@ -0,0 +1,146 @@
# 22.01.25
# Fix import
import sys
import os
src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
sys.path.append(src_path)
# Import
import unittest
from unittest.mock import patch
from StreamingCommunity.Util.os import OsManager
class TestOsManager(unittest.TestCase):
def setUp(self):
self.test_paths = {
'windows': {
'network': [
(r'\\server\share\folder\file.txt', r'\\server\share\folder\file.txt'),
(r'\\192.168.1.100\share\folder\file.txt', r'\\192.168.1.100\share\folder\file.txt'),
(r'\\server\share', r'\\server\share'),
(r'\\server\share\\folder//subfolder\file.txt', r'\\server\share\folder\subfolder\file.txt')
],
'drive': [
('C:\\folder\\file.txt', 'C:\\folder\\file.txt'),
('C:/folder/file.txt', 'C:\\folder\\file.txt'),
('D:\\Test\\file.txt', 'D:\\Test\\file.txt'),
('D:/Test/file.txt', 'D:\\Test\\file.txt')
],
'relative': [
('folder\\file.txt', 'folder\\file.txt'),
('folder/file.txt', 'folder\\file.txt'),
('.\\folder\\file.txt', 'folder\\file.txt')
]
},
'darwin': {
'absolute': [
('/media/TV/show.mp4', '/media/TV/show.mp4'),
('/Users/name/Documents/file.txt', '/Users/name/Documents/file.txt'),
('/media/TV/show.mp4', '/media/TV/show.mp4')
],
'relative': [
('folder/file.txt', 'folder/file.txt'),
('folder/file.txt', 'folder/file.txt')
]
},
'linux': {
'absolute': [
('/home/user/file.txt', '/home/user/file.txt'),
('/mnt/data/file.txt', '/mnt/data/file.txt'),
('/home/user/file.txt', '/home/user/file.txt')
],
'relative': [
('folder/file.txt', 'folder/file.txt'),
('folder/file.txt', 'folder/file.txt')
]
}
}
def test_sanitize_file(self):
with patch('platform.system', return_value='Windows'):
manager = OsManager()
test_cases = [
('file.txt', 'file.txt'),
('filéš.txt', 'files.txt')
]
for input_name, expected in test_cases:
with self.subTest(input_name=input_name):
result = manager.get_sanitize_file(input_name)
self.assertEqual(result, expected)
def test_windows_paths(self):
with patch('platform.system', return_value='Windows'):
manager = OsManager()
# Test network paths (including IP)
for input_path, expected in self.test_paths['windows']['network']:
with self.subTest(input_path=input_path):
result = manager.get_sanitize_path(input_path)
self.assertEqual(result, expected)
# Test drive paths
for input_path, expected in self.test_paths['windows']['drive']:
with self.subTest(input_path=input_path):
result = manager.get_sanitize_path(input_path)
self.assertEqual(result, expected)
def test_macos_paths(self):
with patch('platform.system', return_value='Darwin'):
manager = OsManager()
# Test absolute paths
for input_path, expected in self.test_paths['darwin']['absolute']:
with self.subTest(input_path=input_path):
result = manager.get_sanitize_path(input_path)
self.assertEqual(result, expected)
# Test relative paths
for input_path, expected in self.test_paths['darwin']['relative']:
with self.subTest(input_path=input_path):
result = manager.get_sanitize_path(input_path)
self.assertEqual(result, expected)
def test_linux_paths(self):
with patch('platform.system', return_value='Linux'):
manager = OsManager()
for input_path, expected in self.test_paths['linux']['absolute']:
with self.subTest(input_path=input_path):
result = manager.get_sanitize_path(input_path)
self.assertEqual(result, expected)
def test_special_characters(self):
with patch('platform.system', return_value='Windows'):
manager = OsManager()
special_cases = [
('\\\\server\\share\\àèìòù\\file.txt', '\\\\server\\share\\aeiou\\file.txt'),
('D:\\Test\\åäö\\file.txt', 'D:\\Test\\aao\\file.txt'),
('\\\\192.168.1.100\\share\\tést\\file.txt', '\\\\192.168.1.100\\share\\test\\file.txt')
]
for input_path, expected in special_cases:
with self.subTest(input_path=input_path):
result = manager.get_sanitize_path(input_path)
self.assertEqual(result, expected)
def test_network_paths_with_ip(self):
with patch('platform.system', return_value='Windows'):
manager = OsManager()
ip_paths = [
('\\\\192.168.1.100\\share\\folder', '\\\\192.168.1.100\\share\\folder'),
('\\\\10.0.0.50\\public\\data.txt', '\\\\10.0.0.50\\public\\data.txt'),
('\\\\172.16.254.1\\backup\\test.txt', '\\\\172.16.254.1\\backup\\test.txt'),
('\\\\192.168.1.100\\share\\folder\\sub dir\\file.txt',
'\\\\192.168.1.100\\share\\folder\\sub dir\\file.txt'),
]
for input_path, expected in ip_paths:
with self.subTest(input_path=input_path):
result = manager.get_sanitize_path(input_path)
self.assertEqual(result, expected)
if __name__ == '__main__':
unittest.main()

View File

@ -122,5 +122,5 @@ if __name__ == "__main__":
domain_to_use, _ = search_domain(site_name=site_name, base_url=f"https://{site_name}.{original_domain}", get_first=True) domain_to_use, _ = search_domain(site_name=site_name, base_url=f"https://{site_name}.{original_domain}", get_first=True)
update_readme(alias, domain_to_use) update_readme(alias, domain_to_use)
print("------------------------------------") print("\n------------------------------------")
time.sleep(2) time.sleep(1)

View File

@ -61,8 +61,8 @@
"streamingcommunity": { "streamingcommunity": {
"domain": "ooo" "domain": "ooo"
}, },
"altadefinizione": { "altadefinizionegratis": {
"domain": "florist" "domain": "info"
}, },
"guardaserie": { "guardaserie": {
"domain": "academy" "domain": "academy"
@ -82,7 +82,7 @@
"domain": "so" "domain": "so"
}, },
"cb01new": { "cb01new": {
"domain": "lol" "domain": "video"
}, },
"1337xx": { "1337xx": {
"domain": "to" "domain": "to"