mirror of
https://github.com/zc-zhangchen/any-auto-register.git
synced 2026-05-08 00:04:07 +08:00
Merge main into pr27-cfworker-domain-pools
Resolve PR #27 conflicts by keeping the configurable CFWorker domain pool changes alongside existing MaliAPI, CodexProxy, and chatgpt settings.
This commit is contained in:
@@ -12,6 +12,7 @@ CONFIG_KEYS = [
|
||||
"freemail_api_url", "freemail_admin_token", "freemail_username", "freemail_password",
|
||||
"moemail_api_url",
|
||||
"mail_provider",
|
||||
"maliapi_base_url", "maliapi_api_key", "maliapi_domain", "maliapi_auto_domain_strategy",
|
||||
"cfworker_api_url", "cfworker_admin_token", "cfworker_custom_auth", "cfworker_domain",
|
||||
"cfworker_domains", "cfworker_enabled_domains", "cfworker_fingerprint",
|
||||
"smstome_cookie", "smstome_country_slugs", "smstome_phone_attempts", "smstome_otp_timeout_seconds",
|
||||
@@ -19,6 +20,7 @@ CONFIG_KEYS = [
|
||||
"luckmail_base_url", "luckmail_api_key", "luckmail_email_type", "luckmail_domain",
|
||||
"cpa_api_url", "cpa_api_key",
|
||||
"team_manager_url", "team_manager_key",
|
||||
"codex_proxy_url", "codex_proxy_key", "codex_proxy_upload_type",
|
||||
"cliproxyapi_management_key",
|
||||
"grok2api_url", "grok2api_app_key", "grok2api_pool", "grok2api_quota",
|
||||
"kiro_manager_path", "kiro_manager_exe",
|
||||
|
||||
@@ -113,6 +113,14 @@ def create_mailbox(provider: str, extra: dict = None, proxy: str = None) -> 'Bas
|
||||
api_url=extra.get("moemail_api_url", "https://sall.cc"),
|
||||
proxy=proxy,
|
||||
)
|
||||
elif provider == "maliapi":
|
||||
return MaliAPIMailbox(
|
||||
api_url=extra.get("maliapi_base_url", "https://maliapi.215.im/v1"),
|
||||
api_key=extra.get("maliapi_api_key", ""),
|
||||
domain=extra.get("maliapi_domain", ""),
|
||||
auto_domain_strategy=extra.get("maliapi_auto_domain_strategy", ""),
|
||||
proxy=proxy,
|
||||
)
|
||||
elif provider == "cfworker":
|
||||
return CFWorkerMailbox(
|
||||
api_url=extra.get("cfworker_api_url", ""),
|
||||
@@ -411,6 +419,188 @@ class DuckMailMailbox(BaseMailbox):
|
||||
raise TimeoutError(f"等待验证码超时 ({timeout}s)")
|
||||
|
||||
|
||||
class MaliAPIMailbox(BaseMailbox):
|
||||
"""YYDS Mail / MaliAPI 临时邮箱服务"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_url: str = "https://maliapi.215.im/v1",
|
||||
api_key: str = "",
|
||||
domain: str = "",
|
||||
auto_domain_strategy: str = "",
|
||||
proxy: str = None,
|
||||
):
|
||||
self.api = (api_url or "https://maliapi.215.im/v1").rstrip("/")
|
||||
self.api_key = str(api_key or "").strip()
|
||||
self.domain = str(domain or "").strip()
|
||||
self.auto_domain_strategy = str(auto_domain_strategy or "").strip()
|
||||
self.proxy = {"http": proxy, "https": proxy} if proxy else None
|
||||
self._email = None
|
||||
self._temp_token = None
|
||||
|
||||
def _headers(self, bearer: str = "") -> dict[str, str]:
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
if self.api_key:
|
||||
headers["X-API-Key"] = self.api_key
|
||||
if bearer:
|
||||
headers["Authorization"] = f"Bearer {bearer}"
|
||||
return headers
|
||||
|
||||
def _request(self, method: str, path: str, *, json_body: dict = None,
|
||||
params: dict = None, bearer: str = "") -> Any:
|
||||
import requests
|
||||
|
||||
response = requests.request(
|
||||
method,
|
||||
f"{self.api}{path}",
|
||||
headers=self._headers(bearer),
|
||||
json=json_body,
|
||||
params=params,
|
||||
proxies=self.proxy,
|
||||
timeout=15,
|
||||
)
|
||||
try:
|
||||
payload = response.json()
|
||||
except Exception:
|
||||
payload = {}
|
||||
|
||||
if response.status_code >= 400:
|
||||
error = response.text or f"HTTP {response.status_code}"
|
||||
error_code = ""
|
||||
if isinstance(payload, dict):
|
||||
error = str(payload.get("error") or error).strip()
|
||||
error_code = str(payload.get("errorCode") or "").strip()
|
||||
if error_code:
|
||||
raise RuntimeError(f"MaliAPI 请求失败: {error} ({error_code})")
|
||||
raise RuntimeError(f"MaliAPI 请求失败: {str(error).strip()}")
|
||||
|
||||
if isinstance(payload, dict):
|
||||
if payload.get("success") is False:
|
||||
error = str(payload.get("error") or "unknown error").strip()
|
||||
error_code = str(payload.get("errorCode") or "").strip()
|
||||
if error_code:
|
||||
raise RuntimeError(f"MaliAPI 请求失败: {error} ({error_code})")
|
||||
raise RuntimeError(f"MaliAPI 请求失败: {error}")
|
||||
if "data" in payload:
|
||||
return payload.get("data")
|
||||
return payload
|
||||
|
||||
def _ensure_api_key(self) -> None:
|
||||
if not self.api_key:
|
||||
raise RuntimeError("MaliAPI 未配置:请在全局设置中填写 maliapi_api_key")
|
||||
|
||||
def _list_messages(self, account: MailboxAccount) -> list[dict]:
|
||||
data = self._request("GET", "/messages", params={"address": account.email})
|
||||
if isinstance(data, dict):
|
||||
messages = data.get("messages", [])
|
||||
else:
|
||||
messages = data
|
||||
return [item for item in (messages or []) if isinstance(item, dict)]
|
||||
|
||||
def _get_message_detail(self, message_id: str) -> dict:
|
||||
data = self._request("GET", f"/messages/{message_id}")
|
||||
if isinstance(data, dict) and isinstance(data.get("message"), dict):
|
||||
return data["message"]
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
def get_email(self) -> MailboxAccount:
|
||||
self._ensure_api_key()
|
||||
body = {}
|
||||
if self.domain:
|
||||
body["domain"] = self.domain
|
||||
if self.auto_domain_strategy:
|
||||
body["autoDomainStrategy"] = self.auto_domain_strategy
|
||||
|
||||
data = self._request("POST", "/accounts", json_body=body)
|
||||
if not isinstance(data, dict):
|
||||
raise RuntimeError(f"MaliAPI 返回异常: {data}")
|
||||
|
||||
email = str(data.get("address") or data.get("email") or "").strip()
|
||||
temp_token = str(
|
||||
data.get("tempToken")
|
||||
or data.get("temp_token")
|
||||
or data.get("token")
|
||||
or ""
|
||||
).strip()
|
||||
inbox_id = str(data.get("id") or "").strip()
|
||||
if not email:
|
||||
raise RuntimeError(f"MaliAPI 返回空邮箱: {data}")
|
||||
|
||||
self._email = email
|
||||
self._temp_token = temp_token
|
||||
self._log(f"[MaliAPI] 生成邮箱: {email}")
|
||||
return MailboxAccount(
|
||||
email=email,
|
||||
account_id=temp_token or inbox_id or email,
|
||||
extra={
|
||||
"provider": "maliapi",
|
||||
"temp_token": temp_token,
|
||||
"inbox_id": inbox_id,
|
||||
},
|
||||
)
|
||||
|
||||
def get_current_ids(self, account: MailboxAccount) -> set:
|
||||
self._ensure_api_key()
|
||||
try:
|
||||
return {
|
||||
str(message.get("id"))
|
||||
for message in self._list_messages(account)
|
||||
if message.get("id") is not None
|
||||
}
|
||||
except Exception:
|
||||
return set()
|
||||
|
||||
def wait_for_code(self, account: MailboxAccount, keyword: str = "",
|
||||
timeout: int = 120, before_ids: set = None,
|
||||
code_pattern: str = None, **kwargs) -> str:
|
||||
import re
|
||||
import time
|
||||
|
||||
self._ensure_api_key()
|
||||
seen = {str(mid) for mid in (before_ids or set())}
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
for message in self._list_messages(account):
|
||||
message_id = str(message.get("id") or "").strip()
|
||||
if not message_id or message_id in seen:
|
||||
continue
|
||||
seen.add(message_id)
|
||||
|
||||
try:
|
||||
detail = self._get_message_detail(message_id)
|
||||
except Exception:
|
||||
detail = message
|
||||
|
||||
search_text = " ".join([
|
||||
str(detail.get("subject") or message.get("subject") or ""),
|
||||
str(detail.get("text") or ""),
|
||||
str(detail.get("html") or ""),
|
||||
str(message.get("subject") or ""),
|
||||
str(message.get("snippet") or ""),
|
||||
]).strip()
|
||||
search_text = self._decode_raw_content(search_text) or search_text
|
||||
search_text = re.sub(
|
||||
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
|
||||
"",
|
||||
search_text,
|
||||
)
|
||||
if keyword and keyword.lower() not in search_text.lower():
|
||||
continue
|
||||
|
||||
code = self._safe_extract(search_text, code_pattern)
|
||||
if code:
|
||||
self._log(f"[MaliAPI] 收到验证码: {code}")
|
||||
return code
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(3)
|
||||
raise TimeoutError(f"等待验证码超时 ({timeout}s)")
|
||||
|
||||
|
||||
class CFWorkerMailbox(BaseMailbox):
|
||||
"""Cloudflare Worker 自建临时邮箱服务"""
|
||||
|
||||
|
||||
@@ -333,6 +333,15 @@ export default function Accounts() {
|
||||
message.success('已复制')
|
||||
}
|
||||
|
||||
const getRefreshToken = (record: any): string => {
|
||||
try {
|
||||
const extra = JSON.parse(record.extra_json || '{}')
|
||||
return extra.refresh_token || ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const exportCsv = () => {
|
||||
const header = 'email,password,status,region,cashier_url,created_at'
|
||||
const rows = accounts.map((a) => [a.email, a.password, a.status, a.region, a.cashier_url, a.created_at].join(','))
|
||||
@@ -415,6 +424,10 @@ export default function Accounts() {
|
||||
laoudo_auth: cfg.laoudo_auth,
|
||||
laoudo_email: cfg.laoudo_email,
|
||||
laoudo_account_id: cfg.laoudo_account_id,
|
||||
maliapi_base_url: cfg.maliapi_base_url,
|
||||
maliapi_api_key: cfg.maliapi_api_key,
|
||||
maliapi_domain: cfg.maliapi_domain,
|
||||
maliapi_auto_domain_strategy: cfg.maliapi_auto_domain_strategy,
|
||||
yescaptcha_key: cfg.yescaptcha_key,
|
||||
moemail_api_url: cfg.moemail_api_url,
|
||||
duckmail_address: cfg.duckmail_address,
|
||||
@@ -569,6 +582,22 @@ export default function Accounts() {
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'RT',
|
||||
key: 'refresh_token',
|
||||
render: (_: any, record: any) => {
|
||||
const rt = getRefreshToken(record)
|
||||
if (!rt) return <span style={{ color: '#ccc' }}>-</span>
|
||||
return (
|
||||
<Space>
|
||||
<Text style={{ fontFamily: 'monospace', fontSize: 11, filter: 'blur(4px)', maxWidth: 80, overflow: 'hidden', display: 'inline-block', verticalAlign: 'middle' }}>
|
||||
{rt.slice(0, 16)}
|
||||
</Text>
|
||||
<Button type="text" size="small" icon={<CopyOutlined />} onClick={() => copyText(rt)} />
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
@@ -820,22 +849,41 @@ export default function Accounts() {
|
||||
maskClosable={false}
|
||||
>
|
||||
{currentAccount && (
|
||||
<Form form={detailForm} layout="vertical" initialValues={currentAccount}>
|
||||
<Form.Item name="status" label="状态">
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'registered', label: '已注册' },
|
||||
{ value: 'trial', label: '试用中' },
|
||||
{ value: 'subscribed', label: '已订阅' },
|
||||
{ value: 'expired', label: '已过期' },
|
||||
{ value: 'invalid', label: '已失效' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="token" label="Token">
|
||||
<Input.TextArea rows={2} style={{ fontFamily: 'monospace' }} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<>
|
||||
<Form form={detailForm} layout="vertical" initialValues={currentAccount}>
|
||||
<Form.Item name="status" label="状态">
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'registered', label: '已注册' },
|
||||
{ value: 'trial', label: '试用中' },
|
||||
{ value: 'subscribed', label: '已订阅' },
|
||||
{ value: 'expired', label: '已过期' },
|
||||
{ value: 'invalid', label: '已失效' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="token" label="Access Token">
|
||||
<Input.TextArea rows={2} style={{ fontFamily: 'monospace' }} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{(() => {
|
||||
const rt = getRefreshToken(currentAccount)
|
||||
if (!rt) return null
|
||||
return (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<div style={{ marginBottom: 4, fontWeight: 500, fontSize: 13 }}>Refresh Token</div>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 8, background: 'rgba(0,0,0,0.03)', border: '1px solid #e5e7eb', borderRadius: 6, padding: '6px 10px' }}>
|
||||
<Text
|
||||
style={{ fontFamily: 'monospace', fontSize: 11, wordBreak: 'break-all', flex: 1, userSelect: 'text' }}
|
||||
copyable={{ text: rt, tooltips: ['复制 RT', '已复制'] }}
|
||||
>
|
||||
{rt}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,10 @@ export default function Register() {
|
||||
laoudo_auth: cfg.laoudo_auth || '',
|
||||
laoudo_email: cfg.laoudo_email || '',
|
||||
laoudo_account_id: cfg.laoudo_account_id || '',
|
||||
maliapi_base_url: cfg.maliapi_base_url || 'https://maliapi.215.im/v1',
|
||||
maliapi_api_key: cfg.maliapi_api_key || '',
|
||||
maliapi_domain: cfg.maliapi_domain || '',
|
||||
maliapi_auto_domain_strategy: cfg.maliapi_auto_domain_strategy || 'balanced',
|
||||
duckmail_api_url: cfg.duckmail_api_url || '',
|
||||
duckmail_provider_url: cfg.duckmail_provider_url || '',
|
||||
duckmail_bearer: cfg.duckmail_bearer || '',
|
||||
@@ -83,6 +87,10 @@ export default function Register() {
|
||||
laoudo_auth: values.laoudo_auth,
|
||||
laoudo_email: values.laoudo_email,
|
||||
laoudo_account_id: values.laoudo_account_id,
|
||||
maliapi_base_url: values.maliapi_base_url,
|
||||
maliapi_api_key: values.maliapi_api_key,
|
||||
maliapi_domain: values.maliapi_domain,
|
||||
maliapi_auto_domain_strategy: values.maliapi_auto_domain_strategy,
|
||||
moemail_api_url: values.moemail_api_url,
|
||||
duckmail_api_url: values.duckmail_api_url,
|
||||
duckmail_provider_url: values.duckmail_provider_url,
|
||||
@@ -157,6 +165,8 @@ export default function Register() {
|
||||
mail_provider: 'moemail',
|
||||
count: 1,
|
||||
register_delay_seconds: 0,
|
||||
maliapi_base_url: 'https://maliapi.215.im/v1',
|
||||
maliapi_auto_domain_strategy: 'balanced',
|
||||
solver_url: 'http://localhost:8889',
|
||||
}}>
|
||||
<Card title="基本配置" style={{ marginBottom: 16 }}>
|
||||
@@ -206,6 +216,7 @@ export default function Register() {
|
||||
options={[
|
||||
{ value: 'moemail', label: 'MoeMail (sall.cc)' },
|
||||
{ value: 'tempmail_lol', label: 'TempMail.lol' },
|
||||
{ value: 'maliapi', label: 'YYDS Mail / MaliAPI' },
|
||||
{ value: 'duckmail', label: 'DuckMail' },
|
||||
{ value: 'freemail', label: 'Freemail' },
|
||||
{ value: 'laoudo', label: 'Laoudo' },
|
||||
@@ -227,6 +238,28 @@ export default function Register() {
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{mailProvider === 'maliapi' && (
|
||||
<>
|
||||
<Form.Item name="maliapi_base_url" label="API URL">
|
||||
<Input placeholder="https://maliapi.215.im/v1" />
|
||||
</Form.Item>
|
||||
<Form.Item name="maliapi_api_key" label="API Key">
|
||||
<Input.Password placeholder="AC-..." />
|
||||
</Form.Item>
|
||||
<Form.Item name="maliapi_domain" label="邮箱域名(可选)">
|
||||
<Input placeholder="example.com" />
|
||||
</Form.Item>
|
||||
<Form.Item name="maliapi_auto_domain_strategy" label="自动域名策略">
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'balanced', label: 'balanced' },
|
||||
{ value: 'prefer_owned', label: 'prefer_owned' },
|
||||
{ value: 'prefer_public', label: 'prefer_public' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{mailProvider === 'cfworker' && (
|
||||
<>
|
||||
<Form.Item name="cfworker_api_url" label="API URL">
|
||||
|
||||
@@ -20,10 +20,16 @@ const SELECT_FIELDS: Record<string, { label: string; value: string }[]> = {
|
||||
{ label: 'TempMail.lol(自动生成)', value: 'tempmail_lol' },
|
||||
{ label: 'DuckMail(自动生成)', value: 'duckmail' },
|
||||
{ label: 'MoeMail (sall.cc)', value: 'moemail' },
|
||||
{ label: 'YYDS Mail / MaliAPI', value: 'maliapi' },
|
||||
{ label: 'Freemail(自建 CF Worker)', value: 'freemail' },
|
||||
{ label: 'CF Worker(自建域名)', value: 'cfworker' },
|
||||
{ label: 'LuckMail(订单接码 / 已购邮箱)', value: 'luckmail' },
|
||||
],
|
||||
maliapi_auto_domain_strategy: [
|
||||
{ label: 'balanced', value: 'balanced' },
|
||||
{ label: 'prefer_owned', value: 'prefer_owned' },
|
||||
{ label: 'prefer_public', value: 'prefer_public' },
|
||||
],
|
||||
default_executor: [
|
||||
{ label: 'API 协议(无浏览器)', value: 'protocol' },
|
||||
{ label: '无头浏览器', value: 'headless' },
|
||||
@@ -34,6 +40,10 @@ const SELECT_FIELDS: Record<string, { label: string; value: string }[]> = {
|
||||
{ label: '本地 Solver (Camoufox)', value: 'local_solver' },
|
||||
{ label: '手动', value: 'manual' },
|
||||
],
|
||||
codex_proxy_upload_type: [
|
||||
{ label: 'AT(Access Token,推荐)', value: 'at' },
|
||||
{ label: 'RT(Refresh Token)', value: 'rt' },
|
||||
],
|
||||
}
|
||||
|
||||
const TAB_ITEMS = [
|
||||
@@ -83,6 +93,16 @@ const TAB_ITEMS = [
|
||||
desc: '自动注册账号并生成临时邮箱',
|
||||
fields: [{ key: 'moemail_api_url', label: 'API URL', placeholder: 'https://sall.cc' }],
|
||||
},
|
||||
{
|
||||
title: 'YYDS Mail / MaliAPI',
|
||||
desc: '基于 API Key 创建临时邮箱并轮询收件箱消息',
|
||||
fields: [
|
||||
{ key: 'maliapi_base_url', label: 'API URL', placeholder: 'https://maliapi.215.im/v1' },
|
||||
{ key: 'maliapi_api_key', label: 'API Key', secret: true },
|
||||
{ key: 'maliapi_domain', label: '邮箱域名(可选)', placeholder: 'example.com' },
|
||||
{ key: 'maliapi_auto_domain_strategy', label: '自动域名策略', type: 'select' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'TempMail.lol',
|
||||
desc: '自动生成邮箱,无需配置,需要代理访问(CN IP 被封)',
|
||||
@@ -104,7 +124,6 @@ const TAB_ITEMS = [
|
||||
{ key: 'cfworker_api_url', label: 'API URL', placeholder: 'https://apimail.example.com' },
|
||||
{ key: 'cfworker_admin_token', label: '管理员 Token', secret: true },
|
||||
{ key: 'cfworker_custom_auth', label: '站点密码', secret: true },
|
||||
{ key: 'cfworker_custom_auth', label: '站点密码', secret: true },
|
||||
{ key: 'cfworker_fingerprint', label: 'Fingerprint', placeholder: '6703363b...' },
|
||||
],
|
||||
},
|
||||
@@ -156,6 +175,15 @@ const TAB_ITEMS = [
|
||||
{ key: 'team_manager_key', label: 'API Key', secret: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'CodexProxy',
|
||||
desc: '注册完成后自动上传到 CodexProxy 管理平台',
|
||||
fields: [
|
||||
{ key: 'codex_proxy_url', label: 'API URL', placeholder: 'https://your-codex-proxy.example.com' },
|
||||
{ key: 'codex_proxy_key', label: 'Admin Key', secret: true },
|
||||
{ key: 'codex_proxy_upload_type', label: '上传类型' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'SMSToMe 手机验证',
|
||||
desc: 'ChatGPT add_phone 阶段自动取号并轮询短信验证码',
|
||||
@@ -709,6 +737,9 @@ export default function Settings() {
|
||||
|
||||
useEffect(() => {
|
||||
apiFetch('/config').then((data) => {
|
||||
if (!data.maliapi_base_url) {
|
||||
data.maliapi_base_url = 'https://maliapi.215.im/v1'
|
||||
}
|
||||
if (!data.luckmail_base_url) {
|
||||
data.luckmail_base_url = 'https://mails.luckyous.com/'
|
||||
}
|
||||
|
||||
@@ -316,6 +316,127 @@ def upload_to_team_manager(
|
||||
return False, f"上传异常: {str(e)}"
|
||||
|
||||
|
||||
def upload_to_codex_proxy(
|
||||
account,
|
||||
api_url: str = None,
|
||||
api_key: str = None,
|
||||
) -> Tuple[bool, str]:
|
||||
"""上传单账号到 CodexProxy(通过 refresh_token,不走代理)。
|
||||
api_url / api_key 为空时自动从 ConfigStore 读取。"""
|
||||
if not api_url:
|
||||
api_url = _get_config_value("codex_proxy_url")
|
||||
if not api_key:
|
||||
api_key = _get_config_value("codex_proxy_key")
|
||||
if not api_url:
|
||||
return False, "CodexProxy API URL 未配置"
|
||||
if not api_key:
|
||||
return False, "CodexProxy Admin Key 未配置"
|
||||
|
||||
refresh_token = getattr(account, "refresh_token", "")
|
||||
if not refresh_token:
|
||||
return False, "账号缺少 refresh_token"
|
||||
|
||||
url = api_url.rstrip("/") + "/api/admin/accounts"
|
||||
headers = {
|
||||
"x-admin-key": api_key,
|
||||
"Content-Type": "application/json",
|
||||
"accept": "*/*",
|
||||
}
|
||||
payload = {
|
||||
"refresh_token": refresh_token,
|
||||
"proxy_url": "",
|
||||
}
|
||||
|
||||
try:
|
||||
resp = cffi_requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
json=payload,
|
||||
proxies=None,
|
||||
verify=False,
|
||||
timeout=30,
|
||||
impersonate="chrome110",
|
||||
)
|
||||
if resp.status_code in (200, 201):
|
||||
try:
|
||||
data = resp.json()
|
||||
msg = data.get("message", "上传成功")
|
||||
except Exception:
|
||||
msg = "上传成功"
|
||||
return True, msg
|
||||
error_msg = f"上传失败: HTTP {resp.status_code}"
|
||||
try:
|
||||
detail = resp.json()
|
||||
if isinstance(detail, dict):
|
||||
error_msg = detail.get("message", error_msg)
|
||||
except Exception:
|
||||
error_msg = f"{error_msg} - {resp.text[:200]}"
|
||||
return False, error_msg
|
||||
except Exception as e:
|
||||
logger.error(f"CodexProxy 上传异常: {e}")
|
||||
return False, f"上传异常: {str(e)}"
|
||||
|
||||
|
||||
def upload_at_to_codex_proxy(
|
||||
account,
|
||||
api_url: str = None,
|
||||
api_key: str = None,
|
||||
) -> Tuple[bool, str]:
|
||||
"""上传单账号到 CodexProxy(通过 access_token,走 /api/admin/accounts/at)。"""
|
||||
if not api_url:
|
||||
api_url = _get_config_value("codex_proxy_url")
|
||||
if not api_key:
|
||||
api_key = _get_config_value("codex_proxy_key")
|
||||
if not api_url:
|
||||
return False, "CodexProxy API URL 未配置"
|
||||
if not api_key:
|
||||
return False, "CodexProxy Admin Key 未配置"
|
||||
|
||||
access_token = getattr(account, "access_token", "")
|
||||
if not access_token:
|
||||
return False, "账号缺少 access_token"
|
||||
|
||||
url = api_url.rstrip("/") + "/api/admin/accounts/at"
|
||||
headers = {
|
||||
"x-admin-key": api_key,
|
||||
"Content-Type": "application/json",
|
||||
"accept": "*/*",
|
||||
}
|
||||
payload = {
|
||||
"access_token": access_token,
|
||||
"proxy_url": "",
|
||||
}
|
||||
|
||||
try:
|
||||
resp = cffi_requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
json=payload,
|
||||
proxies=None,
|
||||
verify=False,
|
||||
timeout=30,
|
||||
impersonate="chrome110",
|
||||
)
|
||||
if resp.status_code in (200, 201):
|
||||
try:
|
||||
data = resp.json()
|
||||
msg = data.get("message", "上传成功")
|
||||
except Exception:
|
||||
msg = "上传成功"
|
||||
return True, msg
|
||||
error_msg = f"上传失败: HTTP {resp.status_code}"
|
||||
try:
|
||||
detail = resp.json()
|
||||
if isinstance(detail, dict):
|
||||
error_msg = detail.get("message", error_msg)
|
||||
except Exception:
|
||||
error_msg = f"{error_msg} - {resp.text[:200]}"
|
||||
return False, error_msg
|
||||
except Exception as e:
|
||||
logger.error(f"CodexProxy AT 上传异常: {e}")
|
||||
return False, f"上传异常: {str(e)}"
|
||||
|
||||
|
||||
def test_cpa_connection(api_url: str, api_token: str, proxy: str = None) -> Tuple[bool, str]:
|
||||
"""测试 CPA 连接(不走代理)"""
|
||||
if not api_url:
|
||||
|
||||
@@ -163,6 +163,11 @@ class ChatGPTPlatform(BasePlatform):
|
||||
{"key": "api_url", "label": "TM API URL", "type": "text"},
|
||||
{"key": "api_key", "label": "TM API Key", "type": "text"},
|
||||
]},
|
||||
{"id": "upload_codex_proxy", "label": "上传 CodexProxy",
|
||||
"params": [
|
||||
{"key": "api_url", "label": "API URL", "type": "text"},
|
||||
{"key": "api_key", "label": "Admin Key", "type": "text"},
|
||||
]},
|
||||
]
|
||||
|
||||
def execute_action(self, action_id: str, account: Account, params: dict) -> dict:
|
||||
@@ -218,4 +223,10 @@ class ChatGPTPlatform(BasePlatform):
|
||||
api_key=params.get("api_key"))
|
||||
return {"ok": ok, "data": msg}
|
||||
|
||||
elif action_id == "upload_codex_proxy":
|
||||
from platforms.chatgpt.cpa_upload import upload_to_codex_proxy
|
||||
ok, msg = upload_to_codex_proxy(a, api_url=params.get("api_url"),
|
||||
api_key=params.get("api_key"))
|
||||
return {"ok": ok, "data": msg}
|
||||
|
||||
raise NotImplementedError(f"未知操作: {action_id}")
|
||||
|
||||
@@ -21,6 +21,27 @@ def sync_account(account) -> list[dict[str, Any]]:
|
||||
persist_cpa_sync_result(account, ok, msg)
|
||||
results.append({"name": "CPA", "ok": ok, "msg": msg})
|
||||
|
||||
codex_proxy_url = str(config_store.get("codex_proxy_url", "") or "").strip()
|
||||
if codex_proxy_url:
|
||||
upload_type = str(config_store.get("codex_proxy_upload_type", "at") or "at").strip().lower()
|
||||
extra = account.extra or {}
|
||||
|
||||
class _CP:
|
||||
pass
|
||||
|
||||
cp = _CP()
|
||||
cp.access_token = extra.get("access_token") or account.token
|
||||
cp.refresh_token = extra.get("refresh_token", "")
|
||||
|
||||
if upload_type == "rt":
|
||||
from platforms.chatgpt.cpa_upload import upload_to_codex_proxy
|
||||
ok, msg = upload_to_codex_proxy(cp)
|
||||
results.append({"name": "CodexProxy(RT)", "ok": ok, "msg": msg})
|
||||
else:
|
||||
from platforms.chatgpt.cpa_upload import upload_at_to_codex_proxy
|
||||
ok, msg = upload_at_to_codex_proxy(cp)
|
||||
results.append({"name": "CodexProxy(AT)", "ok": ok, "msg": msg})
|
||||
|
||||
elif platform == "grok":
|
||||
grok2api_url = str(config_store.get("grok2api_url", "") or "").strip()
|
||||
if grok2api_url:
|
||||
|
||||
Reference in New Issue
Block a user