#!/usr/bin/env python3 """ Tool to convert JSON cookies to the standard Netscape txt format. """ import argparse import json import sys import logging # Configure logging logger = logging.getLogger('cookie_tool') def convert_json_to_netscape(json_data): """ Converts a list of cookie dictionaries to a Netscape format string. """ netscape_cookies = [] # The header is optional but good practice for some tools. netscape_cookies.append("# Netscape HTTP Cookie File") netscape_cookies.append("# http://www.netscape.com/newsref/std/cookie_spec.html") netscape_cookies.append("# This is a generated file! Do not edit.") netscape_cookies.append("") if not isinstance(json_data, list): raise TypeError("Input JSON must be a list of cookie objects.") for cookie in json_data: if not isinstance(cookie, dict): logger.warning(f"Skipping non-dictionary item in JSON list: {cookie}") continue domain = cookie.get('domain', '') # The 'hostOnly' flag determines if the domain is accessible to subdomains. # Netscape format's flag is TRUE if subdomains can access it. # So, hostOnly=false means flag=TRUE. # A leading dot in the domain also implies this for some implementations. if domain.startswith('.'): include_subdomains = 'TRUE' else: include_subdomains = 'FALSE' if cookie.get('hostOnly', True) else 'TRUE' path = cookie.get('path', '/') secure = 'TRUE' if cookie.get('secure', False) else 'FALSE' # Expiration date. If session cookie or no expiration, use 0. if cookie.get('session', False) or 'expirationDate' not in cookie or cookie['expirationDate'] is None: expires = 0 else: expires = int(cookie['expirationDate']) name = cookie.get('name', '') value = str(cookie.get('value', '')) # Skip cookies without essential fields if not domain or not name: logger.warning(f"Skipping cookie with missing domain or name: {cookie}") continue netscape_cookies.append( f"{domain}\t{include_subdomains}\t{path}\t{secure}\t{expires}\t{name}\t{value}" ) return "\n".join(netscape_cookies) def add_cookie_tool_parser(subparsers): """Add the parser for the 'convert-cookies' command.""" parser = subparsers.add_parser( 'convert-cookies', description='Convert JSON cookies to Netscape format.', formatter_class=argparse.RawTextHelpFormatter, help='Convert JSON cookies to Netscape format.', epilog=""" Reads a JSON array of cookie objects from stdin and prints the Netscape cookie file format to stdout. Example JSON input format (per cookie): { "domain": ".example.com", "hostOnly": false, "path": "/", "secure": true, "expirationDate": 1672531199, "name": "my_cookie", "value": "my_value" } Example usage: cat cookies.json | yt-ops-client convert-cookies > cookies.txt """ ) parser.add_argument( 'input_file', nargs='?', type=argparse.FileType('r', encoding='utf-8'), default=sys.stdin, help="Path to the JSON cookie file. Reads from stdin if not provided." ) parser.add_argument( '-o', '--output', type=argparse.FileType('w', encoding='utf-8'), default=sys.stdout, help="Output file path for the Netscape cookies. Defaults to stdout." ) parser.add_argument('--verbose', action='store_true', help='Enable verbose logging.') return parser def main_cookie_tool(args): """Main logic for the 'convert-cookies' command.""" if args.verbose: logging.getLogger().setLevel(logging.DEBUG) logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s', stream=sys.stderr) else: logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s', stream=sys.stderr) try: json_content = args.input_file.read() if not json_content.strip(): logger.error("Input is empty.") return 1 cookie_data = json.loads(json_content) netscape_string = convert_json_to_netscape(cookie_data) args.output.write(netscape_string + '\n') if args.output is not sys.stdout: logger.info(f"Successfully converted cookies to {args.output.name}") return 0 except json.JSONDecodeError: logger.error("Invalid JSON provided. Please check the input file.") return 1 except TypeError as e: logger.error(f"Error processing JSON: {e}") return 1 except Exception as e: logger.error(f"An unexpected error occurred: {e}", exc_info=args.verbose) return 1