feat: 添加 MaliAPI 临时邮箱支持

This commit is contained in:
zhangchen
2026-03-30 03:54:34 +08:00
parent b659c3a9b0
commit 044be032f8
5 changed files with 247 additions and 0 deletions

View File

@@ -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_domain", "cfworker_fingerprint",
"luckmail_base_url", "luckmail_api_key", "luckmail_email_type", "luckmail_domain",
"cpa_api_url", "cpa_api_key",

View File

@@ -112,6 +112,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", ""),
@@ -406,6 +414,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 自建临时邮箱服务"""

View File

@@ -296,6 +296,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,

View File

@@ -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 || '',
@@ -76,6 +80,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,
@@ -143,6 +151,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 }}>
@@ -192,6 +202,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' },
@@ -213,6 +224,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">

View File

@@ -19,10 +19,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' },
@@ -82,6 +88,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 被封)',
@@ -533,6 +549,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/'
}