| 229 | return email_info |
| 230 | |
| 231 | def get_verification_code( |
| 232 | self, |
| 233 | email: str, |
| 234 | email_id: str = None, |
| 235 | timeout: int = 120, |
| 236 | pattern: str = OTP_CODE_PATTERN, |
| 237 | otp_sent_at: Optional[float] = None, |
| 238 | ) -> Optional[str]: |
| 239 | account_info = self._get_account_info(email=email, email_id=email_id) |
| 240 | if not account_info: |
| 241 | logger.warning(f"DuckMail 未找到邮箱缓存: {email}, {email_id}") |
| 242 | return None |
| 243 | |
| 244 | token = account_info.get("token") |
| 245 | if not token: |
| 246 | logger.warning(f"DuckMail 邮箱缺少访问 token: {email}") |
| 247 | return None |
| 248 | |
| 249 | start_time = time.time() |
| 250 | seen_message_ids = set() |
| 251 | |
| 252 | while time.time() - start_time < timeout: |
| 253 | try: |
| 254 | response = self._make_request( |
| 255 | "GET", |
| 256 | "/messages", |
| 257 | token=token, |
| 258 | params={"page": 1}, |
| 259 | ) |
| 260 | messages = response.get("hydra:member", []) |
| 261 | |
| 262 | for message in messages: |
| 263 | message_id = str(message.get("id") or "").strip() |
| 264 | if not message_id or message_id in seen_message_ids: |
| 265 | continue |
| 266 | |
| 267 | created_at = self._parse_message_time(message.get("createdAt")) |
| 268 | if otp_sent_at and created_at and created_at + 1 < otp_sent_at: |
| 269 | continue |
| 270 | |
| 271 | seen_message_ids.add(message_id) |
| 272 | detail = self._make_request( |
| 273 | "GET", |
| 274 | f"/messages/{message_id}", |
| 275 | token=token, |
| 276 | ) |
| 277 | |
| 278 | content = self._message_search_text(message, detail) |
| 279 | if "openai" not in content.lower(): |
| 280 | continue |
| 281 | |
| 282 | match = re.search(pattern, content) |
| 283 | if match: |
| 284 | self.update_status(True) |
| 285 | return match.group(1) |
| 286 | except Exception as e: |
| 287 | logger.debug(f"DuckMail 轮询验证码失败: {e}") |
| 288 | |