+ Added/refactored new custom GPTs scripts
- added gen_gpt_templ script - improved Custom GPTs template generator
This commit is contained in:
654
Tools/openai_gpts/gen_gpt_templ.py
Normal file
654
Tools/openai_gpts/gen_gpt_templ.py
Normal file
@@ -0,0 +1,654 @@
|
||||
"""
|
||||
Generate markdown templates for ChatGPT GPTs by downloading and parsing their metadata.
|
||||
|
||||
By Elias Bachaalany
|
||||
|
||||
Usage:
|
||||
gen_template.py <gpt_url|gpt_id|g-prefixed_id> [--debug]
|
||||
gen_template.py @response_file.txt [--debug]
|
||||
"""
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import requests
|
||||
from collections import namedtuple
|
||||
|
||||
# Named tuple for generate_template return value
|
||||
GenerateTemplateResult = namedtuple('GenerateTemplateResult',
|
||||
['template', 'short_url', 'gpt_id', 'parser'])
|
||||
|
||||
# Global template string
|
||||
TEMPLATE = """GPT URL: https://chatgpt.com/g/{short_url}
|
||||
|
||||
GPT logo: <img src="{profile_pic}" width="100px" />
|
||||
|
||||
GPT Title: {title}
|
||||
|
||||
GPT Description: {description} - By {author_display_name}
|
||||
|
||||
GPT instructions:
|
||||
|
||||
```markdown
|
||||
|
||||
```"""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
def parse_gpt_id(url):
|
||||
"""
|
||||
Parse the GPT ID from a ChatGPT URL
|
||||
|
||||
Args:
|
||||
url (str): Full ChatGPT URL like https://chatgpt.com/g/g-VgbIr9TQQ-ida-pro-c-sdk-and-decompiler
|
||||
|
||||
Returns:
|
||||
str or None: The GPT ID (e.g., 'VgbIr9TQQ') or None if not found
|
||||
"""
|
||||
# Pattern to match g- followed by 9 characters
|
||||
pattern = r'/g/g-([a-zA-Z0-9]{9})'
|
||||
match = re.search(pattern, url)
|
||||
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Compile regex to extract streamController.enqueue arguments
|
||||
_ENQUEUE_RE = re.compile(
|
||||
r'window\.__reactRouterContext\.streamController\.enqueue\(\s*' # find the call
|
||||
r'(?P<q>["\'])' # capture whether it's " or '
|
||||
r'(?P<raw>(?:\\.|(?!\1).)*?)' # any escaped-char or char not the opening quote
|
||||
r'(?P=q)\s*' # matching closing quote
|
||||
r'\)',
|
||||
flags=re.DOTALL
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
def extract_enqueue_args(html_text, decode_escapes=True):
|
||||
"""
|
||||
Scans html_text for all streamController.enqueue(...) calls,
|
||||
returns a list of the raw string-literals inside the quotes.
|
||||
"""
|
||||
args = []
|
||||
for m in _ENQUEUE_RE.finditer(html_text):
|
||||
raw = m.group('raw')
|
||||
if decode_escapes:
|
||||
# Only decode actual escape sequences, not Unicode characters
|
||||
# This prevents double-encoding of emojis and other Unicode chars
|
||||
try:
|
||||
# First try to parse as JSON string to handle escapes properly
|
||||
raw = json.loads('"' + raw + '"')
|
||||
except:
|
||||
# Fallback to simple replacement of common escapes
|
||||
raw = raw.replace('\\n', '\n').replace('\\t', '\t').replace('\\"', '"').replace("\\'", "'").replace('\\\\', '\\')
|
||||
args.append(raw)
|
||||
return args
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
class CustomGPTParser:
|
||||
def __init__(self):
|
||||
self._parse_cache = {} # Cache for parsed data
|
||||
self._parsed_items = None # Store parsed items internally
|
||||
|
||||
def parse(self, source, debug: bool = False):
|
||||
# Determine if source is a filename or content
|
||||
# First check if it could be a file (avoid treating content as filename)
|
||||
is_likely_filename = (
|
||||
len(source) < 1000 and # Reasonable filename length
|
||||
'|' not in source and # Filenames don't contain pipes
|
||||
os.path.isfile(source)
|
||||
)
|
||||
|
||||
if is_likely_filename:
|
||||
try:
|
||||
with open(source, encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
except Exception as e:
|
||||
return (False, f"Error reading file: {e}")
|
||||
else:
|
||||
# Treat as content
|
||||
content = source
|
||||
|
||||
# Parse the content
|
||||
if not (enqueue_args := extract_enqueue_args(content)):
|
||||
msg = "No enqueue arguments found in the provided string."
|
||||
if debug:
|
||||
print(msg)
|
||||
return (False, msg)
|
||||
|
||||
if not enqueue_args: # Additional safety check for empty list
|
||||
msg = "No enqueue arguments found in the provided string."
|
||||
if debug:
|
||||
print(msg)
|
||||
return (False, msg)
|
||||
|
||||
try:
|
||||
# Use the argument with the longest length (most likely the Gizmo data)
|
||||
s = max(enqueue_args, key=len)
|
||||
data = json.loads(s)
|
||||
parsed_items = []
|
||||
for item in data:
|
||||
if isinstance(item, dict):
|
||||
for k, v in item.items():
|
||||
parsed_items.append((k, v))
|
||||
else:
|
||||
if debug:
|
||||
print(f" {item} (type: {type(item).__name__})")
|
||||
parsed_items.append(item)
|
||||
|
||||
self._parsed_items = parsed_items
|
||||
return (True, None)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
return (False, f"JSON decoding error: {e}")
|
||||
|
||||
def get_title(self):
|
||||
"""
|
||||
Extract the title of the GPT by finding the item preceding 'description'.
|
||||
|
||||
The algorithm walks through items to find 'description', then returns
|
||||
the immediately preceding item as the title.
|
||||
|
||||
Returns:
|
||||
str: The title value or empty string on failure
|
||||
"""
|
||||
# Check cache first
|
||||
if 'title' in self._parse_cache:
|
||||
return self._parse_cache['title']
|
||||
|
||||
# Need parsed items to work with
|
||||
if not self._parsed_items:
|
||||
return ''
|
||||
|
||||
# Convert to list if not already to allow indexing
|
||||
items_list = list(self._parsed_items)
|
||||
|
||||
# Find 'description' and get the preceding item
|
||||
for i, item in enumerate(items_list):
|
||||
# Skip tuples
|
||||
if isinstance(item, tuple):
|
||||
continue
|
||||
|
||||
# Found 'description'?
|
||||
if item == 'description' and i > 0:
|
||||
# Get the previous item as title
|
||||
prev_item = items_list[i - 1]
|
||||
|
||||
# Make sure it's a string value, not a tuple
|
||||
if isinstance(prev_item, str):
|
||||
self._parse_cache['title'] = prev_item
|
||||
return prev_item
|
||||
|
||||
# Not found
|
||||
return ''
|
||||
|
||||
def get_author_display_name(self):
|
||||
"""
|
||||
Extract the author display name by finding the item after 'user-{id}'.
|
||||
|
||||
The pattern is:
|
||||
- 'user_id' (literal string)
|
||||
- 'user-{actual_user_id}' (e.g., 'user-IUwuaeXwGuwv0UoRPaeEqlzs')
|
||||
- '{author_display_name}' (e.g., 'Elias Bachaalany')
|
||||
|
||||
Returns:
|
||||
str: The author display name or empty string on failure
|
||||
"""
|
||||
# Check cache first
|
||||
if 'author_display_name' in self._parse_cache:
|
||||
return self._parse_cache['author_display_name']
|
||||
|
||||
# Need parsed items to work with
|
||||
if not self._parsed_items:
|
||||
return ''
|
||||
|
||||
# Convert to list if not already to allow indexing
|
||||
items_list = list(self._parsed_items)
|
||||
|
||||
# Find pattern: 'user_id' -> 'user-{id}' -> '{display_name}'
|
||||
for i, item in enumerate(items_list):
|
||||
# Skip tuples
|
||||
if isinstance(item, tuple):
|
||||
continue
|
||||
|
||||
# Found 'user_id'?
|
||||
if item == 'user_id' and i + 2 < len(items_list):
|
||||
# Check if next item is a user ID (starts with 'user-')
|
||||
next_item = items_list[i + 1]
|
||||
if isinstance(next_item, str) and next_item.startswith('user-'):
|
||||
# The item after that should be the display name
|
||||
display_name_item = items_list[i + 2]
|
||||
if isinstance(display_name_item, str):
|
||||
self._parse_cache['author_display_name'] = display_name_item
|
||||
return display_name_item
|
||||
|
||||
# Not found
|
||||
return ''
|
||||
def get_str_value(self, name: str, default: str = None):
|
||||
"""
|
||||
Get a string value by name from the parsed items.
|
||||
|
||||
Args:
|
||||
name: The key/name to search for
|
||||
default: Default value if not found
|
||||
|
||||
Returns:
|
||||
str: The value associated with the name or default
|
||||
"""
|
||||
# Check cache first
|
||||
if name in self._parse_cache:
|
||||
return self._parse_cache[name]
|
||||
|
||||
# Need parsed items to work with
|
||||
if not self._parsed_items:
|
||||
return default
|
||||
|
||||
# Search through items
|
||||
it = iter(self._parsed_items)
|
||||
for item in it:
|
||||
# Handle tuple items (key-value pairs from dictionaries)
|
||||
if isinstance(item, tuple):
|
||||
# We don't handle tuple items now
|
||||
continue
|
||||
|
||||
# Handle flat list items (name followed by value)
|
||||
if item == name:
|
||||
try:
|
||||
val = next(it)
|
||||
# Cache and return the value
|
||||
self._parse_cache[name] = str(val)
|
||||
return str(val)
|
||||
except StopIteration:
|
||||
return default
|
||||
|
||||
return default
|
||||
|
||||
def clear_cache(self):
|
||||
"""Clear the internal cache"""
|
||||
self._parse_cache.clear()
|
||||
|
||||
def get_parsed_items(self):
|
||||
"""Get the parsed items (for backward compatibility)"""
|
||||
return self._parsed_items if self._parsed_items else []
|
||||
|
||||
def dump(self, safe_ascii=True):
|
||||
"""
|
||||
Dump all parsed items in a formatted way.
|
||||
|
||||
Args:
|
||||
safe_ascii (bool): If True, encode non-ASCII characters safely
|
||||
|
||||
Returns:
|
||||
None (prints to stdout)
|
||||
"""
|
||||
if not self._parsed_items:
|
||||
print("No parsed items to dump")
|
||||
return
|
||||
|
||||
print(f"Dumping {len(self._parsed_items)} parsed items:")
|
||||
print("-" * 60)
|
||||
|
||||
for item in self._parsed_items:
|
||||
if isinstance(item, tuple) and len(item) == 2:
|
||||
# Handle key-value pairs from dictionaries
|
||||
k, v = item
|
||||
if safe_ascii:
|
||||
# Handle Unicode characters safely by encoding to ASCII with replacement
|
||||
k_safe = str(k).encode('ascii', errors='replace').decode('ascii')
|
||||
v_safe = str(v).encode('ascii', errors='replace').decode('ascii')
|
||||
print(f" {k_safe}: {v_safe} (type: {type(v).__name__})")
|
||||
else:
|
||||
print(f" {k}: {v} (type: {type(v).__name__})")
|
||||
else:
|
||||
# Handle non-tuple items
|
||||
if safe_ascii:
|
||||
# Handle Unicode characters safely for non-dict items
|
||||
item_safe = str(item).encode('ascii', errors='replace').decode('ascii')
|
||||
print(f" {item_safe} (type: {type(item).__name__})")
|
||||
else:
|
||||
print(f" {item} (type: {type(item).__name__})")
|
||||
|
||||
print("-" * 60)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
def download_page(url: str, out_filename: str = '') -> tuple[bool, object]:
|
||||
"""
|
||||
Download a page using browser-like headers
|
||||
|
||||
Args:
|
||||
url (str): The full URL to download
|
||||
out_filename (str): Optional filename to save to. If empty, no file is written.
|
||||
|
||||
Returns:
|
||||
tuple[bool, object]: (success, content/error_message)
|
||||
- (True, content) if successful
|
||||
- (False, error_message) if failed
|
||||
"""
|
||||
# Ensure we have a full URL
|
||||
if not url.startswith('http'):
|
||||
return (False, "Please provide a full URL starting with http:// or https://")
|
||||
|
||||
# Base headers from the sample request
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
# Remove Accept-Encoding to get uncompressed response
|
||||
# 'Accept-Encoding': 'gzip, deflate, br, zstd',
|
||||
'DNT': '1',
|
||||
'Sec-GPC': '1',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'Sec-Fetch-Dest': 'document',
|
||||
'Sec-Fetch-Mode': 'navigate',
|
||||
'Sec-Fetch-Site': 'none',
|
||||
'Sec-Fetch-User': '?1',
|
||||
'Priority': 'u=0, i',
|
||||
'TE': 'trailers'
|
||||
}
|
||||
|
||||
try:
|
||||
# Create a session to handle cookies and connections properly
|
||||
session = requests.Session()
|
||||
session.headers.update(headers)
|
||||
|
||||
# Make the GET request
|
||||
response = session.get(url, timeout=30)
|
||||
|
||||
# Check if request was successful
|
||||
response.raise_for_status()
|
||||
|
||||
# Save to file if filename provided
|
||||
if out_filename:
|
||||
with open(out_filename, 'w', encoding='utf-8') as f:
|
||||
f.write(response.text)
|
||||
|
||||
return (True, response.text)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
return (False, f"Error downloading page: {e}")
|
||||
except Exception as e:
|
||||
return (False, f"Unexpected error: {e}")
|
||||
|
||||
# ----------------------------------------------------------
|
||||
def process_gpt_input(input_str):
|
||||
"""
|
||||
Process GPT input which can be:
|
||||
- Full URL: https://chatgpt.com/g/g-VgbIr9TQQ-ida-pro-c-sdk-and-decompiler
|
||||
- Conversation URL: https://chatgpt.com/g/g-m5lMeGifF-sql-expert-querygpt/c/682cd38c-ca8c-800d-b6e2-33b8ba763824
|
||||
- GPT ID: VgbIr9TQQ
|
||||
- Prefixed GPT ID: g-VgbIr9TQQ
|
||||
|
||||
Returns:
|
||||
tuple: (full_url, gpt_id)
|
||||
"""
|
||||
# Check if it's a full URL
|
||||
if input_str.startswith('https://') or input_str.startswith('http://'):
|
||||
gpt_id = parse_gpt_id(input_str)
|
||||
if not gpt_id:
|
||||
raise ValueError(f"Could not parse GPT ID from URL: {input_str}")
|
||||
|
||||
# If it's a conversation URL (contains /c/), extract the base GPT URL
|
||||
if '/c/' in input_str:
|
||||
# Extract the GPT part before /c/
|
||||
base_url = input_str.split('/c/')[0]
|
||||
return (base_url, gpt_id)
|
||||
|
||||
return (input_str, gpt_id)
|
||||
|
||||
# Check if it's a prefixed GPT ID (g-XXXXXXXXX)
|
||||
if input_str.startswith('g-') and len(input_str) >= 11:
|
||||
# Extract just the 9-character ID after 'g-'
|
||||
gpt_id = input_str[2:11] # Get exactly 9 characters after 'g-'
|
||||
url = f"https://chatgpt.com/g/{input_str}"
|
||||
return (url, gpt_id)
|
||||
|
||||
# Assume it's a bare GPT ID (9 characters)
|
||||
if len(input_str) == 9:
|
||||
url = f"https://chatgpt.com/g/g-{input_str}"
|
||||
return (url, input_str)
|
||||
|
||||
raise ValueError(f"Invalid GPT input format: {input_str}")
|
||||
|
||||
def generate_template(url, debug=False, dump=False):
|
||||
"""
|
||||
Download and parse GPT data, then generate markdown template
|
||||
|
||||
Args:
|
||||
url: Full GPT URL
|
||||
debug: Whether to save debug files (HTML and dump)
|
||||
dump: Whether to print parsed items to console
|
||||
|
||||
Returns:
|
||||
tuple: (success, result_or_error)
|
||||
- (True, GenerateTemplateResult) if successful
|
||||
- (False, error_message) if failed
|
||||
"""
|
||||
print(f"[DOWNLOAD] Fetching page from: {url}")
|
||||
# Download the page
|
||||
save_file = None
|
||||
if debug:
|
||||
save_file = "debug_download.html"
|
||||
print(f"[DEBUG] Will save HTML to: {save_file}")
|
||||
|
||||
success, content = download_page(url, save_file)
|
||||
if not success:
|
||||
return (False, f"Download failed: {content}")
|
||||
|
||||
print(f"[DOWNLOAD] Successfully downloaded {len(content)} bytes")
|
||||
|
||||
# Parse the content
|
||||
print(f"[PARSE] Parsing GPT data...")
|
||||
parser = CustomGPTParser()
|
||||
success, error = parser.parse(content)
|
||||
if not success:
|
||||
return (False, f"Parsing failed: {error}")
|
||||
|
||||
print(f"[PARSE] Successfully parsed {len(parser.get_parsed_items())} items")
|
||||
|
||||
# Save dump if debug mode
|
||||
if debug:
|
||||
from io import StringIO
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = buffer = StringIO()
|
||||
parser.dump(safe_ascii=True)
|
||||
dump_content = buffer.getvalue()
|
||||
sys.stdout = old_stdout
|
||||
|
||||
dump_file = "debug_dump.txt"
|
||||
with open(dump_file, 'w', encoding='utf-8') as f:
|
||||
f.write(dump_content)
|
||||
print(f"[DEBUG] Saved parsed data dump to: {dump_file}")
|
||||
|
||||
# Extract required fields
|
||||
print(f"[EXTRACT] Extracting GPT metadata...")
|
||||
short_url = parser.get_str_value('short_url', 'UNKNOWN')
|
||||
profile_pic = parser.get_str_value('profile_picture_url', '')
|
||||
title = parser.get_title()
|
||||
description = parser.get_str_value('description', '')
|
||||
author_display_name = parser.get_author_display_name()
|
||||
|
||||
print(f"[EXTRACT] Found:")
|
||||
print(f" - Short URL: {short_url}")
|
||||
print(f" - Title: {title}")
|
||||
print(f" - Author: {author_display_name}")
|
||||
try:
|
||||
print(f" - Description: {description[:50]}..." if len(description) > 50 else f" - Description: {description}")
|
||||
except UnicodeEncodeError:
|
||||
# Handle special characters that can't be printed to console
|
||||
safe_desc = description.encode('ascii', errors='replace').decode('ascii')
|
||||
print(f" - Description: {safe_desc[:50]}..." if len(safe_desc) > 50 else f" - Description: {safe_desc}")
|
||||
print(f" - Profile Pic: {'Yes' if profile_pic else 'No'}")
|
||||
|
||||
# Dump parsed items if requested
|
||||
if dump:
|
||||
print("\n[DUMP] Parsed items:")
|
||||
parser.dump(safe_ascii=False)
|
||||
|
||||
# Generate template
|
||||
template = TEMPLATE.format(
|
||||
short_url=short_url,
|
||||
profile_pic=profile_pic,
|
||||
title=title,
|
||||
description=description,
|
||||
author_display_name=author_display_name
|
||||
)
|
||||
|
||||
# Extract GPT ID from short_url (remove 'g-' prefix if it exists)
|
||||
gpt_id = short_url[2:] if short_url.startswith('g-') else short_url
|
||||
|
||||
return (True, GenerateTemplateResult(template, short_url, gpt_id, parser))
|
||||
|
||||
def process_response_file(filename, debug=False, dump=False):
|
||||
"""
|
||||
Process a response file containing multiple GPT URLs/IDs
|
||||
|
||||
Args:
|
||||
filename: Path to the response file
|
||||
debug: Whether to save debug files
|
||||
dump: Whether to dump parsed items
|
||||
|
||||
Returns:
|
||||
tuple: (success_count, error_count)
|
||||
"""
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
except Exception as e:
|
||||
print(f"Error reading response file: {e}")
|
||||
return (0, 1)
|
||||
|
||||
# Process each non-empty line
|
||||
inputs = [line.strip() for line in lines if line.strip() and not line.strip().startswith('#')]
|
||||
|
||||
if not inputs:
|
||||
print(f"No valid inputs found in {filename}")
|
||||
return (0, 0)
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f"PROCESSING RESPONSE FILE: {filename}")
|
||||
print(f"Found {len(inputs)} items to process")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
|
||||
for i, input_str in enumerate(inputs, 1):
|
||||
print(f"\n[ITEM {i}/{len(inputs)}] Processing: {input_str}")
|
||||
print("-" * 60)
|
||||
|
||||
try:
|
||||
# Process input
|
||||
url, gpt_id = process_gpt_input(input_str)
|
||||
print(f"[PARSED] Full URL: {url}")
|
||||
print(f"[PARSED] GPT ID: {gpt_id}")
|
||||
|
||||
# Generate template
|
||||
success, result = generate_template(url, debug, dump)
|
||||
if success:
|
||||
filename = f"{result.gpt_id}.md"
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(result.template)
|
||||
print(f"[SUCCESS] Template saved to: {filename}")
|
||||
|
||||
# Save dump file if requested
|
||||
if dump:
|
||||
dump_filename = f"{result.gpt_id}.txt"
|
||||
from io import StringIO
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = buffer = StringIO()
|
||||
result.parser.dump(safe_ascii=True)
|
||||
dump_content = buffer.getvalue()
|
||||
sys.stdout = old_stdout
|
||||
|
||||
with open(dump_filename, 'w', encoding='utf-8') as f:
|
||||
f.write(dump_content)
|
||||
print(f"[SUCCESS] Dump saved to: {dump_filename}")
|
||||
|
||||
success_count += 1
|
||||
else:
|
||||
print(f"[FAILED] Error: {result}")
|
||||
error_count += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Exception: {e}")
|
||||
error_count += 1
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f"RESPONSE FILE COMPLETE")
|
||||
print(f"Success: {success_count}, Errors: {error_count}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
return (success_count, error_count)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate markdown template for ChatGPT GPTs')
|
||||
parser.add_argument('input', nargs='?', help='GPT URL, GPT ID, g-prefixed GPT ID, or @response_file')
|
||||
parser.add_argument('--debug', action='store_true', help='Save debug files (HTML and dump)')
|
||||
parser.add_argument('--dump', action='store_true', help='Save parsed names and values to .txt file')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check if input was provided
|
||||
if not args.input:
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
try:
|
||||
# Check if input is a response file
|
||||
if args.input.startswith('@'):
|
||||
# Process response file
|
||||
filename = args.input[1:] # Remove the @ prefix
|
||||
success_count, error_count = process_response_file(filename, args.debug, args.dump)
|
||||
sys.exit(0 if error_count == 0 else 1)
|
||||
else:
|
||||
# Process single input
|
||||
print(f"\n[INPUT] Processing: {args.input}")
|
||||
url, gpt_id = process_gpt_input(args.input)
|
||||
print(f"[PARSED] Full URL: {url}")
|
||||
print(f"[PARSED] GPT ID: {gpt_id}")
|
||||
|
||||
# Generate template
|
||||
success, result = generate_template(url, args.debug, args.dump)
|
||||
if success:
|
||||
# Save to file
|
||||
filename = f"{result.gpt_id}.md"
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(result.template)
|
||||
print(f"Template saved to: {filename}")
|
||||
|
||||
# Save dump file if requested
|
||||
if args.dump:
|
||||
dump_filename = f"{result.gpt_id}.txt"
|
||||
from io import StringIO
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = buffer = StringIO()
|
||||
result.parser.dump(safe_ascii=True)
|
||||
dump_content = buffer.getvalue()
|
||||
sys.stdout = old_stdout
|
||||
|
||||
with open(dump_filename, 'w', encoding='utf-8') as f:
|
||||
f.write(dump_content)
|
||||
print(f"Dump saved to: {dump_filename}")
|
||||
|
||||
# Also print the template
|
||||
print("\nGenerated template:")
|
||||
print("=" * 50)
|
||||
try:
|
||||
print(result.template)
|
||||
except UnicodeEncodeError:
|
||||
# Handle special characters that can't be printed to console
|
||||
safe_template = result.template.encode('ascii', errors='replace').decode('ascii')
|
||||
print(safe_template)
|
||||
else:
|
||||
print(f"Error: {result}")
|
||||
return 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user