Open-source Grok free registration CLI, xai_enroller auth pipeline, local auth service, tests and docs.
172 lines
5.3 KiB
Python
172 lines
5.3 KiB
Python
"""
|
|
邮件接收 API 服务器
|
|
====================
|
|
接收 Cloudflare Email Routing 转发的邮件,供注册服务查询。
|
|
|
|
端点:
|
|
POST /webhook — Cloudflare 转发邮件到这里
|
|
GET /check/<email> — 查询某邮箱的验证码
|
|
GET /domains — 返回可用域名
|
|
GET /health — 健康检查
|
|
|
|
用法:
|
|
bash start.sh --email-service
|
|
EMAIL_DOMAIN=your.domain bash start.sh --email-service --port 8080
|
|
"""
|
|
import os, re, json, time, sys
|
|
try:
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
except ImportError:
|
|
pass
|
|
from http.server import HTTPServer, ThreadingHTTPServer, BaseHTTPRequestHandler
|
|
from urllib.parse import urlparse, parse_qs
|
|
from threading import Lock
|
|
|
|
# 配置
|
|
DEFAULT_DOMAIN = os.environ.get("EMAIL_DOMAIN", "")
|
|
DEFAULT_PORT = 8080
|
|
|
|
# 存储
|
|
emails = {} # {email_address: [{"code": "ABC123", "time": timestamp, "raw": "..."}]}
|
|
emails_lock = Lock()
|
|
|
|
# 清理过期邮件(5 分钟)
|
|
def cleanup_old():
|
|
now = time.time()
|
|
with emails_lock:
|
|
for addr in list(emails.keys()):
|
|
emails[addr] = [e for e in emails[addr] if now - e['time'] < 300]
|
|
if not emails[addr]:
|
|
del emails[addr]
|
|
|
|
|
|
def extract_code(text):
|
|
"""从邮件内容提取验证码(ABC-DEF 或 ABCDEF 格式)"""
|
|
# 格式1: ABC-DEF
|
|
m = re.search(r'>([A-Z0-9]{3}-[A-Z0-9]{3})<', text)
|
|
if m:
|
|
return m.group(1).replace('-', '')
|
|
# 格式2: 直接 6 位
|
|
m = re.search(r'>([A-Z0-9]{6})<', text)
|
|
if m:
|
|
return m.group(1)
|
|
# 格式3: 正文中的 6 位
|
|
m = re.search(r'\b([A-Z0-9]{3}-?[A-Z0-9]{3})\b', text)
|
|
if m:
|
|
return m.group(1).replace('-', '')
|
|
return None
|
|
|
|
|
|
class EmailHandler(BaseHTTPRequestHandler):
|
|
def do_POST(self):
|
|
if self.path == '/webhook':
|
|
content_length = int(self.headers.get('Content-Length', 0))
|
|
body = self.rfile.read(content_length).decode('utf-8')
|
|
|
|
try:
|
|
data = json.loads(body)
|
|
except:
|
|
data = {}
|
|
|
|
# Cloudflare Email Routing 格式
|
|
to_addr = data.get('to', data.get('recipient', ''))
|
|
from_addr = data.get('from', data.get('sender', ''))
|
|
subject = data.get('subject', '')
|
|
text = data.get('text', '')
|
|
html = data.get('html', '')
|
|
|
|
# 提取验证码
|
|
content = f"{subject}\n{text}\n{html}"
|
|
code = extract_code(content)
|
|
|
|
if to_addr and code:
|
|
with emails_lock:
|
|
if to_addr not in emails:
|
|
emails[to_addr] = []
|
|
emails[to_addr].append({
|
|
'code': code,
|
|
'time': time.time(),
|
|
'from': from_addr,
|
|
'subject': subject
|
|
})
|
|
print(f'[+] {to_addr} code={code}', flush=True)
|
|
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({"ok": True, "code": code}).encode())
|
|
else:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
def do_GET(self):
|
|
parsed = urlparse(self.path)
|
|
path = parsed.path
|
|
|
|
if path == '/health':
|
|
self._json({"status": "ok", "emails": len(emails)})
|
|
|
|
elif path == '/domains':
|
|
self._json({"domains": [DEFAULT_DOMAIN]})
|
|
|
|
elif path.startswith('/check/'):
|
|
addr = path[7:] # 去掉 /check/
|
|
cleanup_old()
|
|
with emails_lock:
|
|
items = emails.get(addr, [])
|
|
if items:
|
|
# 返回最新的验证码
|
|
latest = items[-1]
|
|
self._json({"code": latest['code'], "from": latest['from']})
|
|
else:
|
|
self._json({"code": None})
|
|
|
|
elif path == '/list':
|
|
# 列出所有有邮件的地址(调试用)
|
|
cleanup_old()
|
|
with emails_lock:
|
|
result = {addr: len(msgs) for addr, msgs in emails.items()}
|
|
self._json(result)
|
|
|
|
else:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
def _json(self, data):
|
|
body = json.dumps(data).encode()
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.send_header('Content-Length', str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def log_message(self, format, *args):
|
|
print(f"[HTTP] {args[0] if args else format}", flush=True)
|
|
|
|
|
|
def main():
|
|
global DEFAULT_DOMAIN
|
|
port = DEFAULT_PORT
|
|
domain = DEFAULT_DOMAIN
|
|
|
|
for i, arg in enumerate(sys.argv[1:]):
|
|
if arg == '--port' and i + 2 <= len(sys.argv):
|
|
port = int(sys.argv[i + 2])
|
|
if arg == '--domain' and i + 2 <= len(sys.argv):
|
|
domain = sys.argv[i + 2]
|
|
|
|
DEFAULT_DOMAIN = domain
|
|
|
|
print(f"[*] Email server starting on :{port}", flush=True)
|
|
print(f"[*] Domain: {domain}", flush=True)
|
|
print(f"[*] Webhook: http://0.0.0.0:{port}/webhook", flush=True)
|
|
print(f"[*] Check: http://localhost:{port}/check/<email>", flush=True)
|
|
|
|
server = ThreadingHTTPServer(('0.0.0.0', port), EmailHandler)
|
|
server.serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|