Przeglądaj źródła

Tyrell can operate within a Linux env

It can perform within a Linux environment

Clicking, typing, and intervals work as expected

Output can infact effect the macro itself

That is, if I could ever emit ` then you could build a multi-macro
execution, which is highly dangerous
Apollo 4 miesięcy temu
rodzic
commit
7c7fa9477e
6 zmienionych plików z 388 dodań i 22 usunięć
  1. 4 0
      .gitignore
  2. 55 0
      example.toml
  3. 20 0
      example_profile.toml
  4. 5 2
      requirements.txt
  5. 9 0
      run.sh
  6. 295 20
      tyrell.py

+ 4 - 0
.gitignore

@@ -1,3 +1,7 @@
+# ---> Tyrell
+keys/
+*.toml
+
 # ---> Python
 # Byte-compiled / optimized / DLL files
 __pycache__/

+ 55 - 0
example.toml

@@ -0,0 +1,55 @@
+# Keybind to what key
+# This can be any key, but must be only 1
+#
+keybind = "h"
+
+# Kind of action to perform
+# click      ->  Click a mouse button
+# key        ->  Type the following keys (pressing each key)
+# click down ->  Click and never release a mouse button
+# key down   ->  Press the following keys (never releasing them)
+#
+kind = "key"
+
+# << Optional >>
+
+# Write this out
+# This is required for 'key' and 'key down' kinds
+# Capital letters will automatically include 'shift'
+# TODO: Separate each of these by space (as space should be 'space' rather than ' ', so other keybinds 'ctrl c' can work)
+#
+write = "Hello World!" # This works, but uses unexpected/undesired space (See TODO above)
+
+# This is commented out because the example doesn't do a repeated thing
+# If this should repeat, like an auto clicker
+# Simply define a duration between clicks (this runs at 20 milliseconds, so 1 full second is 50)
+#
+#duration = 50
+
+# This is commented out so this example works
+# Use this Button
+# This is required for 'click' and 'click down' kinds
+# There is 'left' and 'right'
+#
+#button = "left"
+
+# << Override >>
+# The below values are set to their defaults
+# These are optional, and only used for this keybind
+
+# Override delay between each key
+# This is how long to wait after sending a key
+# Value in milliseconds (1000/second)
+#
+delay = 50
+
+# Override hold delay for each key
+# Keys are sent like below:
+# 1. key down
+# 2. hold delay
+# 3. key up
+# 4. hold delay
+# 5. delay (see above)
+# Value in milliseconds (1000/second)
+#
+hold = 20

+ 20 - 0
example_profile.toml

@@ -0,0 +1,20 @@
+
+# Placeholders
+
+# Use '{name}' to emit profile name (the file name)
+placeholder_name = false
+
+# Required Features
+
+# Tick speed
+# Value in milliseconds
+# Defaults to Minecraft's Tick speed (20 ticks per second, 50 ms)
+tick = 50
+
+# Delay in-between keys
+# Value in milliseconds
+delay = 50
+
+# Hold delay of each key
+# Value in milliseconds
+hold = 20

+ 5 - 2
requirements.txt

@@ -1,12 +1,15 @@
-click==8.2.1
+click==8.1.8
 colorama==0.4.6
 keyboard==0.13.5
 mouse==0.7.1
 MouseInfo==0.1.3
+pillow==10.4.0
 PyAutoGUI==0.9.54
 PyGetWindow==0.0.9
 PyMsgBox==1.0.9
 pyperclip==1.9.0
 PyRect==0.2.0
-PyScreeze==1.0.1
+pyscreeze==1.0.1
+python3-xlib==0.15
 pytweening==1.2.0
+toml==0.10.2

+ 9 - 0
run.sh

@@ -0,0 +1,9 @@
+#/bin/bash
+
+# This is used under Linux because Python's 'keyboard' module requires root
+# (Root is needed for 'keyboard' module to access dev)
+#
+# sudo ./run.sh
+
+. env/bin/activate
+python3 tyrell.py "$@"

+ 295 - 20
tyrell.py

@@ -1,37 +1,312 @@
 
-from sys import exit
-
-if __name__ != "__main__":
-    print("Library codebase isn't implemented yet.")
-    exit()
+from sys import exit, argv
 
 from os import name as OS_NAME
-# from os import makedirs, removedirs
-#from os.path import exists, join
-from tkinter import Tk, N as tk_N, E as tk_E, S as tk_S, W as tk_W
-from tkinter.ttk import Frame, Label, Button
+from os import listdir
+from os.path import exists, join, isdir
+
+from time import sleep
+
+from typing import Dict
 
 try:
     from click import echo, style
 except ImportError:
-    print("!!! FATAL ERROR !!!")
-    print("Please activate a virtual environment and install requirements.txt")
+    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, on_release as keyboard_hook
+# 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):
+    for k in msg:
+        if k.isupper():
+            k = k.lower()
+            key_down('shift')
+            key_down(k)
+            sleep(hold / 1000)
+            key_up(k)
+            key_up('shift')
+            sleep(hold / 1000)
+        elif k in ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')']:
+            m = {'!': '1', '@': '2', '#': '3', '$': '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0'}
+            key_down('shift')
+            key_down(m[k])
+            sleep(hold / 1000)
+            key_up(m[k])
+            key_up('shift')
+            sleep(hold / 1000)
+        elif k in ['\r', '\n']:
+            m = {'\r': 'enter', '\n': 'enter'}
+            key_down(m[k])
+            sleep(hold / 1000)
+            key_up(m[k])
+            sleep(hold / 1000)
+        else:
+            key_down(k)
+            sleep(hold / 1000)
+            key_up(k)
+            sleep(hold / 1000)
+        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 = ""
-if "linux" in OS_NAME.lower():
+os_name = OS_NAME.lower()
+if "linux" in os_name or "posix" in os_name:
     platform = "LINUX"
-elif "windows" in OS_NAME.lower() or "nt" in OS_NAME.lower():
+elif "windows" in os_name or "nt" in os_name:
     platform = "WINDOWS"
-elif "darwin" in OS_NAME.lower():
+elif "darwin" in os_name:
     platform = "MACOS"
 else:
-    echo(style("ERROR", fg="bright_red") + " Platform 'Linux', 'Windows' or 'MacOS' expected")
-    print(f"      Platform: {OS_NAME}")
+    print_err("Platform 'Linux', 'Windows' or 'MacOS' expected")
+    print_info(f"Platform: {OS_NAME}")
     exit()
 
-root = Tk()
-root.title("Tyrell")
+class Action():
+    kind: str
+    extra: Dict
+    toggled: bool
+    delay: int
+    max_delay: int
+    def __init__(self, kind, extra=None, toggled=False, delay=0):
+        self.kind = kind
+        self.extra = 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 Tyrell():
+    keybinds: Dict[str, Action]
+    toggled: bool
+    delay: int
+    hold: int
+    def __init__(self, profile_name: str):
+        self.profile = {
+            "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 exists(self.name+".toml"):
+            with open(self.name+".toml", "r") as f:
+                try:
+                    t = toml_load(f)
+                except Exception as err:
+                    print_err(f"Invalid profile '{self.name+'.toml'}'")
+                    print(err)
+                    exit()
+                self.profile = t
+                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+'.toml'}'")
+            exit()
+        self.delay = self.profile["delay"]
+        self.hold = self.profile["hold"]
+        self.keybinds = {}
+        self.toggled = False
+
+    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: Action):
+        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)
+        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 tick(self):
+        for key in self.keybinds:
+            act = self.keybinds[key]
+            if act.tick():
+                act.do(self.delay, self.hold)
+
+    def callback(self, event):
+        key_name = event.name
+        if key_name == "`":
+            if self.toggle():
+                print_ok("ON")
+            else:
+                print_ok("OFF")
+        elif self.toggled:
+            if 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()
+
+    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)
+        await task_group(
+            self.background()
+        )
 
-root.focus()
-root.mainloop()
+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 = Tyrell(",".join(argv[1:]))
+    if not exists("keys"):
+        print_err("Missing 'keys' directory")
+        print_info("(Might want some keybinds)")
+        exit()
+    for ent in listdir("keys"):
+        if ent.startswith(".") or not ent.endswith(".toml") or isdir(ent):
+            continue
+        with open(join("keys", ent), "r") as f:
+            t = toml_load(f)
+            if "duration" in t:
+                ty.add_action(t["keybind"], Action(t["kind"], extra=t, delay=t["duration"]))
+            else:
+                ty.add_action(t["keybind"], Action(t["kind"], extra=t))
+            print(f"{t['keybind']} -> {t['kind']}")
+    if len(ty.keybinds) == 0:
+        print_err("Missing keybinds")
+        print_info("(Might want some keybinds, place them in a 'keys' directory)")
+        print_info("(  Need an example? Look at 'example.toml')")
+        exit()
+    ty.enable(all_notickers=True)
+    ty.disable()
+    try:
+        run(ty.mainloop())
+    except KeyboardInterrupt:
+        exit()