tyrell.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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, KeyboardEvent
  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. "activator": "`",
  127. "helper": "?",
  128. "delay": 50,
  129. "hold": 20,
  130. "tick": 50,
  131. "placeholder_name": False,
  132. }
  133. self.name = profile_name
  134. if profile_name.endswith(".toml"):
  135. self.name = profile_name.removesuffix(".toml")
  136. if exists(self.name+".toml"):
  137. with open(self.name+".toml", "r") as f:
  138. try:
  139. t = toml_load(f)
  140. except Exception as err:
  141. print_err(f"Invalid profile '{self.name+'.toml'}'")
  142. print(err)
  143. exit()
  144. self.profile = t
  145. if "activator" not in self.profile:
  146. self.profile["activator"] = "`"
  147. if "helper" not in self.profile:
  148. self.profile["helper"] = "?"
  149. if "hold" not in self.profile:
  150. self.profile["hold"] = 20
  151. if "delay" not in self.profile:
  152. self.profile["delay"] = 50
  153. if "tick" not in self.profile:
  154. self.profile["tick"] = 50
  155. if "placeholder_name" not in self.profile:
  156. self.profile["placeholder_name"] = False
  157. else:
  158. print_err(f"Invalid profile '{self.name+'.toml'}'")
  159. exit()
  160. self.delay = self.profile["delay"]
  161. self.hold = self.profile["hold"]
  162. self.keybinds = {}
  163. self.toggled = False
  164. def toggle(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False) -> bool:
  165. self.toggled = not self.toggled
  166. if all:
  167. for key in self.keybinds:
  168. act = self.keybinds[key]
  169. act.toggle()
  170. elif all_tickers:
  171. for key in self.keybinds:
  172. act = self.keybinds[key]
  173. if act.max_delay != 0:
  174. act.toggle()
  175. elif all_notickers:
  176. for key in self.keybinds:
  177. act = self.keybinds[key]
  178. if act.max_delay == 0:
  179. act.toggle()
  180. return self.toggled
  181. def disable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  182. self.toggled = False
  183. if all:
  184. for key in self.keybinds:
  185. act = self.keybinds[key]
  186. act.toggled = False
  187. elif all_tickers:
  188. for key in self.keybinds:
  189. act = self.keybinds[key]
  190. if act.max_delay != 0:
  191. act.toggled = False
  192. elif all_notickers:
  193. for key in self.keybinds:
  194. act = self.keybinds[key]
  195. if act.max_delay == 0:
  196. act.toggled = False
  197. def enable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  198. self.toggled = True
  199. if all:
  200. for key in self.keybinds:
  201. act = self.keybinds[key]
  202. act.toggled = True
  203. elif all_tickers:
  204. for key in self.keybinds:
  205. act = self.keybinds[key]
  206. if act.max_delay != 0:
  207. act.toggled = True
  208. elif all_notickers:
  209. for key in self.keybinds:
  210. act = self.keybinds[key]
  211. if act.max_delay == 0:
  212. act.toggled = True
  213. def add_action(self, bind: str, act: Action):
  214. act.toggled = False
  215. if "placeholder_name" in self.profile:
  216. if self.profile["placeholder_name"] and "write" in act.extra:
  217. act.extra["write"] = act.extra["write"].replace("{name}", self.name)
  218. self.keybinds[bind] = act
  219. def remove_action(self, bind: str):
  220. self.keybinds[bind] = None
  221. def is_action(self, bind: str) -> bool:
  222. return self.keybinds[bind] is not None
  223. def tick(self):
  224. for key in self.keybinds:
  225. act = self.keybinds[key]
  226. if act.tick():
  227. act.do(self.delay, self.hold)
  228. def callback(self, event: KeyboardEvent):
  229. key_name = event.name
  230. if key_name == self.profile["activator"]:
  231. if self.toggle():
  232. print_ok("ON")
  233. else:
  234. print_ok("OFF")
  235. elif self.toggled:
  236. #print(f"Name: {event.name}")
  237. #print(f"Code: {event.scan_code}")
  238. #print(f"Modifiers: {event.modifiers}")
  239. if self.profile["helper"] == "?" and "shift" in event.modifiers and event.name == "/" or key_name == self.profile["helper"]:
  240. print_ok(f"{int(1000 / self.profile['tick'])} ticks per second ({self.profile['tick']} ms per tick)")
  241. print_ok(f"Name placeholder: {self.profile['placeholder_name']}")
  242. print_warn(f"{self.profile["activator"]} -> Activate/Deactivate")
  243. print_warn(f"{self.profile["helper"]} -> Displays Help")
  244. for key in self.keybinds:
  245. act = self.keybinds[key]
  246. if act.max_delay == 0 and act.toggled:
  247. print_info(f"{key} -> {act.kind}")
  248. else:
  249. print_info(f"{key} -> {act.kind} = {act.toggled}")
  250. print_ok("Please use " + style("CTRL+C", fg="bright_yellow") + " to stop")
  251. elif key_name in self.keybinds:
  252. act = self.keybinds[key_name]
  253. if act.max_delay == 0 and act.toggled:
  254. print_info(f"{key_name} -> {act.kind}")
  255. act.do(self.delay, self.hold)
  256. else:
  257. print_info(f"{key_name} -> {act.kind} = {act.toggle()}")
  258. self.toggle()
  259. print_ok("OFF")
  260. async def background(self):
  261. while True:
  262. self.tick()
  263. await asleep(self.profile["tick"] / 1000) # 50 ms (20 per second, same as Minecraft)
  264. async def mainloop(self):
  265. keyboard_hook(self.callback)
  266. await task_group(
  267. self.background()
  268. )
  269. if __name__ == "__main__":
  270. if len(argv) == 1:
  271. print_err("Missing profile filename")
  272. print_info("Example: 'tyrell.py Apollo' would look for 'Apollo.toml'")
  273. print()
  274. print_info("Profiles allow you to add placeholders,")
  275. print_info("And defines in a \"global\" sense key delay and hold delay.")
  276. print()
  277. exit()
  278. ty = Tyrell(",".join(argv[1:]))
  279. if not exists("keys"):
  280. print_err("Missing 'keys' directory")
  281. print_info("(Might want some keybinds)")
  282. exit()
  283. print_ok(f"{int(1000 / ty.profile['tick'])} ticks per second ({ty.profile['tick']} ms per tick)")
  284. print_ok(f"Name placeholder: {ty.profile['placeholder_name']}")
  285. print_warn(f"{ty.profile["activator"]} -> Activate/Deactivate")
  286. print_warn(f"{ty.profile["helper"]} -> Displays Help")
  287. for ent in listdir("keys"):
  288. if ent.startswith(".") or not ent.endswith(".toml") or isdir(ent):
  289. continue
  290. with open(join("keys", ent), "r") as f:
  291. t = toml_load(f)
  292. if "duration" in t:
  293. ty.add_action(t["keybind"], Action(t["kind"], extra=t, delay=t["duration"]))
  294. else:
  295. ty.add_action(t["keybind"], Action(t["kind"], extra=t))
  296. print_info(f"{t['keybind']} -> {t['kind']}")
  297. if len(ty.keybinds) == 0:
  298. print_err("Missing keybinds")
  299. print_info("(Might want some keybinds, place them in a 'keys' directory)")
  300. print_info("( Need an example? Look at 'example.toml')")
  301. exit()
  302. ty.enable(all_notickers=True)
  303. ty.disable()
  304. print_ok("Please use " + style("CTRL+C", fg="bright_yellow") + " to stop")
  305. try:
  306. run(ty.mainloop())
  307. except KeyboardInterrupt:
  308. exit()