diff --git a/api/config.py b/api/config.py
index ceaa325..828a10b 100644
--- a/api/config.py
+++ b/api/config.py
@@ -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",
diff --git a/core/base_mailbox.py b/core/base_mailbox.py
index bba17c8..83551bf 100644
--- a/core/base_mailbox.py
+++ b/core/base_mailbox.py
@@ -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 自建临时邮箱服务"""
diff --git a/frontend/src/pages/Accounts.tsx b/frontend/src/pages/Accounts.tsx
index 755980e..d402d40 100644
--- a/frontend/src/pages/Accounts.tsx
+++ b/frontend/src/pages/Accounts.tsx
@@ -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() {
),
},
+ {
+ title: 'RT',
+ key: 'refresh_token',
+ render: (_: any, record: any) => {
+ const rt = getRefreshToken(record)
+ if (!rt) return -
+ return (
+