mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-09-05 23:47:33 +08:00
feat(accounts): 新增 Sub2Api 格式导出功能
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "codex-register-v2"
|
name = "codex-register-v2"
|
||||||
version = "0.1.0"
|
version = "1.0,4"
|
||||||
description = "OpenAI/Codex CLI 自动注册系统"
|
description = "OpenAI 自动注册系统 v2"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curl-cffi>=0.14.0",
|
"curl-cffi>=0.14.0",
|
||||||
|
|||||||
@@ -393,6 +393,82 @@ async def export_accounts_csv(request: BatchExportRequest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/export/sub2api")
|
||||||
|
async def export_accounts_sub2api(request: BatchExportRequest):
|
||||||
|
"""导出账号为 Sub2Api 格式(每个账号单独一个 JSON 文件,多个打包为 ZIP)"""
|
||||||
|
import io
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
def make_sub2api_json(acc) -> dict:
|
||||||
|
expires_at = int(acc.expires_at.timestamp()) if acc.expires_at else 0
|
||||||
|
return {
|
||||||
|
"proxies": [],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": acc.email,
|
||||||
|
"platform": "openai",
|
||||||
|
"type": "oauth",
|
||||||
|
"credentials": {
|
||||||
|
"access_token": acc.access_token or "",
|
||||||
|
"chatgpt_account_id": acc.account_id or "",
|
||||||
|
"chatgpt_user_id": "",
|
||||||
|
"client_id": acc.client_id or "",
|
||||||
|
"expires_at": expires_at,
|
||||||
|
"expires_in": 863999,
|
||||||
|
"model_mapping": {
|
||||||
|
"gpt-5.1": "gpt-5.1",
|
||||||
|
"gpt-5.1-codex": "gpt-5.1-codex",
|
||||||
|
"gpt-5.1-codex-max": "gpt-5.1-codex-max",
|
||||||
|
"gpt-5.1-codex-mini": "gpt-5.1-codex-mini",
|
||||||
|
"gpt-5.2": "gpt-5.2",
|
||||||
|
"gpt-5.2-codex": "gpt-5.2-codex"
|
||||||
|
},
|
||||||
|
"organization_id": acc.workspace_id or "",
|
||||||
|
"refresh_token": acc.refresh_token or ""
|
||||||
|
},
|
||||||
|
"extra": {},
|
||||||
|
"concurrency": 10,
|
||||||
|
"priority": 1,
|
||||||
|
"rate_multiplier": 1,
|
||||||
|
"auto_pause_on_expired": True
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
with get_db() as db:
|
||||||
|
ids = resolve_account_ids(
|
||||||
|
db, request.ids, request.select_all,
|
||||||
|
request.status_filter, request.email_service_filter, request.search_filter
|
||||||
|
)
|
||||||
|
accounts = db.query(Account).filter(Account.id.in_(ids)).all()
|
||||||
|
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
|
if len(accounts) == 1:
|
||||||
|
acc = accounts[0]
|
||||||
|
content = json.dumps(make_sub2api_json(acc), ensure_ascii=False, indent=2)
|
||||||
|
filename = f"{acc.email}_sub2api.json"
|
||||||
|
return StreamingResponse(
|
||||||
|
iter([content]),
|
||||||
|
media_type="application/json",
|
||||||
|
headers={"Content-Disposition": f"attachment; filename={filename}"}
|
||||||
|
)
|
||||||
|
|
||||||
|
zip_buffer = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||||
|
for acc in accounts:
|
||||||
|
content = json.dumps(make_sub2api_json(acc), ensure_ascii=False, indent=2)
|
||||||
|
zf.writestr(f"{acc.email}_sub2api.json", content)
|
||||||
|
|
||||||
|
zip_buffer.seek(0)
|
||||||
|
zip_filename = f"sub2api_tokens_{timestamp}.zip"
|
||||||
|
return StreamingResponse(
|
||||||
|
zip_buffer,
|
||||||
|
media_type="application/zip",
|
||||||
|
headers={"Content-Disposition": f"attachment; filename={zip_filename}"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/export/cpa")
|
@router.post("/export/cpa")
|
||||||
async def export_accounts_cpa(request: BatchExportRequest):
|
async def export_accounts_cpa(request: BatchExportRequest):
|
||||||
"""导出账号为 CPA Token JSON 格式(每个账号单独一个 JSON 文件,打包为 ZIP)"""
|
"""导出账号为 CPA Token JSON 格式(每个账号单独一个 JSON 文件,打包为 ZIP)"""
|
||||||
|
|||||||
@@ -696,7 +696,7 @@ async function exportAccounts(format) {
|
|||||||
|
|
||||||
// 从 Content-Disposition 获取文件名
|
// 从 Content-Disposition 获取文件名
|
||||||
const disposition = response.headers.get('Content-Disposition');
|
const disposition = response.headers.get('Content-Disposition');
|
||||||
let filename = `accounts_${Date.now()}.${format === 'cpa' ? 'json' : format}`;
|
let filename = `accounts_${Date.now()}.${(format === 'cpa' || format === 'sub2api') ? 'json' : format}`;
|
||||||
if (disposition) {
|
if (disposition) {
|
||||||
const match = disposition.match(/filename=(.+)/);
|
const match = disposition.match(/filename=(.+)/);
|
||||||
if (match) {
|
if (match) {
|
||||||
|
|||||||
@@ -144,6 +144,7 @@
|
|||||||
<a href="#" class="dropdown-item" data-format="json">导出 JSON</a>
|
<a href="#" class="dropdown-item" data-format="json">导出 JSON</a>
|
||||||
<a href="#" class="dropdown-item" data-format="csv">导出 CSV</a>
|
<a href="#" class="dropdown-item" data-format="csv">导出 CSV</a>
|
||||||
<a href="#" class="dropdown-item" data-format="cpa">导出 CPA 格式</a>
|
<a href="#" class="dropdown-item" data-format="cpa">导出 CPA 格式</a>
|
||||||
|
<a href="#" class="dropdown-item" data-format="sub2api">导出 Sub2Api 格式</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user