优化了 gui

This commit is contained in:
yml2213
2026-06-21 23:38:52 +08:00
parent af3951ec6f
commit 220fcd4052
4 changed files with 125 additions and 20 deletions
+5 -2
View File
@@ -5,6 +5,8 @@ from pathlib import Path
from dataclasses import dataclass from dataclasses import dataclass
from typing import List, Optional from typing import List, Optional
from .email_verifier import get_email_config_for_account
@dataclass @dataclass
class Account: class Account:
@@ -45,13 +47,14 @@ class Config:
"""获取账号列表""" """获取账号列表"""
accounts = [] accounts = []
for acc in self._config.get('accounts', []): for acc in self._config.get('accounts', []):
email_config = get_email_config_for_account(acc['email'])
accounts.append(Account( accounts.append(Account(
username=acc['username'], username=acc['username'],
password=acc['password'], password=acc['password'],
email=acc['email'], email=acc['email'],
email_password=acc['email_password'], email_password=acc['email_password'],
email_imap_server=acc.get('email_imap_server', ''), email_imap_server=acc.get('email_imap_server') or email_config['server'],
email_imap_port=acc.get('email_imap_port', 993), email_imap_port=acc.get('email_imap_port') or email_config['port'],
)) ))
return accounts return accounts
+13
View File
@@ -40,6 +40,7 @@ class EmailVerifier:
"""连接IMAP服务器""" """连接IMAP服务器"""
try: try:
logger.info(f"连接IMAP服务器: {self.imap_server}:{self.imap_port}") logger.info(f"连接IMAP服务器: {self.imap_server}:{self.imap_port}")
self._validate_login_text()
self._connection = imaplib.IMAP4_SSL( self._connection = imaplib.IMAP4_SSL(
self.imap_server, self.imap_server,
self.imap_port, self.imap_port,
@@ -51,6 +52,17 @@ class EmailVerifier:
logger.error(f"IMAP连接失败: {e}") logger.error(f"IMAP连接失败: {e}")
raise raise
def _validate_login_text(self) -> None:
"""提前检查IMAP登录字段,给出比ascii编码异常更明确的提示。"""
for label, value in (("邮箱账号", self.username), ("邮箱密码/授权码", self.password)):
try:
value.encode("ascii")
except UnicodeEncodeError as exc:
raise ValueError(
f"{label}包含中文或其他非ASCII字符,IMAP无法登录;"
"请检查导入格式是否为:用户名|密码|邮箱|邮箱密码"
) from exc
def disconnect(self) -> None: def disconnect(self) -> None:
"""断开IMAP连接""" """断开IMAP连接"""
if self._connection: if self._connection:
@@ -324,6 +336,7 @@ class EmailVerifier:
def get_email_config_for_account(email_address: str) -> dict: def get_email_config_for_account(email_address: str) -> dict:
"""根据邮箱地址自动配置IMAP服务器""" """根据邮箱地址自动配置IMAP服务器"""
configs = { configs = {
'bdhg.xyz': {'server': 'mail.bdhg.xyz', 'port': 993},
'qq.com': {'server': 'imap.qq.com', 'port': 993}, 'qq.com': {'server': 'imap.qq.com', 'port': 993},
'163.com': {'server': 'imap.163.com', 'port': 993}, '163.com': {'server': 'imap.163.com', 'port': 993},
'126.com': {'server': 'imap.126.com', 'port': 993}, '126.com': {'server': 'imap.126.com', 'port': 993},
+81 -2
View File
@@ -9,11 +9,26 @@ from pathlib import Path
from queue import Queue, Empty from queue import Queue, Empty
from typing import List from typing import List
from loguru import logger
from douyu.config import Account, ProxyConfig from douyu.config import Account, ProxyConfig
from utils import setup_logger
from .login_worker import LoginWorker, parse_accounts_text from .login_worker import LoginWorker, parse_accounts_text
from .widgets import AccountTable, LogPanel from .widgets import AccountTable, LogPanel
LOG_LEVELS = ('DEBUG', 'INFO', 'WARNING', 'ERROR')
LOG_LEVEL_TAGS = {
'TRACE': 'debug',
'DEBUG': 'debug',
'INFO': 'info',
'SUCCESS': 'success',
'WARNING': 'warning',
'ERROR': 'error',
'CRITICAL': 'error',
}
class DouyuLoginApp: class DouyuLoginApp:
"""斗鱼批量登录GUI应用""" """斗鱼批量登录GUI应用"""
@@ -31,6 +46,8 @@ class DouyuLoginApp:
# 加载配置 # 加载配置
self.proxy_config = self._load_proxy_config() self.proxy_config = self._load_proxy_config()
self.geetest_retries = self._load_geetest_retries() self.geetest_retries = self._load_geetest_retries()
self.log_level = self._load_log_level()
self._configure_logging(self.log_level)
# 创建主窗口 # 创建主窗口
self.root = tk.Tk() self.root = tk.Tk()
@@ -77,6 +94,45 @@ class DouyuLoginApp:
except: except:
return 5 return 5
def _load_log_level(self) -> str:
"""从配置文件加载默认日志等级。"""
try:
import yaml
with open(self.config_path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
level = config.get('log', {}).get('level', 'INFO').upper()
return level if level in LOG_LEVELS else 'INFO'
except:
return 'INFO'
def _configure_logging(self, level: str) -> None:
"""配置终端和GUI日志输出等级。"""
self.log_level = level if level in LOG_LEVELS else 'INFO'
setup_logger(level=self.log_level)
logger.add(
self._enqueue_log_record,
level=self.log_level,
format='{message}',
colorize=False,
)
def _enqueue_log_record(self, message) -> None:
"""把loguru日志转发到GUI日志队列。"""
record = message.record
level = record['level'].name
tag = LOG_LEVEL_TAGS.get(level, 'info')
text = (
f"{record['name']}:{record['function']}:{record['line']} | "
f"{record['message']}"
)
self.log_queue.put((tag, text))
def _on_log_level_change(self, _event=None) -> None:
"""切换日志等级。"""
level = self.log_level_var.get().upper()
self._configure_logging(level)
self.log_panel.append_log('info', f'日志等级已切换为: {level}')
def _create_ui(self): def _create_ui(self):
"""创建用户界面""" """创建用户界面"""
# 主容器 # 主容器
@@ -163,9 +219,13 @@ class DouyuLoginApp:
self.proxy_https_entry = ttk.Entry(static_frame, textvariable=self.proxy_https_var, width=20) self.proxy_https_entry = ttk.Entry(static_frame, textvariable=self.proxy_https_var, width=20)
self.proxy_https_entry.pack(side='left', padx=(5, 0)) self.proxy_https_entry.pack(side='left', padx=(5, 0))
# 右侧配置:极验和日志
side_frame = ttk.Frame(config_frame)
side_frame.pack(side='right', fill='y', padx=(5, 0))
# 极验配置 # 极验配置
geetest_frame = ttk.LabelFrame(config_frame, text='极验配置', padding=10) geetest_frame = ttk.LabelFrame(side_frame, text='极验配置', padding=10)
geetest_frame.pack(side='right', fill='y', padx=(5, 0)) geetest_frame.pack(fill='x')
ttk.Label(geetest_frame, text='最大重试:').pack(anchor='w') ttk.Label(geetest_frame, text='最大重试:').pack(anchor='w')
self.geetest_retries_var = tk.IntVar(value=self.geetest_retries) self.geetest_retries_var = tk.IntVar(value=self.geetest_retries)
@@ -173,6 +233,22 @@ class DouyuLoginApp:
textvariable=self.geetest_retries_var) textvariable=self.geetest_retries_var)
retries_spin.pack(anchor='w', pady=(5, 0)) retries_spin.pack(anchor='w', pady=(5, 0))
# 日志配置
log_frame = ttk.LabelFrame(side_frame, text='日志配置', padding=10)
log_frame.pack(fill='x', pady=(8, 0))
ttk.Label(log_frame, text='等级:').pack(anchor='w')
self.log_level_var = tk.StringVar(value=self.log_level)
log_level_combo = ttk.Combobox(
log_frame,
values=LOG_LEVELS,
textvariable=self.log_level_var,
state='readonly',
width=10,
)
log_level_combo.pack(anchor='w', pady=(5, 0))
log_level_combo.bind('<<ComboboxSelected>>', self._on_log_level_change)
# 初始状态 # 初始状态
self._on_proxy_toggle() self._on_proxy_toggle()
@@ -333,6 +409,8 @@ class DouyuLoginApp:
# 获取当前GUI配置 # 获取当前GUI配置
proxy_config = self._get_proxy_config() proxy_config = self._get_proxy_config()
geetest_retries = self.geetest_retries_var.get() geetest_retries = self.geetest_retries_var.get()
log_level = self.log_level_var.get().upper()
self._configure_logging(log_level)
# 验证代理配置 # 验证代理配置
if proxy_config.enabled: if proxy_config.enabled:
@@ -366,6 +444,7 @@ class DouyuLoginApp:
self.log_panel.append_log('info', '未使用代理') self.log_panel.append_log('info', '未使用代理')
self.log_panel.append_log('info', f'极验最大重试: {geetest_retries}') self.log_panel.append_log('info', f'极验最大重试: {geetest_retries}')
self.log_panel.append_log('info', f'日志等级: {log_level}')
self.progress_var.set('登录中...') self.progress_var.set('登录中...')
# 启动登录线程 # 启动登录线程
+26 -16
View File
@@ -1,5 +1,6 @@
"""登录工作线程模块""" """登录工作线程模块"""
import re
import threading import threading
import time import time
from queue import Queue from queue import Queue
@@ -8,33 +9,31 @@ from loguru import logger
from douyu import DouyuLogin from douyu import DouyuLogin
from douyu.config import Account, ProxyConfig from douyu.config import Account, ProxyConfig
from douyu.email_verifier import get_email_config_for_account
# IMAP服务器映射 EMAIL_PATTERN = re.compile(r'^[^\s@|]+@[^\s@|]+\.[^\s@|]+$')
IMAP_SERVERS = {
'qq.com': 'imap.qq.com',
'163.com': 'imap.163.com',
'126.com': 'imap.126.com',
'gmail.com': 'imap.gmail.com',
'outlook.com': 'outlook.office365.com',
'hotmail.com': 'outlook.office365.com',
'yeah.net': 'imap.yeah.net',
'sohu.com': 'imap.sohu.com',
'sina.com': 'imap.sina.com',
}
def get_imap_server(email: str) -> str: def get_imap_server(email: str) -> str:
"""从邮箱地址推导IMAP服务器""" """从邮箱地址推导IMAP服务器"""
domain = email.split('@')[-1].lower() return get_email_config_for_account(email)['server']
return IMAP_SERVERS.get(domain, f'imap.{domain}')
def _is_ascii(value: str) -> bool:
"""检查字符串是否为ASCII。"""
try:
value.encode('ascii')
return True
except UnicodeEncodeError:
return False
def parse_accounts_text(text: str) -> list[Account]: def parse_accounts_text(text: str) -> list[Account]:
""" """
解析账号文本 解析账号文本
格式:用户名|密码|邮箱|邮箱密码(每行一个) 格式:用户名|密码|邮箱|邮箱密码
Returns: Returns:
Account列表 Account列表
@@ -47,7 +46,7 @@ def parse_accounts_text(text: str) -> list[Account]:
continue continue
parts = line.split('|') parts = line.split('|')
if len(parts) < 4: if len(parts) != 4:
logger.warning(f"{line_num}行格式错误,需要4个字段,实际{len(parts)}个: {line}") logger.warning(f"{line_num}行格式错误,需要4个字段,实际{len(parts)}个: {line}")
continue continue
@@ -57,6 +56,17 @@ def parse_accounts_text(text: str) -> list[Account]:
logger.warning(f"{line_num}行存在空字段: {line}") logger.warning(f"{line_num}行存在空字段: {line}")
continue continue
if not EMAIL_PATTERN.match(email) or not _is_ascii(email):
logger.warning(
f"{line_num}行邮箱格式不正确: {email}"
"请确认格式为:用户名|密码|邮箱|邮箱密码"
)
continue
if not _is_ascii(email_password):
logger.warning(f"{line_num}行邮箱密码/授权码包含非ASCII字符,IMAP可能无法登录")
continue
accounts.append(Account( accounts.append(Account(
username=username, username=username,
password=password, password=password,