This commit is contained in:
GitHub Actions
2026-03-25 06:44:13 +08:00
parent 8678b966d9
commit 5c054228ef
2 changed files with 460 additions and 22 deletions

184
gui.py
View File

@@ -1,9 +1,11 @@
from html import unescape
from pathlib import Path
import re
import sys
import time
from PySide6.QtCore import QTimer, Qt
from PySide6.QtGui import QFont
from PySide6.QtGui import QFont, QTextDocumentFragment
from PySide6.QtWidgets import (
QApplication,
QCheckBox,
@@ -65,6 +67,55 @@ def _email_text_from_detail(detail):
return str(detail.get("raw") or "")
def _looks_like_html(value):
text = str(value or "").strip()
if not text:
return False
if "<html" in text.lower() or "<body" in text.lower():
return True
return bool(re.search(r"<\s*[a-zA-Z][^>]*>", text))
def _normalize_email_body(body):
if isinstance(body, list):
return "\n\n".join(str(item) for item in body if item is not None)
if body is None:
return ""
return str(body)
def _html_to_plain_text(html_text):
text = QTextDocumentFragment.fromHtml(html_text).toPlainText().strip()
if text:
return text
return unescape(re.sub(r"<[^>]+>", " ", html_text)).strip()
def _extract_verification_code(text):
if not text:
return ""
match = re.search(r"(?<!\d)(\d{6})(?!\d)", text)
if match:
return match.group(1)
return ""
def _highlight_verification_code_html(html_text, code):
if not html_text or not code:
return html_text
pattern = re.compile(rf"(?<!\d){re.escape(code)}(?!\d)")
return pattern.sub(
(
'<span style="background:#fff1bf;color:#8a3b12;'
"font-weight:700;padding:2px 6px;border-radius:6px;"
'letter-spacing:1px;">'
f"{code}</span>"
),
html_text,
count=1,
)
def _generate_password(length=14):
import random
import string
@@ -183,10 +234,13 @@ class MainWindow(QMainWindow):
self.reload_accounts_btn.clicked.connect(self._on_reload_accounts)
self.search_input.textChanged.connect(self._apply_account_filter)
self.show_passwords_check.toggled.connect(self._on_toggle_password_visibility)
self.backend_type_input.currentTextChanged.connect(self._on_backend_type_changed)
self.backend_type_input.currentTextChanged.connect(
self._on_backend_type_changed
)
self.db_type_input.currentTextChanged.connect(self._on_db_type_changed)
self.test_db_btn.clicked.connect(self._on_test_db_connection)
self.init_db_btn.clicked.connect(self._on_init_db)
self.copy_verification_code_btn.clicked.connect(self._copy_verification_code)
def _migrate_legacy_config_if_needed(self):
if CONFIG_PATH.exists():
@@ -227,13 +281,13 @@ class MainWindow(QMainWindow):
details_group = QGroupBox("账号详情")
details_layout = QFormLayout(details_group)
self.detail_email = QLabel("-")
self.detail_mail_password = QLabel("-")
self.detail_chatgpt_password = QLabel("-")
self.detail_name = QLabel("-")
self.detail_birthdate = QLabel("-")
self.detail_mail_token = QLabel("-")
self.detail_source = QLabel("postgres")
self.detail_email = self._create_copyable_label("-")
self.detail_mail_password = self._create_copyable_label("-")
self.detail_chatgpt_password = self._create_copyable_label("-")
self.detail_name = self._create_copyable_label("-")
self.detail_birthdate = self._create_copyable_label("-")
self.detail_mail_token = self._create_copyable_label("-")
self.detail_source = self._create_copyable_label("postgres")
details_layout.addRow("邮箱", self.detail_email)
details_layout.addRow("邮箱密码", self.detail_mail_password)
details_layout.addRow("Mail Token", self.detail_mail_token)
@@ -248,6 +302,18 @@ class MainWindow(QMainWindow):
right_layout.addWidget(QLabel("邮件列表"))
self.emails_list = QListWidget()
right_layout.addWidget(self.emails_list)
code_layout = QHBoxLayout()
self.verification_code_label = QLabel("验证码")
self.verification_code_value = QLineEdit()
self.verification_code_value.setReadOnly(True)
self.verification_code_value.setPlaceholderText("自动提取 6 位验证码")
self.verification_code_value.setFocusPolicy(Qt.StrongFocus)
self.copy_verification_code_btn = QPushButton("复制")
self.copy_verification_code_btn.setEnabled(False)
code_layout.addWidget(self.verification_code_label)
code_layout.addWidget(self.verification_code_value)
code_layout.addWidget(self.copy_verification_code_btn)
right_layout.addLayout(code_layout)
right_layout.addWidget(QLabel("邮件内容"))
self.email_content = QTextEdit()
self.email_content.setReadOnly(True)
@@ -360,11 +426,27 @@ class MainWindow(QMainWindow):
layout.addStretch(1)
return page
def _create_copyable_label(self, text=""):
label = QLabel(text)
label.setTextInteractionFlags(Qt.TextSelectableByMouse)
label.setCursor(Qt.IBeamCursor)
label.setWordWrap(True)
return label
def _setup_timer(self):
self.timer = QTimer(self)
self.timer.setInterval(self.refresh_interval.value() * 1000)
self.timer.timeout.connect(self._auto_refresh)
def _copy_verification_code(self):
code = self.verification_code_value.text().strip()
if not code:
return
QApplication.clipboard().setText(code)
self.verification_code_value.setFocus()
self.verification_code_value.selectAll()
self._log(f"验证码已复制: {code}")
def _log(self, message):
ts = time.strftime("%H:%M:%S")
self.log_box.append(f"[{ts}] {message}")
@@ -408,9 +490,14 @@ class MainWindow(QMainWindow):
def _load_config(self):
data = load_config(CONFIG_PATH)
self.api_base_input.setText(data.get("api_base", "https://temp-email-api.example.com"))
self.api_base_input.setText(
data.get("api_base", "https://temp-email-api.example.com")
)
self.domain_input.setText(data.get("domain", "example.com"))
backend_type = str(data.get("backend_type", "cloudflare_temp_email")).strip().lower() or "cloudflare_temp_email"
backend_type = (
str(data.get("backend_type", "cloudflare_temp_email")).strip().lower()
or "cloudflare_temp_email"
)
backend_index = self.backend_type_input.findText(backend_type)
if backend_index >= 0:
self.backend_type_input.setCurrentIndex(backend_index)
@@ -418,18 +505,33 @@ class MainWindow(QMainWindow):
self.proxy_input.setText(data.get("proxy", ""))
self.show_passwords_check.setChecked(bool(data.get("show_passwords", False)))
self.auto_refresh_check.setChecked(bool(data.get("auto_refresh", False)))
self.refresh_interval.setValue(max(10, min(300, int(data.get("refresh_interval", 30) or 30))))
self.pg_enabled_check.setChecked(bool(data.get("db_enabled", data.get("pg_enabled", True))))
self.refresh_interval.setValue(
max(10, min(300, int(data.get("refresh_interval", 30) or 30)))
)
self.pg_enabled_check.setChecked(
bool(data.get("db_enabled", data.get("pg_enabled", True)))
)
db_type = str(data.get("db_type", "postgresql")).strip().lower() or "postgresql"
index = self.db_type_input.findText(db_type)
if index >= 0:
self.db_type_input.setCurrentIndex(index)
self.pg_host_input.setText(data.get("db_host", data.get("pg_host", "")))
self.pg_port_input.setValue(int(data.get("db_port", data.get("pg_port", 5432 if db_type != "mysql" else 3306)) or (3306 if db_type == "mysql" else 5432)))
self.pg_db_input.setText(data.get("db_name", data.get("pg_db", "mail_accounts_db")))
self.pg_port_input.setValue(
int(
data.get(
"db_port", data.get("pg_port", 5432 if db_type != "mysql" else 3306)
)
or (3306 if db_type == "mysql" else 5432)
)
)
self.pg_db_input.setText(
data.get("db_name", data.get("pg_db", "mail_accounts_db"))
)
self.db_table_input.setText(data.get("db_table", "registered_accounts"))
self.pg_user_input.setText(data.get("db_user", data.get("pg_user", "")))
self.pg_password_input.setText(data.get("db_password", data.get("pg_password", "")))
self.pg_password_input.setText(
data.get("db_password", data.get("pg_password", ""))
)
self.db_auto_create_check.setChecked(bool(data.get("db_auto_create", False)))
self._on_toggle_password_visibility(self.show_passwords_check.isChecked())
@@ -522,7 +624,11 @@ class MainWindow(QMainWindow):
def _apply_account_filter(self):
keyword = self.search_input.text().strip().lower()
filtered = [item for item in self.all_accounts if self._account_matches_search(item, keyword)]
filtered = [
item
for item in self.all_accounts
if self._account_matches_search(item, keyword)
]
self._render_accounts_list(filtered)
if not filtered:
self.current_mail_token = ""
@@ -538,7 +644,9 @@ class MainWindow(QMainWindow):
try:
self.all_accounts = load_accounts(config)
self._apply_account_filter()
self._log(f"已加载 {config.get('db_type', 'postgresql')} 账号: {len(self.all_accounts)}")
self._log(
f"已加载 {config.get('db_type', 'postgresql')} 账号: {len(self.all_accounts)}"
)
except Exception as e:
self.all_accounts = []
self._apply_account_filter()
@@ -635,9 +743,13 @@ class MainWindow(QMainWindow):
return
token_status = "已存在" if account.get("mail_token", "") else "缺失"
self.detail_email.setText(account.get("email", ""))
self.detail_mail_password.setText(self._mask_secret(account.get("mail_password", ""), "(管理员模式下可留空)"))
self.detail_mail_password.setText(
self._mask_secret(account.get("mail_password", ""), "(管理员模式下可留空)")
)
self.detail_mail_token.setText(token_status)
self.detail_chatgpt_password.setText(self._mask_secret(account.get("chatgpt_password", "")))
self.detail_chatgpt_password.setText(
self._mask_secret(account.get("chatgpt_password", ""))
)
self.detail_name.setText(account.get("name", ""))
self.detail_birthdate.setText(account.get("birthdate", ""))
self.detail_source.setText(account.get("source", "postgres"))
@@ -710,7 +822,9 @@ class MainWindow(QMainWindow):
if not items:
return
email_item = items[0].data(Qt.UserRole)
msg_id = email_item.get("id") or email_item.get("@id") or email_item.get("messageId")
msg_id = (
email_item.get("id") or email_item.get("@id") or email_item.get("messageId")
)
body = (
email_item.get("text")
or email_item.get("message")
@@ -725,7 +839,33 @@ class MainWindow(QMainWindow):
body = _email_text_from_detail(detail)
except Exception as e:
body = f"(获取邮件详情失败: {e})"
self.email_content.setPlainText(str(body or "(无正文)"))
content = _normalize_email_body(body)
if not content:
self.verification_code_value.clear()
self.copy_verification_code_btn.setEnabled(False)
self.verification_code_label.setText("验证码")
self.email_content.setToolTip("")
self.email_content.setPlainText("(无正文)")
return
plain_text = (
_html_to_plain_text(content) if _looks_like_html(content) else content
)
verification_code = _extract_verification_code(plain_text)
self.verification_code_value.setText(verification_code)
self.copy_verification_code_btn.setEnabled(bool(verification_code))
self.verification_code_label.setText(
"验证码 (已提取)" if verification_code else "验证码"
)
display_text = plain_text
if verification_code:
display_text = re.sub(
rf"(?<!\d)({re.escape(verification_code)})(?!\d)",
r"[验证码: \1]",
plain_text,
count=1,
)
self.email_content.setToolTip("")
self.email_content.setPlainText(display_text)
def _on_auto_refresh_toggle(self, checked):
if checked: