Skip to content

Macro creator class which lets developers create advanced macros in python

License

Notifications You must be signed in to change notification settings

theeman05/MacroCreator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

41 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Python Macro Creator

License Python Status Buy Me A Coffee

Limitless automation, powered by Python.

The Python Macro Creator is a robust automation framework that bridges the gap between simple macro recorders and complex software development. Unlike traditional click-recorders, this engine allows you to script logic in pure Python, giving you access to the full power of the language, from computer vision (OpenCV) to API requests while managing the lifecycle of your tasks through a user-friendly GUI.

๐Ÿš€ Key Features

๐Ÿ Infinite Possibilities

If you can code it in Python, you can automate it. Import any library, use complex logic, and interact with the OS at a deep level. You are not limited to "click here, wait 5 seconds."

๐ŸŽ›๏ธ Granular Task Control

The core of the engine is its Task Manager. Unlike standard scripts that run from top to bottom:

  • Individual Control: Pause, Stop, or Restart specific tasks without stopping the whole engine.
  • Hot-Swappable: Update variables in real-time while the macro is running.
  • Resilient: If one task crashes or is stopped, the rest of the engine keeps running.

๐Ÿงฉ Variable Management

Predefine variables (Integers, Booleans, Regions, Points, etc.) that are exposed in the GUI. Users can tweak settings (like click_point or scan_region) safely via the interface without ever touching the code.

โšก Smart Config

Variables are type-safe and validated instantly. As of right now, the engine supports complex types like QRect (Regions) and QPoint (Coordinates) with visual overlays, ensuring users don't have to guess pixel coordinates (but they still can if they want to)!

๐Ÿงฌ Extensible Type System

The Engine features a robust Global Type Registry that bridges the gap between Python objects and the User Interface. You don't need to manually build widgets for your settings; simply defining a type handler automatically grants you:

  • Smart UI Handling: Users see friendly names (e.g., "Screen Region") instead of raw class names (e.g., PySide6.QtCore.QRect).
  • Input Validation: The UI provides immediate visual feedback (red borders/tooltips) if user input doesn't match your parser's rules.
  • Automatic Serialization: The engine knows how to save and load your object from config files.

๐Ÿ› ๏ธ Usage

1. Create a Standard Task (Generators)

The most efficient way to write tasks is using Python Generators. This allows the engine to run hundreds of tasks simultaneously on a single thread.

  • Key Rule: Use yield from taskSleep(seconds) instead of time.sleep() in standard tasks.
from macro_creator import taskSleep


def my_task():
    # Can print to the python terminal
    print("Task starting...")
    # Engine runs other tasks while this waits
    yield from taskSleep(1)
    print("Task resumed!")


class BasicMacro:
    def __init__(self, macro_creator):
        self.engine = macro_creator
        # Add the task to the creator
        macro_creator.addRunTask(my_task)

2. Controlling Tasks

When you add a task, the engine returns a Task Controller. You can use this object to pause, resume, or stop other tasks dynamically.

    def __init__(self, macro_creator):
        self.engine = macro_creator
        # Save the controller to a variable
        self.worker_ctrl = macro_creator.addRunTask(self.my_task)
        # Add a variable so the user can choose to sleep "my_task" or not and set the default value to "True"
        macro_creator.addVariable("Sleep My Task", bool, True, "Sleeps My Task On Execute")
        macro_creator.addRunTask(self.manager_task)

    def manager_task(self, controller):
        # Log directly to the ui
        controller.log("I am going to sleep")
        # Get a user defined variable from the engine
        if controller.getVar("Sleep My Task"):
            self.worker_ctrl.pause() # Worker stops immediately
            yield from taskSleep(2)
            self.worker_ctrl.resume() # Worker continues

3. Threaded Tasks (Blocking Code)

Sometimes you need to run blocking code (like heavy calculations or network requests) that doesn't support generators. You can run these in a separate thread while keeping them synchronized with the engine's Pause/Stop system.

  • Key Rule: Pass the TaskController to your thread and use controller.sleep(seconds). This ensures the thread pauses correctly if the task is paused.
import threading
from macro_creator import taskSleep, taskAwaitThread


# 1. Define the function to run in the thread
def heavy_lifting(controller):
    print("Running in a separate thread!")
    # SAFE SLEEP: Checks if the user paused the engine while sleeping
    controller.sleep(5)
    print("Thread finished work.")


def launcher(controller):
    # Create and start thread task with the argument being the task controller
    controller.log("Starting background work...")
    # Yield while the thread task is running
    yield from taskAwaitThread(heavy_lifting, controller=controller)


class ThreadMacro:
    def __init__(self, macro_creator):
        # 2. Add a task that spawns the thread
        # We pass 'self.launcher' so we can get its controller
        self.engine = macro_creator
        self.controller = macro_creator.addRunTask(launcher)

4. Running the Engine

Launch the GUI. Your tasks and variables will automatically appear.

from macro_creator import MacroCreator
from Examples.basic_macro import BasicMacro

if __name__ == "__main__":
    creator = MacroCreator(macro_name="Basic Macro Example")

    # Add steps and tasks from BasicMacro
    BasicMacro(creator)

    creator.launch()
  • Edit Configs: Click any task to modify its variables.
  • Start/Pause/Stop: Use the global controls or manage tasks individually.
  • Visual Debugging: Hover over region variables to see them highlighted on screen.

โš™๏ธ How it Works Under the Hood

  • Generator Tasks: Use Cooperative Multitasking. The engine cycles through tasks, running them until they yield. This makes the bot extremely lightweight and CPU efficient.
  • Threaded Tasks: Run in parallel. By using controller.sleep(), you bridge the gap, allowing the main engine to safely pause or stop these threads even though they are running outside the main loop.

