优化登录重试与配置清理
This commit is contained in:
+10
-137
@@ -1,6 +1,7 @@
|
||||
"""邮箱验证模块 - 优先通过 Roundcube Webmail API 获取验证码,read.php 作为备用"""
|
||||
"""邮箱验证模块 - 通过 Roundcube Webmail API 获取验证码。"""
|
||||
|
||||
import html
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
@@ -11,15 +12,12 @@ from loguru import logger
|
||||
import requests
|
||||
|
||||
|
||||
# 旧接口(时间解析不可靠,作为备用)
|
||||
READ_PHP_URL = "http://111.229.206.54:8000/read.php"
|
||||
|
||||
# Roundcube Webmail 地址
|
||||
ROUNDCUBE_URL = "http://111.229.206.54:8000/"
|
||||
# Roundcube Webmail 地址。可通过环境变量覆盖。
|
||||
ROUNDCUBE_URL = os.getenv("MAIL_ROUNDCUBE_URL", "http://111.229.206.54:8000/")
|
||||
|
||||
|
||||
class EmailVerifier:
|
||||
"""邮箱验证器(Roundcube API 优先,read.php 备用)"""
|
||||
"""邮箱验证器(Roundcube API)。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -32,7 +30,7 @@ class EmailVerifier:
|
||||
lookback_minutes: int = 10,
|
||||
max_messages: int = 20,
|
||||
use_ssl: bool = False,
|
||||
mail_api_url: str = READ_PHP_URL,
|
||||
roundcube_url: str = "",
|
||||
):
|
||||
# 兼容旧参数名
|
||||
self.imap_server = imap_server
|
||||
@@ -40,8 +38,7 @@ class EmailVerifier:
|
||||
self.username = username # 邮箱地址
|
||||
self.password = password # 邮箱密码
|
||||
self.timeout = timeout
|
||||
self.read_php_url = mail_api_url or READ_PHP_URL
|
||||
self.roundcube_url = ROUNDCUBE_URL
|
||||
self.roundcube_url = roundcube_url or ROUNDCUBE_URL
|
||||
|
||||
# Roundcube 会话(懒初始化)
|
||||
self._rc_session: Optional[requests.Session] = None
|
||||
@@ -265,7 +262,7 @@ class EmailVerifier:
|
||||
stop_event: Optional[threading.Event] = None,
|
||||
) -> str:
|
||||
"""
|
||||
轮询获取斗鱼验证码。优先使用 Roundcube API,失败则回退到 read.php。
|
||||
轮询获取斗鱼验证码。
|
||||
|
||||
Args:
|
||||
max_wait: 最大等待时间(秒)
|
||||
@@ -281,33 +278,19 @@ class EmailVerifier:
|
||||
|
||||
deadline = time.monotonic() + max_wait
|
||||
last_error = ""
|
||||
tried_roundcube = False
|
||||
|
||||
while time.monotonic() < deadline:
|
||||
if stop_event and stop_event.is_set():
|
||||
raise InterruptedError("任务已停止")
|
||||
|
||||
# 优先尝试 Roundcube
|
||||
if not tried_roundcube or self._rc_logged_in:
|
||||
try:
|
||||
code = self._fetch_code_via_roundcube(after_timestamp, allow_old_seconds)
|
||||
if code:
|
||||
logger.success(f"获取到验证码: {code}")
|
||||
return code
|
||||
except Exception as e:
|
||||
last_error = str(e)
|
||||
logger.warning(f"Roundcube 读邮件异常: {e}")
|
||||
tried_roundcube = True
|
||||
|
||||
# 回退到 read.php
|
||||
try:
|
||||
code = self._fetch_code_via_readphp(after_timestamp, allow_old_seconds)
|
||||
code = self._fetch_code_via_roundcube(after_timestamp, allow_old_seconds)
|
||||
if code:
|
||||
logger.success(f"获取到验证码: {code}")
|
||||
return code
|
||||
except Exception as e:
|
||||
last_error = str(e)
|
||||
logger.warning(f"read.php 读邮件异常: {e}")
|
||||
logger.warning(f"Roundcube 读邮件异常: {e}")
|
||||
|
||||
logger.debug("未找到验证码,等待中...")
|
||||
sleep_deadline = time.monotonic() + interval
|
||||
@@ -354,118 +337,8 @@ class EmailVerifier:
|
||||
|
||||
return None
|
||||
|
||||
# ── read.php 方式获取验证码(备用) ─────────────────────
|
||||
|
||||
def _fetch_code_via_readphp(
|
||||
self,
|
||||
after_timestamp: Optional[float] = None,
|
||||
allow_old_seconds: int = 15,
|
||||
) -> Optional[str]:
|
||||
"""通过 read.php HTTP 接口读取最新邮件并提取验证码"""
|
||||
try:
|
||||
response = requests.get(
|
||||
self.read_php_url,
|
||||
params={
|
||||
"yhm": self.username,
|
||||
"mm": self.password,
|
||||
},
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
content = response.text
|
||||
|
||||
# 检查是否是斗鱼验证码邮件
|
||||
if not self._is_douyu_email(content):
|
||||
logger.debug("read.php: 最新邮件不是斗鱼验证码邮件")
|
||||
return None
|
||||
|
||||
# 检查邮件时间(read.php 时间不可靠,放宽判断)
|
||||
if after_timestamp and not self._is_email_recent(content, after_timestamp, allow_old_seconds):
|
||||
logger.debug("read.php: 邮件时间早于验证码发送时间,跳过")
|
||||
return None
|
||||
|
||||
# 提取验证码
|
||||
code = self._extract_verification_code(content)
|
||||
if code:
|
||||
logger.info(f"read.php: 从邮件中提取到验证码: {code}")
|
||||
return code
|
||||
|
||||
return None
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.warning(f"read.php: 请求失败: {e}")
|
||||
return None
|
||||
|
||||
# ── 通用工具方法 ───────────────────────────────────────
|
||||
|
||||
def _is_douyu_email(self, content: str) -> bool:
|
||||
"""判断邮件内容是否是斗鱼验证码邮件"""
|
||||
douyu_keywords = ['斗鱼', 'douyu', 'douyutv']
|
||||
verify_keywords = ['验证码', '安全验证', '登录验证', '动态码', '校验码']
|
||||
combined = content.lower()
|
||||
|
||||
return (
|
||||
any(keyword in combined for keyword in douyu_keywords)
|
||||
or any(keyword in content for keyword in verify_keywords)
|
||||
)
|
||||
|
||||
def _is_email_recent(
|
||||
self,
|
||||
content: str,
|
||||
after_timestamp: float,
|
||||
allow_old_seconds: int = 15,
|
||||
) -> bool:
|
||||
"""
|
||||
检查 read.php 返回的邮件时间是否晚于验证码发送时间。
|
||||
注意:read.php 的时间解析不可靠,此处仅作参考判断。
|
||||
"""
|
||||
from email.utils import parsedate_to_datetime
|
||||
|
||||
# 先清理 HTML 标签,再提取时间
|
||||
clean = re.sub(r'<[^>]+>', ' ', content)
|
||||
clean = re.sub(r'\s+', ' ', clean)
|
||||
|
||||
# 匹配 RFC2822 格式时间
|
||||
time_match = re.search(
|
||||
r'发信时间[::]?\s*'
|
||||
r'((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s*\d{1,2}\s+'
|
||||
r'(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+'
|
||||
r'\d{4}\s+\d{2}:\d{2}:\d{2}\s*[+-]\d{4})',
|
||||
clean,
|
||||
)
|
||||
if not time_match:
|
||||
# 尝试匹配不带星期的时间格式
|
||||
time_match2 = re.search(
|
||||
r'发信时间[::]?\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})',
|
||||
clean,
|
||||
)
|
||||
if time_match2:
|
||||
time_str = time_match2.group(1).strip()
|
||||
try:
|
||||
from datetime import datetime as dt
|
||||
msg_time = dt.strptime(time_str, '%Y-%m-%d %H:%M:%S')
|
||||
if msg_time.timestamp() < after_timestamp - allow_old_seconds:
|
||||
logger.debug(f"邮件时间 {time_str} 早于发送时间,跳过旧邮件")
|
||||
return False
|
||||
return True
|
||||
except ValueError:
|
||||
pass
|
||||
# read.php 时间不可靠时,不再保守拒绝,改为放行
|
||||
# 让验证码提取逻辑自行判断
|
||||
logger.debug("read.php: 未找到可解析的发信时间,跳过时间检查直接提取验证码")
|
||||
return True
|
||||
|
||||
time_str = time_match.group(1).strip()
|
||||
try:
|
||||
msg_time = parsedate_to_datetime(time_str)
|
||||
if msg_time and msg_time.timestamp() < after_timestamp - allow_old_seconds:
|
||||
logger.debug(f"邮件时间 {time_str} 早于发送时间,跳过旧邮件")
|
||||
return False
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
logger.debug(f"无法解析邮件时间: {time_str},跳过时间检查")
|
||||
return True
|
||||
|
||||
def _extract_verification_code(self, text: str) -> Optional[str]:
|
||||
"""从文本中提取6位验证码"""
|
||||
# 清理HTML标签和实体
|
||||
|
||||
Reference in New Issue
Block a user