Adapt GUI for cloudflare temp mail
This commit is contained in:
208
gui.py
208
gui.py
@@ -1,3 +1,4 @@
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
|
||||
@@ -6,6 +7,7 @@ from PySide6.QtGui import QFont
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QFormLayout,
|
||||
QFrame,
|
||||
QGroupBox,
|
||||
@@ -26,10 +28,18 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
from duckmail_client import DuckMailClient
|
||||
from account_store import load_accounts, save_account, load_config, save_config
|
||||
from account_store import (
|
||||
ensure_database_and_table,
|
||||
load_accounts,
|
||||
save_account,
|
||||
load_config,
|
||||
save_config,
|
||||
test_connection,
|
||||
)
|
||||
|
||||
|
||||
CONFIG_PATH = "config_gui.json"
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
CONFIG_PATH = BASE_DIR / "config_gui.json"
|
||||
LEGACY_CONFIG_PATH = BASE_DIR.parent / "config_gui.json"
|
||||
|
||||
|
||||
def _format_sender(sender_value):
|
||||
@@ -44,10 +54,15 @@ def _email_text_from_detail(detail):
|
||||
text = detail.get("text") or ""
|
||||
if text:
|
||||
return str(text)
|
||||
message = detail.get("message") or ""
|
||||
if message:
|
||||
return str(message)
|
||||
html_val = detail.get("html")
|
||||
if isinstance(html_val, list):
|
||||
return "\n".join(str(item) for item in html_val)
|
||||
return str(html_val or "")
|
||||
if html_val:
|
||||
return str(html_val)
|
||||
return str(detail.get("raw") or "")
|
||||
|
||||
|
||||
def _generate_password(length=14):
|
||||
@@ -132,13 +147,14 @@ def _random_birthdate():
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("DuckMail GUI")
|
||||
self.setWindowTitle("Temp Mail GUI")
|
||||
self.resize(1280, 780)
|
||||
|
||||
self.current_mail_token = ""
|
||||
self.current_emails = []
|
||||
self.all_accounts = []
|
||||
|
||||
self._migrate_legacy_config_if_needed()
|
||||
self._build_ui()
|
||||
self._setup_timer()
|
||||
self._load_config()
|
||||
@@ -166,6 +182,24 @@ class MainWindow(QMainWindow):
|
||||
self.import_btn.clicked.connect(self._on_import_account)
|
||||
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.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)
|
||||
|
||||
def _migrate_legacy_config_if_needed(self):
|
||||
if CONFIG_PATH.exists():
|
||||
return
|
||||
if not LEGACY_CONFIG_PATH.exists():
|
||||
return
|
||||
try:
|
||||
CONFIG_PATH.write_text(
|
||||
LEGACY_CONFIG_PATH.read_text(encoding="utf-8"),
|
||||
encoding="utf-8",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _build_accounts_page(self):
|
||||
page = QWidget()
|
||||
@@ -238,17 +272,22 @@ class MainWindow(QMainWindow):
|
||||
page = QWidget()
|
||||
layout = QVBoxLayout(page)
|
||||
|
||||
config_group = QGroupBox("DuckMail 配置")
|
||||
config_group = QGroupBox("Cloudflare Temp Mail 配置")
|
||||
config_layout = QFormLayout(config_group)
|
||||
self.api_base_input = QLineEdit()
|
||||
self.domain_input = QLineEdit()
|
||||
self.backend_type_input = QComboBox()
|
||||
self.backend_type_input.addItems(["cloudflare_temp_email"])
|
||||
self.bearer_input = QLineEdit()
|
||||
self.bearer_input.setEchoMode(QLineEdit.Password)
|
||||
self.proxy_input = QLineEdit()
|
||||
self.show_passwords_check = QCheckBox("显示密码")
|
||||
config_layout.addRow("API Base", self.api_base_input)
|
||||
config_layout.addRow("Domain", self.domain_input)
|
||||
config_layout.addRow("Bearer", self.bearer_input)
|
||||
config_layout.addRow("Backend Type", self.backend_type_input)
|
||||
config_layout.addRow("Admin Password", self.bearer_input)
|
||||
config_layout.addRow("Proxy", self.proxy_input)
|
||||
config_layout.addRow("密码显示", self.show_passwords_check)
|
||||
|
||||
auto_layout = QHBoxLayout()
|
||||
self.auto_refresh_check = QCheckBox("自动刷新")
|
||||
@@ -262,23 +301,36 @@ class MainWindow(QMainWindow):
|
||||
auto_layout.addStretch(1)
|
||||
config_layout.addRow("刷新", auto_layout)
|
||||
|
||||
pg_group = QGroupBox("PostgreSQL 配置")
|
||||
pg_group = QGroupBox("数据库配置")
|
||||
pg_layout = QFormLayout(pg_group)
|
||||
self.pg_enabled_check = QCheckBox("启用 PostgreSQL")
|
||||
self.pg_enabled_check = QCheckBox("启用数据库")
|
||||
self.db_type_input = QComboBox()
|
||||
self.db_type_input.addItems(["postgresql", "mysql"])
|
||||
self.pg_host_input = QLineEdit()
|
||||
self.pg_port_input = QSpinBox()
|
||||
self.pg_port_input.setRange(1, 65535)
|
||||
self.pg_port_input.setValue(5432)
|
||||
self.pg_db_input = QLineEdit()
|
||||
self.db_table_input = QLineEdit()
|
||||
self.pg_user_input = QLineEdit()
|
||||
self.pg_password_input = QLineEdit()
|
||||
self.pg_password_input.setEchoMode(QLineEdit.Password)
|
||||
self.db_auto_create_check = QCheckBox("数据库或表不存在时允许创建")
|
||||
self.test_db_btn = QPushButton("测试连接")
|
||||
self.init_db_btn = QPushButton("初始化库表")
|
||||
db_actions = QHBoxLayout()
|
||||
db_actions.addWidget(self.test_db_btn)
|
||||
db_actions.addWidget(self.init_db_btn)
|
||||
pg_layout.addRow("开关", self.pg_enabled_check)
|
||||
pg_layout.addRow("类型", self.db_type_input)
|
||||
pg_layout.addRow("Host", self.pg_host_input)
|
||||
pg_layout.addRow("Port", self.pg_port_input)
|
||||
pg_layout.addRow("DB", self.pg_db_input)
|
||||
pg_layout.addRow("Table", self.db_table_input)
|
||||
pg_layout.addRow("User", self.pg_user_input)
|
||||
pg_layout.addRow("Password", self.pg_password_input)
|
||||
pg_layout.addRow("自动创建", self.db_auto_create_check)
|
||||
pg_layout.addRow("操作", db_actions)
|
||||
|
||||
import_group = QGroupBox("账号写入数据库")
|
||||
import_layout = QFormLayout(import_group)
|
||||
@@ -288,14 +340,14 @@ class MainWindow(QMainWindow):
|
||||
self.import_chatgpt_password_input = QLineEdit()
|
||||
self.import_name_input = QLineEdit()
|
||||
self.import_birthdate_input = QLineEdit()
|
||||
self.import_btn = QPushButton("导入并获取 Token")
|
||||
self.import_btn = QPushButton("导入账号并补 Token")
|
||||
self.create_email_btn = QPushButton("创建邮箱并写库")
|
||||
import_actions = QHBoxLayout()
|
||||
import_actions.addWidget(self.import_btn)
|
||||
import_actions.addWidget(self.create_email_btn)
|
||||
self.save_config_btn = QPushButton("保存设置")
|
||||
import_layout.addRow("邮箱", self.import_email_input)
|
||||
import_layout.addRow("邮箱密码", self.import_mail_password_input)
|
||||
import_layout.addRow("邮箱密码(可选)", self.import_mail_password_input)
|
||||
import_layout.addRow("ChatGPT密码(可选)", self.import_chatgpt_password_input)
|
||||
import_layout.addRow("姓名(可选)", self.import_name_input)
|
||||
import_layout.addRow("生日(可选)", self.import_birthdate_input)
|
||||
@@ -321,10 +373,21 @@ class MainWindow(QMainWindow):
|
||||
return {
|
||||
"api_base": self.api_base_input.text().strip(),
|
||||
"domain": self.domain_input.text().strip(),
|
||||
"backend_type": self.backend_type_input.currentText(),
|
||||
"bearer": self.bearer_input.text().strip(),
|
||||
"proxy": self.proxy_input.text().strip(),
|
||||
"show_passwords": self.show_passwords_check.isChecked(),
|
||||
"auto_refresh": self.auto_refresh_check.isChecked(),
|
||||
"refresh_interval": self.refresh_interval.value(),
|
||||
"db_enabled": self.pg_enabled_check.isChecked(),
|
||||
"db_type": self.db_type_input.currentText(),
|
||||
"db_host": self.pg_host_input.text().strip(),
|
||||
"db_port": self.pg_port_input.value(),
|
||||
"db_name": self.pg_db_input.text().strip(),
|
||||
"db_table": self.db_table_input.text().strip(),
|
||||
"db_user": self.pg_user_input.text().strip(),
|
||||
"db_password": self.pg_password_input.text().strip(),
|
||||
"db_auto_create": self.db_auto_create_check.isChecked(),
|
||||
"pg_enabled": self.pg_enabled_check.isChecked(),
|
||||
"pg_host": self.pg_host_input.text().strip(),
|
||||
"pg_port": self.pg_port_input.value(),
|
||||
@@ -340,28 +403,102 @@ class MainWindow(QMainWindow):
|
||||
config.get("domain"),
|
||||
config.get("bearer"),
|
||||
proxy=config.get("proxy"),
|
||||
backend_type=config.get("backend_type"),
|
||||
)
|
||||
|
||||
def _load_config(self):
|
||||
data = load_config(CONFIG_PATH)
|
||||
self.api_base_input.setText(data.get("api_base", ""))
|
||||
self.domain_input.setText(data.get("domain", ""))
|
||||
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_index = self.backend_type_input.findText(backend_type)
|
||||
if backend_index >= 0:
|
||||
self.backend_type_input.setCurrentIndex(backend_index)
|
||||
self.bearer_input.setText(data.get("bearer", ""))
|
||||
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("pg_enabled", True)))
|
||||
self.pg_host_input.setText(data.get("pg_host", ""))
|
||||
self.pg_port_input.setValue(int(data.get("pg_port", 5432) or 5432))
|
||||
self.pg_db_input.setText(data.get("pg_db", "mail_accounts_db"))
|
||||
self.pg_user_input.setText(data.get("pg_user", ""))
|
||||
self.pg_password_input.setText(data.get("pg_password", ""))
|
||||
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.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.db_auto_create_check.setChecked(bool(data.get("db_auto_create", False)))
|
||||
self._on_toggle_password_visibility(self.show_passwords_check.isChecked())
|
||||
|
||||
def _save_config(self):
|
||||
data = self._get_config()
|
||||
save_config(CONFIG_PATH, data)
|
||||
return data
|
||||
|
||||
def _mask_secret(self, value, placeholder="(空)"):
|
||||
text = str(value or "")
|
||||
if not text:
|
||||
return placeholder
|
||||
if self.show_passwords_check.isChecked():
|
||||
return text
|
||||
if len(text) <= 4:
|
||||
return "*" * len(text)
|
||||
return f"{text[:2]}{'*' * max(len(text) - 4, 2)}{text[-2:]}"
|
||||
|
||||
def _on_toggle_password_visibility(self, checked):
|
||||
mode = QLineEdit.Normal if checked else QLineEdit.Password
|
||||
self.bearer_input.setEchoMode(mode)
|
||||
self.pg_password_input.setEchoMode(mode)
|
||||
self.import_mail_password_input.setEchoMode(mode)
|
||||
self._render_account_detail(self._get_selected_account())
|
||||
|
||||
def _on_backend_type_changed(self, backend_type):
|
||||
self._save_config()
|
||||
|
||||
def _on_db_type_changed(self, db_type):
|
||||
db_type = (db_type or "").strip().lower()
|
||||
default_port = 3306 if db_type == "mysql" else 5432
|
||||
current_port = self.pg_port_input.value()
|
||||
if current_port in (3306, 5432):
|
||||
self.pg_port_input.setValue(default_port)
|
||||
|
||||
def _db_config_for_action(self):
|
||||
config = self._save_config()
|
||||
if not config.get("db_enabled"):
|
||||
raise RuntimeError("请先启用数据库")
|
||||
return config
|
||||
|
||||
def _on_test_db_connection(self):
|
||||
try:
|
||||
config = self._db_config_for_action()
|
||||
result = test_connection(config)
|
||||
self._log(
|
||||
f"数据库连接成功: type={result['db_type']} db_exists={result['database_exists']} table_exists={result['table_exists']} admin_access={result['admin_access']}"
|
||||
)
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"数据库连接成功",
|
||||
f"类型: {result['db_type']}\n数据库存在: {result['database_exists']}\n表存在: {result['table_exists']}\n管理权限: {result['admin_access']}",
|
||||
)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "数据库连接失败", str(e))
|
||||
self._log(f"数据库连接失败: {e}")
|
||||
|
||||
def _on_init_db(self):
|
||||
try:
|
||||
config = self._db_config_for_action()
|
||||
result = ensure_database_and_table(config)
|
||||
self._log(
|
||||
f"数据库初始化完成: type={result['db_type']} db_exists={result['database_exists']} table_exists={result['table_exists']} admin_access={result['admin_access']}"
|
||||
)
|
||||
QMessageBox.information(self, "初始化成功", "数据库和表已经就绪")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "初始化失败", str(e))
|
||||
self._log(f"初始化数据库失败: {e}")
|
||||
|
||||
def _account_matches_search(self, account, keyword):
|
||||
if not keyword:
|
||||
return True
|
||||
@@ -393,19 +530,19 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def _load_accounts(self):
|
||||
config = self._save_config()
|
||||
if not config.get("pg_enabled"):
|
||||
if not config.get("db_enabled", config.get("pg_enabled")):
|
||||
self.all_accounts = []
|
||||
self._apply_account_filter()
|
||||
self._log("PostgreSQL 未启用")
|
||||
self._log("数据库未启用")
|
||||
return
|
||||
try:
|
||||
self.all_accounts = load_accounts(config)
|
||||
self._apply_account_filter()
|
||||
self._log(f"已加载 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()
|
||||
self._log(f"加载 PostgreSQL 账号失败: {e}")
|
||||
self._log(f"加载数据库账号失败: {e}")
|
||||
|
||||
def _on_reload_accounts(self):
|
||||
self._load_accounts()
|
||||
@@ -453,8 +590,8 @@ class MainWindow(QMainWindow):
|
||||
chatgpt_password = self.import_chatgpt_password_input.text().strip()
|
||||
name = self.import_name_input.text().strip()
|
||||
birthdate = self.import_birthdate_input.text().strip()
|
||||
if not email or not mail_password:
|
||||
QMessageBox.information(self, "提示", "请输入邮箱和邮箱密码")
|
||||
if not email:
|
||||
QMessageBox.information(self, "提示", "请输入邮箱")
|
||||
return
|
||||
try:
|
||||
config = self._save_config()
|
||||
@@ -498,9 +635,9 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
token_status = "已存在" if account.get("mail_token", "") else "缺失"
|
||||
self.detail_email.setText(account.get("email", ""))
|
||||
self.detail_mail_password.setText(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(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"))
|
||||
@@ -531,10 +668,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
email = str(account.get("email", "")).strip()
|
||||
mail_password = str(account.get("mail_password", "")).strip()
|
||||
if not mail_password:
|
||||
raise RuntimeError("当前账号缺少 mail_token,且没有 mail_password 可用于换取 token")
|
||||
|
||||
self._log(f"账号 {email} 缺少 mail_token,正在自动获取")
|
||||
self._log(f"账号 {email} 缺少 mail_token,正在通过管理员接口补全")
|
||||
client = self._client_from_inputs()
|
||||
mail_token = client.get_token(email, mail_password)
|
||||
if not mail_token:
|
||||
@@ -563,8 +697,8 @@ class MainWindow(QMainWindow):
|
||||
def _render_emails_list(self, emails):
|
||||
self.emails_list.clear()
|
||||
for item in emails:
|
||||
subject = str(item.get("subject", ""))
|
||||
sender = _format_sender(item.get("from"))
|
||||
subject = str(item.get("subject", "") or "(无主题)")
|
||||
sender = _format_sender(item.get("from") or item.get("source"))
|
||||
date = str(item.get("createdAt", ""))
|
||||
label = f"{subject} | {sender} | {date}"
|
||||
list_item = QListWidgetItem(label)
|
||||
@@ -577,7 +711,13 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
email_item = items[0].data(Qt.UserRole)
|
||||
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("html") or email_item.get("intro")
|
||||
body = (
|
||||
email_item.get("text")
|
||||
or email_item.get("message")
|
||||
or email_item.get("html")
|
||||
or email_item.get("intro")
|
||||
or email_item.get("raw")
|
||||
)
|
||||
if not body and msg_id and self.current_mail_token:
|
||||
try:
|
||||
client = self._client_from_inputs()
|
||||
|
||||
Reference in New Issue
Block a user