#!/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()