diff --git a/.gitignore b/.gitignore index af1db6a..fb8905f 100644 --- a/.gitignore +++ b/.gitignore @@ -47,5 +47,4 @@ venv.bak/ Video note.txt list_proxy.txt -cmd.txt -downloaded_files \ No newline at end of file +cmd.txt \ No newline at end of file diff --git a/README.md b/README.md index 8cbfa2c..adcf1b2 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,6 @@ Donate - - License - Commits @@ -21,17 +18,11 @@

+ + License + - PyPI Downloads - - - Forks - - - Code Size - - - Repo Size + PyPI Downloads

@@ -477,10 +468,10 @@ The `run-container` command mounts also the `config.json` file, so any change to | Website | Status | |:-------------------|:------:| | [1337xx](https://1337xx.to/) | ✅ | -| [Altadefinizione](https://altadefinizione.florist/) | ✅ | +| [AltadefinizioneGratis](https://altadefinizionegratis.info/) | ✅ | | [AnimeUnity](https://animeunity.so/) | ✅ | | [Ilcorsaronero](https://ilcorsaronero.link/) | ✅ | -| [CB01New](https://cb01new.lol/) | ✅ | +| [CB01New](https://cb01new.video/) | ✅ | | [DDLStreamItaly](https://ddlstreamitaly.co/) | ✅ | | [GuardaSerie](https://guardaserie.academy/) | ✅ | | [MostraGuarda](https://mostraguarda.stream/) | ✅ | diff --git a/StreamingCommunity/Api/Site/altadefinizione/__init__.py b/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py similarity index 100% rename from StreamingCommunity/Api/Site/altadefinizione/__init__.py rename to StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py diff --git a/StreamingCommunity/Api/Site/altadefinizione/costant.py b/StreamingCommunity/Api/Site/altadefinizionegratis/costant.py similarity index 100% rename from StreamingCommunity/Api/Site/altadefinizione/costant.py rename to StreamingCommunity/Api/Site/altadefinizionegratis/costant.py diff --git a/StreamingCommunity/Api/Site/altadefinizione/film.py b/StreamingCommunity/Api/Site/altadefinizionegratis/film.py similarity index 100% rename from StreamingCommunity/Api/Site/altadefinizione/film.py rename to StreamingCommunity/Api/Site/altadefinizionegratis/film.py diff --git a/StreamingCommunity/Api/Site/altadefinizione/site.py b/StreamingCommunity/Api/Site/altadefinizionegratis/site.py similarity index 100% rename from StreamingCommunity/Api/Site/altadefinizione/site.py rename to StreamingCommunity/Api/Site/altadefinizionegratis/site.py diff --git a/StreamingCommunity/Api/Site/streamingcommunity/series.py b/StreamingCommunity/Api/Site/streamingcommunity/series.py index 11755de..afb2211 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/series.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/series.py @@ -199,7 +199,7 @@ def display_episodes_list(scrape_serie) -> str: # Run the table and handle user input last_command = table_show_manager.run() - if last_command == "q": + if last_command == "q" or last_command == "quit": console.print("\n[red]Quit [white]...") sys.exit(0) diff --git a/StreamingCommunity/Api/Site/streamingcommunity/site.py b/StreamingCommunity/Api/Site/streamingcommunity/site.py index ba75c2a..4ca9fc6 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/site.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/site.py @@ -1,6 +1,5 @@ # 10.12.23 -import sys import json import logging import secrets @@ -83,8 +82,12 @@ def get_version_and_domain(): if not disable_searchDomain: 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 diff --git a/StreamingCommunity/Api/Template/Util/get_domain.py b/StreamingCommunity/Api/Template/Util/get_domain.py index ad5bd58..bfa86ee 100644 --- a/StreamingCommunity/Api/Template/Util/get_domain.py +++ b/StreamingCommunity/Api/Template/Util/get_domain.py @@ -2,7 +2,6 @@ import ssl import time -import certifi from urllib.parse import urlparse, unquote @@ -20,14 +19,14 @@ from StreamingCommunity.Util._jsonConfig import config_manager 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-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7', - 'cache-control': 'max-age=0', 'dnt': '1', 'priority': 'u=0, i', + 'referer': '', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', - 'sec-fetch-site': 'none', + 'sec-fetch-site': 'same-origin', 'sec-fetch-user': '?1', 'upgrade-insecure-requests': '1', '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 base_domain = get_base_domain(base_url) url_domain = get_base_domain(url) + + base_headers['referer'] = url base_headers['user-agent'] = get_headers() 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 client = httpx.Client( - verify=certifi.where(), + verify=False, headers=base_headers, timeout=max_timeout ) diff --git a/StreamingCommunity/Api/Template/site.py b/StreamingCommunity/Api/Template/site.py index e4121c2..30aa5d9 100644 --- a/StreamingCommunity/Api/Template/site.py +++ b/StreamingCommunity/Api/Template/site.py @@ -74,7 +74,7 @@ def get_select_title(table_show_manager, media_search_manager): table_show_manager.clear() # Handle user's quit command - if last_command == "q": + if last_command == "q" or last_command == "quit": console.print("\n[red]Quit [white]...") sys.exit(0) diff --git a/StreamingCommunity/Lib/TMBD/tmdb.py b/StreamingCommunity/Lib/TMBD/tmdb.py index 271bcc3..9539aa3 100644 --- a/StreamingCommunity/Lib/TMBD/tmdb.py +++ b/StreamingCommunity/Lib/TMBD/tmdb.py @@ -75,7 +75,7 @@ def get_select_title(table_show_manager, generic_obj): table_show_manager.clear() # Handle user's quit command - if last_command == "q": + if last_command == "q" or last_command == "quit": Console.print("\n[red]Quit [white]...") sys.exit(0) diff --git a/StreamingCommunity/Upload/version.py b/StreamingCommunity/Upload/version.py index 956341d..e8f4c86 100644 --- a/StreamingCommunity/Upload/version.py +++ b/StreamingCommunity/Upload/version.py @@ -1,5 +1,5 @@ __title__ = 'StreamingCommunity' -__version__ = '2.3.0' +__version__ = '2.4.0' __author__ = 'Lovi-0' __description__ = 'A command-line program to download film' __copyright__ = 'Copyright 2024' diff --git a/StreamingCommunity/Util/ffmpeg_installer.py b/StreamingCommunity/Util/ffmpeg_installer.py index 1f2986c..bc34db4 100644 --- a/StreamingCommunity/Util/ffmpeg_installer.py +++ b/StreamingCommunity/Util/ffmpeg_installer.py @@ -19,6 +19,8 @@ from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeRe # Variable console = Console() + +# https://github.com/eugeneware/ffmpeg-static/releases FFMPEG_CONFIGURATION = { 'windows': { 'base_dir': lambda home: os.path.join(os.path.splitdrive(home)[0] + os.path.sep, 'binary'), @@ -28,13 +30,13 @@ FFMPEG_CONFIGURATION = { }, 'darwin': { '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', 'executables': ['ffmpeg', 'ffprobe', 'ffplay'] }, 'linux': { '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', 'executables': ['ffmpeg', 'ffprobe', 'ffplay'] } @@ -150,19 +152,26 @@ class FFMPEGDownloader: 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: - Optional[str]: The latest version string, or None if retrieval fails + Optional[str]: The latest version string, or None if retrieval fails. Raises: - requests.exceptions.RequestException: If there are network-related errors + requests.exceptions.RequestException: If there are network-related errors. """ try: - version_url = 'https://www.gyan.dev/ffmpeg/builds/release-version' - return requests.get(version_url).text.strip() + # Use GitHub API to fetch the latest release + 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: - logging.error(f"Unable to get version: {e}") + logging.error(f"Unable to get version from GitHub: {e}") return None def _download_file(self, url: str, destination: str) -> bool: diff --git a/StreamingCommunity/Util/logger.py b/StreamingCommunity/Util/logger.py index 0e5f3d0..35c13eb 100644 --- a/StreamingCommunity/Util/logger.py +++ b/StreamingCommunity/Util/logger.py @@ -1,5 +1,6 @@ # 26.03.24 +import os import logging from logging.handlers import RotatingFileHandler @@ -26,6 +27,7 @@ class Logger: # Configure file logging if debug mode and logging to file are both enabled if self.log_to_file: + self.remove_existing_log_file() self.configure_file_logging() else: @@ -51,3 +53,10 @@ class Logger: formatter = logging.Formatter('[%(filename)s:%(lineno)s - %(funcName)20s() ] %(asctime)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) 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) \ No newline at end of file diff --git a/StreamingCommunity/Util/os.py b/StreamingCommunity/Util/os.py index 46281a9..70c88eb 100644 --- a/StreamingCommunity/Util/os.py +++ b/StreamingCommunity/Util/os.py @@ -8,16 +8,16 @@ import shutil import hashlib import logging import platform -import unidecode import subprocess import contextlib -import pathvalidate import urllib.request import importlib.metadata # External library import httpx +from unidecode import unidecode +from pathvalidate import sanitize_filename, sanitize_filepath # Internal utilities @@ -25,172 +25,133 @@ from .ffmpeg_installer import check_ffmpeg 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: def __init__(self): self.system = self._detect_system() - self.config = OS_CONFIGURATIONS.get(self.system, {}) + self.max_length = self._get_max_length() def _detect_system(self) -> str: """Detect and normalize operating system name.""" 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: - return system - - raise ValueError(f"Unsupported operating system: {system}") + def _get_max_length(self) -> int: + """Get max filename length based on OS.""" + return 255 if self.system == 'windows' else 4096 def _normalize_windows_path(self, path: str) -> str: - """ - Normalize Windows paths to handle drive letters correctly. - - Args: - path (str): Original path that might contain a drive letter. - - Returns: - str: Properly normalized absolute path. - """ - if self.system != 'windows': + """Normalize Windows paths.""" + if not path or self.system != 'windows': 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] == ':': drive = path[0:2] - rest = path[2:].lstrip(os.sep) - # Ensure proper absolute path format - 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 + rest = path[2:].replace('/', '\\').lstrip('\\') + return f"{drive}\\{rest}" - def _truncate_filename(self, name: str) -> str: - """ - Truncate filename based on OS-specific rules. - - Args: - name (str): Original filename. - - Returns: - str: Truncated filename. - """ - logging.info("_truncate_filename: ", name) + return path.replace('/', '\\') - if self.system == 'windows': - return name[:self.config['max_length'] - 3] + '___' - elif self.system == 'darwin': - return name[:self.config['max_length']] - elif self.system == 'linux': - return name[:self.config['max_length'] - 2] + '___' + def _normalize_mac_path(self, path: str) -> str: + """Normalize macOS paths.""" + if not path or self.system != 'darwin': + return path + + # 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: - """ - Sanitize filename using pathvalidate with unidecode. - - Args: - filename (str): Original filename. - - Returns: - str: Sanitized filename. - """ + """Sanitize filename.""" + if not filename: + return filename - # Decode unicode characters and sanitize - decoded_filename = unidecode.unidecode(filename) - sanitized_filename = pathvalidate.sanitize_filename(decoded_filename) + # Decode and sanitize + decoded = unidecode(filename) + sanitized = sanitize_filename(decoded) - # Truncate if necessary based on OS configuration - name, ext = os.path.splitext(sanitized_filename) - if len(name) > self.config['max_length']: - name = self._truncate_filename(name) + # Split name and extension + name, ext = os.path.splitext(sanitized) - result = name + ext - return result + # Calculate available length for name considering the '...' and extension + 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: - """ - Sanitize folder path using pathvalidate with unidecode. - - Args: - path (str): Original folder path. - - Returns: - str: Sanitized folder path. - """ + """Sanitize complete path.""" + if not path: + return path - # Normalize path for Windows drive letters first - path = self._normalize_windows_path(path) - - # Decode unicode characters and sanitize - decoded_path = unidecode.unidecode(path) - sanitized_path = pathvalidate.sanitize_filepath(decoded_path) + # Decode unicode characters + decoded = unidecode(path) - # Split path and process each component - path_components = os.path.normpath(sanitized_path).split(os.sep) - - # 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] + # Basic path sanitization + sanitized = sanitize_filepath(decoded) + 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: - 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: """ Create directory path with specified permissions. @@ -272,6 +233,7 @@ class OsManager: logging.error(f"An error occurred while checking file existence: {e}") return False + class InternManager(): def format_file_size(self, size_bytes: float) -> str: diff --git a/StreamingCommunity/Util/table.py b/StreamingCommunity/Util/table.py index 53a9001..65190c2 100644 --- a/StreamingCommunity/Util/table.py +++ b/StreamingCommunity/Util/table.py @@ -174,12 +174,12 @@ class TVShowManager: else: 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) last_command = key - if key.lower() == "q": + if key.lower() == "q" or key.lower() == "quit": break elif key == "": @@ -188,7 +188,7 @@ class TVShowManager: if 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) else: @@ -205,19 +205,19 @@ class TVShowManager: else: 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) last_command = key - if key.lower() == "q": + if key.lower() == "q" or key.lower() == "quit": break elif key == "": self.slice_start = 0 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) else: diff --git a/StreamingCommunity/run.py b/StreamingCommunity/run.py index 8e30799..f1a616e 100644 --- a/StreamingCommunity/run.py +++ b/StreamingCommunity/run.py @@ -125,7 +125,6 @@ def initialize(): def main(): - start = time.time() # Create logger @@ -136,9 +135,39 @@ def main(): search_functions = load_search_functions() logging.info(f"Load module in: {time.time() - start} s") - # Create dynamic argument parser - parser = argparse.ArgumentParser(description='Script to download film and series from the internet.') + # Create argument parser + 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 = { "anime": "red", "film_serie": "yellow", @@ -153,10 +182,35 @@ def main(): 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.') - # Parse command line arguments + # Parse command-line arguments 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()} # Check which argument is provided and run the corresponding function @@ -188,4 +242,4 @@ def main(): run_function(input_to_function[category]) else: console.print("[red]Invalid category.") - sys.exit(0) + sys.exit(0) \ No newline at end of file diff --git a/Test/Util/oss.py b/Test/Util/oss.py new file mode 100644 index 0000000..df5318e --- /dev/null +++ b/Test/Util/oss.py @@ -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() \ No newline at end of file diff --git a/Test/call_updateDomain.py b/Test/call_updateDomain.py index 0fb5d6d..68d84b9 100644 --- a/Test/call_updateDomain.py +++ b/Test/call_updateDomain.py @@ -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) update_readme(alias, domain_to_use) - print("------------------------------------") - time.sleep(2) \ No newline at end of file + print("\n------------------------------------") + time.sleep(1) \ No newline at end of file diff --git a/config.json b/config.json index f05892a..62208e6 100644 --- a/config.json +++ b/config.json @@ -61,8 +61,8 @@ "streamingcommunity": { "domain": "ooo" }, - "altadefinizione": { - "domain": "florist" + "altadefinizionegratis": { + "domain": "info" }, "guardaserie": { "domain": "academy" @@ -82,7 +82,7 @@ "domain": "so" }, "cb01new": { - "domain": "lol" + "domain": "video" }, "1337xx": { "domain": "to"