๐Ÿ›ก๏ธ Handling Control Flow: Pauses & Stops

The Engine uses exceptions to control your tasks. You must handle these correctly to ensure your macro pauses and stops safely when the user expects it to.


โš ๏ธ 1. Handling Task Interruptions

The Exception: TaskInterruptedException The Scenario: When the user interrupts a task, the engine immediately interrupts the current action (like a long sleep) to release keys and clean up.

  • If you DO NOT catch it: The exception bubbles up and exits your task. Your loop will terminate prematurely.
  • If you DO catch it: You can save the state, yield a wait command, and then resume the loop when the user is ready.

โœ… Correct Pattern: The Resumable Loop To make a robust loop that survives an interruption, wrap your logic in a try/except block and delegate control to taskWaitForResume.

from macro_creator import TaskInterruptedException, taskSleep, taskWaitForResume

def task_count_to_ten():
    counter = 0
    while counter < 10:
        try:
            # 1. Try to sleep normally
            yield from taskSleep(1)

        except TaskInterruptedException:
            # 2. INTERRUPTED! The task was interrupted while paused.
            # We yield to the pause handler so the engine waits here.
            yield from taskWaitForResume()
            
            # 3. When we return here, the loop continues naturally.
        
        counter += 1

    print("Task finished successfully!")

โŒ Incorrect Pattern: The Fragile Loop In this example, an interruption crashes the loop because the exception is not handled.

def task_fragile_count():
    counter = 0
    while counter < 10:
        # DANGER: If interrupted, this line raises TaskInterruptedException.
        # Since it isn't caught, the function aborts immediately!
        yield from task_sleep(1) 
        counter += 1

    # This line is NEVER reached if the task is interrupted.
    print("Task finished!") 

๐Ÿ›‘ 2. Handling Stops (Aborting)

The Exception: TaskAbortException The Scenario: When the user clicks Stop, the engine raises this exception in any blocking method (controller.sleep, waitForResume) to halt execution immediately.

The Rule: You must never catch and ignore this exception.

  • Do: Use try/finally blocks to ensure resources (files, database connections) are closed.
  • Do Not: Use a bare except: or except TaskAbortException: that swallows the stop signal.

โœ… Correct Pattern: The finally Cleanup Use finally to guarantee cleanup. You do not need to explicitly catch TaskAbortException because you want it to propagate up and stop the thread.

def task_write_log(controller):
    # Open a resource that MUST be closed later
    f = open("log.txt", "w")
    
    try:
        while True:
            # If user clicks STOP, 'controller.sleep' raises TaskAbortException
            controller.sleep(1)
            f.write("Working...\n")
            
    finally:
        # This block ALWAYS runs, even if the task is Stopped/Aborted.
        print("Cleanup: Closing file safely.")
        f.close()

โŒ Incorrect Pattern: The "Phantom Thread" Swallowing the exception causes the thread to stay alive as a "zombie" process, continuing to run even after the user thinks they stopped it.

from macro_creator import TaskAbortException

def task_zombie_log(controller):
    while True:
        try:
            controller.sleep(1)
            do_work()
            
        except TaskAbortException as e:
            # โ›” DANGER: You caught the Stop signal and only printed it!
            # The loop will just spin around and run again.
            print(f"Stop ignored: {e}")

๐Ÿงฌ How to Add Custom Types

Registering a new type is as simple as adding a decorator. You define how to Read (Parse) and Write (Format) the value, and the engine handles the rest.

from macro_creator import register_handler


@register_handler
class HeroData:
    """
    A custom class to store hero configuration.
    The 'display_name' attribute determines what the user sees in the UI tooltip.
    """
    display_name = "Hero Configuration"

    def __init__(self, name, level):
        self.name = name
        self.level = level

    @staticmethod
    def toString(obj):
        # Convert object to string for the UI/Config file
        return f"{obj.name}:{obj.level}"

    @staticmethod
    def fromString(text):
        # Convert string back to object
        try:
            name, level = text.split(':')
            return HeroData(name, int(level))
        except ValueError:
            raise ValueError("Format must be 'Name:Level'")

Supported Out-of-the-Box

The engine comes pre-configured with handlers for standard and GUI types:

  • Python Primitives: int, float, bool, str, list, tuple
  • Qt Geometry: QRect (Screen Region), QPoint (Coordinate)
  • Custom Extensions: Add any class you want using the @registerHandler decorator or the type handler's register method.

๐Ÿ”ฎ Roadmap & Coming Soon

I am actively working to make this the ultimate automation platform. Here is what is coming next:

๐ŸŽฅ Visual Task Recorder (No-Code)

I will be lowering the barrier to entry!

  • Record: Create new tasks by simply recording your mouse and keyboard actionsโ€”no coding required.
  • Edit: Fine-tune your recorded actions directly in the Engine's GUI (change delays, adjust coordinates) without opening a text editor.

๐Ÿค Contributing

Contributions are welcome! Whether you are fixing bugs, adding new features, or creating example tasks, I would love to see your work :)

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

โ˜• Support the Project

If you find this creator helpful and want to support its development, consider buying me a coffee! It helps keep the updates coming.

Buy Me A Coffee

๐Ÿ“„ License

Distributed under the GNU GPLv3 License. See LICENSE for more information. This means that if you modify and distribute this engine or build a product on top of it, you must keep it open-source.

About

Macro creator class which lets developers create advanced macros in python

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages