抽人软件v3


基于Python 3 Tkinter开发的一款多名单加密抽人软件——自动抽人v3.0版 base加密v1.0版

下载链接:蓝奏云 免登录 不限速 密码:e4vc

使用提示:

1.请解压使用 双击运行exe

2.目录中nameinfo文件夹里面存放的是名单加密文件,删除后运行软件自动生成。

3.目录中_internal文件夹里面存放的是加密运行库,删除后程序无法正常运行。

4.加密名单文件一旦被篡改,自动重置。

##公示源代码:
import tkinter as tk
from tkinter import ttk, messagebox
import random
import os
import time
import base64
import shutil

##Ky1n编写  Written By Ky1n
##BASE64加密部分由Deepseek-R1辅助编写  The BASE64 Auxiliary Preparation By DeepSeek-R1 Large Model
##注释由AI生成  AI-Generated Code Comments
##变量名由AI规范  AI-Standardized Variable Names
##Windows可执行程序由py-to-exe打包  Windows EXE Via py-to-exe

class RandomPickerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("抽人v3 拖此处可移动")
        
        # 设置主界面大小为300x200
        self.root.geometry("300x200")
        self.root.minsize(300, 200)
        
        # 初始化名单
        self.lists = {}
        self.current_list = None
        self.topmost = False
        self.hidden = False
        
        # 确保nameinfo目录存在
        if not os.path.exists("nameinfo"):
            os.makedirs("nameinfo")
        
        # 创建主界面
        self.create_main_interface()
        
        # 加载名单
        if not self.load_lists():
            # 如果加载失败,显示错误窗口
            self.show_decryption_error_dialog()
        
        # 置顶按钮状态
        self.update_top_button()
        
        # 使窗口可拖动
        self.root.bind("<ButtonPress-1>", self.start_move)
        self.root.bind("<B1-Motion>", self.do_move)
    
    def create_main_interface(self):
        # 主框架
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 结果显示 - 使用更大的空间
        result_frame = ttk.Frame(main_frame)
        result_frame.pack(fill=tk.BOTH, expand=True)
        
        self.result_label = ttk.Label(
            result_frame, 
            text="请选择名单", 
            font=("Arial", 32, "bold"),
            foreground="blue",
            anchor=tk.CENTER,
            justify=tk.CENTER
        )
        self.result_label.pack(fill=tk.BOTH, expand=True)
        
        # 底部控制区域
        control_frame = ttk.Frame(main_frame)
        control_frame.pack(fill=tk.X, pady=(5, 0))
        
        # 名单选择放在底部
        list_frame = ttk.Frame(control_frame)
        list_frame.pack(fill=tk.X, pady=2)
        
        self.list_var = tk.StringVar()
        self.list_combobox = ttk.Combobox(list_frame, textvariable=self.list_var, state="readonly", width=15)
        self.list_combobox.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
        self.list_combobox.bind("<<ComboboxSelected>>", self.on_list_selected)
        
        # 按钮区域
        button_frame = ttk.Frame(control_frame)
        button_frame.pack(fill=tk.X, pady=2)
        
        # 使用网格布局按钮
        self.pick_button = ttk.Button(
            button_frame, 
            text="抽取", 
            command=self.pick_random,
            width=6
        )
        self.pick_button.grid(row=0, column=0, padx=2)
        
        self.top_button = ttk.Button(
            button_frame, 
            text="置顶", 
            command=self.toggle_topmost,
            width=6
        )
        self.top_button.grid(row=0, column=1, padx=2)
        
        edit_button = ttk.Button(
            button_frame, 
            text="编辑", 
            command=self.open_edit_window,
            width=6
        )
        edit_button.grid(row=0, column=2, padx=2)
        
        # 隐藏按钮
        hide_button = ttk.Button(
            button_frame, 
            text="隐藏", 
            command=self.hide_window,
            width=6
        )
        hide_button.grid(row=0, column=3, padx=2)
        
        # 配置按钮框架的列权重
        button_frame.columnconfigure(0, weight=1)
        button_frame.columnconfigure(1, weight=1)
        button_frame.columnconfigure(2, weight=1)
        button_frame.columnconfigure(3, weight=1)
    
    def start_move(self, event):
        # 允许在窗口任意位置拖动
        self.x = event.x
        self.y = event.y
    
    def do_move(self, event):
        deltax = event.x - self.x
        deltay = event.y - self.y
        x = self.root.winfo_x() + deltax
        y = self.root.winfo_y() + deltay
        self.root.geometry(f"+{x}+{y}")
    
    @staticmethod
    def encrypt_data(data):
        """加密数据"""
        # 使用简单的异或加密
        key = "Ky1nSecretKey"  # 加密密钥
        encrypted = bytearray()
        for i, char in enumerate(data.encode('utf-8')):
            encrypted.append(char ^ ord(key[i % len(key)]))
        return base64.b64encode(encrypted).decode('utf-8')
    
    @staticmethod
    def decrypt_data(data):
        """解密数据"""
        try:
            encrypted = base64.b64decode(data.encode('utf-8'))
            key = "Ky1nSecretKey"  # 加密密钥
            decrypted = bytearray()
            for i, char in enumerate(encrypted):
                decrypted.append(char ^ ord(key[i % len(key)]))
            return decrypted.decode('utf-8')
        except:
            return None
    
    def load_lists(self):
        """加载名单文件,返回是否成功"""
        self.lists = {}
        list_names = []
        decryption_failed = False
        
        for i in range(1, 9):
            filename = f"nameinfo/name.info{i}"
            if os.path.exists(filename):
                try:
                    with open(filename, "r", encoding="utf-8") as f:
                        encrypted_content = f.read()
                    
                    # 解密内容
                    decrypted_content = RandomPickerApp.decrypt_data(encrypted_content)
                    if decrypted_content is None:
                        print(f"解密名单 {filename} 失败")
                        decryption_failed = True
                        continue
                    
                    lines = decrypted_content.splitlines()
                    
                    if len(lines) >= 2:
                        list_name = lines[0].strip()
                        try:
                            count = int(lines[1].strip())
                        except ValueError:
                            count = 0
                        
                        names = [line.strip() for line in lines[2:2+count] if line.strip()]
                        self.lists[filename] = {
                            "name": list_name,
                            "count": count,
                            "names": names
                        }
                        list_names.append(list_name)
                except Exception as e:
                    print(f"加载名单 {filename} 出错: {e}")
                    decryption_failed = True
        
        # 更新下拉菜单
        self.list_combobox["values"] = list_names
        if list_names:
            self.list_var.set(list_names[0])
            self.on_list_selected()
        
        # 如果有解密失败的文件,返回False
        return not decryption_failed
    
    def show_decryption_error_dialog(self):
        """显示解密错误对话框"""
        # 创建错误对话框
        error_dialog = tk.Toplevel(self.root)
        error_dialog.title("数据错误")
        error_dialog.geometry("300x150")
        error_dialog.resizable(False, False)
        error_dialog.transient(self.root)
        error_dialog.grab_set()
        
        # 居中显示
        error_dialog.update_idletasks()
        x = (error_dialog.winfo_screenwidth() - error_dialog.winfo_width()) // 2
        y = (error_dialog.winfo_screenheight() - error_dialog.winfo_height()) // 2
        error_dialog.geometry(f"+{x}+{y}")
        
        # 错误信息
        error_label = ttk.Label(
            error_dialog, 
            text="数据文件已损坏或已被篡改!\n请选择操作:",
            font=("Arial", 10),
            justify=tk.CENTER
        )
        error_label.pack(pady=20)
        
        # 按钮框架
        button_frame = ttk.Frame(error_dialog)
        button_frame.pack(pady=10)
        
        # 退出按钮
        exit_button = ttk.Button(
            button_frame, 
            text="退出程序", 
            command=lambda: self.exit_program(error_dialog),
            width=12
        )
        exit_button.pack(side=tk.LEFT, padx=10)
        
        # 重置按钮
        reset_button = ttk.Button(
            button_frame, 
            text="重置名单", 
            command=lambda: self.reset_lists(error_dialog),
            width=12
        )
        reset_button.pack(side=tk.LEFT, padx=10)
    
    def exit_program(self, dialog):
        """退出程序"""
        if dialog:
            dialog.destroy()
        self.root.quit()
        self.root.destroy()
    
    def reset_lists(self, dialog):
        """重置名单文件"""
        try:
            # 备份原文件(如果存在)
            if os.path.exists("nameinfo"):
                backup_dir = "nameinfo_backup_" + time.strftime("%Y%m%d_%H%M%S")
                shutil.copytree("nameinfo", backup_dir)
            
            # 删除原目录
            if os.path.exists("nameinfo"):
                shutil.rmtree("nameinfo")
            
            # 重新创建目录和示例文件
            os.makedirs("nameinfo")
            self.create_sample_files()
            
            # 重新加载名单
            if self.load_lists():
                if dialog:
                    dialog.destroy()
                messagebox.showinfo("成功", "名单已重置为默认值。")
            else:
                messagebox.showerror("错误", "重置名单失败,请重试。")
        except Exception as e:
            messagebox.showerror("错误", f"重置名单时出错: {str(e)}")
    
    def create_sample_files(self):
        """创建示例名单文件"""
        for i in range(1, 9):
            filename = f"nameinfo/name.info{i}"
            # 构建文件内容
            content = f"名单{i}\n5\n"
            for j in range(1, 6):
                content += f"人员{i}-{j}\n"
            
            # 使用静态方法加密
            encrypted_content = RandomPickerApp.encrypt_data(content)
            
            with open(filename, "w", encoding="utf-8") as f:
                f.write(encrypted_content)
    
    def on_list_selected(self, event=None):
        selected_name = self.list_var.get()
        for filename, data in self.lists.items():
            if data["name"] == selected_name:
                self.current_list = filename
                break
        else:
            self.current_list = None
    
    def pick_random(self):
        if not self.current_list:
            return
        
        list_data = self.lists.get(self.current_list)
        if not list_data:
            return
        
        if list_data["count"] <= 0:
            self.result_label.config(text="Error", foreground="red")
            return
        
        if not list_data["names"]:
            self.result_label.config(text="名单为空", foreground="orange")
            return
        
        # 禁用抽取按钮
        self.pick_button.config(state=tk.DISABLED)
        
        # 动画效果:快速抽取20次
        original_names = list_data["names"]
        for i in range(20):
            # 随机选择一个人
            selected = random.choice(original_names)
            self.result_label.config(text=selected, foreground="purple")
            self.root.update()
            time.sleep(0.04)  # 间隔0.04秒
        
        # 最终选择一个人
        selected = random.choice(original_names)
        self.result_label.config(text=selected, foreground="green")
        
        # 启用抽取按钮
        self.pick_button.config(state=tk.NORMAL)
    
    def toggle_topmost(self):
        self.topmost = not self.topmost
        self.root.attributes("-topmost", self.topmost)
        self.update_top_button()
    
    def update_top_button(self):
        text = "取消置顶" if self.topmost else "置顶"
        self.top_button.config(text=text)
    
    def open_edit_window(self):
        EditWindow(self)
    
    def hide_window(self):
        # 获取窗口位置
        x = self.root.winfo_x()
        y = self.root.winfo_y()
        screen_width = self.root.winfo_screenwidth()
        
        # 判断窗口在屏幕左侧还是右侧
        if x < screen_width / 2:
            # 左侧
            position = (0, y)
        else:
            # 右侧
            position = (screen_width - 50, y)  # 增加宽度确保显示完整
        
        # 隐藏主窗口
        self.root.withdraw()
        self.hidden = True
        
        # 创建美观的抽人按钮
        self.create_beautiful_button(position)
    
    def create_beautiful_button(self, position):
        self.compact_button_window = tk.Toplevel()
        self.compact_button_window.overrideredirect(True)  # 无边框
        self.compact_button_window.attributes("-topmost", True)
        
        # 设置位置和大小(增加高度确保文字完整显示)
        self.compact_button_window.geometry(f"50x60+{position[0]}+{position[1]}")
        
        # 设置背景色
        bg_color = "#f0f0f0"  # 浅灰色背景
        
        # 创建主框架 - 用于实现圆角效果
        main_frame = tk.Frame(
            self.compact_button_window, 
            bg=bg_color,
            highlightbackground="#c0c0c0",  # 边框颜色
            highlightthickness=1  # 边框厚度
        )
        main_frame.pack(fill=tk.BOTH, expand=True, padx=1, pady=1)
        
        # 创建Canvas用于绘制圆角
        canvas = tk.Canvas(
            main_frame,
            width=48,  # 比框架小2像素
            height=58,  # 比框架小2像素
            bg=bg_color,
            highlightthickness=0  # 无边框
        )
        canvas.pack(fill=tk.BOTH, expand=True)
        
        # 绘制圆角矩形
        canvas.create_rounded_rectangle(
            0, 0, 48, 58, 
            radius=10,  # 圆角半径
            fill=bg_color,
            outline="#c0c0c0"
        )
        
        # 创建紧凑布局:"抽"在上,"人"在下
        top_label = tk.Label(
            canvas,
            text="抽",
            font=("Arial", 14, "bold"),
            anchor=tk.CENTER,
            bg=bg_color,
            fg="#333333"  # 深灰色文字
        )
        # 放置标签 - 确保位置居中
        top_label.place(relx=0.5, rely=0.3, anchor=tk.CENTER)  # 上移位置
        
        bottom_label = tk.Label(
            canvas,
            text="人",
            font=("Arial", 14, "bold"),
            anchor=tk.CENTER,
            bg=bg_color,
            fg="#333333"  # 深灰色文字
        )
        # 放置标签 - 确保位置居中
        bottom_label.place(relx=0.5, rely=0.7, anchor=tk.CENTER)  # 下移位置
        
        # 为整个窗口绑定点击事件
        self.compact_button_window.bind("<Button-1>", lambda e: self.show_main_window())
        
        # 添加悬停效果
        def on_enter(e):
            canvas.config(bg="#e0e0e0")
            top_label.config(bg="#e0e0e0")
            bottom_label.config(bg="#e0e0e0")
            # 重绘圆角矩形
            canvas.delete("all")
            canvas.create_rounded_rectangle(
                0, 0, 48, 58, 
                radius=10,
                fill="#e0e0e0",
                outline="#c0c0c0"
            )
            # 重新放置标签
            top_label.place(relx=0.5, rely=0.3, anchor=tk.CENTER)
            bottom_label.place(relx=0.5, rely=0.7, anchor=tk.CENTER)
        
        def on_leave(e):
            canvas.config(bg=bg_color)
            top_label.config(bg=bg_color)
            bottom_label.config(bg=bg_color)
            # 重绘圆角矩形
            canvas.delete("all")
            canvas.create_rounded_rectangle(
                0, 0, 48, 58, 
                radius=10,
                fill=bg_color,
                outline="#c0c0c0"
            )
            # 重新放置标签
            top_label.place(relx=0.5, rely=0.3, anchor=tk.CENTER)
            bottom_label.place(relx=0.5, rely=0.7, anchor=tk.CENTER)
        
        self.compact_button_window.bind("<Enter>", on_enter)
        self.compact_button_window.bind("<Leave>", on_leave)
    
    def show_main_window(self):
        # 销毁紧凑按钮窗口
        self.compact_button_window.destroy()
        
        # 显示主窗口
        self.root.deiconify()
        self.hidden = False
    
    def save_list(self, filename, list_name, names):
        count = len(names)
        # 构建文件内容
        content = f"{list_name}\n{count}\n"
        content += "\n".join(names)
        
        # 加密内容
        encrypted_content = RandomPickerApp.encrypt_data(content)
        
        with open(filename, "w", encoding="utf-8") as f:
            f.write(encrypted_content)
        
        # 重新加载名单
        self.load_lists()
        
        # 更新当前选择
        if self.current_list == filename:
            self.list_var.set(list_name)


