from sys import exit, argv from os import name as OS_NAME from os import listdir from os.path import exists, join, isdir from time import sleep from typing import Dict, Tuple try: from click import echo, style except ImportError: print("ERROR Please activate a virtual environment and install requirements.txt") exit() from pyautogui import mouseDown as mouse_down, mouseUp as mouse_up from keyboard import press as key_down, release as key_up, KeyboardEvent, add_hotkey, hook as key_hook, write as key_write # Not entirely sure why this doesn't work #from mouse import press as mouse_down, release as mouse_up, click as mouse_press, move as mouse_move from toml import load as toml_load from asyncio import run, sleep as asleep from asyncio.tasks import gather as task_group def print_err(msg): echo(style("ERROR", fg="bright_red") + " " + msg) def print_warn(msg): echo(style("WARN ", fg="bright_yellow") + " " + msg) def print_ok(msg): echo(style(" OK ", fg="bright_green") + " " + msg) def print_info(msg): print(" " + msg) def keyboard_output(msg: str, delay: int=50, hold: int=20): key_write(msg, delay=hold) sleep(delay/1000) def mouse_output(button: str, delay: int=50, hold: int=20): mouse_down(button=button) sleep(hold / 1000) mouse_up(button=button) sleep(hold / 1000) sleep(delay / 1000) platform: str = "" os_name = OS_NAME.lower() if "linux" in os_name or "posix" in os_name: platform = "LINUX" elif "windows" in os_name or "nt" in os_name: platform = "WINDOWS" elif "darwin" in os_name: platform = "MACOS" else: print_err("Platform 'Linux', 'Windows' or 'MacOS' expected") print_info(f"Platform: {OS_NAME}") exit() class ActionKind(int): """ Actions can be defined to do different things Because of this complexity, Actions are separated into Mouse Actions: - Click (Press mouse button down, then release mouse button up) - Click Down (Press mouse button down, never releasing up) Keyboard Actions: - Key (Press key down, then release key up) - Key Down (Press key down, never releasing up) Mirror Actions will typically be one of the above (mouse or keyboard actions, typically Click or Key) They Mirror a key's state, if it's down: pressing or clicking down, if it's up: releasing up """ NONE = 0 """ Invalid Action state """ CLICK = 1 """ Mouse Action, Click (Press mouse button down, then release mouse button up) """ CLICK_DOWN = 2 """ Mouse Action, Click Down (Press mouse button down, never releasing up) """ KEY = 3 """ Keyboard Action, Key (Press key down, then release key up) """ KEY_DOWN = 4 """ Keyboard Action, Key Down (Press key down, never releasing up) """ MIRROR = 5 """ Mirror Actions will typically be one of the above (mouse or keyboard actions, typically Click or Key) They Mirror a key's state, if it's down: pressing or clicking down, if it's up: releasing up """ def __init__(self, kind: str): # Unlike most __init__ methods, this converts a string into a ActionKind (returning the ActionKind) k = kind.lower().strip() if k == "click": return ActionKind.CLICK elif k == "click down": return ActionKind.CLICK_DOWN elif k == "key": return ActionKind.KEY elif k == "key down": return ActionKind.KEY_DOWN elif k == "mirror": return ActionKind.MIRROR else: return ActionKind.NONE def __str__(self) -> str: # Converts the ActionKind into a string, mostly for debugging if self == ActionKind.CLICK: return "click" elif self == ActionKind.CLICK_DOWN: return "click down" elif self == ActionKind.KEY: return "key" elif self == ActionKind.KEY_DOWN: return "key down" elif self == ActionKind.MIRROR: return "mirror" else: return f"" def is_mouse(self) -> bool: """ Is this Action a Mouse Action Mouse Actions: - Click (Press mouse button down, then release mouse button up) - Click Down (Press mouse button down, never releasing up) """ return self == ActionKind.CLICK or self == ActionKind.CLICK_DOWN def is_keyboard(self) -> bool: """ Is this Action a Keyboard Action Keyboard Actions: - Key (Press key down, then release key up) - Key Down (Press key down, never releasing up) """ return self == ActionKind.KEY or self == ActionKind.KEY_DOWN class Action: name: str """ Action's can be named to describe and clarify what the Action should do i.e. 'auto click' for some Auto Clicker, 'shop' for something that opens a shop (such as '/shop') """ kind: ActionKind """ The Kind of Action to do See tyrell.ActionKind """ extra: Dict enabled: bool delay: int max_delay: int def __init__(self, name: str, kind: str, extra: Dict, on: bool=False, delay: int=0): self.name = name self.kind = ActionKind(kind) self.enabled = on self.delay = delay self.max_delay = delay def toggle(self) -> bool: self.enabled = not self.enabled return self.enabled def enable(self): self.enabled = True def disable(self): self.enabled = False def is_ticker(self) -> bool: """ Actions can be time based or not Tickers are: Actions time based will fire after a number of ticks (at a set delay, tick speed) All Actions not time based fire only when the selected "keybind" is pressed (where tickers use the "keybind" to toggle them on and off) """ return self.max_delay != 0 def is_mirror(self, key: str) -> bool: if self.kind != ActionKind.MIRROR: return False return key == self.extra["mirror"] def tick(self) -> bool: if not self.is_ticker(): return False if not self.enabled: if self.delay != self.max_delay: self.delay = self.max_delay return False self.delay -= 1 if self.delay <= 0: self.delay = self.max_delay return True return False def do(self, key: Tuple[str, int], delay: int=50, hold: int=20): if not self.enabled: return d = delay h = hold if "delay" in self.extra: d = self.extra["delay"] if "hold" in self.extra: h = self.extra["hold"] if self.kind == ActionKind.CLICK: mouse_output(self.extra["button"], d, h) elif self.kind == ActionKind.CLICK_DOWN: mouse_down(button=self.extra["button"]) elif self.kind == ActionKind.KEY: keyboard_output(self.extra["write"], d, h) elif self.kind == ActionKind.KEY_DOWN: key_down(self.extra["write"]) elif self.kind == ActionKind.MIRROR: if key[1] == 0: # Up if "write" in self.extra: # Key Mirror key_up(self.extra["write"]) elif "button" in self.extra: # Mouse Mirror mouse_up(button=self.extra["button"]) elif key[1] == 1: # Down if "write" in self.extra: # Key Mirror key_down(self.extra["write"]) elif "button" in self.extra: # Mouse Mirror mouse_down(button=self.extra["button"]) def do_tick(self, delay: int=50, hold: int=20): if self.tick(): self.do(delay, hold) class Tyrell: pass class OldAction(): name: str kind: str extra: Dict toggled: bool delay: int max_delay: int def __init__(self, name, kind, extra=None, toggled=False, delay=0): self.name = name self.kind = kind if extra is not None: self.extra = extra else: self.extra = {} self.toggled = toggled self.delay = delay self.max_delay = delay def toggle(self) -> bool: self.toggled = not self.toggled return self.toggled def tick(self) -> bool: if not self.toggled: if self.delay != self.max_delay: self.delay = self.max_delay return False if self.max_delay != 0: self.delay -= 1 if self.delay <= 0: self.delay = self.max_delay return True return False def do(self, delay: int=50, hold: int=20): if not self.toggled: return d = delay h = hold if self.extra is not None: if "delay" in self.extra: d = self.extra["delay"] if "hold" in self.extra: h = self.extra["hold"] if self.kind == "click": mouse_output(self.extra["button"], d, h) elif self.kind == "click down": mouse_down(self.extra["button"]) elif self.kind == "key": keyboard_output(self.extra["write"], d, h) elif self.kind == "key down": key_down(self.extra["write"]) class OldTyrell(): keybinds: Dict[str, OldAction] mirrors: Dict[str, str] # mirror key -> key in keybinds toggled: bool delay: int hold: int def __init__(self, profile_name: str): self.profile = { "activator": "`", "helper": "?", "delay": 50, "hold": 20, "tick": 50, "placeholder_name": False, } self.name = profile_name if profile_name.endswith(".toml"): self.name = profile_name.removesuffix(".toml") if not exists(self.name) or not isdir(self.name): print_err(f"Invalid profile '{self.name}'") print_info(f"This should be a directory") exit() profile_config = join(self.name, self.name+".toml") if exists(profile_config): with open(profile_config, "r") as f: try: t = toml_load(f) except Exception as err: print_err(f"Invalid profile '{self.name}'") print_info(f"Invalid '{self.name+'.toml'}' config") print(err) exit() self.profile = t if "activator" not in self.profile: self.profile["activator"] = "`" if "helper" not in self.profile: self.profile["helper"] = "?" if "hold" not in self.profile: self.profile["hold"] = 20 if "delay" not in self.profile: self.profile["delay"] = 50 if "tick" not in self.profile: self.profile["tick"] = 50 if "placeholder_name" not in self.profile: self.profile["placeholder_name"] = False else: print_err(f"Invalid profile '{self.name}'") print_info(f"Missing '{self.name+'.toml'}' config") exit() self.delay = self.profile["delay"] self.hold = self.profile["hold"] self.keybinds = {} self.mirrors = {} self.toggled = False if not exists(join(self.name, 'keys')): print_err(f"Invalid profile '{self.name}'") print_info("Missing 'keys' directory (for keybinds)") exit() else: for ent in listdir(join(self.name, "keys")): if ent.startswith(".") or isdir(ent) or not ent.endswith(".toml"): continue with open(join(self.name, "keys", ent), "r") as f: t = toml_load(f) if t["keybind"] == "?" or t["keybind"] == "`": continue if "duration" in t: self.add_action(t["keybind"], OldAction(ent.removesuffix(".toml"), t["kind"], extra=t, delay=t["duration"])) else: self.add_action(t["keybind"], OldAction(ent.removesuffix(".toml"), t["kind"], extra=t)) def toggle(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False) -> bool: self.toggled = not self.toggled if all: for key in self.keybinds: act = self.keybinds[key] act.toggle() elif all_tickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay != 0: act.toggle() elif all_notickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay == 0: act.toggle() return self.toggled def disable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False): self.toggled = False if all: for key in self.keybinds: act = self.keybinds[key] act.toggled = False elif all_tickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay != 0: act.toggled = False elif all_notickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay == 0: act.toggled = False def enable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False): self.toggled = True if all: for key in self.keybinds: act = self.keybinds[key] act.toggled = True elif all_tickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay != 0: act.toggled = True elif all_notickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay == 0: act.toggled = True def add_action(self, bind: str, act: OldAction): act.toggled = False if "placeholder_name" in self.profile: if self.profile["placeholder_name"] and "write" in act.extra: act.extra["write"] = act.extra["write"].replace("{name}", self.name) if act.kind == "mirror" and "mirror" in act.extra: self.mirrors[act.extra["mirror"]] = bind self.keybinds[bind] = act def remove_action(self, bind: str): self.keybinds[bind] = None def is_action(self, bind: str) -> bool: return self.keybinds[bind] is not None def print_help(self): print_ok(f"{int(1000 / self.profile['tick'])} ticks per second ({self.profile['tick']} ms per tick)") print_ok(f"Name placeholder: {self.profile['placeholder_name']}") print_warn(f"{self.profile['activator']} -> Activate/Deactivate") print_warn(f"{self.profile['helper']} -> Displays Help") for key in self.keybinds: act = self.keybinds[key] if act.max_delay == 0 and act.toggled: print_info(f"{key} -> {act.name}") else: print_info(f"{key} -> {act.name} = {act.toggled} ({act.max_delay} ticks)") print_ok("Please use " + style("CTRL+C", fg="bright_yellow") + " to stop") def tick(self): for key in self.keybinds: act = self.keybinds[key] if act.tick(): act.do(self.delay, self.hold) # def callback(self, event: KeyboardEvent): # key_name = event.name # #if key_name in self.mirrors: # # # key mirrors currently lag # # act = self.keybinds[self.mirrors[key_name]] # # if act.toggled: # # if "write" in act.extra: # # if event.event_type == "down": # # key_down(act.extra["write"]) # # else: # # key_up(act.extra["write"]) # # elif "button" in act.extra: # # if event.event_type == "down": # # mouse_down(button=act.extra["button"]) # # else: # # mouse_up(button=act.extra["button"]) # #if event.event_type == "up": # if key_name == self.profile["activator"]: # if self.toggle(): # print_ok("ON") # else: # print_ok("OFF") # elif self.toggled: # #print(f"Name: {event.name}") # #print(f"Code: {event.scan_code}") # #print(f"Modifiers: {event.modifiers}") # if self.profile["helper"] == "?" and "shift" in event.modifiers and event.name == "/" or key_name == self.profile["helper"]: # self.print_help() # elif key_name in self.keybinds: # act = self.keybinds[key_name] # if act.max_delay == 0 and act.toggled: # print_info(f"{key_name} -> {act.kind}") # act.do(self.delay, self.hold) # else: # print_info(f"{key_name} -> {act.kind} = {act.toggle()}") # self.toggle() # print_ok("OFF") def callback(self, bind, state=None, mirror_host=None): if bind == "__toggle__": if self.toggle(): print_info(style("ON", fg="bright_green")) else: print_info(style("OFF", fg="bright_red")) return if state == "up" or state == "down": act = self.keybinds[mirror_host] if act.toggled: if act.extra["write"] != None or len(act.extra["write"]) != 0: if state == "up": print(f"'{bind}' -> {act.name} UP") key_up(act.extra["write"]) elif state == "down": print(f"'{bind}' -> {act.name} DOWN") key_down(act.extra["write"]) elif act.extra["button"] != None or len(act.extra["button"]) != 0: if state == "up": print(f"'{bind}' -> {act.name} UP") mouse_up(button=act.extra["button"]) elif state == "down": print(f"'{bind}' -> {act.name} DOWN") mouse_down(button=act.extra["button"]) return if not self.toggled: return if bind == "__help__": self.print_help() self.disable() print_info(style("OFF", fg="bright_red")) return act = self.keybinds[bind] if act.toggled: print(f"'{bind}' -> {act.name}") act.do(self.delay, self.hold) elif act.max_delay != 0 or act.kind == "mirror": if act.toggle(): print(f"'{bind}' -> {act.name} " + style("ON", fg="bright_green")) else: print(f"'{bind}' -> {act.name} " + style("OFF", fg="bright_red")) self.disable() print_info(style("OFF", fg="bright_red")) async def background(self): while True: self.tick() await asleep(self.profile["tick"] / 1000) # 50 ms (20 per second, same as Minecraft) async def mainloop(self): #keyboard_hook(self.callback) add_hotkey(41, self.callback, args=["__toggle__"]) add_hotkey("shift+?", self.callback, args=["__help__"]) for bind in self.keybinds: act = self.keybinds[bind] if act.kind == "key" or act.kind == "key down": print(f"hotkey -=> {bind}") add_hotkey(bind, self.callback, args=[bind]) elif act.kind == "mirror": print(f"hotkey -=> {bind} (mirror {act.extra['mirror']})") add_hotkey(bind, self.callback, args=[bind]) add_hotkey(bind, self.callback, args=[act.extra["mirror"], "down", bind]) add_hotkey(bind, self.callback, args=[act.extra["mirror"], "up", bind], trigger_on_release=True) await task_group( self.background() ) if __name__ == "__main__": if len(argv) == 1: print_err("Missing profile filename") print_info("Example: 'tyrell.py Apollo' would look for 'Apollo.toml'") print() print_info("Profiles allow you to add placeholders,") print_info("And defines in a \"global\" sense key delay and hold delay.") print() exit() ty = OldTyrell(",".join(argv[1:])) if len(ty.keybinds) == 0: print_err("Missing keybinds") print_info("(Might want some keybinds, place them in a 'keys' directory)") print_info(f"( Need an example? Look at '{join('_example', 'keys', 'example.toml')}')") exit() ty.enable(all_notickers=True) ty.disable() ty.print_help() if ty.name == "_example": print_warn("This is the " + style("example", fg="bright_yellow") + ", please define you're own profile") print_info("Please DO NOT EDIT this example profile") try: run(ty.mainloop()) except KeyboardInterrupt: exit()