fierr's picture
Deploy chatgpt-register runner
7868d50 verified
Raw
History Blame Contribute Delete
6.66 kB
"""Sub2API bundle export — ChatGPT tokens → sub2api JSON format.
Replicates the conversion logic from https://gpt.learnlicen.dpdns.org/
Input: registered account records (from register or login)
Output: sub2api bundle JSON with team-scoped tokens
"""
from __future__ import annotations
import json
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from chatgpt_register_sub2api.utils.jwt import (
backfill_id_token_claims,
decode_jwt_payload,
extract_account_info,
)
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
def _coerce_expires_at(
access_token: str,
fallback_expired: str = "",
) -> int:
"""Extract exp from JWT or compute fallback (now + ~10 days)."""
if access_token:
info = extract_account_info(access_token)
exp = info.get("exp", 0)
if exp and exp > 0:
return exp
# Fallback: parse expired string or use now + 10 days
if fallback_expired:
try:
return int(fallback_expired)
except (TypeError, ValueError):
pass
return int(time.time()) + 863999 # ~10 days
def build_credentials(
account: dict[str, Any],
) -> dict[str, Any]:
"""Build the credentials dict for a single sub2api account entry.
Args:
account: Account record with keys:
email, password, access_token, refresh_token, id_token,
chatgpt_account_id, chatgpt_user_id, plan_type, etc.
Returns:
Sub2API credentials dict
"""
access_token = str(account.get("access_token") or "").strip()
refresh_token = str(account.get("refresh_token") or "").strip()
id_token = str(account.get("id_token") or "").strip()
email = str(account.get("email") or "").strip()
# Extract claims from access token JWT
info = extract_account_info(access_token) if access_token else {}
chatgpt_account_id = (
str(account.get("chatgpt_account_id") or "").strip()
or info.get("chatgpt_account_id", "")
)
chatgpt_user_id = (
str(account.get("chatgpt_user_id") or "").strip()
or info.get("chatgpt_user_id", "")
)
plan_type = (
str(account.get("plan_type") or "").strip()
or info.get("plan_type", "free")
)
organization_id = (
str(account.get("organization_id") or "").strip()
or info.get("organization_id", "")
)
user_email = email or info.get("email", "")
expires_at = _coerce_expires_at(
access_token,
str(account.get("expired") or account.get("expires_at") or ""),
)
# Backfill id_token claims if needed
if id_token and (not chatgpt_account_id or not chatgpt_user_id):
id_token = backfill_id_token_claims(
id_token,
chatgpt_account_id=chatgpt_account_id,
account_id=chatgpt_user_id,
)
return {
"access_token": access_token,
"refresh_token": refresh_token,
"id_token": id_token,
"chatgpt_account_id": chatgpt_account_id,
"chatgpt_user_id": chatgpt_user_id,
"client_id": account.get("client_id", "app_2SKx67EdpoN0G6j64rFvigXD"),
"email": user_email,
"expires_at": expires_at,
"organization_id": organization_id,
"plan_type": plan_type or "free",
"session_token": str(account.get("session_token") or "").strip(),
}
def build_account_entry(
account: dict[str, Any],
) -> dict[str, Any]:
"""Build a single sub2api account entry from an account record."""
email = str(account.get("email") or "").strip()
source = str(account.get("source_type") or account.get("source") or "registration")
return {
"name": email,
"platform": "openai",
"type": "oauth",
"credentials": build_credentials(account),
"extra": {
"email": email,
"auth_provider": "chatgpt2api",
"source": source,
"openai_oauth_responses_websockets_v2_enabled": False,
"openai_oauth_responses_websockets_v2_mode": "off",
"privacy_mode": str(account.get("privacy_mode") or "training_off"),
},
"concurrency": 10,
"priority": 1,
"rate_multiplier": 1,
"auto_pause_on_expired": True,
}
def build_sub2api_bundle(
accounts: list[dict[str, Any]],
) -> dict[str, Any]:
"""Build a complete sub2api bundle from a list of account records.
Args:
accounts: List of account dicts (from registration or login)
Returns:
Sub2API bundle dict ready for JSON serialization
"""
return {
"exported_at": _now_iso(),
"proxies": [],
"accounts": [build_account_entry(acc) for acc in accounts],
}
def export_sub2api_json(
accounts: list[dict[str, Any]],
output_path: str | Path | None = None,
) -> str:
"""Export accounts as sub2api bundle JSON.
Args:
accounts: List of account records
output_path: Path to write. If None, auto-generates as
sub2api-YYYYMMDD-HHMMSS.json in cwd.
Returns:
JSON string of the sub2api bundle
"""
bundle = build_sub2api_bundle(accounts)
json_str = json.dumps(bundle, ensure_ascii=False, indent=2) + "\n"
if output_path is None:
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
output_path = Path(f"sub2api-{timestamp}.json")
path = Path(output_path)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json_str, encoding="utf-8")
return json_str, str(path.resolve())
def export_single_account_json(
account: dict[str, Any],
output_path: str | Path | None = None,
) -> str:
"""Export a single account as a CPA-style JSON (for debugging)."""
creds = build_credentials(account)
record = {
"type": "codex",
"email": str(account.get("email") or ""),
"expired": creds.get("expires_at", ""),
"id_token": creds.get("id_token", ""),
"account_id": creds.get("chatgpt_account_id", ""),
"disabled": False,
"access_token": creds.get("access_token", ""),
"session_token": creds.get("session_token", ""),
"last_refresh": _now_iso(),
"refresh_token": creds.get("refresh_token", ""),
"plan_type": creds.get("plan_type", ""),
}
json_str = json.dumps(record, ensure_ascii=False, indent=2) + "\n"
if output_path:
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
Path(output_path).write_text(json_str, encoding="utf-8")
return json_str