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

View File

@@ -0,0 +1,298 @@
allow-lan: true
mode: rule
bind-address: "0.0.0.0"
mixed-port: 7891
socks-port: 7890
ipv6: true
unified-delay: true
log-level: warning
find-process-mode: strict
tcp-concurrent: true
keep-alive-idle: 600
keep-alive-interval: 15
profile:
store-selected: true
store-fake-ip: true
external-controller: 0.0.0.0:9090
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
NodeParam: &NodeParam
type: http
interval: 86400
health-check:
enable: true
url: https://www.gstatic.com/generate_204
interval: 300
proxy-providers:
airport:
<<: *NodeParam
url: "http://172.30.0.2:25500/sub?target=clash&new_name=true&url=https%3A%2F%2Fdash.pqjc.site%2Fapi%2Fv1%2Fpq%2Fdce38d7c4a39d9b8fb6818a658e2f554"
path: ./providers/airport.yaml
dns:
enable: true
cache-algorithm: arc
listen: 0.0.0.0:1053
ipv6: true
respect-rules: true
use-hosts: true
use-system-hosts: true
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter-mode: blacklist
fake-ip-filter:
- rule-set:fakeipfilter
nameserver:
- https://223.5.5.5/dns-query
- https://doh.pub/dns-query
proxy-server-nameserver:
- https://223.5.5.5/dns-query
- https://doh.pub/dns-query
sniffer:
enable: true
parse-pure-ip: true
sniff:
HTTP:
ports: [80, 8080-8880]
override-destination: true
TLS:
ports: [443, 8443]
QUIC:
ports: [443, 8443]
skip-domain:
- "Mijia Cloud"
- "+.push.apple.com"
FilterUS: &FilterUS '^(?=.*?(?i)(United States|America|\bUS\b|硅谷|家宽|洛杉矶|西雅图|纽约|芝加哥|拉斯维加斯|美帝|美国)).*(?<!(?i)(回国|过期|剩余|官网|网址|节点|订阅|机场|客服|校园|本土)).*$'
FilterJP: &FilterJP '^(?=.*?(?i)(Japan|\bJP\b|东京|大阪|埼玉|川日|泉日|日区|日本)).*(?<!(?i)(回国|过期|剩余|官网|网址|节点|订阅|机场|客服|校园|本土)).*$'
FilterSG: &FilterSG '^(?=.*?(?i)(Singapore|\bSG\b|新加坡|狮城|狮)).*(?<!(?i)(回国|过期|剩余|官网|网址|节点|订阅|机场|客服|校园|本土)).*$'
FilterTW: &FilterTW '^(?=.*?(?i)(Taiwan|\bTW\b|\bTai(?:wan)?\b|台湾|台北|台中|台南|高雄|新竹)).*(?<!(?i)(回国|过期|剩余|官网|网址|节点|订阅|机场|客服|校园|本土)).*$'
FilterHK: &FilterHK '^(?=.*?(?i)(\bHK\b|Hong.?Kong|香港|深港|广港|港区)).*(?<!(?i)(回国|过期|剩余|官网|网址|节点|订阅|机场|客服|校园|本土)).*$'
FilterAll: &FilterAll '^(?!.*?(?i)(GB\s*[\|丨]\s*GB|GB|Traffic|Reset|Days|Left|Expire|Date|Direct|直连|套餐|流量|重置|剩余|到期|时间|频道)).*$'
Select: &Select
type: select
url: https://www.gstatic.com/generate_204
disable-udp: false
hidden: false
include-all: true
UrlTest: &UrlTest
type: url-test
url: https://www.gstatic.com/generate_204
disable-udp: false
hidden: true
include-all: true
interval: 300
lazy: true
tolerance: 50
timeout: 2000
max-failed-times: 3
FallBack: &FallBack
type: fallback
url: https://www.gstatic.com/generate_204
disable-udp: false
hidden: true
include-all: true
interval: 300
lazy: true
timeout: 2000
max-failed-times: 3
pg_FallBack_US: &pg_FallBack_US [US_FALLBACK, JP_FALLBACK, SG_FALLBACK, TW_FALLBACK, HK_FALLBACK]
pg_Proxy: &pg_Proxy [AUTO, ALL_NODES, ROTATE_RR, ROTATE_AUTO, ROTATE_NODES, DIRECT, US_AUTO, JP_AUTO, SG_AUTO, TW_AUTO, HK_AUTO, US_FALLBACK, JP_FALLBACK, SG_FALLBACK, TW_FALLBACK, HK_FALLBACK, US_NODES, JP_NODES, SG_NODES, TW_NODES, HK_NODES]
pg_Direct: &pg_Direct [DIRECT, AUTO, ALL_NODES, ROTATE_RR, ROTATE_AUTO, ROTATE_NODES, US_AUTO, JP_AUTO, SG_AUTO, TW_AUTO, HK_AUTO, US_FALLBACK, JP_FALLBACK, SG_FALLBACK, TW_FALLBACK, HK_FALLBACK, US_NODES, JP_NODES, SG_NODES, TW_NODES, HK_NODES]
proxy-groups:
- {name: AUTO, <<: *UrlTest, filter: *FilterAll}
- {name: ALL_NODES, <<: *Select, filter: *FilterAll}
- {name: ROTATE_NODES, type: select, use: [airport], filter: '^(美国01-0\.1x \| 电信联通移动推荐|美国08-0\.1x \| 电信联通移动推荐|香港01 \| 移动联通推荐|韩国 \| 高速专线-hy2|迪拜 \| 高速专线-hy2|新加坡4 \| 高速专线-hy2|日本东京3 \| 移动联通推荐-hy2)$'}
- {name: ROTATE_AUTO, type: url-test, use: [airport], filter: '^(美国01-0\.1x \| 电信联通移动推荐|美国08-0\.1x \| 电信联通移动推荐|香港01 \| 移动联通推荐|韩国 \| 高速专线-hy2|迪拜 \| 高速专线-hy2|新加坡4 \| 高速专线-hy2|日本东京3 \| 移动联通推荐-hy2)$', url: https://www.gstatic.com/generate_204, interval: 300, tolerance: 50, lazy: true, timeout: 2000, max-failed-times: 3}
- {name: ROTATE_RR, type: load-balance, strategy: round-robin, use: [airport], filter: '^(美国01-0\.1x \| 电信联通移动推荐|美国08-0\.1x \| 电信联通移动推荐|香港01 \| 移动联通推荐|韩国 \| 高速专线-hy2|迪拜 \| 高速专线-hy2|新加坡4 \| 高速专线-hy2|日本东京3 \| 移动联通推荐-hy2)$', url: https://www.gstatic.com/generate_204, interval: 300, lazy: true, timeout: 2000, max-failed-times: 3}
- {name: DEFAULT_PROXY, type: select, proxies: *pg_Proxy}
- {name: AIGC, type: select, proxies: *pg_FallBack_US}
- {name: STEAM, type: select, proxies: *pg_Direct}
- {name: ONEDRIVE, type: select, proxies: *pg_Direct}
- {name: MICROSOFT, type: select, proxies: *pg_Direct}
- {name: GITHUB, type: select, proxies: *pg_Proxy}
- {name: X, type: select, proxies: *pg_Proxy}
- {name: SONY, type: select, proxies: *pg_Proxy}
- {name: TELEGRAM, type: select, proxies: *pg_Proxy}
- {name: GOOGLE, type: select, proxies: *pg_FallBack_US}
- {name: YOUTUBE, type: select, proxies: *pg_Proxy}
- {name: FINAL, type: select, proxies: *pg_Proxy}
- {name: US_NODES, <<: *Select, filter: *FilterUS}
- {name: JP_NODES, <<: *Select, filter: *FilterJP}
- {name: SG_NODES, <<: *Select, filter: *FilterSG}
- {name: TW_NODES, <<: *Select, filter: *FilterTW}
- {name: HK_NODES, <<: *Select, filter: *FilterHK}
- {name: US_FALLBACK, <<: *FallBack, filter: *FilterUS}
- {name: JP_FALLBACK, <<: *FallBack, filter: *FilterJP}
- {name: SG_FALLBACK, <<: *FallBack, filter: *FilterSG}
- {name: TW_FALLBACK, <<: *FallBack, filter: *FilterTW}
- {name: HK_FALLBACK, <<: *FallBack, filter: *FilterHK}
- {name: US_AUTO, <<: *UrlTest, filter: *FilterUS}
- {name: JP_AUTO, <<: *UrlTest, filter: *FilterJP}
- {name: SG_AUTO, <<: *UrlTest, filter: *FilterSG}
- {name: TW_AUTO, <<: *UrlTest, filter: *FilterTW}
- {name: HK_AUTO, <<: *UrlTest, filter: *FilterHK}
rules:
- IP-CIDR,169.254.169.254/32,REJECT,no-resolve
- DST-PORT,11434,REJECT
- DOMAIN,t-ring-fdv2.msedge.net,REJECT,no-resolve
- IP-CIDR,223.5.5.5/32,DIRECT,no-resolve
- IP-CIDR,223.6.6.6/32,DIRECT,no-resolve
- RULE-SET,custom_proxy,DEFAULT_PROXY
- RULE-SET,custom_direct,DIRECT
- RULE-SET,private_ip,DIRECT,no-resolve
- RULE-SET,private_domain,DIRECT
- RULE-SET,speedtest_domain,DIRECT
- RULE-SET,apple_domain,DIRECT
- RULE-SET,apple_ip,DIRECT,no-resolve
- RULE-SET,ai_domain,AIGC
- RULE-SET,onedrive_domain,ONEDRIVE
- RULE-SET,steam_domain,STEAM
- RULE-SET,github_domain,GITHUB
- RULE-SET,x_domain,X
- RULE-SET,telegram_ip,TELEGRAM,no-resolve
- RULE-SET,telegram_domain,TELEGRAM
- RULE-SET,sony_domain,SONY
- RULE-SET,microsoft_domain,MICROSOFT
- RULE-SET,youtube_domain,YOUTUBE
- RULE-SET,google_domain,GOOGLE
- RULE-SET,google_ip,GOOGLE,no-resolve
- RULE-SET,cn_domain,DIRECT
- RULE-SET,cn_ip,DIRECT,no-resolve
- RULE-SET,geolocation-!cn_domain,DEFAULT_PROXY
- MATCH,FINAL
RuleSet_classical_text: &RuleSet_classical_text
type: http
behavior: classical
interval: 43200
format: text
RuleSet_domain_mrs: &RuleSet_domain_mrs
type: http
behavior: domain
interval: 43200
format: mrs
RuleSet_domain_text: &RuleSet_domain_text
type: http
behavior: domain
interval: 43200
format: text
RuleSet_ipcidr_mrs: &RuleSet_ipcidr_mrs
type: http
behavior: ipcidr
interval: 43200
format: mrs
rule-providers:
custom_proxy:
<<: *RuleSet_classical_text
url: "https://raw.githubusercontent.com/lvbibir/clash/refs/heads/master/Ruleset/Proxy.list"
path: ./rule-providers/custom_proxy.list
custom_direct:
<<: *RuleSet_classical_text
url: "https://raw.githubusercontent.com/lvbibir/clash/refs/heads/master/Ruleset/Direct.list"
path: ./rule-providers/custom_direct.list
fakeipfilter:
<<: *RuleSet_domain_text
url: "https://raw.githubusercontent.com/juewuy/ShellCrash/refs/heads/dev/public/fake_ip_filter.list"
path: ./rule-providers/fakeipfilter.list
private_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/private.mrs"
path: ./rule-providers/private_domain.mrs
ai_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/category-ai-!cn.mrs"
path: ./rule-providers/ai_domain.mrs
x_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/x.mrs"
path: ./rule-providers/x_domain.mrs
youtube_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/youtube.mrs"
path: ./rule-providers/youtube_domain.mrs
google_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/google.mrs"
path: ./rule-providers/google_domain.mrs
github_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/github.mrs"
path: ./rule-providers/github_domain.mrs
steam_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/steam.mrs"
path: ./rule-providers/steam_domain.mrs
sony_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/sony.mrs"
path: ./rule-providers/sony_domain.mrs
telegram_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/telegram.mrs"
path: ./rule-providers/telegram_domain.mrs
onedrive_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/onedrive.mrs"
path: ./rule-providers/onedrive_domain.mrs
microsoft_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/microsoft.mrs"
path: ./rule-providers/microsoft_domain.mrs
apple_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/apple.mrs"
path: ./rule-providers/apple_domain.mrs
speedtest_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/ookla-speedtest.mrs"
path: ./rule-providers/speedtest_domain.mrs
geolocation-!cn_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/geolocation-!cn.mrs"
path: ./rule-providers/geolocation-!cn_domain.mrs
cn_domain:
<<: *RuleSet_domain_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/cn.mrs"
path: ./rule-providers/cn_domain.mrs
private_ip:
<<: *RuleSet_ipcidr_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geoip/private.mrs"
path: ./rule-providers/private_ip.mrs
cn_ip:
<<: *RuleSet_ipcidr_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geoip/cn.mrs"
path: ./rule-providers/cn_ip.mrs
google_ip:
<<: *RuleSet_ipcidr_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geoip/google.mrs"
path: ./rule-providers/google_ip.mrs
apple_ip:
<<: *RuleSet_ipcidr_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo-lite/geoip/apple.mrs"
path: ./rule-providers/apple_ip.mrs
telegram_ip:
<<: *RuleSet_ipcidr_mrs
url: "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo-lite/geoip/telegram.mrs"
path: ./rule-providers/telegram_ip.mrs

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: