#!/usr/bin/env python3 import sys import argparse import os from datetime import datetime # --- Version Info --- try: # Get path relative to this file script_dir = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.abspath(os.path.join(script_dir, '..')) version_file_path = os.path.join(project_root, 'VERSION.client') with open(version_file_path, 'r') as f: __version__ = f.read().strip() mod_time = os.path.getmtime(version_file_path) __build_date__ = datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d') except Exception: __version__ = "unknown" __build_date__ = "unknown" # Import the functions that define and execute the logic for each subcommand from .list_formats_tool import add_list_formats_parser, main_list_formats from .get_info_tool import add_get_info_parser, main_get_info from .download_tool import add_download_parser, main_download from .stress_policy_tool import add_stress_policy_parser, main_stress_policy from .stress_formats_tool import add_stress_formats_parser, main_stress_formats from .cookie_tool import add_cookie_tool_parser, main_cookie_tool from .download_aria_tool import add_download_aria_parser, main_download_aria from .download_native_py_tool import add_download_native_py_parser, main_download_native_py from .check_expiry_tool import add_check_expiry_parser, main_check_expiry from .config_tool import add_flags_to_json_parser, main_flags_to_json, add_json_to_flags_parser, main_json_to_flags from .manage_tool import add_manage_parser, main_manage from .profile_manager_tool import add_profile_manager_parser, main_profile_manager from .profile_allocator_tool import add_profile_allocator_parser, main_profile_allocator from .policy_enforcer_tool import add_policy_enforcer_parser, main_policy_enforcer from .profile_setup_tool import add_setup_profiles_parser, main_setup_profiles from .simulation_tool import add_simulation_parser, main_simulation from .locking_download_emulator_tool import add_locking_download_emulator_parser, main_locking_download_emulator from .task_generator_tool import add_task_generator_parser, main_task_generator from .yt_dlp_dummy_tool import add_yt_dlp_dummy_parser, main_yt_dlp_dummy from .check_log_pattern_tool import add_check_log_pattern_parser, main_check_log_pattern from .queue_manager_tool import add_queue_manager_parser, main_queue_manager def main(): """ Main entry point for the yt-ops-client CLI. Parses arguments and dispatches to the appropriate subcommand function. """ # Workaround for argparse behavior with positional arguments that start with a hyphen. # If the command is 'get-info' and the last argument looks like a video ID # starting with a '-', we insert '--' before it to tell argparse to treat it # as a positional argument, not an option. This assumes the URL is the last argument. if len(sys.argv) >= 3 and sys.argv[1] == 'get-info': last_arg = sys.argv[-1] # A YouTube video ID is 11 characters. if last_arg.startswith('-') and len(last_arg) == 11: import re if re.fullmatch(r'-[a-zA-Z0-9_-]{10}', last_arg): # Only insert '--' if it's not already the preceding argument. # This prevents `stress_policy_tool` which already adds '--' from causing an error. if sys.argv[-2] != '--': sys.argv.insert(len(sys.argv) - 1, '--') parser = argparse.ArgumentParser( description="YT Ops Client Tools", formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument( '--version', action='version', version=f'ytops-client version {__version__} (build date: {__build_date__})' ) subparsers = parser.add_subparsers(dest='command', help='Available sub-commands') # Add subparsers from each tool module add_list_formats_parser(subparsers) add_get_info_parser(subparsers) # Create a top-level 'download' command with its own subcommands download_parser = subparsers.add_parser( 'download', help='Download using different methods.', description='Provides access to various download tools. Use "download --help" for details.' ) download_subparsers = download_parser.add_subparsers(dest='download_command', help='Available downloaders', required=True) add_download_parser(download_subparsers) # Adds 'cli' subcommand add_download_native_py_parser(download_subparsers) # Adds 'py' subcommand add_download_aria_parser(download_subparsers) # Adds 'aria-rpc' subcommand add_stress_policy_parser(subparsers) add_stress_formats_parser(subparsers) add_cookie_tool_parser(subparsers) add_check_expiry_parser(subparsers) add_flags_to_json_parser(subparsers) add_json_to_flags_parser(subparsers) add_manage_parser(subparsers) add_profile_manager_parser(subparsers) add_profile_allocator_parser(subparsers) add_policy_enforcer_parser(subparsers) add_setup_profiles_parser(subparsers) add_simulation_parser(subparsers) add_locking_download_emulator_parser(subparsers) add_task_generator_parser(subparsers) add_yt_dlp_dummy_parser(subparsers) add_check_log_pattern_parser(subparsers) add_queue_manager_parser(subparsers) args = parser.parse_args() # If no command is provided, print help and exit. if not args.command: parser.print_help() return 1 # Dispatch to the correct main function based on the command if args.command == 'list-formats': return main_list_formats(args) elif args.command == 'get-info': return main_get_info(args) elif args.command == 'download': if args.download_command == 'cli': return main_download(args) elif args.download_command == 'py': return main_download_native_py(args) elif args.download_command == 'aria-rpc': return main_download_aria(args) elif args.command == 'stress-policy': return main_stress_policy(args) elif args.command == 'stress-formats': return main_stress_formats(args) elif args.command == 'convert-cookies': return main_cookie_tool(args) elif args.command == 'check-expiry': return main_check_expiry(args) elif args.command == 'flags-to-json': return main_flags_to_json(args) elif args.command == 'json-to-flags': return main_json_to_flags(args) elif args.command == 'manage': return main_manage(args) elif args.command == 'profile': return main_profile_manager(args) elif args.command == 'profile-allocator': return main_profile_allocator(args) elif args.command == 'policy-enforcer': return main_policy_enforcer(args) elif args.command == 'setup-profiles': return main_setup_profiles(args) elif args.command == 'simulation': return main_simulation(args) elif args.command == 'download-emulator': return main_locking_download_emulator(args) elif args.command == 'task-generator': return main_task_generator(args) elif args.command == 'yt-dlp-dummy': return main_yt_dlp_dummy(args) elif args.command == 'check-log-pattern': return main_check_log_pattern(args) elif args.command == 'queue': return main_queue_manager(args) # This path should not be reachable if a command is required or handled above. parser.print_help() return 1 if __name__ == "__main__": sys.exit(main())