#!/usr/bin/env python3
# wechat_multi_gui.py
"""
微信多账号免密启动 - Tkinter GUI 版本
支持:
- 保存当前账号(同名则覆盖)
- 删除账号
"""
import os
import json
import shutil
import subprocess
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
CONFIG_FILE = Path(__file__).with_name("wechat_cli_config.json")
def load_config():
if CONFIG_FILE.exists():
try:
return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
except Exception:
return {}
return {}
def save_config(cfg: dict):
try:
CONFIG_FILE.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8")
except Exception as e:
messagebox.showerror("错误", f"保存配置文件失败: {e}")
def ensure_backup_dir(config_path):
cfg_parent = Path(config_path).parent
backup_dir = cfg_parent / "config_backup"
backup_dir.mkdir(parents=True, exist_ok=True)
return backup_dir
def list_saved_accounts(backup_dir):
accounts = []
for p in backup_dir.iterdir():
if p.is_dir() and p.name.isdigit():
display = f"账号 {p.name}"
meta = p / "account.meta"
if meta.exists():
try:
j = json.loads(meta.read_text(encoding="utf-8"))
display = j.get("displayName", display)
except Exception:
pass
accounts.append({"id": int(p.name), "folder": p, "displayName": display})
accounts.sort(key=lambda x: x["id"])
return accounts
def save_current_account(config_path, backup_dir, display_name=None):
# 如果同名则覆盖更新
if display_name:
for acc in list_saved_accounts(backup_dir):
if acc["displayName"] == display_name:
shutil.copy2(config_path, acc["folder"] / "config.data")
meta = {"displayName": display_name}
(acc["folder"] / "account.meta").write_text(json.dumps(meta, ensure_ascii=False), encoding="utf-8")
return acc["id"]
# 否则新建
ids = [int(p.name) for p in backup_dir.iterdir() if p.is_dir() and p.name.isdigit()]
new_id = max(ids) + 1 if ids else 1
new_dir = backup_dir / str(new_id)
new_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(config_path, new_dir / "config.data")
meta = {"displayName": display_name or f"账号 {new_id}"}
(new_dir / "account.meta").write_text(json.dumps(meta, ensure_ascii=False), encoding="utf-8")
return new_id
def start_wechat_with_config(exe_path, config_path, backup_dir, account_folder=None):
if not Path(config_path).exists():
raise FileNotFoundError("目标 config.data 不存在")
# 备份当前
shutil.copy2(config_path, backup_dir / "current_config.data")
if account_folder is None:
raise ValueError("account_folder 必须提供")
src = Path(account_folder) / "config.data"
if not src.exists():
raise FileNotFoundError("选定账号的 config.data 丢失")
shutil.copy2(src, config_path)
subprocess.Popen([exe_path], shell=False)
class WeChatGUI:
def __init__(self, root):
self.root = root
self.root.title("微信多账号免密启动")
self.config = load_config()
# 路径输入
tk.Label(root, text="微信 EXE 路径:").grid(row=0, column=0, sticky="e")
self.exe_var = tk.StringVar(value=self.config.get("ExePath", ""))
tk.Entry(root, textvariable=self.exe_var, width=50).grid(row=0, column=1)
tk.Button(root, text="选择", command=self.select_exe).grid(row=0, column=2)
tk.Label(root, text="config.data 路径:").grid(row=1, column=0, sticky="e")
self.config_var = tk.StringVar(value=self.config.get("ConfigPath", ""))
tk.Entry(root, textvariable=self.config_var, width=50).grid(row=1, column=1)
tk.Button(root, text="选择", command=self.select_config).grid(row=1, column=2)
tk.Button(root, text="保存设置", command=self.save_settings).grid(row=2, column=0, columnspan=3, pady=5)
# 账号列表
self.account_listbox = tk.Listbox(root, height=10, width=50)
self.account_listbox.grid(row=3, column=0, columnspan=3, pady=5)
tk.Button(root, text="启动选中账号", command=self.start_selected).grid(row=4, column=0)
tk.Button(root, text="保存当前账号", command=self.save_current).grid(row=4, column=1)
tk.Button(root, text="删除选中账号", command=self.delete_selected).grid(row=4, column=2)
tk.Button(root, text="刷新列表", command=self.refresh_list).grid(row=5, column=0, columnspan=3, pady=5)
self.refresh_list()
def select_exe(self):
path = filedialog.askopenfilename(title="选择 WeChat.exe", filetypes=[("微信程序", "*.exe")])
if path:
self.exe_var.set(path)
def select_config(self):
path = filedialog.askopenfilename(title="选择 config.data", filetypes=[("config.data", "config.data")])
if path:
self.config_var.set(path)
def save_settings(self):
exe_path = self.exe_var.get()
cfg_path = self.config_var.get()
if not Path(exe_path).exists():
messagebox.showerror("错误", "微信 exe 路径无效")
return
if not Path(cfg_path).exists():
messagebox.showerror("错误", "config.data 路径无效")
return
self.config["ExePath"] = exe_path
self.config["ConfigPath"] = cfg_path
save_config(self.config)
messagebox.showinfo("提示", "设置已保存")
def refresh_list(self):
self.account_listbox.delete(0, tk.END)
cfg_path = self.config.get("ConfigPath")
if not cfg_path or not Path(cfg_path).exists():
self.account_listbox.insert(tk.END, "⚠ 请先设置正确的 config.data 路径")
self.accounts = []
return
backup_dir = ensure_backup_dir(cfg_path)
accounts = list_saved_accounts(backup_dir)
for acc in accounts:
self.account_listbox.insert(tk.END, f"{acc['displayName']} (ID: {acc['id']})")
self.accounts = accounts
def start_selected(self):
if not hasattr(self, "accounts") or not self.accounts:
return
sel = self.account_listbox.curselection()
if not sel:
messagebox.showwarning("提示", "请先选择一个账号")
return
idx = sel[0]
account = self.accounts[idx]
try:
start_wechat_with_config(self.config["ExePath"], self.config["ConfigPath"],
ensure_backup_dir(self.config["ConfigPath"]), account["folder"])
messagebox.showinfo("提示", "已启动微信(如无异常会免密登录)")
except Exception as e:
messagebox.showerror("错误", str(e))
def save_current(self):
cfg_path = self.config.get("ConfigPath")
if not cfg_path or not Path(cfg_path).exists():
messagebox.showerror("错误", "config.data 路径无效")
return
name = simpledialog.askstring("保存账号", "输入显示名称(可留空使用默认)")
try:
new_id = save_current_account(cfg_path, ensure_backup_dir(cfg_path), name)
messagebox.showinfo("提示", f"账号已保存/更新(ID: {new_id})")
self.refresh_list()
except Exception as e:
messagebox.showerror("错误", str(e))
def delete_selected(self):
if not hasattr(self, "accounts") or not self.accounts:
return
sel = self.account_listbox.curselection()
if not sel:
messagebox.showwarning("提示", "请先选择一个账号")
return
idx = sel[0]
account = self.accounts[idx]
confirm = messagebox.askyesno("确认删除", f"确定要删除账号 {account['displayName']} 吗?")
if confirm:
try:
shutil.rmtree(account["folder"])
messagebox.showinfo("提示", "账号已删除")
self.refresh_list()
except Exception as e:
messagebox.showerror("错误", str(e))
if __name__ == "__main__":
root = tk.Tk()
app = WeChatGUI(root)
root.mainloop()
最后修改:2025 年 08 月 11 日
© 允许规范转载