主要更新: - ✨ 新增 Telegram Bot 交互界面 - ✨ 新增欧洲账单自动生成功能 - 📦 整理项目结构,部署文件移至 deployment/ 目录 - 📝 完善文档,新增 CHANGELOG 和 Bot 部署指南 - 🔧 统一使用 pyproject.toml 管理依赖(支持 uv) - 🛡️ 增强 .gitignore,防止敏感配置泄露 新增文件: - tg_bot.py: Telegram Bot 主程序 - generate_billing.py: 独立账单生成工具 - modules/billing.py: 欧洲账单生成模块 - deployment/: Docker、systemd 等部署配置 - docs/: 完整的文档和更新日志 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
271 lines
8.0 KiB
Python
Executable File
271 lines
8.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Batch EU Billing Generator for Registered Accounts
|
|
|
|
Reads accounts from registered_accounts.json and generates EU billing checkout URLs.
|
|
|
|
Usage:
|
|
python generate_billing.py # Process registered_accounts.json
|
|
python generate_billing.py --input custom.json # Process custom file
|
|
python generate_billing.py --email user@example.com --password pass # Single account
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from typing import List, Dict, Optional
|
|
from datetime import datetime
|
|
|
|
from modules.billing import EUBillingGenerator
|
|
from modules.register import OpenAIRegistrar
|
|
from modules.tempmail import TempMailClient
|
|
from config import TEMPMAIL_CONFIG, DEBUG
|
|
|
|
|
|
def login_and_get_token(email: str, password: str) -> Optional[str]:
|
|
"""Login with email/password and retrieve access token
|
|
|
|
This function creates a new OpenAIRegistrar instance and performs
|
|
the full login flow to obtain an authenticated session, then retrieves
|
|
the access token.
|
|
|
|
Args:
|
|
email: Account email
|
|
password: Account password
|
|
|
|
Returns:
|
|
Access token or None if login fails
|
|
|
|
Raises:
|
|
Exception: If login or token retrieval fails
|
|
"""
|
|
if DEBUG:
|
|
print(f"\n[Login] Authenticating {email}...")
|
|
|
|
# Create registrar with no tempmail client (we already have credentials)
|
|
registrar = OpenAIRegistrar(tempmail_client=None)
|
|
|
|
try:
|
|
# Execute login flow (Steps 1-4)
|
|
# Note: This reuses the registration flow logic, but since the account
|
|
# already exists, we just need to authenticate to get the session
|
|
registrar._step1_init_through_chatgpt(email)
|
|
|
|
# Initialize Sentinel (needed for authentication)
|
|
registrar._step2_init_sentinel()
|
|
registrar._step2_5_submit_sentinel()
|
|
|
|
# Check if PoW is required
|
|
registrar._step2_6_solve_pow()
|
|
if hasattr(registrar, 'pow_answer') and registrar.pow_answer:
|
|
registrar._step2_7_submit_pow()
|
|
|
|
# Now retrieve access token from authenticated session
|
|
access_token = registrar._step5_get_access_token()
|
|
|
|
if DEBUG:
|
|
print(f"✅ [Login] Authentication successful")
|
|
|
|
return access_token
|
|
|
|
except Exception as e:
|
|
if DEBUG:
|
|
print(f"❌ [Login] Authentication failed: {e}")
|
|
raise
|
|
|
|
|
|
def generate_billing_for_account(email: str, password: str) -> Dict:
|
|
"""Generate billing URL for a single account
|
|
|
|
Args:
|
|
email: Account email
|
|
password: Account password
|
|
|
|
Returns:
|
|
Result dict with checkout_url or error
|
|
"""
|
|
try:
|
|
# Login and get access token
|
|
access_token = login_and_get_token(email, password)
|
|
|
|
# Generate billing URL
|
|
billing_gen = EUBillingGenerator()
|
|
billing_result = billing_gen.generate_checkout_url(access_token)
|
|
|
|
if billing_result.success:
|
|
return {
|
|
'success': True,
|
|
'email': email,
|
|
'checkout_url': billing_result.checkout_url,
|
|
'generated_at': datetime.utcnow().isoformat() + 'Z'
|
|
}
|
|
else:
|
|
return {
|
|
'success': False,
|
|
'email': email,
|
|
'error': billing_result.error
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'success': False,
|
|
'email': email,
|
|
'error': str(e)
|
|
}
|
|
|
|
|
|
def process_batch_accounts(input_file: str, output_file: str):
|
|
"""Process accounts from JSON file and generate billing URLs
|
|
|
|
Args:
|
|
input_file: Input JSON file with accounts
|
|
output_file: Output JSON file with billing URLs
|
|
"""
|
|
print(f"\n{'='*60}")
|
|
print(f"Batch EU Billing Generator")
|
|
print(f"{'='*60}\n")
|
|
|
|
# Read input file
|
|
try:
|
|
with open(input_file, 'r', encoding='utf-8') as f:
|
|
accounts = json.load(f)
|
|
except FileNotFoundError:
|
|
print(f"❌ Error: Input file not found: {input_file}")
|
|
sys.exit(1)
|
|
except json.JSONDecodeError as e:
|
|
print(f"❌ Error: Invalid JSON in input file: {e}")
|
|
sys.exit(1)
|
|
|
|
if not isinstance(accounts, list):
|
|
print(f"❌ Error: Input file must contain a JSON array of accounts")
|
|
sys.exit(1)
|
|
|
|
print(f"📋 Loaded {len(accounts)} accounts from {input_file}\n")
|
|
|
|
# Process each account
|
|
results = []
|
|
success_count = 0
|
|
failed_count = 0
|
|
|
|
for i, account in enumerate(accounts, 1):
|
|
email = account.get('email')
|
|
password = account.get('password')
|
|
|
|
if not email or not password:
|
|
print(f"[{i}/{len(accounts)}] ⚠️ Skipping account (missing email or password)")
|
|
failed_count += 1
|
|
results.append({
|
|
'success': False,
|
|
'email': email or 'N/A',
|
|
'error': 'Missing email or password in input'
|
|
})
|
|
continue
|
|
|
|
print(f"\n{'─'*60}")
|
|
print(f"[{i}/{len(accounts)}] Processing: {email}")
|
|
print(f"{'─'*60}")
|
|
|
|
result = generate_billing_for_account(email, password)
|
|
results.append(result)
|
|
|
|
if result.get('success'):
|
|
success_count += 1
|
|
print(f"✅ Billing URL generated")
|
|
print(f" URL: {result['checkout_url']}")
|
|
else:
|
|
failed_count += 1
|
|
print(f"❌ Failed: {result.get('error')}")
|
|
|
|
# Save results to output file
|
|
try:
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
json.dump(results, f, indent=2, ensure_ascii=False)
|
|
print(f"\n{'='*60}")
|
|
print(f"Results saved to: {output_file}")
|
|
except Exception as e:
|
|
print(f"\n❌ Error saving results: {e}")
|
|
sys.exit(1)
|
|
|
|
# Print summary
|
|
print(f"{'='*60}")
|
|
print(f"Total: {len(accounts)} accounts")
|
|
print(f"Success: {success_count} ✅")
|
|
print(f"Failed: {failed_count} ❌")
|
|
print(f"{'='*60}\n")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Batch EU Billing Generator',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
python generate_billing.py # Process registered_accounts.json
|
|
python generate_billing.py -i accounts.json -o billing.json # Custom input/output
|
|
python generate_billing.py --email user@example.com --password pass # Single account
|
|
"""
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-i', '--input',
|
|
type=str,
|
|
default='registered_accounts.json',
|
|
help='Input JSON file with registered accounts (default: registered_accounts.json)'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'-o', '--output',
|
|
type=str,
|
|
default='billing_urls.json',
|
|
help='Output JSON file for billing URLs (default: billing_urls.json)'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--email',
|
|
type=str,
|
|
help='Single account email (requires --password)'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--password',
|
|
type=str,
|
|
help='Single account password (requires --email)'
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Single account mode
|
|
if args.email or args.password:
|
|
if not (args.email and args.password):
|
|
print("❌ Error: --email and --password must be used together")
|
|
sys.exit(1)
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"Single Account Billing Generator")
|
|
print(f"{'='*60}\n")
|
|
|
|
result = generate_billing_for_account(args.email, args.password)
|
|
|
|
print(f"\n{'='*60}")
|
|
if result.get('success'):
|
|
print(f"✅ SUCCESS")
|
|
print(f"{'='*60}")
|
|
print(f"Email: {result['email']}")
|
|
print(f"Checkout URL:")
|
|
print(f" {result['checkout_url']}")
|
|
print(f"{'='*60}\n")
|
|
sys.exit(0)
|
|
else:
|
|
print(f"❌ FAILED")
|
|
print(f"{'='*60}")
|
|
print(f"Email: {result['email']}")
|
|
print(f"Error: {result['error']}")
|
|
print(f"{'='*60}\n")
|
|
sys.exit(1)
|
|
|
|
# Batch mode
|
|
process_batch_accounts(args.input, args.output)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|