import os
import json
import time
import random
import threading
import datetime
import pyautogui
import keyboard
from tkinter import *
from tkinter import ttk, messagebox, filedialog
from tkinter.font import Font
class ActionRecorder:
def __init__(self):
self.recording = False
self.playing = False
self.actions = []
self.current_config = {
"random_interval": 100,
"loop_times": 1,
"script_name": "未命名脚本"
}
self.configs = {}
self.load_configs()
# 创建日志目录
if not os.path.exists("logs"):
os.makedirs("logs")
# 初始化GUI
self.init_gui()
# 设置快捷键
self.setup_hotkeys()
# 鼠标和键盘状态跟踪
self.mouse_state = {
"left": False,
"right": False,
"middle": False
}
self.keyboard_state = {}
# 鼠标和键盘监听器
self.keyboard_hook = None
self.mouse_hook = None
# 忽略的按键
self.ignored_keys = {'f1', 'f2'}
def init_gui(self):
self.root = Tk()
self.root.title("动作录制器 v3.3")
self.root.geometry("800x700")
# 设置字体
bold_font = Font(family="微软雅黑", size=10, weight="bold")
normal_font = Font(family="微软雅黑", size=9)
# 配置区域
config_frame = LabelFrame(self.root, text="配置", padx=10, pady=10, font=bold_font)
config_frame.pack(fill="x", padx=10, pady=5)
# 脚本名称
Label(config_frame, text="脚本名称:", font=normal_font).grid(row=0, column=0, sticky="e", pady=5)
self.script_name_entry = Entry(config_frame, font=normal_font)
self.script_name_entry.grid(row=0, column=1, sticky="we", padx=5, pady=5)
self.script_name_entry.insert(0, self.current_config["script_name"])
# 随机间隔
Label(config_frame, text="随机间隔(ms):", font=normal_font).grid(row=1, column=0, sticky="e", pady=5)
self.interval_entry = Entry(config_frame, font=normal_font)
self.interval_entry.grid(row=1, column=1, sticky="we", padx=5, pady=5)
self.interval_entry.insert(0, str(self.current_config["random_interval"]))
# 循环次数
Label(config_frame, text="循环次数:", font=normal_font).grid(row=2, column=0, sticky="e", pady=5)
self.loop_entry = Entry(config_frame, font=normal_font)
self.loop_entry.grid(row=2, column=1, sticky="we", padx=5, pady=5)
self.loop_entry.insert(0, str(self.current_config["loop_times"]))
# 按钮区域
button_frame = Frame(self.root)
button_frame.pack(fill="x", padx=10, pady=10)
self.record_btn = Button(button_frame, text="开始/停止录制 (F1)", command=self.toggle_recording,
font=bold_font, bg="#4CAF50", fg="white")
self.record_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
self.play_btn = Button(button_frame, text="开始/停止执行 (F2)", command=self.toggle_playing,
font=bold_font, bg="#2196F3", fg="white")
self.play_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
self.save_btn = Button(button_frame, text="保存脚本", command=self.save_script,
font=bold_font, bg="#9C27B0", fg="white")
self.save_btn.pack(side="right", padx=5, ipadx=10, ipady=5)
self.delete_btn = Button(button_frame, text="删除脚本", command=self.delete_script,
font=bold_font, bg="#607D8B", fg="white")
self.delete_btn.pack(side="right", padx=5, ipadx=10, ipady=5)
# 主内容区域
main_frame = Frame(self.root)
main_frame.pack(fill="both", expand=True, padx=10, pady=5)
# 脚本列表
script_frame = LabelFrame(main_frame, text="保存的脚本", padx=10, pady=10, font=bold_font)
script_frame.pack(side="left", fill="y", padx=5, pady=5)
self.script_listbox = Listbox(script_frame, font=normal_font, width=25, height=15)
self.script_listbox.pack(fill="both", expand=True, padx=5, pady=5)
self.script_listbox.bind("<Double-Button-1>", self.load_script)
# 日志区域
log_frame = LabelFrame(main_frame, text="操作日志", padx=10, pady=10, font=bold_font)
log_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)
self.log_text = Text(log_frame, font=normal_font, wrap=WORD)
scrollbar = Scrollbar(log_frame, command=self.log_text.yview)
self.log_text.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
self.log_text.pack(fill="both", expand=True, padx=5, pady=5)
# 状态栏
self.status_var = StringVar()
self.status_var.set("就绪")
status_bar = Label(self.root, textvariable=self.status_var, bd=1, relief=SUNKEN, anchor=W, font=normal_font)
status_bar.pack(fill="x", padx=10, pady=5)
# 更新脚本列表
self.update_script_list()
def setup_hotkeys(self):
keyboard.add_hotkey('f1', self.toggle_recording, suppress=True)
keyboard.add_hotkey('f2', self.toggle_playing, suppress=True)
def toggle_recording(self):
if self.recording:
self.stop_recording()
else:
self.start_recording()
def toggle_playing(self):
if self.playing:
self.stop_playing()
else:
self.start_playing()
def log_message(self, message):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] {message}\n"
self.log_text.insert(END, log_entry)
self.log_text.see(END)
self.status_var.set(message)
# 写入日志文件
today = datetime.datetime.now().strftime("%Y-%m-%d")
log_file = f"logs/{today}.log"
with open(log_file, "a", encoding="utf-8") as f:
f.write(log_entry)
def save_configs(self):
with open("configs.json", "w", encoding="utf-8") as f:
json.dump(self.configs, f, ensure_ascii=False, indent=2)
def load_configs(self):
try:
if os.path.exists("configs.json"):
with open("configs.json", "r", encoding="utf-8") as f:
self.configs = json.load(f)
except Exception as e:
self.log_message(f"加载配置失败: {str(e)}")
self.configs = {}
def update_current_config(self):
try:
self.current_config["script_name"] = self.script_name_entry.get()
self.current_config["random_interval"] = int(self.interval_entry.get())
self.current_config["loop_times"] = int(self.loop_entry.get())
return True
except ValueError:
messagebox.showerror("错误", "请输入有效的数字")
return False
def save_script(self):
if not self.update_current_config():
return
if not self.actions:
messagebox.showwarning("警告", "没有录制的动作可以保存")
return
script_name = self.current_config["script_name"]
self.configs[script_name] = {
"config": self.current_config,
"actions": self.actions
}
self.save_configs()
self.update_script_list()
self.log_message(f"脚本 '{script_name}' 已保存")
def delete_script(self):
selection = self.script_listbox.curselection()
if not selection:
messagebox.showwarning("警告", "请先选择一个脚本")
return
script_name = self.script_listbox.get(selection[0])
if messagebox.askyesno("确认", f"确定要删除脚本 '{script_name}' 吗?"):
if script_name in self.configs:
del self.configs[script_name]
self.save_configs()
self.update_script_list()
self.log_message(f"脚本 '{script_name}' 已删除")
def update_script_list(self):
self.script_listbox.delete(0, END)
for script_name in sorted(self.configs.keys()):
self.script_listbox.insert(END, script_name)
def load_script(self, event=None):
selection = self.script_listbox.curselection()
if not selection:
return
script_name = self.script_listbox.get(selection[0])
if script_name in self.configs:
script_data = self.configs[script_name]
self.current_config = script_data["config"]
self.actions = script_data["actions"]
# 更新UI
self.script_name_entry.delete(0, END)
self.script_name_entry.insert(0, self.current_config["script_name"])
self.interval_entry.delete(0, END)
self.interval_entry.insert(0, str(self.current_config["random_interval"]))
self.loop_entry.delete(0, END)
self.loop_entry.insert(0, str(self.current_config["loop_times"]))
self.log_message(f"已加载脚本 '{script_name}'")
self.log_message(f"共 {len(self.actions)} 个动作")
def start_recording(self):
if self.recording or self.playing:
return
if not self.update_current_config():
return
self.recording = True
self.actions = []
self.record_btn.config(bg="#F44336", text="停止录制 (F1)")
self.play_btn.config(state=DISABLED)
self.save_btn.config(state=DISABLED)
self.delete_btn.config(state=DISABLED)
# 重置鼠标和键盘状态
self.mouse_state = {
"left": False,
"right": False,
"middle": False
}
self.keyboard_state = {}
# 设置键盘和鼠标钩子
self.keyboard_hook = keyboard.hook(self.on_key_event)
self.mouse_hook = keyboard.hook(self.on_mouse_event)
self.log_message("开始录制...")
self.log_message("请开始您的操作,按F1停止录制")
self.log_message("正在录制: 鼠标点击、滚轮和键盘操作")
def stop_recording(self):
if not self.recording:
return
self.recording = False
self.record_btn.config(bg="#4CAF50", text="开始录制 (F1)")
self.play_btn.config(state=NORMAL)
self.save_btn.config(state=NORMAL)
self.delete_btn.config(state=NORMAL)
# 移除钩子
if self.keyboard_hook:
keyboard.unhook(self.keyboard_hook)
if self.mouse_hook:
keyboard.unhook(self.mouse_hook)
self.log_message(f"停止录制,共录制了 {len(self.actions)} 个动作")
self.save_script()
def on_mouse_event(self, event):
if not self.recording:
return
current_pos = pyautogui.position()
if event.event_type in ('down', 'up'):
button_mapping = {
"left": "left",
"right": "right",
"middle": "middle",
"x1": "x1",
"x2": "x2"
}
button = button_mapping.get(event.name, "left") # 默认使用左键
action_type = "mousedown" if event.event_type == 'down' else "mouseup"
self.actions.append({
"type": action_type,
"button": button,
"x": current_pos.x,
"y": current_pos.y,
"time": time.time()
})
action_desc = f"鼠标{'按下' if event.event_type == 'down' else '释放'}: {button}键"
self.log_message(f"{action_desc} 位置: ({current_pos.x}, {current_pos.y})")
elif event.event_type == 'wheel':
direction = "上" if event.delta > 0 else "下"
self.actions.append({
"type": "wheel",
"delta": event.delta,
"x": current_pos.x,
"y": current_pos.y,
"time": time.time()
})
self.log_message(f"滚轮滚动: 方向 {direction} 位置: ({current_pos.x}, {current_pos.y})")
def on_key_event(self, event):
if not self.recording:
return
# 忽略F1/F2按键
if event.name.lower() in self.ignored_keys:
return
if event.event_type == "down":
# 只记录第一次按下,不记录重复按下
if event.name not in self.keyboard_state:
self.keyboard_state[event.name] = True
self.actions.append({
"type": "keydown",
"key": event.name,
"time": time.time()
})
self.log_message(f"按键按下: {event.name}")
elif event.event_type == "up":
if event.name in self.keyboard_state:
del self.keyboard_state[event.name]
self.actions.append({
"type": "keyup",
"key": event.name,
"time": time.time()
})
self.log_message(f"按键释放: {event.name}")
def start_playing(self):
if self.playing or self.recording or not self.actions:
return
if not self.update_current_config():
return
self.playing = True
self.play_btn.config(bg="#FF9800", text="停止执行 (F2)")
self.record_btn.config(state=DISABLED)
self.save_btn.config(state=DISABLED)
self.delete_btn.config(state=DISABLED)
# 开始执行线程
threading.Thread(target=self.play_actions, daemon=True).start()
self.log_message("开始执行脚本...")
def stop_playing(self):
if not self.playing:
return
self.playing = False
self.play_btn.config(bg="#2196F3", text="开始执行 (F2)")
self.record_btn.config(state=NORMAL)
self.save_btn.config(state=NORMAL)
self.delete_btn.config(state=NORMAL)
self.log_message("停止执行脚本")
def play_actions(self):
loop_times = self.current_config["loop_times"]
random_interval = self.current_config["random_interval"] / 1000.0 # 转换为秒
for loop in range(loop_times):
if not self.playing:
break
self.log_message(f"开始第 {loop + 1} 次循环 (共 {loop_times} 次)")
prev_time = None
for i, action in enumerate(self.actions):
if not self.playing:
break
# 计算延迟
if prev_time is not None:
delay = action["time"] - prev_time
# 添加随机间隔
if random_interval > 0:
delay += random.uniform(0, random_interval)
time.sleep(max(0, delay))
prev_time = action["time"]
# 执行动作
try:
if action["type"] == "mousedown":
# 确保只使用有效的按钮参数
button = action["button"] if action["button"] in ('left', 'middle', 'right') else 'left'
pyautogui.mouseDown(x=action["x"], y=action["y"], button=button)
self.log_message(
f"执行动作 {i + 1}/{len(self.actions)}: 在({action['x']}, {action['y']})按下 {button}键")
elif action["type"] == "mouseup":
button = action["button"] if action["button"] in ('left', 'middle', 'right') else 'left'
pyautogui.mouseUp(x=action["x"], y=action["y"], button=button)
self.log_message(
f"执行动作 {i + 1}/{len(self.actions)}: 在({action['x']}, {action['y']})释放 {button}键")
elif action["type"] == "wheel":
pyautogui.scroll(action["delta"])
self.log_message(
f"执行动作 {i + 1}/{len(self.actions)}: 滚轮滚动 {'上' if action['delta'] > 0 else '下'}")
elif action["type"] == "keydown":
keyboard.press(action["key"])
self.log_message(f"执行动作 {i + 1}/{len(self.actions)}: 按下 {action['key']}键")
elif action["type"] == "keyup":
keyboard.release(action["key"])
self.log_message(f"执行动作 {i + 1}/{len(self.actions)}: 释放 {action['key']}键")
except Exception as e:
self.log_message(f"执行动作时出错: {str(e)}")
if loop < loop_times - 1 and self.playing:
time.sleep(1) # 循环之间的间隔
self.stop_playing()
def run(self):
self.root.mainloop()
if __name__ == "__main__":
recorder = ActionRecorder()
recorder.run()
请帮我检查这个键盘鼠标脚本录制器错误地方,并帮我修改!