优化了 gui
This commit is contained in:
+5
-2
@@ -5,6 +5,8 @@ from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
from .email_verifier import get_email_config_for_account
|
||||
|
||||
|
||||
@dataclass
|
||||
class Account:
|
||||
@@ -45,13 +47,14 @@ class Config:
|
||||
"""获取账号列表"""
|
||||
accounts = []
|
||||
for acc in self._config.get('accounts', []):
|
||||
email_config = get_email_config_for_account(acc['email'])
|
||||
accounts.append(Account(
|
||||
username=acc['username'],
|
||||
password=acc['password'],
|
||||
email=acc['email'],
|
||||
email_password=acc['email_password'],
|
||||
email_imap_server=acc.get('email_imap_server', ''),
|
||||
email_imap_port=acc.get('email_imap_port', 993),
|
||||
email_imap_server=acc.get('email_imap_server') or email_config['server'],
|
||||
email_imap_port=acc.get('email_imap_port') or email_config['port'],
|
||||
))
|
||||
return accounts
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ class EmailVerifier:
|
||||
"""连接IMAP服务器"""
|
||||
try:
|
||||
logger.info(f"连接IMAP服务器: {self.imap_server}:{self.imap_port}")
|
||||
self._validate_login_text()
|
||||
self._connection = imaplib.IMAP4_SSL(
|
||||
self.imap_server,
|
||||
self.imap_port,
|
||||
@@ -51,6 +52,17 @@ class EmailVerifier:
|
||||
logger.error(f"IMAP连接失败: {e}")
|
||||
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:
|
||||
"""断开IMAP连接"""
|
||||
if self._connection:
|
||||
@@ -324,6 +336,7 @@ class EmailVerifier:
|
||||
def get_email_config_for_account(email_address: str) -> dict:
|
||||
"""根据邮箱地址自动配置IMAP服务器"""
|
||||
configs = {
|
||||
'bdhg.xyz': {'server': 'mail.bdhg.xyz', 'port': 993},
|
||||
'qq.com': {'server': 'imap.qq.com', 'port': 993},
|
||||
'163.com': {'server': 'imap.163.com', 'port': 993},
|
||||
'126.com': {'server': 'imap.126.com', 'port': 993},
|
||||
|
||||
+81
-2
@@ -9,11 +9,26 @@ from pathlib import Path
|
||||
from queue import Queue, Empty
|
||||
from typing import List
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from douyu.config import Account, ProxyConfig
|
||||
from utils import setup_logger
|
||||
from .login_worker import LoginWorker, parse_accounts_text
|
||||
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:
|
||||
"""斗鱼批量登录GUI应用"""
|
||||
|
||||
@@ -31,6 +46,8 @@ class DouyuLoginApp:
|
||||
# 加载配置
|
||||
self.proxy_config = self._load_proxy_config()
|
||||
self.geetest_retries = self._load_geetest_retries()
|
||||
self.log_level = self._load_log_level()
|
||||
self._configure_logging(self.log_level)
|
||||
|
||||
# 创建主窗口
|
||||
self.root = tk.Tk()
|
||||
@@ -77,6 +94,45 @@ class DouyuLoginApp:
|
||||
except:
|
||||
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):
|
||||
"""创建用户界面"""
|
||||
# 主容器
|
||||
@@ -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.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.pack(side='right', fill='y', padx=(5, 0))
|
||||
geetest_frame = ttk.LabelFrame(side_frame, text='极验配置', padding=10)
|
||||
geetest_frame.pack(fill='x')
|
||||
|
||||
ttk.Label(geetest_frame, text='最大重试:').pack(anchor='w')
|
||||
self.geetest_retries_var = tk.IntVar(value=self.geetest_retries)
|
||||
@@ -173,6 +233,22 @@ class DouyuLoginApp:
|
||||
textvariable=self.geetest_retries_var)
|
||||
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()
|
||||
|
||||
@@ -333,6 +409,8 @@ class DouyuLoginApp:
|
||||
# 获取当前GUI配置
|
||||
proxy_config = self._get_proxy_config()
|
||||
geetest_retries = self.geetest_retries_var.get()
|
||||
log_level = self.log_level_var.get().upper()
|
||||
self._configure_logging(log_level)
|
||||
|
||||
# 验证代理配置
|
||||
if proxy_config.enabled:
|
||||
@@ -366,6 +444,7 @@ class DouyuLoginApp:
|
||||
self.log_panel.append_log('info', '未使用代理')
|
||||
|
||||
self.log_panel.append_log('info', f'极验最大重试: {geetest_retries} 次')
|
||||
self.log_panel.append_log('info', f'日志等级: {log_level}')
|
||||
self.progress_var.set('登录中...')
|
||||
|
||||
# 启动登录线程
|
||||
|
||||
+26
-16
@@ -1,5 +1,6 @@
|
||||
"""登录工作线程模块"""
|
||||
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from queue import Queue
|
||||
@@ -8,33 +9,31 @@ from loguru import logger
|
||||
|
||||
from douyu import DouyuLogin
|
||||
from douyu.config import Account, ProxyConfig
|
||||
from douyu.email_verifier import get_email_config_for_account
|
||||
|
||||
|
||||
# IMAP服务器映射
|
||||
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',
|
||||
}
|
||||
EMAIL_PATTERN = re.compile(r'^[^\s@|]+@[^\s@|]+\.[^\s@|]+$')
|
||||
|
||||
|
||||
def get_imap_server(email: str) -> str:
|
||||
"""从邮箱地址推导IMAP服务器"""
|
||||
domain = email.split('@')[-1].lower()
|
||||
return IMAP_SERVERS.get(domain, f'imap.{domain}')
|
||||
return get_email_config_for_account(email)['server']
|
||||
|
||||
|
||||
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]:
|
||||
"""
|
||||
解析账号文本
|
||||
|
||||
格式:用户名|密码|邮箱|邮箱密码(每行一个)
|
||||
格式:用户名|密码|邮箱|邮箱密码
|
||||
|
||||
Returns:
|
||||
Account列表
|
||||
@@ -47,7 +46,7 @@ def parse_accounts_text(text: str) -> list[Account]:
|
||||
continue
|
||||
|
||||
parts = line.split('|')
|
||||
if len(parts) < 4:
|
||||
if len(parts) != 4:
|
||||
logger.warning(f"第{line_num}行格式错误,需要4个字段,实际{len(parts)}个: {line}")
|
||||
continue
|
||||
|
||||
@@ -57,6 +56,17 @@ def parse_accounts_text(text: str) -> list[Account]:
|
||||
logger.warning(f"第{line_num}行存在空字段: {line}")
|
||||
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(
|
||||
username=username,
|
||||
password=password,
|
||||
|
||||
Reference in New Issue
Block a user