マウス・キーボード自動操作ツール『記録と模倣を司る時計仕掛けの自動人形』 ソースコード

以下は『記録と模倣を司る時計仕掛けの自動人形』のソースコード全文です:

 
        import tkinter as tk
        from tkinter import filedialog, messagebox, ttk
        import json
        import time
        import threading
        from pynput import mouse, keyboard
        from pynput.mouse import Button as MouseButton
        from pynput.keyboard import Key, Controller as KeyboardController
        
        recording = False
        recorded_actions = []
        listener_threads = []
        stop_flag = threading.Event()
        
        mouse_controller = mouse.Controller()
        keyboard_controller = KeyboardController()
        
        def start_recording():
            global recording, recorded_actions
            if recording:
                return
            recorded_actions = []
            recording = True
            clear_tree()
            start_button.config(state="disabled")
            stop_button.config(state="normal")
            status_label.config(text="記録中...(F1で停止)")
        
            def on_click(x, y, button, pressed):
                if recording and pressed:
                    action = {
                        "type": "click",
                        "x": x,
                        "y": y,
                        "button": "left" if button == MouseButton.left else "right",
                        "wait": 0.5
                    }
                    recorded_actions.append(action)
                    insert_action_to_tree(action)
        
            def on_press(key):
                global recording
                if key == Key.f1:
                    stop_recording()
                    return False
                else:
                    try:
                        key_str = key.char if hasattr(key, 'char') and key.char else str(key)
                    except AttributeError:
                        key_str = str(key)
                    action = {"type": "key", "key": key_str, "wait": 0.5}
                    recorded_actions.append(action)
                    insert_action_to_tree(action)
        
            mouse_listener = mouse.Listener(on_click=on_click)
            keyboard_listener = keyboard.Listener(on_press=on_press)
            listener_threads.clear()
            listener_threads.extend([mouse_listener, keyboard_listener])
            mouse_listener.start()
            keyboard_listener.start()
        
        def stop_recording():
            global recording
            recording = False
            for listener in listener_threads:
                listener.stop()
            start_button.config(state="normal")
            stop_button.config(state="disabled")
            status_label.config(text="記録停止")
        
        def insert_action_to_tree(action):
            if action["type"] == "click":
                desc = f"Click {action['button']} at ({action['x']}, {action['y']})"
            elif action["type"] == "key":
                desc = f"Key {action['key']}"
            wait = action["wait"]
            tree.insert("", "end", values=(desc, wait))
        
        def clear_tree():
            for item in tree.get_children():
                tree.delete(item)
        
        def update_wait_times():
            for i, item_id in enumerate(tree.get_children()):
                values = tree.item(item_id)["values"]
                try:
                    wait = float(values[1])
                except ValueError:
                    wait = 0.5
                recorded_actions[i]["wait"] = wait
        
        def play_actions():
            if not recorded_actions:
                messagebox.showwarning("警告", "記録された操作がありません。")
                return
        
            try:
                repeat = int(repeat_entry.get())
                if repeat < 1:
                    raise ValueError
            except ValueError:
                messagebox.showerror("入力エラー", "繰り返し回数は1以上の整数で指定してください。")
                return
        
            stop_flag.clear()
            update_wait_times()
            play_button.config(state="disabled")
            interrupt_button.config(state="normal")
        
            def runner():
                try:
                    for i in range(repeat):
                        if stop_flag.is_set():
                            status_label.config(text="中断されました")
                            break
                        status_label.config(text=f"{i+1}回目/{repeat}回中 登録操作実行中…")
                        for action in recorded_actions:
                            if stop_flag.is_set():
                                break
                            wait = action.get("wait", 0.5)
                            if action["type"] == "click":
                                mouse_controller.position = (action["x"], action["y"])
                                btn = MouseButton.left if action["button"] == "left" else MouseButton.right
                                mouse_controller.click(btn)
                            elif action["type"] == "key":
                                key = action["key"]
                                try:
                                    if key.startswith("Key."):
                                        k = getattr(Key, key.replace("Key.", ""))
                                        keyboard_controller.press(k)
                                        keyboard_controller.release(k)
                                    else:
                                        keyboard_controller.press(key)
                                        keyboard_controller.release(key)
                                except Exception as e:
                                    print(f"キーエラー: {key} → {e}")
                            time.sleep(wait)
                    if not stop_flag.is_set():
                        status_label.config(text="操作完了")
                except Exception as e:
                    messagebox.showerror("実行エラー", str(e))
                    status_label.config(text="エラー")
                finally:
                    play_button.config(state="normal")
                    interrupt_button.config(state="disabled")
        
            threading.Thread(target=runner, daemon=True).start()
        
        def stop_execution():
            stop_flag.set()
        
        def save_actions():
            if not recorded_actions:
                return
            update_wait_times()
            path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON", "*.json")])
            if path:
                with open(path, "w", encoding="utf-8") as f:
                    json.dump(recorded_actions, f, indent=2)
                messagebox.showinfo("保存完了", "操作を保存しました。")
        
        def load_actions():
            global recorded_actions
            path = filedialog.askopenfilename(filetypes=[("JSON", "*.json")])
            if path:
                with open(path, "r", encoding="utf-8") as f:
                    recorded_actions = json.load(f)
                clear_tree()
                for act in recorded_actions:
                    insert_action_to_tree(act)
        
        def delete_selected():
            selected = tree.selection()
            if not selected:
                return
            for item_id in selected:
                idx = tree.index(item_id)
                tree.delete(item_id)
                recorded_actions.pop(idx)
        
        def edit_wait_cell(event):
            region = tree.identify("region", event.x, event.y)
            if region != "cell":
                return
            column = tree.identify_column(event.x)
            if column != "#2":
                return
            row_id = tree.identify_row(event.y)
            if not row_id:
                return
            x, y, width, height = tree.bbox(row_id, column)
            value = tree.item(row_id, "values")[1]
            edit_box = tk.Entry(tree)
            edit_box.place(x=x, y=y, width=width, height=height)
            edit_box.insert(0, value)
            edit_box.focus_set()
            def save_edit(event=None):
                try:
                    new_value = float(edit_box.get())
                    current_values = tree.item(row_id, "values")
                    tree.item(row_id, values=(current_values[0], new_value))
                    index = tree.index(row_id)
                    recorded_actions[index]["wait"] = new_value
                except ValueError:
                    messagebox.showerror("入力エラー", "数字を入力してください。")
                finally:
                    edit_box.destroy()
            edit_box.bind("<Return>", save_edit)
            edit_box.bind("<FocusOut>", lambda e: edit_box.destroy())
        
        def apply_bulk_wait():
            try:
                val = float(bulk_wait_entry.get())
                for i, item_id in enumerate(tree.get_children()):
                    desc = tree.item(item_id)["values"][0]
                    tree.item(item_id, values=(desc, val))
                    recorded_actions[i]["wait"] = val
            except ValueError:
                messagebox.showerror("エラー", "数字を入力してください。")
        
        # --- GUI構築 ---
        root = tk.Tk()
        
        ##タイトルバーアイコン表示用
        data = '''R0lGODdhEAAQAIUAAAAAAAICAigoJgICAjU2MwgICBoaGVdXVklJR2lpaAMDAxQUFBYWFZWVlBAQEMnJx9vb24ODgwEBAXR1chcXFwICAQYGBjg4NhwcGiAgHSIiIiYnJjk7OaSkouLi4DAwKz8/P/Ly8ENDQy4uLSEhIUtLS0ZGRiEhHyAgH52dnRkZF5ycnP///3V1c83NzFVVVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAEAAQAEAI1wABAAgQAIACBxYsBFgwsIJAAAMAWCgwIMCBBCcUKHgxUOAABSoIFGiQgEAHBAYiFoQIQIOAAgUOHDBQQACJjh0vCCCAACaCAwIc4BywoGaEBhAeJEhwQQGFlRE1EAD608ABBAxwFkwxoQIBAwYIVEiwQqsEAAwy0CyQocBZCSsLMhBAl+aECUGHYjAgIAGCBw+mEhjAAqoCmAFchPDQ4gMADhQishywwQQBugQIlAAAIi4ADHQRNOgwk8CGoQAWRBghAKwAFBE4DF0AIcHO1pcPQBAxgEJAADs=''' 
        root.tk.call('wm', 'iconphoto', root._w, tk.PhotoImage(data=data))
        ####
        
        root.title("マウス・キーボード自動操作ツール - 記録と模倣を司る時計仕掛けの自動人形")
        
        top_frame = tk.Frame(root)
        top_frame.pack(padx=10, pady=5)
        
        start_button = tk.Button(top_frame, text="登録開始", command=start_recording)
        start_button.grid(row=0, column=0, padx=5)
        stop_button = tk.Button(top_frame, text="登録終了(F1)", state="disabled", command=stop_recording)
        stop_button.grid(row=0, column=1, padx=5)
        play_button = tk.Button(top_frame, text="操作開始", command=play_actions)
        play_button.grid(row=0, column=2, padx=5)
        interrupt_button = tk.Button(top_frame, text="中断", state="disabled", command=stop_execution)
        interrupt_button.grid(row=0, column=3, padx=5)
        save_button = tk.Button(top_frame, text="保存", command=save_actions)
        save_button.grid(row=0, column=4, padx=5)
        load_button = tk.Button(top_frame, text="読み込み", command=load_actions)
        load_button.grid(row=0, column=5, padx=5)
        delete_button = tk.Button(top_frame, text="選択削除", command=delete_selected)
        delete_button.grid(row=0, column=6, padx=5)
        
        repeat_label = tk.Label(top_frame, text="繰り返し回数:")
        repeat_label.grid(row=0, column=7, padx=(15, 0))
        repeat_entry = tk.Entry(top_frame, width=5)
        repeat_entry.insert(0, "1")
        repeat_entry.grid(row=0, column=8, padx=2)
        
        bulk_wait_label = tk.Label(top_frame, text="待機秒数一括:")
        bulk_wait_label.grid(row=0, column=9, padx=(15, 0))
        bulk_wait_entry = tk.Entry(top_frame, width=5)
        bulk_wait_entry.insert(0, "0.5")
        bulk_wait_entry.grid(row=0, column=10, padx=2)
        bulk_wait_button = tk.Button(top_frame, text="一括適用", command=apply_bulk_wait)
        bulk_wait_button.grid(row=0, column=11, padx=2)
        
        status_label = tk.Label(root, text="準備完了")
        status_label.pack()
        
        tree = ttk.Treeview(root, columns=("Action", "Wait"), show="headings", height=15)
        tree.heading("Action", text="操作内容")
        tree.heading("Wait", text="待機秒数(次操作まで)")
        tree.column("Action", width=300)
        tree.column("Wait", width=150)
        tree.pack(padx=10, pady=10)
        tree.bind("<Double-1>", edit_wait_cell)
        
        root.mainloop()
               
    

📦 ダウンロード 🏠 ホームへ