1393 lines
55 KiB
Python
1393 lines
55 KiB
Python
import os
|
|
import tkinter as tk
|
|
from tkinter import filedialog, messagebox
|
|
import customtkinter as ctk
|
|
import logging
|
|
import traceback
|
|
from datetime import datetime
|
|
from typing import Callable, Optional, Dict, Any, List
|
|
|
|
from douyin_imbot.browser.browser_manager import BrowserManager
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ProfileManagerPanel(ctk.CTkFrame):
|
|
"""浏览器配置文件管理界面."""
|
|
|
|
def __init__(self, parent, browser_manager, on_profile_change=None):
|
|
"""
|
|
初始化配置管理界面.
|
|
|
|
Args:
|
|
parent: 父组件
|
|
browser_manager: 浏览器管理器实例
|
|
on_profile_change: 配置变更时的回调函数
|
|
"""
|
|
try:
|
|
super().__init__(parent)
|
|
self.browser_manager = browser_manager
|
|
self.on_profile_change = on_profile_change
|
|
self.profiles = []
|
|
self.running_profiles = (
|
|
{}
|
|
) # 存储正在运行的配置文件 profile_name: browser_instance
|
|
|
|
# 配置网格
|
|
self.grid_columnconfigure(0, weight=1)
|
|
self.grid_rowconfigure(1, weight=1)
|
|
|
|
# 创建UI组件
|
|
self._create_header()
|
|
self._create_table()
|
|
|
|
# 加载配置文件列表
|
|
self.refresh_profiles()
|
|
|
|
logger.info("配置文件管理面板初始化完成")
|
|
except Exception as e:
|
|
logger.error(f"初始化配置文件管理面板失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
raise
|
|
|
|
def _create_header(self):
|
|
"""创建头部区域,包含标题和添加按钮."""
|
|
try:
|
|
header_frame = ctk.CTkFrame(self)
|
|
header_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=(10, 5))
|
|
header_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
# 标题
|
|
title = ctk.CTkLabel(
|
|
header_frame,
|
|
text="浏览器配置文件管理",
|
|
font=ctk.CTkFont(size=18, weight="bold"),
|
|
)
|
|
title.grid(row=0, column=0, sticky="w", padx=10, pady=5)
|
|
|
|
# 添加配置按钮(右上角)
|
|
add_button = ctk.CTkButton(
|
|
header_frame,
|
|
text="添加配置",
|
|
command=self.add_profile,
|
|
width=120,
|
|
height=32,
|
|
)
|
|
add_button.grid(row=0, column=1, sticky="e", padx=10, pady=5)
|
|
|
|
logger.debug("创建头部区域完成")
|
|
except Exception as e:
|
|
logger.error(f"创建头部区域失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def _create_table(self):
|
|
"""创建配置文件表格界面."""
|
|
try:
|
|
# 表格容器
|
|
table_frame = ctk.CTkFrame(self)
|
|
table_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)
|
|
table_frame.grid_columnconfigure((0, 1, 2, 3, 4), weight=1)
|
|
|
|
# 添加选择列
|
|
headers = ["配置名称", "用户数据目录", "最后使用时间", "操作"]
|
|
for i, header in enumerate(headers):
|
|
header_label = ctk.CTkLabel(
|
|
table_frame,
|
|
text=header,
|
|
font=ctk.CTkFont(weight="bold"),
|
|
anchor="center",
|
|
)
|
|
header_label.grid(row=0, column=i, sticky="ew", padx=5, pady=5)
|
|
|
|
# 保存表格框架的引用,以便后续更新
|
|
self.table_frame = table_frame
|
|
|
|
# 创建表格内容的滚动区域
|
|
self.content_frame = ctk.CTkScrollableFrame(table_frame)
|
|
self.content_frame.grid(
|
|
row=1, column=0, columnspan=5, sticky="nsew", padx=5, pady=5
|
|
)
|
|
self.content_frame.grid_columnconfigure((0, 1, 2, 3, 4), weight=1)
|
|
|
|
# 使表格框架可滚动
|
|
table_frame.grid_rowconfigure(1, weight=1)
|
|
|
|
# 存储选择变量
|
|
self.selected_profiles = {}
|
|
|
|
logger.debug("创建表格界面完成")
|
|
except Exception as e:
|
|
logger.error(f"创建表格界面失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def _truncate_text(self, text, max_length=25):
|
|
"""
|
|
截断文本,使其不超过最大长度,并在截断处添加省略号。
|
|
|
|
Args:
|
|
text: 要截断的文本
|
|
max_length: 最大长度(默认25个字符)
|
|
|
|
Returns:
|
|
截断后的文本
|
|
"""
|
|
if not text:
|
|
return ""
|
|
|
|
if len(text) <= max_length:
|
|
return text
|
|
|
|
# 保留路径的特性,尝试保留开头和结尾
|
|
if "/" in text or "\\" in text:
|
|
# 对于路径,保留开头的几个字符和结尾的几个字符
|
|
parts = text.split(os.path.sep)
|
|
if len(parts) > 2:
|
|
# 保留第一部分和最后一部分
|
|
return f"{parts[0]}/.../{parts[-1]}"
|
|
else:
|
|
# 简单地从中间截断
|
|
return f"{text[:max_length//2]}...{text[-max_length//2:]}"
|
|
else:
|
|
# 非路径,简单地截断并添加省略号
|
|
return text[: max_length - 3] + "..."
|
|
|
|
def _create_tooltip(self, widget, text):
|
|
"""
|
|
为小部件创建一个简单的工具提示。
|
|
|
|
Args:
|
|
widget: 要添加工具提示的小部件
|
|
text: 工具提示显示的文本
|
|
"""
|
|
if not text:
|
|
return
|
|
|
|
def show_tooltip(event):
|
|
x, y, _, _ = widget.bbox("insert")
|
|
x += widget.winfo_rootx() + 25
|
|
y += widget.winfo_rooty() + 25
|
|
|
|
# 创建一个新的顶层窗口
|
|
tooltip = tk.Toplevel(widget)
|
|
tooltip.wm_overrideredirect(True)
|
|
tooltip.wm_geometry(f"+{x}+{y}")
|
|
|
|
label = tk.Label(
|
|
tooltip,
|
|
text=text,
|
|
justify="left",
|
|
background="#ffffe0",
|
|
relief="solid",
|
|
borderwidth=1,
|
|
font=("TkDefaultFont", "10", "normal"),
|
|
)
|
|
label.pack(padx=2, pady=2)
|
|
|
|
widget.tooltip = tooltip
|
|
|
|
def hide_tooltip(event):
|
|
if hasattr(widget, "tooltip"):
|
|
widget.tooltip.destroy()
|
|
|
|
widget.bind("<Enter>", show_tooltip)
|
|
widget.bind("<Leave>", hide_tooltip)
|
|
|
|
def refresh_profiles(self):
|
|
"""刷新配置文件列表."""
|
|
try:
|
|
# 获取所有配置文件
|
|
self.profiles = self.browser_manager.get_profiles() or []
|
|
|
|
# 清空表格内容
|
|
for widget in self.content_frame.winfo_children():
|
|
widget.destroy()
|
|
|
|
# 重置选择状态
|
|
self.selected_profiles = {}
|
|
|
|
if not self.profiles:
|
|
# 显示无数据提示
|
|
no_data_label = ctk.CTkLabel(
|
|
self.content_frame,
|
|
text="没有可用的配置文件,请点击'添加配置'按钮创建",
|
|
font=ctk.CTkFont(size=14),
|
|
anchor="center",
|
|
)
|
|
no_data_label.grid(
|
|
row=0, column=0, columnspan=5, sticky="ew", padx=10, pady=20
|
|
)
|
|
logger.info("没有找到配置文件")
|
|
return
|
|
|
|
# 添加所有配置文件到表格
|
|
for i, profile_name in enumerate(self.profiles):
|
|
# 获取详情
|
|
details = self.browser_manager.get_profile_details(profile_name) or {}
|
|
user_data_dir = details.get("user_data_dir", "未知")
|
|
last_used = details.get("last_used", "从未")
|
|
douyin_account = details.get("douyin_account", "未设置")
|
|
|
|
# 截断长文本以确保表格正常显示
|
|
display_name = self._truncate_text(profile_name, 20)
|
|
display_dir = self._truncate_text(user_data_dir, 30)
|
|
display_account = self._truncate_text(douyin_account, 20)
|
|
|
|
# 配置名称和抖音账号合并显示
|
|
name_text = f"{display_name} ({display_account})"
|
|
name_label = ctk.CTkLabel(
|
|
self.content_frame, text=name_text, anchor="w"
|
|
)
|
|
name_label.grid(row=i, column=1, sticky="ew", padx=5, pady=5)
|
|
# 为完整文本添加工具提示
|
|
full_name_text = f"配置名: {profile_name}\n抖音账号: {douyin_account}"
|
|
self._create_tooltip(name_label, full_name_text)
|
|
|
|
# 用户数据目录
|
|
dir_label = ctk.CTkLabel(
|
|
self.content_frame, text=display_dir, anchor="w"
|
|
)
|
|
dir_label.grid(row=i, column=2, sticky="ew", padx=5, pady=5)
|
|
# 为完整路径添加工具提示
|
|
if display_dir != user_data_dir:
|
|
self._create_tooltip(dir_label, user_data_dir)
|
|
|
|
# 最后使用时间
|
|
last_used_label = ctk.CTkLabel(
|
|
self.content_frame, text=last_used, anchor="w"
|
|
)
|
|
last_used_label.grid(row=i, column=3, sticky="ew", padx=5, pady=5)
|
|
|
|
# 状态
|
|
is_running = profile_name in self.running_profiles
|
|
status_color = "green" if is_running else "red"
|
|
status_text = "运行中" if is_running else "已停止"
|
|
|
|
status_label = ctk.CTkLabel(
|
|
self.content_frame,
|
|
text=status_text,
|
|
text_color=status_color,
|
|
anchor="center",
|
|
)
|
|
status_label.grid(row=i, column=3, sticky="ew", padx=5, pady=5)
|
|
|
|
# 操作按钮
|
|
actions_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
|
|
actions_frame.grid(row=i, column=4, sticky="ew", padx=5, pady=2)
|
|
|
|
# 编辑按钮
|
|
edit_button = ctk.CTkButton(
|
|
actions_frame,
|
|
text="编辑",
|
|
command=lambda p=profile_name: self.edit_profile(p),
|
|
width=60,
|
|
height=28,
|
|
)
|
|
edit_button.grid(row=0, column=0, padx=2, pady=2)
|
|
|
|
# 删除按钮
|
|
delete_button = ctk.CTkButton(
|
|
actions_frame,
|
|
text="删除",
|
|
command=lambda p=profile_name: self.delete_profile(p),
|
|
width=60,
|
|
height=28,
|
|
fg_color="#d9534f",
|
|
hover_color="#c9302c",
|
|
)
|
|
delete_button.grid(row=0, column=1, padx=2, pady=2)
|
|
|
|
# 启动/停止按钮
|
|
if is_running:
|
|
stop_button = ctk.CTkButton(
|
|
actions_frame,
|
|
text="停止",
|
|
command=lambda p=profile_name: self.stop_robot(p),
|
|
width=60,
|
|
height=28,
|
|
fg_color="#f0ad4e",
|
|
hover_color="#ec971f",
|
|
)
|
|
stop_button.grid(row=0, column=2, padx=2, pady=2)
|
|
else:
|
|
start_button = ctk.CTkButton(
|
|
actions_frame,
|
|
text="启动",
|
|
command=lambda p=profile_name: self.start_robot(p),
|
|
width=60,
|
|
height=28,
|
|
fg_color="#5cb85c",
|
|
hover_color="#449d44",
|
|
)
|
|
start_button.grid(row=0, column=2, padx=2, pady=2)
|
|
|
|
logger.info(f"刷新了 {len(self.profiles)} 个配置文件")
|
|
except Exception as e:
|
|
logger.error(f"刷新配置文件列表失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def add_profile(self):
|
|
"""添加新的配置文件."""
|
|
try:
|
|
# 创建对话框
|
|
dialog = ctk.CTkToplevel(self)
|
|
dialog.title("添加配置文件")
|
|
dialog.geometry("400x350") # 增加高度以适应抖音账号字段
|
|
dialog.transient(self.winfo_toplevel())
|
|
dialog.grab_set() # 模态对话框
|
|
|
|
# 配置对话框
|
|
dialog.grid_columnconfigure(0, weight=1)
|
|
|
|
# 标题
|
|
title = ctk.CTkLabel(
|
|
dialog, text="添加新配置文件", font=ctk.CTkFont(size=16, weight="bold")
|
|
)
|
|
title.grid(row=0, column=0, padx=20, pady=(20, 10))
|
|
|
|
# 表单
|
|
form_frame = ctk.CTkFrame(dialog)
|
|
form_frame.grid(row=1, column=0, padx=20, pady=10, sticky="ew")
|
|
form_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
# 配置名称
|
|
name_label = ctk.CTkLabel(form_frame, text="配置名称:")
|
|
name_label.grid(row=0, column=0, padx=10, pady=10, sticky="w")
|
|
name_entry = ctk.CTkEntry(form_frame, width=250)
|
|
name_entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
|
|
|
|
# 抖音账号
|
|
douyin_label = ctk.CTkLabel(form_frame, text="抖音账号:")
|
|
douyin_label.grid(row=1, column=0, padx=10, pady=10, sticky="w")
|
|
douyin_entry = ctk.CTkEntry(form_frame, width=250)
|
|
douyin_entry.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
|
|
|
|
# 用户数据目录说明
|
|
dir_info_label = ctk.CTkLabel(
|
|
form_frame,
|
|
text="用户数据目录将自动生成",
|
|
font=ctk.CTkFont(size=12, slant="italic"),
|
|
text_color="gray",
|
|
)
|
|
dir_info_label.grid(
|
|
row=2, column=0, columnspan=2, padx=10, pady=(0, 10), sticky="w"
|
|
)
|
|
|
|
# 按钮区域
|
|
button_frame = ctk.CTkFrame(dialog, fg_color="transparent")
|
|
button_frame.grid(row=2, column=0, padx=20, pady=20)
|
|
|
|
# 保存按钮
|
|
save_button = ctk.CTkButton(
|
|
button_frame,
|
|
text="保存",
|
|
command=lambda: self._save_profile(
|
|
dialog, name_entry.get(), douyin_entry.get()
|
|
),
|
|
width=100,
|
|
)
|
|
save_button.grid(row=0, column=0, padx=10)
|
|
|
|
# 取消按钮
|
|
cancel_button = ctk.CTkButton(
|
|
button_frame,
|
|
text="取消",
|
|
command=dialog.destroy,
|
|
width=100,
|
|
fg_color="#6c757d",
|
|
hover_color="#5a6268",
|
|
)
|
|
cancel_button.grid(row=0, column=1, padx=10)
|
|
|
|
logger.info("打开添加配置对话框")
|
|
except Exception as e:
|
|
logger.error(f"打开添加配置对话框失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def _save_profile(self, dialog, profile_name, douyin_account):
|
|
"""
|
|
保存新配置文件.
|
|
|
|
Args:
|
|
dialog: 对话框实例
|
|
profile_name: 配置文件名称
|
|
douyin_account: 抖音账号
|
|
"""
|
|
try:
|
|
if not profile_name:
|
|
messagebox.showerror("错误", "配置名称不能为空", parent=dialog)
|
|
return
|
|
|
|
if not douyin_account:
|
|
messagebox.showerror("错误", "抖音账号不能为空", parent=dialog)
|
|
return
|
|
|
|
# 检查是否已存在同名配置
|
|
existing_profiles = self.browser_manager.get_profiles() or []
|
|
if profile_name in existing_profiles:
|
|
messagebox.showerror(
|
|
"错误", f"配置名称 '{profile_name}' 已存在", parent=dialog
|
|
)
|
|
return
|
|
|
|
# 创建配置文件 - 将抖音账号信息传递给创建函数
|
|
# 注意配置文件目录会在browser_manager中自动生成
|
|
success = self.browser_manager.create_profile(
|
|
profile_name=profile_name, douyin_account=douyin_account
|
|
)
|
|
|
|
if success:
|
|
logger.info(
|
|
f"成功创建配置文件: {profile_name}, 抖音账号: {douyin_account}"
|
|
)
|
|
messagebox.showinfo(
|
|
"成功",
|
|
f"配置文件 '{profile_name}' 已成功创建\n关联抖音账号: {douyin_account}",
|
|
parent=dialog,
|
|
)
|
|
|
|
# 关闭对话框
|
|
dialog.destroy()
|
|
|
|
# 刷新列表
|
|
self.refresh_profiles()
|
|
|
|
# 调用回调函数
|
|
if self.on_profile_change:
|
|
self.on_profile_change()
|
|
else:
|
|
logger.error(f"创建配置文件失败: {profile_name}")
|
|
messagebox.showerror(
|
|
"错误", f"无法创建配置文件 '{profile_name}'", parent=dialog
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"保存配置文件时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror("错误", f"保存配置文件时出错: {str(e)}", parent=dialog)
|
|
|
|
def edit_profile(self, profile_name):
|
|
"""编辑配置文件."""
|
|
try:
|
|
# 获取配置详情
|
|
details = self.browser_manager.get_profile_details(profile_name) or {}
|
|
user_data_dir = details.get("user_data_dir", "")
|
|
douyin_account = details.get("douyin_account", "")
|
|
|
|
# 创建对话框
|
|
dialog = ctk.CTkToplevel(self)
|
|
dialog.title(f"编辑配置: {profile_name}")
|
|
dialog.geometry("400x350") # 增加高度以适应抖音账号字段
|
|
dialog.transient(self.winfo_toplevel())
|
|
dialog.grab_set() # 模态对话框
|
|
|
|
# 配置对话框
|
|
dialog.grid_columnconfigure(0, weight=1)
|
|
|
|
# 标题
|
|
title = ctk.CTkLabel(
|
|
dialog,
|
|
text=f"编辑配置: {profile_name}",
|
|
font=ctk.CTkFont(size=16, weight="bold"),
|
|
)
|
|
title.grid(row=0, column=0, padx=20, pady=(20, 10))
|
|
|
|
# 表单
|
|
form_frame = ctk.CTkFrame(dialog)
|
|
form_frame.grid(row=1, column=0, padx=20, pady=10, sticky="ew")
|
|
form_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
# 配置名称
|
|
name_label = ctk.CTkLabel(form_frame, text="配置名称:")
|
|
name_label.grid(row=0, column=0, padx=10, pady=10, sticky="w")
|
|
name_entry = ctk.CTkEntry(form_frame, width=250)
|
|
name_entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
|
|
name_entry.insert(0, profile_name)
|
|
|
|
# 抖音账号
|
|
douyin_label = ctk.CTkLabel(form_frame, text="抖音账号:")
|
|
douyin_label.grid(row=1, column=0, padx=10, pady=10, sticky="w")
|
|
douyin_entry = ctk.CTkEntry(form_frame, width=250)
|
|
douyin_entry.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
|
|
douyin_entry.insert(0, douyin_account)
|
|
|
|
# 用户数据目录(只读)
|
|
dir_label = ctk.CTkLabel(form_frame, text="用户数据目录:")
|
|
dir_label.grid(row=2, column=0, padx=10, pady=10, sticky="w")
|
|
dir_entry = ctk.CTkEntry(form_frame, width=250, state="readonly")
|
|
dir_entry.grid(row=2, column=1, padx=10, pady=10, sticky="ew")
|
|
dir_entry.insert(0, user_data_dir)
|
|
|
|
# 按钮区域
|
|
button_frame = ctk.CTkFrame(dialog, fg_color="transparent")
|
|
button_frame.grid(row=2, column=0, padx=20, pady=20)
|
|
|
|
# 保存按钮
|
|
save_button = ctk.CTkButton(
|
|
button_frame,
|
|
text="保存",
|
|
command=lambda: self._update_profile(
|
|
dialog, profile_name, name_entry.get(), douyin_entry.get()
|
|
),
|
|
width=100,
|
|
)
|
|
save_button.grid(row=0, column=0, padx=10)
|
|
|
|
# 取消按钮
|
|
cancel_button = ctk.CTkButton(
|
|
button_frame,
|
|
text="取消",
|
|
command=dialog.destroy,
|
|
width=100,
|
|
fg_color="#6c757d",
|
|
hover_color="#5a6268",
|
|
)
|
|
cancel_button.grid(row=0, column=1, padx=10)
|
|
|
|
logger.info(f"打开编辑配置对话框: {profile_name}")
|
|
except Exception as e:
|
|
logger.error(f"打开编辑配置对话框失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def _update_profile(self, dialog, old_name, new_name, douyin_account):
|
|
"""
|
|
更新配置文件.
|
|
|
|
Args:
|
|
dialog: 对话框实例
|
|
old_name: 原配置文件名称
|
|
new_name: 新配置文件名称
|
|
douyin_account: 抖音账号
|
|
"""
|
|
try:
|
|
if not new_name:
|
|
messagebox.showerror("错误", "配置名称不能为空", parent=dialog)
|
|
return
|
|
|
|
if not douyin_account:
|
|
messagebox.showerror("错误", "抖音账号不能为空", parent=dialog)
|
|
return
|
|
|
|
# 如果名称发生变化,检查是否已存在
|
|
if old_name != new_name:
|
|
existing_profiles = self.browser_manager.get_profiles() or []
|
|
if new_name in existing_profiles:
|
|
messagebox.showerror(
|
|
"错误", f"配置名称 '{new_name}' 已存在", parent=dialog
|
|
)
|
|
return
|
|
|
|
# 更新配置文件,包括抖音账号信息
|
|
success = self.browser_manager.update_profile(
|
|
old_name, new_name=new_name, douyin_account=douyin_account
|
|
)
|
|
|
|
if success:
|
|
logger.info(f"成功更新配置文件: {old_name} -> {new_name}")
|
|
messagebox.showinfo("成功", f"配置文件已成功更新", parent=dialog)
|
|
|
|
# 如果正在运行中,更新运行列表
|
|
if old_name in self.running_profiles:
|
|
self.running_profiles[new_name] = self.running_profiles.pop(
|
|
old_name
|
|
)
|
|
|
|
# 关闭对话框
|
|
dialog.destroy()
|
|
|
|
# 刷新列表
|
|
self.refresh_profiles()
|
|
|
|
# 调用回调函数
|
|
if self.on_profile_change:
|
|
self.on_profile_change()
|
|
else:
|
|
logger.error(f"更新配置文件失败: {old_name} -> {new_name}")
|
|
messagebox.showerror("错误", f"无法更新配置文件", parent=dialog)
|
|
except Exception as e:
|
|
logger.error(f"更新配置文件时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror("错误", f"更新配置文件时出错: {str(e)}", parent=dialog)
|
|
|
|
def delete_profile(self, profile_name):
|
|
"""删除配置文件."""
|
|
try:
|
|
# 确认对话框
|
|
confirm = messagebox.askyesno(
|
|
"确认删除",
|
|
f"您确定要删除配置文件 '{profile_name}' 吗?此操作不可撤销。",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
|
|
if confirm:
|
|
self.update_delete_status(f"正在删除配置文件 '{profile_name}'...")
|
|
|
|
# 如果正在运行,先停止
|
|
if profile_name in self.running_profiles:
|
|
self.stop_robot(profile_name)
|
|
|
|
# 获取配置详情,记录目录便于后续验证
|
|
details = self.browser_manager.get_profile_details(profile_name) or {}
|
|
profile_dir = details.get("user_data_dir", "")
|
|
|
|
# 删除配置文件,强制使用delete_files=True
|
|
success = self.browser_manager.delete_profile(
|
|
profile_name, delete_files=True
|
|
)
|
|
|
|
# 验证删除结果
|
|
dir_deleted = not profile_dir or not os.path.exists(profile_dir)
|
|
|
|
if success and dir_deleted:
|
|
logger.info(
|
|
f"成功删除配置文件: {profile_name}, 目录已删除: {profile_dir}"
|
|
)
|
|
messagebox.showinfo(
|
|
"成功",
|
|
f"配置文件 '{profile_name}' 已成功删除",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
|
|
# 如果在选择列表中,移除它
|
|
if profile_name in self.selected_profiles:
|
|
del self.selected_profiles[profile_name]
|
|
|
|
# 强制清除缓存
|
|
self.browser_manager.clear_profile_cache()
|
|
|
|
# 刷新列表
|
|
self.refresh_profiles()
|
|
|
|
# 调用回调函数
|
|
if self.on_profile_change:
|
|
self.on_profile_change()
|
|
else:
|
|
# 处理删除失败情况
|
|
failure_reason = ""
|
|
if not success:
|
|
failure_reason = "配置记录删除失败"
|
|
elif not dir_deleted:
|
|
failure_reason = f"目录删除失败 ({profile_dir})"
|
|
|
|
logger.error(f"删除配置文件失败: {profile_name} - {failure_reason}")
|
|
|
|
# 询问是否强制从列表中移除
|
|
force_remove = messagebox.askyesno(
|
|
"删除失败",
|
|
f"无法完全删除配置文件 '{profile_name}'。{failure_reason}\n"
|
|
f"是否从列表中强制移除此配置?",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
|
|
if force_remove:
|
|
# 尝试手动清理列表
|
|
try:
|
|
# 从浏览器管理器的profiles缓存中移除
|
|
success = self._force_remove_profile(profile_name)
|
|
|
|
if success:
|
|
logger.info(f"强制移除配置文件成功: {profile_name}")
|
|
messagebox.showinfo(
|
|
"成功",
|
|
f"配置文件 '{profile_name}' 已从列表中移除",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
|
|
# 如果在选择列表中,移除它
|
|
if profile_name in self.selected_profiles:
|
|
del self.selected_profiles[profile_name]
|
|
|
|
# 强制清除缓存
|
|
self.browser_manager.clear_profile_cache()
|
|
|
|
# 刷新列表
|
|
self.refresh_profiles()
|
|
|
|
# 调用回调函数
|
|
if self.on_profile_change:
|
|
self.on_profile_change()
|
|
else:
|
|
logger.error(f"强制移除配置文件失败: {profile_name}")
|
|
messagebox.showerror(
|
|
"错误",
|
|
f"无法从列表中移除配置文件 '{profile_name}'",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"强制移除配置文件时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror(
|
|
"错误",
|
|
f"强制移除配置文件时出错: {str(e)}",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
else:
|
|
messagebox.showerror(
|
|
"错误",
|
|
f"无法删除配置文件 '{profile_name}'",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"删除配置文件时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror(
|
|
"错误", f"删除配置文件时出错: {str(e)}", parent=self.winfo_toplevel()
|
|
)
|
|
return False
|
|
|
|
return True
|
|
|
|
def update_delete_status(self, message):
|
|
"""更新删除状态到日志"""
|
|
try:
|
|
# 尝试访问主窗口的日志更新方法
|
|
# 假设主窗口有一个update_log方法
|
|
main_window = self.winfo_toplevel()
|
|
if hasattr(main_window, "update_log"):
|
|
main_window.update_log(message)
|
|
else:
|
|
# 记录到logger
|
|
logger.info(message)
|
|
except Exception as e:
|
|
logger.error(f"更新删除状态失败: {str(e)}")
|
|
|
|
def _force_remove_profile(self, profile_name):
|
|
"""
|
|
强制从配置记录中移除配置文件。
|
|
当配置文件实际已不存在但仍在列表中显示时使用此方法。
|
|
|
|
Args:
|
|
profile_name: 配置文件名称
|
|
|
|
Returns:
|
|
bool: 是否成功移除
|
|
"""
|
|
try:
|
|
# 检查是否有配置文件记录的设置文件
|
|
from douyin_imbot.config.settings import load_settings, save_settings
|
|
|
|
# 加载设置
|
|
settings = load_settings()
|
|
|
|
# 检查是否有browser_profiles设置
|
|
if "browser_profiles" in settings and isinstance(
|
|
settings["browser_profiles"], list
|
|
):
|
|
profiles = settings["browser_profiles"]
|
|
|
|
# 寻找匹配的配置
|
|
for i, profile in enumerate(profiles):
|
|
if (
|
|
isinstance(profile, dict)
|
|
and profile.get("name") == profile_name
|
|
):
|
|
# 找到了,从列表中移除
|
|
profiles.pop(i)
|
|
|
|
# 保存修改后的设置
|
|
settings["browser_profiles"] = profiles
|
|
if save_settings(settings):
|
|
logger.info(f"成功从设置中移除配置记录: {profile_name}")
|
|
return True
|
|
else:
|
|
logger.error(
|
|
f"保存设置失败,无法移除配置记录: {profile_name}"
|
|
)
|
|
return False
|
|
|
|
# 如果运行到这里,说明没有找到配置记录或没有browser_profiles设置
|
|
logger.warning(f"在设置中未找到配置记录: {profile_name}")
|
|
|
|
# 即使没有找到记录,也返回成功,因为我们的目的是从列表中移除
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"强制移除配置记录时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
return False
|
|
|
|
def start_robot(self, profile_name):
|
|
"""启动机器人."""
|
|
try:
|
|
if profile_name in self.running_profiles:
|
|
logger.warning(f"机器人已经在运行中: {profile_name}")
|
|
messagebox.showinfo(
|
|
"提示",
|
|
f"机器人 '{profile_name}' 已经在运行中",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
return False
|
|
|
|
# 检查配置文件是否有效
|
|
details = self.browser_manager.get_profile_details(profile_name) or {}
|
|
if details.get("error") or not details.get("is_valid", False):
|
|
logger.error(f"配置文件不存在或无效: {profile_name}")
|
|
messagebox.showerror(
|
|
"错误",
|
|
f"配置文件 '{profile_name}' 不存在或已损坏,请先创建配置文件",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
return False
|
|
|
|
# 确保使用正确的配置文件名称启动浏览器
|
|
self.update_delete_status(
|
|
f"正在使用配置文件 '{profile_name}' 启动机器人..."
|
|
)
|
|
|
|
# 启动浏览器 - 明确指定使用profile_name
|
|
browser = self.browser_manager.launch_browser(
|
|
profile_name=profile_name, headless=False
|
|
)
|
|
|
|
if browser:
|
|
# 导航到抖音
|
|
success = self.browser_manager.navigate_to("https://www.douyin.com/")
|
|
if not success:
|
|
logger.warning("导航到抖音网站失败")
|
|
self.update_delete_status("导航到抖音网站失败,但浏览器已启动")
|
|
|
|
# 记录正在运行的浏览器
|
|
self.running_profiles[profile_name] = browser
|
|
|
|
logger.info(f"成功启动机器人: {profile_name}")
|
|
messagebox.showinfo(
|
|
"成功",
|
|
f"机器人 '{profile_name}' 已成功启动",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
|
|
# 刷新界面
|
|
self.refresh_profiles()
|
|
return True
|
|
else:
|
|
logger.error(f"启动机器人失败: {profile_name}")
|
|
messagebox.showerror(
|
|
"错误",
|
|
f"无法启动机器人 '{profile_name}',请检查配置文件是否有效",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"启动机器人时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror(
|
|
"错误", f"启动机器人时出错: {str(e)}", parent=self.winfo_toplevel()
|
|
)
|
|
return False
|
|
|
|
def stop_robot(self, profile_name):
|
|
"""停止机器人."""
|
|
try:
|
|
if profile_name not in self.running_profiles:
|
|
logger.warning(f"机器人未在运行: {profile_name}")
|
|
messagebox.showinfo(
|
|
"提示",
|
|
f"机器人 '{profile_name}' 未在运行",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
return
|
|
|
|
# 关闭浏览器
|
|
self.browser_manager.close_browser()
|
|
|
|
# 从运行列表中移除
|
|
if profile_name in self.running_profiles:
|
|
del self.running_profiles[profile_name]
|
|
|
|
logger.info(f"成功停止机器人: {profile_name}")
|
|
messagebox.showinfo(
|
|
"成功",
|
|
f"机器人 '{profile_name}' 已成功停止",
|
|
parent=self.winfo_toplevel(),
|
|
)
|
|
|
|
# 刷新界面
|
|
self.refresh_profiles()
|
|
except Exception as e:
|
|
logger.error(f"停止机器人时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror(
|
|
"错误", f"停止机器人时出错: {str(e)}", parent=self.winfo_toplevel()
|
|
)
|
|
|
|
def get_selected_profiles(self):
|
|
"""获取选中的配置文件列表"""
|
|
try:
|
|
selected = []
|
|
for profile_name, var in self.selected_profiles.items():
|
|
if var.get():
|
|
selected.append(profile_name)
|
|
return selected
|
|
except Exception as e:
|
|
logger.error(f"获取选中配置文件失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
return []
|
|
|
|
def create_profile_list(self):
|
|
"""创建配置文件列表区域"""
|
|
try:
|
|
# 配置文件列表框
|
|
self.profile_list_frame = ctk.CTkFrame(self)
|
|
self.profile_list_frame.grid(row=1, column=0, columnspan=2, padx=10, pady=10, sticky="nsew")
|
|
self.profile_list_frame.grid_columnconfigure(0, weight=1)
|
|
self.profile_list_frame.grid_rowconfigure(1, weight=1)
|
|
|
|
# 列表标题
|
|
list_label = ctk.CTkLabel(self.profile_list_frame, text="可用配置文件", anchor="w")
|
|
list_label.grid(row=0, column=0, padx=10, pady=5, sticky="w")
|
|
|
|
# 配置文件列表
|
|
self.profile_listbox = ctk.CTkOptionMenu(
|
|
self.profile_list_frame,
|
|
values=["无可用配置"],
|
|
command=self.on_profile_selected
|
|
)
|
|
self.profile_listbox.grid(row=1, column=0, padx=10, pady=(0, 5), sticky="ew")
|
|
|
|
# 刷新按钮
|
|
refresh_button = ctk.CTkButton(
|
|
self.profile_list_frame,
|
|
text="刷新",
|
|
width=80,
|
|
command=self.refresh_profiles
|
|
)
|
|
refresh_button.grid(row=2, column=0, padx=10, pady=(0, 10), sticky="e")
|
|
except Exception as e:
|
|
logger.error(f"创建配置文件列表失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def create_profile_details(self):
|
|
"""创建配置文件详情区域"""
|
|
try:
|
|
# 配置文件详情框
|
|
self.details_frame = ctk.CTkFrame(self)
|
|
self.details_frame.grid(row=1, column=2, columnspan=2, padx=10, pady=10, sticky="nsew")
|
|
self.details_frame.grid_columnconfigure(0, weight=1)
|
|
self.details_frame.grid_rowconfigure(2, weight=1)
|
|
|
|
# 详情标题
|
|
details_label = ctk.CTkLabel(self.details_frame, text="配置详情", anchor="w")
|
|
details_label.grid(row=0, column=0, padx=10, pady=5, sticky="w")
|
|
|
|
# 详情内容
|
|
self.profile_details_label = ctk.CTkLabel(
|
|
self.details_frame,
|
|
text="未选择配置文件",
|
|
justify="left",
|
|
anchor="w"
|
|
)
|
|
self.profile_details_label.grid(row=1, column=0, padx=10, pady=5, sticky="w")
|
|
|
|
# 运行状态
|
|
self.status_label = ctk.CTkLabel(
|
|
self.details_frame,
|
|
text="状态: 未运行",
|
|
text_color="gray"
|
|
)
|
|
self.status_label.grid(row=2, column=0, padx=10, pady=5, sticky="w")
|
|
except Exception as e:
|
|
logger.error(f"创建配置文件详情区域失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def create_control_buttons(self):
|
|
"""创建控制按钮区域"""
|
|
try:
|
|
# 控制按钮区域
|
|
self.control_frame = ctk.CTkFrame(self)
|
|
self.control_frame.grid(row=2, column=0, columnspan=4, padx=10, pady=10, sticky="ew")
|
|
self.control_frame.grid_columnconfigure((0, 1, 2, 3), weight=1, uniform="equal")
|
|
|
|
# 创建配置按钮
|
|
self.create_button = ctk.CTkButton(
|
|
self.control_frame,
|
|
text="新建配置",
|
|
command=self.create_profile
|
|
)
|
|
self.create_button.grid(row=0, column=0, padx=10, pady=10, sticky="ew")
|
|
|
|
# 删除配置按钮
|
|
self.delete_button = ctk.CTkButton(
|
|
self.control_frame,
|
|
text="删除配置",
|
|
command=self.delete_profile
|
|
)
|
|
self.delete_button.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
|
|
|
|
# 启动浏览器按钮
|
|
self.launch_button = ctk.CTkButton(
|
|
self.control_frame,
|
|
text="启动浏览器",
|
|
command=self.launch_browser
|
|
)
|
|
self.launch_button.grid(row=0, column=2, padx=10, pady=10, sticky="ew")
|
|
|
|
# 关闭浏览器按钮
|
|
self.close_button = ctk.CTkButton(
|
|
self.control_frame,
|
|
text="关闭浏览器",
|
|
command=self.close_browser
|
|
)
|
|
self.close_button.grid(row=0, column=3, padx=10, pady=10, sticky="ew")
|
|
except Exception as e:
|
|
logger.error(f"创建控制按钮区域失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def create_multi_browser_controls(self):
|
|
"""创建多浏览器控制区域"""
|
|
try:
|
|
# 多浏览器控制区域
|
|
self.multi_browser_frame = ctk.CTkFrame(self)
|
|
self.multi_browser_frame.grid(row=3, column=0, columnspan=4, padx=10, pady=10, sticky="ew")
|
|
self.multi_browser_frame.grid_columnconfigure((0, 1, 2), weight=1, uniform="equal")
|
|
|
|
# 标题
|
|
multi_title = ctk.CTkLabel(
|
|
self.multi_browser_frame,
|
|
text="多浏览器控制",
|
|
font=ctk.CTkFont(size=14, weight="bold")
|
|
)
|
|
multi_title.grid(row=0, column=0, columnspan=3, padx=10, pady=(10, 5), sticky="w")
|
|
|
|
# 启动全部浏览器按钮
|
|
self.launch_all_button = ctk.CTkButton(
|
|
self.multi_browser_frame,
|
|
text="启动全部浏览器",
|
|
command=self.launch_all_browsers
|
|
)
|
|
self.launch_all_button.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
|
|
|
|
# 关闭全部浏览器按钮
|
|
self.close_all_button = ctk.CTkButton(
|
|
self.multi_browser_frame,
|
|
text="关闭全部浏览器",
|
|
command=self.close_all_browsers
|
|
)
|
|
self.close_all_button.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
|
|
|
|
# 浏览器状态指示
|
|
self.browser_status = ctk.CTkLabel(
|
|
self.multi_browser_frame,
|
|
text="浏览器: 0/0 运行中",
|
|
font=ctk.CTkFont(size=14)
|
|
)
|
|
self.browser_status.grid(row=1, column=2, padx=10, pady=10)
|
|
except Exception as e:
|
|
logger.error(f"创建多浏览器控制区域失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def refresh_profiles(self):
|
|
"""刷新配置文件列表"""
|
|
try:
|
|
profiles = self.browser_manager.get_profiles()
|
|
|
|
if not profiles:
|
|
self.profile_listbox.configure(values=["无可用配置"])
|
|
self.profile_listbox.set("无可用配置")
|
|
self.current_profile = None
|
|
return
|
|
|
|
# 更新下拉框
|
|
self.profile_listbox.configure(values=profiles)
|
|
|
|
# 如果当前选择的配置不在列表中,重置选择
|
|
if self.current_profile not in profiles:
|
|
self.profile_listbox.set(profiles[0])
|
|
self.current_profile = profiles[0]
|
|
self.on_profile_selected(profiles[0])
|
|
else:
|
|
self.profile_listbox.set(self.current_profile)
|
|
|
|
# 更新浏览器状态
|
|
self.update_browser_status()
|
|
|
|
logger.debug(f"已刷新配置文件列表,共 {len(profiles)} 个配置")
|
|
except Exception as e:
|
|
logger.error(f"刷新配置文件列表失败: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def update_browser_status(self):
|
|
"""更新浏览器状态显示"""
|
|
try:
|
|
total_profiles = len(self.browser_manager.get_profiles())
|
|
running_browsers = len(self.browser_manager.browsers)
|
|
self.browser_status.configure(text=f"浏览器: {running_browsers}/{total_profiles} 运行中")
|
|
|
|
# 更新当前选择的配置文件的状态
|
|
if self.current_profile:
|
|
if self.current_profile in self.browser_manager.browsers:
|
|
self.status_label.configure(text="状态: 运行中", text_color="green")
|
|
else:
|
|
self.status_label.configure(text="状态: 未运行", text_color="gray")
|
|
except Exception as e:
|
|
logger.error(f"更新浏览器状态失败: {str(e)}")
|
|
|
|
def on_profile_selected(self, profile_name):
|
|
"""当用户选择配置文件时触发"""
|
|
try:
|
|
if not profile_name or profile_name == "无可用配置":
|
|
self.current_profile = None
|
|
self.profile_details_label.configure(text="未选择配置文件")
|
|
return
|
|
|
|
self.current_profile = profile_name
|
|
logger.info(f"选择了配置文件: {profile_name}")
|
|
|
|
# 更新配置文件详情
|
|
profile_details = self.browser_manager.get_profile_details(profile_name) or {}
|
|
details_text = f"用户目录: {profile_details.get('user_data_dir', '无')}\n"
|
|
details_text += f"上次使用: {profile_details.get('last_used', '从未')}"
|
|
self.profile_details_label.configure(text=details_text)
|
|
|
|
# 更新状态显示
|
|
if profile_name in self.browser_manager.browsers:
|
|
self.status_label.configure(text="状态: 运行中", text_color="green")
|
|
else:
|
|
self.status_label.configure(text="状态: 未运行", text_color="gray")
|
|
except Exception as e:
|
|
logger.error(f"选择配置文件时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
def create_profile(self):
|
|
"""创建新的配置文件"""
|
|
try:
|
|
# 创建对话框
|
|
dialog = ctk.CTkInputDialog(
|
|
text="请输入新配置文件名称:",
|
|
title="创建新配置文件"
|
|
)
|
|
profile_name = dialog.get_input()
|
|
|
|
if not profile_name:
|
|
return
|
|
|
|
if self.browser_manager.create_profile(profile_name):
|
|
logger.info(f"创建了新配置文件: {profile_name}")
|
|
self.refresh_profiles()
|
|
if self.on_profile_change:
|
|
self.on_profile_change()
|
|
|
|
# 选择新创建的配置文件
|
|
self.profile_listbox.set(profile_name)
|
|
self.current_profile = profile_name
|
|
self.on_profile_selected(profile_name)
|
|
else:
|
|
messagebox.showerror("错误", f"无法创建配置文件: {profile_name}")
|
|
except Exception as e:
|
|
logger.error(f"创建配置文件时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror("错误", f"创建配置文件时出错: {str(e)}")
|
|
|
|
def delete_profile(self):
|
|
"""删除选定的配置文件"""
|
|
try:
|
|
if not self.current_profile or self.current_profile == "无可用配置":
|
|
messagebox.showwarning("警告", "请先选择要删除的配置文件")
|
|
return
|
|
|
|
# 确认删除
|
|
if not messagebox.askyesno("确认删除", f"确定要删除配置文件 '{self.current_profile}' 吗?"):
|
|
return
|
|
|
|
if self.browser_manager.delete_profile(self.current_profile):
|
|
logger.info(f"删除了配置文件: {self.current_profile}")
|
|
# 重置当前选择
|
|
self.current_profile = None
|
|
self.refresh_profiles()
|
|
if self.on_profile_change:
|
|
self.on_profile_change()
|
|
else:
|
|
messagebox.showerror("错误", f"无法删除配置文件: {self.current_profile}")
|
|
except Exception as e:
|
|
logger.error(f"删除配置文件时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror("错误", f"删除配置文件时出错: {str(e)}")
|
|
|
|
def launch_browser(self):
|
|
"""启动选定配置的浏览器"""
|
|
try:
|
|
if not self.current_profile or self.current_profile == "无可用配置":
|
|
messagebox.showwarning("警告", "请先选择配置文件")
|
|
return
|
|
|
|
# 检查是否已经启动
|
|
if self.current_profile in self.browser_manager.browsers:
|
|
messagebox.showinfo("提示", f"配置 '{self.current_profile}' 的浏览器已在运行")
|
|
return
|
|
|
|
# 显示进度对话框
|
|
progress_window = ctk.CTkToplevel(self)
|
|
progress_window.title("启动浏览器")
|
|
progress_window.geometry("300x150")
|
|
progress_window.transient(self) # 设置为模态
|
|
progress_window.grab_set()
|
|
|
|
# 添加进度条
|
|
label = ctk.CTkLabel(progress_window, text=f"正在启动浏览器 '{self.current_profile}'...")
|
|
label.pack(pady=(20, 10))
|
|
|
|
progress = ctk.CTkProgressBar(progress_window, width=250)
|
|
progress.pack(pady=10)
|
|
progress.set(0.5) # 不确定模式
|
|
|
|
# 定义回调函数
|
|
def on_browser_launch_complete(browser, page, success):
|
|
progress_window.after(500, progress_window.destroy) # 延迟关闭,避免闪烁
|
|
|
|
if success:
|
|
logger.info(f"浏览器 '{self.current_profile}' 启动成功")
|
|
self.update_browser_status()
|
|
self.status_label.configure(text="状态: 运行中", text_color="green")
|
|
else:
|
|
logger.error(f"浏览器 '{self.current_profile}' 启动失败")
|
|
messagebox.showerror("错误", f"启动浏览器失败: {self.current_profile}")
|
|
|
|
# 启动浏览器
|
|
self.browser_manager.launch_browser(
|
|
self.current_profile,
|
|
headless=False,
|
|
on_complete=on_browser_launch_complete
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"启动浏览器时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror("错误", f"启动浏览器时出错: {str(e)}")
|
|
|
|
def close_browser(self):
|
|
"""关闭选定配置的浏览器"""
|
|
try:
|
|
if not self.current_profile or self.current_profile == "无可用配置":
|
|
messagebox.showwarning("警告", "请先选择配置文件")
|
|
return
|
|
|
|
# 检查是否已经启动
|
|
if self.current_profile not in self.browser_manager.browsers:
|
|
messagebox.showinfo("提示", f"配置 '{self.current_profile}' 的浏览器未在运行")
|
|
return
|
|
|
|
# 关闭浏览器
|
|
self.browser_manager._close_browser(self.current_profile)
|
|
logger.info(f"关闭了浏览器: {self.current_profile}")
|
|
|
|
# 更新状态
|
|
self.update_browser_status()
|
|
self.status_label.configure(text="状态: 未运行", text_color="gray")
|
|
|
|
except Exception as e:
|
|
logger.error(f"关闭浏览器时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror("错误", f"关闭浏览器时出错: {str(e)}")
|
|
|
|
def launch_all_browsers(self):
|
|
"""启动所有配置的浏览器"""
|
|
try:
|
|
profiles = self.browser_manager.get_profiles()
|
|
if not profiles:
|
|
messagebox.showinfo("提示", "没有可用的配置文件")
|
|
return
|
|
|
|
# 过滤掉已经启动的浏览器
|
|
profiles_to_launch = [p for p in profiles if p not in self.browser_manager.browsers]
|
|
|
|
if not profiles_to_launch:
|
|
messagebox.showinfo("提示", "所有浏览器都已经在运行")
|
|
return
|
|
|
|
# 显示进度对话框
|
|
progress_window = ctk.CTkToplevel(self)
|
|
progress_window.title("启动多个浏览器")
|
|
progress_window.geometry("400x200")
|
|
progress_window.transient(self)
|
|
progress_window.grab_set()
|
|
|
|
# 添加进度信息
|
|
label = ctk.CTkLabel(
|
|
progress_window,
|
|
text=f"正在启动 {len(profiles_to_launch)} 个浏览器...",
|
|
font=ctk.CTkFont(size=16)
|
|
)
|
|
label.pack(pady=(20, 10))
|
|
|
|
# 创建状态文本框
|
|
status_text = ctk.CTkTextbox(progress_window, height=100, width=350)
|
|
status_text.pack(pady=10, padx=20)
|
|
status_text.insert("0.0", "准备启动浏览器...\n")
|
|
|
|
# 已完成计数
|
|
completed_count = [0]
|
|
|
|
# 定义单个浏览器完成回调
|
|
def on_browser_launch(browser, page, success, profile):
|
|
completed_count[0] += 1
|
|
status_text.configure(state="normal")
|
|
|
|
if success:
|
|
status_text.insert("end", f"✅ 浏览器 '{profile}' 启动成功\n")
|
|
self.running_profiles.add(profile)
|
|
else:
|
|
status_text.insert("end", f"❌ 浏览器 '{profile}' 启动失败\n")
|
|
|
|
status_text.see("end")
|
|
status_text.configure(state="disabled")
|
|
|
|
# 更新进度信息
|
|
label.configure(text=f"启动进度: {completed_count[0]}/{len(profiles_to_launch)}")
|
|
|
|
# 更新浏览器状态
|
|
self.update_browser_status()
|
|
|
|
# 全部完成回调
|
|
def on_all_complete(results):
|
|
progress_window.after(2000, progress_window.destroy) # 延迟关闭,让用户看到结果
|
|
|
|
# 计算成功和失败的数量
|
|
success_count = sum(1 for b, p, s in results.values() if s)
|
|
fail_count = len(results) - success_count
|
|
|
|
if fail_count == 0:
|
|
messagebox.showinfo("完成", f"成功启动了 {success_count} 个浏览器")
|
|
else:
|
|
messagebox.showwarning("完成", f"启动了 {success_count} 个浏览器,{fail_count} 个失败")
|
|
|
|
# 更新当前选择的配置文件状态
|
|
self.on_profile_selected(self.current_profile)
|
|
|
|
# 启动所有浏览器
|
|
self.browser_manager.launch_multiple_browsers(
|
|
profiles_to_launch,
|
|
headless=False,
|
|
on_each_complete=on_browser_launch,
|
|
on_all_complete=on_all_complete
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"启动所有浏览器时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror("错误", f"启动所有浏览器时出错: {str(e)}")
|
|
|
|
def close_all_browsers(self):
|
|
"""关闭所有浏览器"""
|
|
try:
|
|
# 检查是否有浏览器在运行
|
|
if not self.browser_manager.browsers:
|
|
messagebox.showinfo("提示", "没有正在运行的浏览器")
|
|
return
|
|
|
|
# 确认关闭
|
|
if not messagebox.askyesno("确认", "确定要关闭所有浏览器吗?"):
|
|
return
|
|
|
|
# 关闭所有浏览器
|
|
self.browser_manager.close_all_browsers()
|
|
logger.info("关闭了所有浏览器")
|
|
|
|
# 清空运行中的浏览器集合
|
|
self.running_profiles.clear()
|
|
|
|
# 更新状态
|
|
self.update_browser_status()
|
|
self.status_label.configure(text="状态: 未运行", text_color="gray")
|
|
|
|
except Exception as e:
|
|
logger.error(f"关闭所有浏览器时出错: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
messagebox.showerror("错误", f"关闭所有浏览器时出错: {str(e)}")
|