#!/usr/bin/env python3 """ Client script to get info.json from the Thrift service. Usage: python get_info_json_client.py [URL] --host [HOST] --port [PORT] [options] Options: --host HOST Thrift server host --port PORT Thrift server port --account-id ID Account ID to use --output FILE Output file path --verbose Enable verbose output """ import argparse import json import os import sys import logging from typing import Dict, Any, Optional # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('info_json_client') # Import Thrift modules sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from thrift.transport import TTransport from pangramia.yt.common.ttypes import TokenUpdateMode from pangramia.yt.exceptions.ttypes import PBServiceException, PBUserException from yt_ops_services.client_utils import get_thrift_client def parse_args(): """Parse command line arguments""" parser = argparse.ArgumentParser(description='Get info.json from Thrift service') parser.add_argument('url', help='YouTube URL or video ID') parser.add_argument('--host', default='127.0.0.1', help="Thrift server host. Using 127.0.0.1 avoids harmless connection errors when the local Envoy proxy only listens on IPv4.") parser.add_argument('--port', type=int, default=9080, help='Thrift server port') parser.add_argument('--profile', default='default_profile', help='The profile name (accountId) to use for the request.') parser.add_argument('--client', help='Specific client to use (e.g., web, ios, android). Overrides server default.') parser.add_argument('--output', help='Output file path for the info.json. If not provided, prints to stdout.') parser.add_argument('--machine-id', help='Identifier for the client machine. Defaults to hostname.') parser.add_argument('--verbose', action='store_true', help='Enable verbose output') return parser.parse_args() def main(): """Main entry point""" args = parse_args() # Set log level if args.verbose: logger.setLevel(logging.DEBUG) transport = None try: # Create Thrift client client, transport = get_thrift_client(args.host, args.port) # Get token data, which includes the info.json logger.info(f"Requesting info.json for URL '{args.url}' using profile '{args.profile}'") # Prepare arguments for the Thrift call machine_id = args.machine_id if not machine_id: import socket machine_id = socket.gethostname() logger.info(f"No machine ID provided, using hostname: {machine_id}") thrift_args = { 'accountId': args.profile, 'updateType': TokenUpdateMode.AUTO, 'url': args.url, 'clients': args.client, 'machineId': machine_id } if args.client: logger.info(f"Requesting to use specific client: {args.client}") else: logger.info("No specific client requested, server will use its default.") token_data = client.getOrRefreshToken(**thrift_args) if not token_data or not hasattr(token_data, 'infoJson') or not token_data.infoJson: logger.error("Server did not return valid info.json data.") print("Error: Server did not return valid info.json data.", file=sys.stderr) return 1 info_json_str = token_data.infoJson # Check if the returned info.json is an error report try: info_data = json.loads(info_json_str) if isinstance(info_data, dict) and 'error' in info_data: error_code = info_data.get('errorCode', 'N/A') error_message = info_data.get('message', info_data.get('error', 'Unknown error')) logger.error(f"Server returned an error in info.json (Code: {error_code}): {error_message}") print(f"Error from server (Code: {error_code}): {error_message}", file=sys.stderr) # Optionally print the full error JSON if args.verbose: print(json.dumps(info_data, indent=2), file=sys.stderr) return 1 except json.JSONDecodeError: logger.error(f"Failed to parse info.json from server: {info_json_str[:200]}...") print("Error: Failed to parse the info.json response from the server.", file=sys.stderr) return 1 logger.info(f"Successfully retrieved info.json ({len(info_json_str)} bytes)") # Write to output file if specified, otherwise print to stdout if args.output: try: with open(args.output, 'w', encoding='utf-8') as f: # Pretty-print the JSON to the file json.dump(info_data, f, indent=2) logger.info(f"Wrote info.json to {args.output}") print(f"Successfully saved info.json to {args.output}") except IOError as e: logger.error(f"Failed to write to output file {args.output}: {e}") print(f"Error: Failed to write to output file {args.output}: {e}", file=sys.stderr) return 1 else: # Pretty-print the JSON to stdout print(json.dumps(info_data, indent=2)) return 0 except (PBServiceException, PBUserException) as e: logger.error(f"A Thrift error occurred: {e.message}", exc_info=args.verbose) print(f"Error: {e.message}", file=sys.stderr) if hasattr(e, 'context') and e.context: print(f"Context: {e.context}", file=sys.stderr) return 1 except TTransport.TTransportException as e: logger.error(f"Connection to server failed: {e}", exc_info=args.verbose) print(f"Error: Connection to server at {args.host}:{args.port} failed.", file=sys.stderr) return 1 except Exception as e: logger.exception(f"An unexpected error occurred: {e}") print(f"An unexpected error occurred: {e}", file=sys.stderr) return 1 finally: if transport and transport.isOpen(): transport.close() logger.info("Thrift connection closed.") if __name__ == "__main__": sys.exit(main())