白名单 ok

This commit is contained in:
yml2213
2026-06-22 11:29:22 +08:00
parent 66b03a7d89
commit 4c924375aa
5 changed files with 544 additions and 2 deletions
+267 -1
View File
@@ -8,12 +8,13 @@ from tkinter import ttk, messagebox, filedialog
from datetime import datetime
from pathlib import Path
from queue import Queue, Empty
from typing import List
from typing import List, Optional
import requests
from loguru import logger
from douyu.config import Account, ProxyConfig
from douyu.whitelist import WhitelistManager, get_exit_ip_via_proxy
from utils import setup_logger
from .account_importer import load_accounts_from_file
from .login_worker import LoginWorker, parse_accounts_text
@@ -67,6 +68,7 @@ class DouyuLoginApp:
self.log_queue = Queue()
self.proxy_test_queue = Queue()
self.login_preflight_queue = Queue()
self.whitelist_test_queue = Queue()
# 加载配置
self.proxy_config = self.state_store.proxy_from_state(self.state)
@@ -268,6 +270,43 @@ class DouyuLoginApp:
ttk.Label(proxy_frame, textvariable=self.proxy_test_status_var,
foreground='gray', wraplength=320).pack(anchor='w', pady=(6, 0))
# 白名单配置
whitelist_frame = ttk.LabelFrame(parent, text='白名单管理', padding=8)
whitelist_frame.pack(fill='x', pady=(0, 8))
whitelist_top_frame = ttk.Frame(whitelist_frame)
whitelist_top_frame.pack(fill='x')
self.whitelist_enabled_var = tk.BooleanVar(value=self.proxy_config.whitelist_enabled)
ttk.Checkbutton(whitelist_top_frame, text='启用白名单自动管理',
variable=self.whitelist_enabled_var,
command=self._on_whitelist_toggle).pack(side='left')
self.whitelist_test_btn = ttk.Button(whitelist_top_frame, text='测试连接',
command=self._test_whitelist_connection)
self.whitelist_test_btn.pack(side='right')
# UID
uid_frame = ttk.Frame(whitelist_frame)
uid_frame.pack(fill='x', pady=(5, 0))
ttk.Label(uid_frame, text='UID:').pack(side='left')
self.whitelist_uid_var = tk.StringVar(value=self.proxy_config.whitelist_uid)
self.whitelist_uid_entry = ttk.Entry(uid_frame, textvariable=self.whitelist_uid_var, width=20)
self.whitelist_uid_entry.pack(side='left', fill='x', expand=True, padx=(5, 0))
# UKEY
ukey_frame = ttk.Frame(whitelist_frame)
ukey_frame.pack(fill='x', pady=(5, 0))
ttk.Label(ukey_frame, text='UKEY:').pack(side='left')
self.whitelist_ukey_var = tk.StringVar(value=self.proxy_config.whitelist_ukey)
self.whitelist_ukey_entry = ttk.Entry(ukey_frame, textvariable=self.whitelist_ukey_var, width=28)
self.whitelist_ukey_entry.pack(side='left', fill='x', expand=True, padx=(5, 0))
self.whitelist_status_var = tk.StringVar(value='未配置')
ttk.Label(whitelist_frame, textvariable=self.whitelist_status_var,
foreground='gray', wraplength=320).pack(anchor='w', pady=(6, 0))
runtime_frame = ttk.LabelFrame(parent, text='运行设置', padding=8)
runtime_frame.pack(fill='x', pady=(0, 8))
runtime_frame.columnconfigure(1, weight=1)
@@ -292,6 +331,7 @@ class DouyuLoginApp:
# 初始状态
self._on_proxy_toggle()
self._on_whitelist_toggle()
def _create_workspace_section(self, parent):
"""创建可拖拽的账号/日志工作区。"""
@@ -433,17 +473,213 @@ class DouyuLoginApp:
self.proxy_https_entry.configure(state='normal')
self._schedule_save_state()
def _on_whitelist_toggle(self):
"""白名单启用/禁用切换"""
enabled = self.whitelist_enabled_var.get()
if hasattr(self, 'whitelist_uid_entry'):
state = 'normal' if enabled else 'disabled'
self.whitelist_uid_entry.configure(state=state)
self.whitelist_ukey_entry.configure(state=state)
self.whitelist_test_btn.configure(state=state)
if not enabled:
self.whitelist_status_var.set('未启用')
else:
self.whitelist_status_var.set('已启用,请填写UID和UKEY')
self._schedule_save_state()
def _test_whitelist_connection(self):
"""测试白名单API连接并将出口IP同步到白名单"""
uid = self.whitelist_uid_var.get().strip()
ukey = self.whitelist_ukey_var.get().strip()
if not uid or not ukey:
messagebox.showwarning('提示', '请填写UID和UKEY')
return
self.whitelist_test_btn.configure(state='disabled')
self.whitelist_status_var.set('测试中...')
self.log_panel.append_log('info', '测试白名单API连接...')
proxy_config = self._get_proxy_config()
def run_test():
try:
manager = WhitelistManager(uid, ukey)
success, api_msg = manager.test_connection()
self.log_queue.put(('info' if success else 'error', f'白名单API: {api_msg}'))
if not success:
self.whitelist_test_queue.put({'status': '连接失败'})
return
self.log_queue.put(('info', '正在获取出口IP...'))
exit_ip = self._get_exit_ip_for_test(proxy_config)
if not exit_ip:
self.log_queue.put(('warning', '无法获取出口IP'))
self.whitelist_test_queue.put({'status': '无法获取出口IP'})
return
self.log_queue.put(('info', f'出口IP: {exit_ip}'))
records = manager.get_whitelist_json()
in_list = any(r.get('IP') == exit_ip for r in records)
self.log_queue.put(('info', f'白名单共 {len(records)} 条记录'))
if in_list:
record = next((r for r in records if r.get('IP') == exit_ip), {})
memo = record.get('MEMO', '')
if memo == manager.memo:
self.log_queue.put(('success', f'出口IP {exit_ip} 已在白名单中 (备注正确)'))
self.whitelist_test_queue.put({'status': f'{exit_ip} 已在白名单中'})
else:
self.log_queue.put(('warning', f'出口IP {exit_ip} 已存在但备注不匹配 (当前: "{memo}"),正在更新...'))
sync_ok, sync_msg = manager.sync_ip(exit_ip)
if sync_ok:
self.log_queue.put(('success', f'白名单备注已更新: {sync_msg}'))
self.whitelist_test_queue.put({'status': f'{exit_ip} 备注已更新'})
else:
self.log_queue.put(('error', f'白名单更新失败: {sync_msg}'))
self.whitelist_test_queue.put({'status': '更新失败'})
else:
self.log_queue.put(('info', f'正在将 {exit_ip} 添加到白名单...'))
sync_ok, sync_msg = manager.sync_ip(exit_ip)
if sync_ok:
self.log_queue.put(('success', f'白名单同步: {sync_msg}'))
self.whitelist_test_queue.put({'status': f'{exit_ip} 已同步'})
else:
self.log_queue.put(('error', f'白名单同步失败: {sync_msg}'))
self.whitelist_test_queue.put({'status': '同步失败'})
except Exception as e:
self.log_queue.put(('error', f'白名单测试失败: {e}'))
self.whitelist_test_queue.put({'status': '测试失败'})
thread = threading.Thread(target=run_test, daemon=True)
thread.start()
def _on_whitelist_test_complete(self, result: dict):
"""处理白名单测试结果。"""
status = str(result.get('status') or '完成')
enabled = self.whitelist_enabled_var.get()
self.whitelist_status_var.set(status)
self.whitelist_test_btn.configure(state='normal' if enabled else 'disabled')
@staticmethod
def _get_exit_ip_for_test(proxy_config: ProxyConfig) -> Optional[str]:
"""获取出口IP用于白名单测试"""
proxy_url = None
if proxy_config.enabled:
if proxy_config.api_url:
try:
resp = requests.get(proxy_config.api_url, timeout=10)
match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', resp.text)
if match:
proxy_url = f"http://{match.group(1)}:{match.group(2)}"
except Exception:
pass
if not proxy_url:
proxy_url = proxy_config.http or proxy_config.https
if proxy_url:
return get_exit_ip_via_proxy(proxy_url)
# 没有代理时,用本机网络获取出口IP(多个探测源fallback)
detect_urls = [
'https://qifu-api.baidubce.com/ip/local/geo/v1/district',
'https://myip.ipip.net',
'https://4.ipw.cn',
]
for url in detect_urls:
try:
resp = requests.get(url, timeout=6, headers={'User-Agent': 'Mozilla/5.0'})
text = resp.text.strip()
match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', text)
if match:
return match.group(1)
except Exception:
continue
return None
def _sync_whitelist(self, proxy_config: ProxyConfig) -> bool:
"""
同步白名单IP
获取当前代理出口IP并同步到白名单
"""
if not proxy_config.whitelist_enabled:
return True
uid = proxy_config.whitelist_uid
ukey = proxy_config.whitelist_ukey
if not uid or not ukey:
self.log_queue.put(('warning', '白名单未配置UID/UKEY,跳过同步'))
return True
# 获取代理地址
proxy_url = None
if proxy_config.http or proxy_config.https:
proxy_url = proxy_config.http or proxy_config.https
elif proxy_config.api_url:
# 从API获取代理
try:
self.log_queue.put(('info', '从代理API获取IP用于白名单同步...'))
response = requests.get(proxy_config.api_url, timeout=10)
response.raise_for_status()
text = response.text.strip()
match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', text)
if match:
proxy_url = f"http://{match.group(1)}:{match.group(2)}"
except Exception as e:
self.log_queue.put(('error', f'获取代理失败: {e}'))
return False
if not proxy_url:
self.log_queue.put(('warning', '无法获取代理地址,跳过白名单同步'))
return True
# 获取出口IP
self.log_queue.put(('info', f'通过代理获取出口IP...'))
exit_ip = get_exit_ip_via_proxy(proxy_url)
if not exit_ip:
self.log_queue.put(('error', '无法获取出口IP,白名单同步失败'))
return False
self.log_queue.put(('info', f'当前出口IP: {exit_ip}'))
# 同步白名单
manager = WhitelistManager(uid, ukey)
success, message = manager.sync_ip(exit_ip)
if success:
self.log_queue.put(('success', f'白名单同步: {message}'))
else:
self.log_queue.put(('error', f'白名单同步: {message}'))
return success
def _get_proxy_config(self) -> ProxyConfig:
"""获取当前代理配置"""
enabled = self.proxy_enabled_var.get()
proxy_type = self.proxy_type_var.get()
# 白名单配置
whitelist_enabled = self.whitelist_enabled_var.get() if hasattr(self, 'whitelist_enabled_var') else False
whitelist_uid = self.whitelist_uid_var.get().strip() if hasattr(self, 'whitelist_uid_var') else ''
whitelist_ukey = self.whitelist_ukey_var.get().strip() if hasattr(self, 'whitelist_ukey_var') else ''
if proxy_type == 'api':
return ProxyConfig(
enabled=enabled,
api_url=self.proxy_api_var.get().strip(),
http='',
https='',
whitelist_enabled=whitelist_enabled,
whitelist_uid=whitelist_uid,
whitelist_ukey=whitelist_ukey,
)
else:
http = self._normalize_proxy_url(self.proxy_http_var.get().strip())
@@ -457,6 +693,9 @@ class DouyuLoginApp:
api_url='',
http=http,
https=https,
whitelist_enabled=whitelist_enabled,
whitelist_uid=whitelist_uid,
whitelist_ukey=whitelist_ukey,
)
@staticmethod
@@ -795,6 +1034,9 @@ class DouyuLoginApp:
self.proxy_https_var,
self.geetest_retries_var,
self.log_level_var,
self.whitelist_enabled_var,
self.whitelist_uid_var,
self.whitelist_ukey_var,
]
for variable in variables:
variable.trace_add('write', lambda *_: self._schedule_save_state())
@@ -825,6 +1067,9 @@ class DouyuLoginApp:
'api_url': proxy_config.api_url,
'http': proxy_config.http,
'https': proxy_config.https,
'whitelist_enabled': proxy_config.whitelist_enabled,
'whitelist_uid': proxy_config.whitelist_uid,
'whitelist_ukey': proxy_config.whitelist_ukey,
},
'proxy_type': self.proxy_type_var.get() if hasattr(self, 'proxy_type_var') else self.proxy_type,
'geetest_retries': self._get_geetest_retries(),
@@ -899,6 +1144,12 @@ class DouyuLoginApp:
self._save_state()
# 同步白名单(手动代理模式,代理地址已知)
if proxy_config.whitelist_enabled and not proxy_config.api_url:
if not self._sync_whitelist(proxy_config):
messagebox.showwarning('提示', '白名单同步失败,请检查配置')
return
if proxy_config.enabled:
self._prepare_login_proxy(proxy_config, geetest_retries, log_level)
return
@@ -936,6 +1187,13 @@ class DouyuLoginApp:
result = self._resolve_working_proxy_config(proxy_config)
result['geetest_retries'] = geetest_retries
result['log_level'] = log_level
# API代理模式:同步白名单(使用已验证的代理地址,避免API返回不同代理导致的不一致)
if proxy_config.whitelist_enabled and proxy_config.api_url and result.get('success'):
synced_config = result.get('proxy_config')
if synced_config:
self._sync_whitelist(synced_config)
self.login_preflight_queue.put(result)
def _on_login_preflight_complete(self, result: dict):
@@ -1067,6 +1325,14 @@ class DouyuLoginApp:
except Empty:
pass
# 处理白名单测试结果
try:
while True:
result = self.whitelist_test_queue.get_nowait()
self._on_whitelist_test_complete(result)
except Empty:
pass
# 处理结果队列
try:
while True: