203 lines
7.0 KiB
Python
203 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""邮箱IMAP读取测试脚本"""
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
# 添加项目根目录到path
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
from douyu.config import Config
|
|
from douyu.email_verifier import EmailVerifier
|
|
from utils.logger import setup_logger
|
|
|
|
|
|
def mask_email(email_address: str) -> str:
|
|
"""隐藏邮箱中间部分,避免控制台泄露完整账号。"""
|
|
if "@" not in email_address:
|
|
return email_address
|
|
|
|
name, domain = email_address.split("@", 1)
|
|
if len(name) <= 2:
|
|
masked_name = name[0] + "*"
|
|
else:
|
|
masked_name = name[:2] + "*" * max(2, len(name) - 4) + name[-2:]
|
|
|
|
return f"{masked_name}@{domain}"
|
|
|
|
|
|
def clean_preview(text: str, max_length: int = 120) -> str:
|
|
"""清理正文预览,保持输出紧凑。"""
|
|
preview = " ".join(text.split())
|
|
preview = re.sub(r"\b\d{6}\b", "******", preview)
|
|
if len(preview) <= max_length:
|
|
return preview
|
|
return preview[:max_length] + "..."
|
|
|
|
|
|
def format_code(code: str, show_code: bool) -> str:
|
|
"""格式化验证码输出,默认隐藏大部分数字。"""
|
|
if not code:
|
|
return "未提取到"
|
|
if show_code:
|
|
return code
|
|
return f"****{code[-2:]}"
|
|
|
|
|
|
def format_message_time(verifier: EmailVerifier, msg) -> str:
|
|
"""格式化邮件时间。"""
|
|
message_time = verifier._parse_message_time(msg)
|
|
if not message_time:
|
|
return "未知"
|
|
return message_time.strftime("%Y-%m-%d %H:%M:%S %z")
|
|
|
|
|
|
def load_account(config_path: str, index: int):
|
|
"""从配置文件加载指定账号。"""
|
|
config = Config(config_path)
|
|
accounts = config.get_accounts()
|
|
if not accounts:
|
|
raise ValueError("config.yaml 中没有配置账号")
|
|
|
|
if index < 0 or index >= len(accounts):
|
|
raise IndexError(f"账号索引超出范围: {index},当前共有 {len(accounts)} 个账号")
|
|
|
|
return accounts[index]
|
|
|
|
|
|
def list_recent_messages(
|
|
verifier: EmailVerifier,
|
|
lookback_minutes: int,
|
|
limit: int,
|
|
show_code: bool,
|
|
) -> None:
|
|
"""列出最近邮件,并测试斗鱼验证码解析。"""
|
|
verifier.connect()
|
|
|
|
try:
|
|
status, _ = verifier._connection.select(verifier.mailbox, readonly=True)
|
|
if status != "OK":
|
|
raise RuntimeError(f"选择邮箱目录失败: {verifier.mailbox}")
|
|
|
|
since_dt = datetime.now() - timedelta(minutes=lookback_minutes)
|
|
since = verifier._format_imap_date(since_dt)
|
|
status, messages = verifier._connection.search(None, "SINCE", since)
|
|
if status != "OK":
|
|
raise RuntimeError(f"搜索邮件失败: status={status}")
|
|
|
|
message_ids = messages[0].split() if messages and messages[0] else []
|
|
if not message_ids:
|
|
print(f"最近 {lookback_minutes} 分钟没有邮件")
|
|
return
|
|
|
|
recent_ids = list(reversed(message_ids[-limit:]))
|
|
print(f"搜索范围: 最近 {lookback_minutes} 分钟")
|
|
print(f"匹配邮件数: {len(message_ids)},展示最新 {len(recent_ids)} 封\n")
|
|
|
|
for index, message_id in enumerate(recent_ids, start=1):
|
|
msg = verifier._fetch_message(message_id)
|
|
if not msg:
|
|
print(f"[{index}] 读取失败: id={message_id.decode(errors='ignore')}")
|
|
continue
|
|
|
|
subject = verifier._decode_subject(msg.get("Subject", ""))
|
|
from_addr = msg.get("From", "")
|
|
body = verifier._get_email_body(msg)
|
|
is_douyu = verifier._is_douyu_email(subject, from_addr, body)
|
|
code = verifier._extract_verification_code(body) if is_douyu else ""
|
|
|
|
print(f"[{index}] 邮件ID: {message_id.decode(errors='ignore')}")
|
|
print(f" 时间: {format_message_time(verifier, msg)}")
|
|
print(f" 发件人: {from_addr}")
|
|
print(f" 主题: {subject}")
|
|
print(f" 斗鱼邮件: {'是' if is_douyu else '否'}")
|
|
print(f" 验证码: {format_code(code, show_code)}")
|
|
print(f" 正文预览: {clean_preview(body)}\n")
|
|
finally:
|
|
verifier.disconnect()
|
|
|
|
|
|
def wait_verification_code(
|
|
account,
|
|
max_wait: int,
|
|
interval: int,
|
|
new_only: bool,
|
|
show_code: bool,
|
|
) -> None:
|
|
"""等待并提取斗鱼验证码,不负责发送验证码邮件。"""
|
|
verifier = EmailVerifier(
|
|
imap_server=account.email_imap_server,
|
|
imap_port=account.email_imap_port,
|
|
username=account.email,
|
|
password=account.email_password,
|
|
)
|
|
|
|
after_timestamp = time.time() if new_only else None
|
|
code = verifier.get_verification_code(
|
|
max_wait=max_wait,
|
|
interval=interval,
|
|
after_timestamp=after_timestamp,
|
|
)
|
|
print(f"等待验证码结果: {format_code(code, show_code)}")
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
"""解析命令行参数。"""
|
|
parser = argparse.ArgumentParser(description="测试邮箱IMAP是否能读取邮件和验证码")
|
|
parser.add_argument("-c", "--config", default="config.yaml", help="配置文件路径")
|
|
parser.add_argument("-i", "--index", type=int, default=0, help="账号索引")
|
|
parser.add_argument("--lookback-minutes", type=int, default=30, help="读取最近多少分钟邮件")
|
|
parser.add_argument("--limit", type=int, default=5, help="展示最近多少封邮件")
|
|
parser.add_argument("--wait-code", action="store_true", help="额外等待并提取斗鱼验证码")
|
|
parser.add_argument("--new-only", action="store_true", help="等待验证码时只接受脚本启动后的新邮件")
|
|
parser.add_argument("--show-code", action="store_true", help="输出完整验证码")
|
|
parser.add_argument("--max-wait", type=int, default=60, help="等待验证码最大秒数")
|
|
parser.add_argument("--interval", type=int, default=2, help="验证码轮询间隔秒数")
|
|
parser.add_argument("--log-level", default="INFO", help="日志级别")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
"""脚本入口。"""
|
|
args = parse_args()
|
|
setup_logger(level=args.log_level)
|
|
|
|
account = load_account(args.config, args.index)
|
|
print("=== 邮箱IMAP读取测试 ===")
|
|
print(f"账号索引: {args.index}")
|
|
print(f"邮箱账号: {mask_email(account.email)}")
|
|
print(f"IMAP服务器: {account.email_imap_server}:{account.email_imap_port}\n")
|
|
|
|
verifier = EmailVerifier(
|
|
imap_server=account.email_imap_server,
|
|
imap_port=account.email_imap_port,
|
|
username=account.email,
|
|
password=account.email_password,
|
|
lookback_minutes=args.lookback_minutes,
|
|
max_messages=args.limit,
|
|
)
|
|
list_recent_messages(
|
|
verifier=verifier,
|
|
lookback_minutes=args.lookback_minutes,
|
|
limit=args.limit,
|
|
show_code=args.show_code,
|
|
)
|
|
|
|
if args.wait_code:
|
|
print("=== 等待斗鱼验证码 ===")
|
|
wait_verification_code(
|
|
account=account,
|
|
max_wait=args.max_wait,
|
|
interval=args.interval,
|
|
new_only=args.new_only,
|
|
show_code=args.show_code,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|