图例

#!/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 日
如果觉得我的文章对你有用,请随意赞赏