# 为Canvas添加绘制圆角矩形的方法
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=25, **kwargs):
    points = [
        x1 + radius, y1,
        x1 + radius, y1,
        x2 - radius, y1,
        x2 - radius, y1,
        x2, y1,
        x2, y1 + radius,
        x2, y1 + radius,
        x2, y2 - radius,
        x2, y2 - radius,
        x2, y2,
        x2 - radius, y2,
        x2 - radius, y2,
        x1 + radius, y2,
        x1 + radius, y2,
        x1, y2,
        x1, y2 - radius,
        x1, y2 - radius,
        x1, y1 + radius,
        x1, y1 + radius,
        x1, y1,
        x1 + radius, y1
    ]
    
    return self.create_polygon(points, **kwargs, smooth=True)

# 将方法添加到Canvas类
tk.Canvas.create_rounded_rectangle = create_rounded_rectangle


class EditWindow:
    def __init__(self, app):
        self.app = app
        self.window = tk.Toplevel(app.root)
        self.window.title("编辑名单")
        
        # 设置编辑界面大小为100x300
        self.window.geometry("100x300")
        self.window.minsize(100, 300)
        
        self.window.transient(app.root)
        self.window.grab_set()
        
        # 创建笔记本式标签页
        self.notebook = ttk.Notebook(self.window)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
        
        # 为每个名单创建标签页
        self.tabs = {}
        self.list_vars = {}
        self.text_widgets = {}
        
        for i in range(1, 9):
            filename = f"nameinfo/name.info{i}"
            tab = ttk.Frame(self.notebook)
            self.notebook.add(tab, text=str(i))  # 只显示数字标签节省空间
            self.tabs[filename] = tab
            
            # 名单名称编辑
            name_frame = ttk.Frame(tab)
            name_frame.pack(fill=tk.X, padx=2, pady=2)
            
            list_var = tk.StringVar()
            self.list_vars[filename] = list_var
            
            name_entry = ttk.Entry(name_frame, textvariable=list_var, width=8)
            name_entry.pack(fill=tk.X, padx=2)
            
            # 添加提示标签
            tip_label = ttk.Label(
                tab, 
                text="一行一个名字", 
                font=("Arial", 6),  # 使用非常小的字体
                foreground="gray",
                anchor=tk.CENTER
            )
            tip_label.pack(fill=tk.X, padx=2, pady=(0, 2))
            
            # 名单内容编辑
            text_frame = ttk.Frame(tab)
            text_frame.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
            
            scrollbar = ttk.Scrollbar(text_frame)
            scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
            
            text_widget = tk.Text(
                text_frame, 
                wrap=tk.WORD, 
                yscrollcommand=scrollbar.set,
                font=("Arial", 8),  # 使用更小的字体
                height=8
            )
            text_widget.pack(fill=tk.BOTH, expand=True)
            
            scrollbar.config(command=text_widget.yview)
            self.text_widgets[filename] = text_widget
            
            # 按钮区域
            button_frame = ttk.Frame(tab)
            button_frame.pack(fill=tk.X, padx=2, pady=2)
            
            save_button = ttk.Button(
                button_frame, 
                text="保存", 
                command=lambda f=filename: self.save_list(f),
                width=5
            )
            save_button.pack(side=tk.LEFT, padx=1)
            
            clear_button = ttk.Button(
                button_frame, 
                text="清空", 
                command=lambda f=filename: self.clear_list(f),
                width=5
            )
            clear_button.pack(side=tk.LEFT, padx=1)
        
        # 加载数据到界面
        self.load_data()
        
        # 设置关闭窗口事件
        self.window.protocol("WM_DELETE_WINDOW", self.on_close)
    
    def load_data(self):
        for filename, tab in self.tabs.items():
            if filename in self.app.lists:
                data = self.app.lists[filename]
                self.list_vars[filename].set(data["name"])
                
                text_widget = self.text_widgets[filename]
                text_widget.delete(1.0, tk.END)
                text_widget.insert(tk.END, "\n".join(data["names"]))
            else:
                self.list_vars[filename].set(f"名单{filename[-1]}")
                self.text_widgets[filename].delete(1.0, tk.END)
    
    def save_list(self, filename):
        list_name = self.list_vars[filename].get()
        if not list_name:
            return
        
        text_content = self.text_widgets[filename].get(1.0, tk.END)
        names = [name.strip() for name in text_content.splitlines() if name.strip()]
        
        self.app.save_list(filename, list_name, names)
    
    def clear_list(self, filename):
        if messagebox.askyesno("确认", "确定要清空此名单吗?"):
            self.text_widgets[filename].delete(1.0, tk.END)
    
    def on_close(self):
        self.window.grab_release()
        self.window.destroy()


if __name__ == "__main__":
    # 确保nameinfo目录存在
    if not os.path.exists("nameinfo"):
        os.makedirs("nameinfo")
    
    # 创建示例名单文件(如果不存在)
    for i in range(1, 9):
        filename = f"nameinfo/name.info{i}"
        if not os.path.exists(filename):
            # 构建文件内容
            content = f"名单{i}\n5\n"
            for j in range(1, 6):
                content += f"人员{i}-{j}\n"
            
            # 使用静态方法加密
            encrypted_content = RandomPickerApp.encrypt_data(content)
            
            with open(filename, "w", encoding="utf-8") as f:
                f.write(encrypted_content)
    
    root = tk.Tk()
    app = RandomPickerApp(root)
    root.mainloop()