优化了 gui
This commit is contained in:
+5
-2
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user