tyrell.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. from sys import exit, argv
  2. from os import name as OS_NAME
  3. from os import listdir
  4. from os.path import exists, join, isdir
  5. from time import sleep
  6. from typing import Dict
  7. try:
  8. from click import echo, style
  9. except ImportError:
  10. print("ERROR Please activate a virtual environment and install requirements.txt")
  11. exit()
  12. from pyautogui import mouseDown as mouse_down, mouseUp as mouse_up
  13. from keyboard import press as key_down, release as key_up, on_release as keyboard_hook
  14. # Not entirely sure why this doesn't work
  15. #from mouse import press as mouse_down, release as mouse_up, click as mouse_press, move as mouse_move
  16. from toml import load as toml_load
  17. from asyncio import run, sleep as asleep
  18. from asyncio.tasks import gather as task_group
  19. def print_err(msg):
  20. echo(style("ERROR", fg="bright_red") + " " + msg)
  21. def print_warn(msg):
  22. echo(style("WARN ", fg="bright_yellow") + " " + msg)
  23. def print_ok(msg):
  24. echo(style(" OK ", fg="bright_green") + " " + msg)
  25. def print_info(msg):
  26. print(" " + msg)
  27. def keyboard_output(msg: str, delay: int=50, hold: int=20):
  28. for k in msg:
  29. if k.isupper():
  30. k = k.lower()
  31. key_down('shift')
  32. key_down(k)
  33. sleep(hold / 1000)
  34. key_up(k)
  35. key_up('shift')
  36. sleep(hold / 1000)
  37. elif k in ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')']:
  38. m = {'!': '1', '@': '2', '#': '3', '$': '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0'}
  39. key_down('shift')
  40. key_down(m[k])
  41. sleep(hold / 1000)
  42. key_up(m[k])
  43. key_up('shift')
  44. sleep(hold / 1000)
  45. elif k in ['\r', '\n']:
  46. m = {'\r': 'enter', '\n': 'enter'}
  47. key_down(m[k])
  48. sleep(hold / 1000)
  49. key_up(m[k])
  50. sleep(hold / 1000)
  51. else:
  52. key_down(k)
  53. sleep(hold / 1000)
  54. key_up(k)
  55. sleep(hold / 1000)
  56. sleep(delay / 1000)
  57. def mouse_output(button: str, delay: int=50, hold: int=20):
  58. mouse_down(button=button)
  59. sleep(hold / 1000)
  60. mouse_up(button=button)
  61. sleep(hold / 1000)
  62. sleep(delay / 1000)
  63. platform: str = ""
  64. os_name = OS_NAME.lower()
  65. if "linux" in os_name or "posix" in os_name:
  66. platform = "LINUX"
  67. elif "windows" in os_name or "nt" in os_name:
  68. platform = "WINDOWS"
  69. elif "darwin" in os_name:
  70. platform = "MACOS"
  71. else:
  72. print_err("Platform 'Linux', 'Windows' or 'MacOS' expected")
  73. print_info(f"Platform: {OS_NAME}")
  74. exit()
  75. class Action():
  76. kind: str
  77. extra: Dict
  78. toggled: bool
  79. delay: int
  80. max_delay: int
  81. def __init__(self, kind, extra=None, toggled=False, delay=0):
  82. self.kind = kind
  83. self.extra = extra
  84. self.toggled = toggled
  85. self.delay = delay
  86. self.max_delay = delay
  87. def toggle(self) -> bool:
  88. self.toggled = not self.toggled
  89. return self.toggled
  90. def tick(self) -> bool:
  91. if not self.toggled:
  92. if self.delay != self.max_delay:
  93. self.delay = self.max_delay
  94. return False
  95. if self.max_delay != 0:
  96. self.delay -= 1
  97. if self.delay <= 0:
  98. self.delay = self.max_delay
  99. return True
  100. return False
  101. def do(self, delay: int=50, hold: int=20):
  102. if not self.toggled:
  103. return
  104. d = delay
  105. h = hold
  106. if self.extra is not None:
  107. if "delay" in self.extra:
  108. d = self.extra["delay"]
  109. if "hold" in self.extra:
  110. h = self.extra["hold"]
  111. if self.kind == "click":
  112. mouse_output(self.extra["button"], d, h)
  113. elif self.kind == "click down":
  114. mouse_down(self.extra["button"])
  115. elif self.kind == "key":
  116. keyboard_output(self.extra["write"], d, h)
  117. elif self.kind == "key down":
  118. key_down(self.extra["write"])
  119. class Tyrell():
  120. keybinds: Dict[str, Action]
  121. toggled: bool
  122. delay: int
  123. hold: int
  124. def __init__(self, profile_name: str):
  125. self.profile = {
  126. "delay": 50,
  127. "hold": 20,
  128. "tick": 50,
  129. "placeholder_name": False,
  130. }
  131. self.name = profile_name
  132. if profile_name.endswith(".toml"):
  133. self.name = profile_name.removesuffix(".toml")
  134. if exists(self.name+".toml"):
  135. with open(self.name+".toml", "r") as f:
  136. try:
  137. t = toml_load(f)
  138. except Exception as err:
  139. print_err(f"Invalid profile '{self.name+'.toml'}'")
  140. print(err)
  141. exit()
  142. self.profile = t
  143. if "hold" not in self.profile:
  144. self.profile["hold"] = 20
  145. if "delay" not in self.profile:
  146. self.profile["delay"] = 50
  147. if "tick" not in self.profile:
  148. self.profile["tick"] = 50
  149. if "placeholder_name" not in self.profile:
  150. self.profile["placeholder_name"] = False
  151. else:
  152. print_err(f"Invalid profile '{self.name+'.toml'}'")
  153. exit()
  154. self.delay = self.profile["delay"]
  155. self.hold = self.profile["hold"]
  156. self.keybinds = {}
  157. self.toggled = False
  158. def toggle(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False) -> bool:
  159. self.toggled = not self.toggled
  160. if all:
  161. for key in self.keybinds:
  162. act = self.keybinds[key]
  163. act.toggle()
  164. elif all_tickers:
  165. for key in self.keybinds:
  166. act = self.keybinds[key]
  167. if act.max_delay != 0:
  168. act.toggle()
  169. elif all_notickers:
  170. for key in self.keybinds:
  171. act = self.keybinds[key]
  172. if act.max_delay == 0:
  173. act.toggle()
  174. return self.toggled
  175. def disable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  176. self.toggled = False
  177. if all:
  178. for key in self.keybinds:
  179. act = self.keybinds[key]
  180. act.toggled = False
  181. elif all_tickers:
  182. for key in self.keybinds:
  183. act = self.keybinds[key]
  184. if act.max_delay != 0:
  185. act.toggled = False
  186. elif all_notickers:
  187. for key in self.keybinds:
  188. act = self.keybinds[key]
  189. if act.max_delay == 0:
  190. act.toggled = False
  191. def enable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  192. self.toggled = True
  193. if all:
  194. for key in self.keybinds:
  195. act = self.keybinds[key]
  196. act.toggled = True
  197. elif all_tickers:
  198. for key in self.keybinds:
  199. act = self.keybinds[key]
  200. if act.max_delay != 0:
  201. act.toggled = True
  202. elif all_notickers:
  203. for key in self.keybinds:
  204. act = self.keybinds[key]
  205. if act.max_delay == 0:
  206. act.toggled = True
  207. def add_action(self, bind: str, act: Action):
  208. act.toggled = False
  209. if "placeholder_name" in self.profile:
  210. if self.profile["placeholder_name"] and "write" in act.extra:
  211. act.extra["write"] = act.extra["write"].replace("{name}", self.name)
  212. self.keybinds[bind] = act
  213. def remove_action(self, bind: str):
  214. self.keybinds[bind] = None
  215. def is_action(self, bind: str) -> bool:
  216. return self.keybinds[bind] is not None
  217. def tick(self):
  218. for key in self.keybinds:
  219. act = self.keybinds[key]
  220. if act.tick():
  221. act.do(self.delay, self.hold)
  222. def callback(self, event):
  223. key_name = event.name
  224. if key_name == "`":
  225. if self.toggle():
  226. print_ok("ON")
  227. else:
  228. print_ok("OFF")
  229. elif self.toggled:
  230. if key_name in self.keybinds:
  231. act = self.keybinds[key_name]
  232. if act.max_delay == 0 and act.toggled:
  233. print_info(f"{key_name} -> {act.kind}")
  234. act.do(self.delay, self.hold)
  235. else:
  236. print_info(f"{key_name} -> {act.kind} = {act.toggle()}")
  237. self.toggle()
  238. async def background(self):
  239. while True:
  240. self.tick()
  241. await asleep(self.profile["tick"] / 1000) # 50 ms (20 per second, same as Minecraft)
  242. async def mainloop(self):
  243. keyboard_hook(self.callback)
  244. await task_group(
  245. self.background()
  246. )
  247. if __name__ == "__main__":
  248. if len(argv) == 1:
  249. print_err("Missing profile filename")
  250. print_info("Example: 'tyrell.py Apollo' would look for 'Apollo.toml'")
  251. print()
  252. print_info("Profiles allow you to add placeholders,")
  253. print_info("And defines in a \"global\" sense key delay and hold delay.")
  254. print()
  255. exit()
  256. ty = Tyrell(",".join(argv[1:]))
  257. if not exists("keys"):
  258. print_err("Missing 'keys' directory")
  259. print_info("(Might want some keybinds)")
  260. exit()
  261. for ent in listdir("keys"):
  262. if ent.startswith(".") or not ent.endswith(".toml") or isdir(ent):
  263. continue
  264. with open(join("keys", ent), "r") as f:
  265. t = toml_load(f)
  266. if "duration" in t:
  267. ty.add_action(t["keybind"], Action(t["kind"], extra=t, delay=t["duration"]))
  268. else:
  269. ty.add_action(t["keybind"], Action(t["kind"], extra=t))
  270. print(f"{t['keybind']} -> {t['kind']}")
  271. if len(ty.keybinds) == 0:
  272. print_err("Missing keybinds")
  273. print_info("(Might want some keybinds, place them in a 'keys' directory)")
  274. print_info("( Need an example? Look at 'example.toml')")
  275. exit()
  276. ty.enable(all_notickers=True)
  277. ty.disable()
  278. try:
  279. run(ty.mainloop())
  280. except KeyboardInterrupt:
  281. exit()