tyrell.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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. name: str
  77. kind: str
  78. extra: Dict
  79. toggled: bool
  80. delay: int
  81. max_delay: int
  82. def __init__(self, name, kind, extra=None, toggled=False, delay=0):
  83. self.name
  84. self.kind = kind
  85. self.extra = extra
  86. self.toggled = toggled
  87. self.delay = delay
  88. self.max_delay = delay
  89. def toggle(self) -> bool:
  90. self.toggled = not self.toggled
  91. return self.toggled
  92. def tick(self) -> bool:
  93. if not self.toggled:
  94. if self.delay != self.max_delay:
  95. self.delay = self.max_delay
  96. return False
  97. if self.max_delay != 0:
  98. self.delay -= 1
  99. if self.delay <= 0:
  100. self.delay = self.max_delay
  101. return True
  102. return False
  103. def do(self, delay: int=50, hold: int=20):
  104. if not self.toggled:
  105. return
  106. d = delay
  107. h = hold
  108. if self.extra is not None:
  109. if "delay" in self.extra:
  110. d = self.extra["delay"]
  111. if "hold" in self.extra:
  112. h = self.extra["hold"]
  113. if self.kind == "click":
  114. mouse_output(self.extra["button"], d, h)
  115. elif self.kind == "click down":
  116. mouse_down(self.extra["button"])
  117. elif self.kind == "key":
  118. keyboard_output(self.extra["write"], d, h)
  119. elif self.kind == "key down":
  120. key_down(self.extra["write"])
  121. class Tyrell():
  122. keybinds: Dict[str, Action]
  123. #mirrors: Dict[str, str] # mirror key -> key in keybinds
  124. toggled: bool
  125. delay: int
  126. hold: int
  127. def __init__(self, profile_name: str):
  128. self.profile = {
  129. "activator": "`",
  130. "helper": "?",
  131. "delay": 50,
  132. "hold": 20,
  133. "tick": 50,
  134. "placeholder_name": False,
  135. }
  136. self.name = profile_name
  137. if profile_name.endswith(".toml"):
  138. self.name = profile_name.removesuffix(".toml")
  139. if not exists(self.name) or not isdir(self.name):
  140. print_err(f"Invalid profile '{self.name}'")
  141. print_info(f"This should be a directory")
  142. exit()
  143. profile_config = join(self.name, self.name+".toml")
  144. if exists(profile_config):
  145. with open(profile_config, "r") as f:
  146. try:
  147. t = toml_load(f)
  148. except Exception as err:
  149. print_err(f"Invalid profile '{self.name}'")
  150. print_info(f"Invalid '{self.name+'.toml'}' config")
  151. print(err)
  152. exit()
  153. self.profile = t
  154. if "activator" not in self.profile:
  155. self.profile["activator"] = "`"
  156. if "helper" not in self.profile:
  157. self.profile["helper"] = "?"
  158. if "hold" not in self.profile:
  159. self.profile["hold"] = 20
  160. if "delay" not in self.profile:
  161. self.profile["delay"] = 50
  162. if "tick" not in self.profile:
  163. self.profile["tick"] = 50
  164. if "placeholder_name" not in self.profile:
  165. self.profile["placeholder_name"] = False
  166. else:
  167. print_err(f"Invalid profile '{self.name}'")
  168. print_info(f"Missing '{self.name+'.toml'}' config")
  169. exit()
  170. self.delay = self.profile["delay"]
  171. self.hold = self.profile["hold"]
  172. self.keybinds = {}
  173. #self.mirrors = {}
  174. self.toggled = False
  175. if not exists(join(self.name, 'keys')):
  176. print_err(f"Invalid profile '{self.name}'")
  177. print_info("Missing 'keys' directory (for keybinds)")
  178. exit()
  179. else:
  180. for ent in listdir(join(self.name, "keys")):
  181. if ent.startswith(".") or isdir(ent) or not ent.endswith(".toml"):
  182. continue
  183. with open(join(self.name, "keys", ent), "r") as f:
  184. t = toml_load(f)
  185. if "duration" in t:
  186. self.add_action(t["keybind"], Action(ent.removesuffix(".toml"), t["kind"], extra=t, delay=t["duration"]))
  187. else:
  188. self.add_action(t["keybind"], Action(ent.removesuffix(".toml"), t["kind"], extra=t))
  189. def toggle(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False) -> bool:
  190. self.toggled = not self.toggled
  191. if all:
  192. for key in self.keybinds:
  193. act = self.keybinds[key]
  194. act.toggle()
  195. elif all_tickers:
  196. for key in self.keybinds:
  197. act = self.keybinds[key]
  198. if act.max_delay != 0:
  199. act.toggle()
  200. elif all_notickers:
  201. for key in self.keybinds:
  202. act = self.keybinds[key]
  203. if act.max_delay == 0:
  204. act.toggle()
  205. return self.toggled
  206. def disable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  207. self.toggled = False
  208. if all:
  209. for key in self.keybinds:
  210. act = self.keybinds[key]
  211. act.toggled = False
  212. elif all_tickers:
  213. for key in self.keybinds:
  214. act = self.keybinds[key]
  215. if act.max_delay != 0:
  216. act.toggled = False
  217. elif all_notickers:
  218. for key in self.keybinds:
  219. act = self.keybinds[key]
  220. if act.max_delay == 0:
  221. act.toggled = False
  222. def enable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  223. self.toggled = True
  224. if all:
  225. for key in self.keybinds:
  226. act = self.keybinds[key]
  227. act.toggled = True
  228. elif all_tickers:
  229. for key in self.keybinds:
  230. act = self.keybinds[key]
  231. if act.max_delay != 0:
  232. act.toggled = True
  233. elif all_notickers:
  234. for key in self.keybinds:
  235. act = self.keybinds[key]
  236. if act.max_delay == 0:
  237. act.toggled = True
  238. def add_action(self, bind: str, act: Action):
  239. act.toggled = False
  240. if "placeholder_name" in self.profile:
  241. if self.profile["placeholder_name"] and "write" in act.extra:
  242. act.extra["write"] = act.extra["write"].replace("{name}", self.name)
  243. if act.kind == "mirror" and "mirror" in act.extra:
  244. self.mirrors[act.extra["mirror"]] = bind
  245. self.keybinds[bind] = act
  246. def remove_action(self, bind: str):
  247. self.keybinds[bind] = None
  248. def is_action(self, bind: str) -> bool:
  249. return self.keybinds[bind] is not None
  250. def print_help(self):
  251. print_ok(f"{int(1000 / self.profile['tick'])} ticks per second ({self.profile['tick']} ms per tick)")
  252. print_ok(f"Name placeholder: {self.profile['placeholder_name']}")
  253. print_warn(f"{self.profile['activator']} -> Activate/Deactivate")
  254. print_warn(f"{self.profile['helper']} -> Displays Help")
  255. for key in self.keybinds:
  256. act = self.keybinds[key]
  257. if act.max_delay == 0 and act.toggled:
  258. print_info(f"{key} -> {act.name}")
  259. else:
  260. print_info(f"{key} -> {act.name} = {act.toggled} ({act.delay} ticks)")
  261. print_ok("Please use " + style("CTRL+C", fg="bright_yellow") + " to stop")
  262. def tick(self):
  263. for key in self.keybinds:
  264. act = self.keybinds[key]
  265. if act.tick():
  266. act.do(self.delay, self.hold)
  267. def callback(self, event: KeyboardEvent):
  268. key_name = event.name
  269. #if key_name in self.mirrors:
  270. # # key mirrors currently lag
  271. # act = self.keybinds[self.mirrors[key_name]]
  272. # if act.toggled:
  273. # if "write" in act.extra:
  274. # if event.event_type == "down":
  275. # key_down(act.extra["write"])
  276. # else:
  277. # key_up(act.extra["write"])
  278. # elif "button" in act.extra:
  279. # if event.event_type == "down":
  280. # mouse_down(button=act.extra["button"])
  281. # else:
  282. # mouse_up(button=act.extra["button"])
  283. #if event.event_type == "up":
  284. if key_name == self.profile["activator"]:
  285. if self.toggle():
  286. print_ok("ON")
  287. else:
  288. print_ok("OFF")
  289. elif self.toggled:
  290. #print(f"Name: {event.name}")
  291. #print(f"Code: {event.scan_code}")
  292. #print(f"Modifiers: {event.modifiers}")
  293. if self.profile["helper"] == "?" and "shift" in event.modifiers and event.name == "/" or key_name == self.profile["helper"]:
  294. self.print_help()
  295. elif key_name in self.keybinds:
  296. act = self.keybinds[key_name]
  297. if act.max_delay == 0 and act.toggled:
  298. print_info(f"{key_name} -> {act.kind}")
  299. act.do(self.delay, self.hold)
  300. else:
  301. print_info(f"{key_name} -> {act.kind} = {act.toggle()}")
  302. self.toggle()
  303. print_ok("OFF")
  304. async def background(self):
  305. while True:
  306. self.tick()
  307. await asleep(self.profile["tick"] / 1000) # 50 ms (20 per second, same as Minecraft)
  308. async def mainloop(self):
  309. keyboard_hook(self.callback)
  310. await task_group(
  311. self.background()
  312. )
  313. if __name__ == "__main__":
  314. if len(argv) == 1:
  315. print_err("Missing profile filename")
  316. print_info("Example: 'tyrell.py Apollo' would look for 'Apollo.toml'")
  317. print()
  318. print_info("Profiles allow you to add placeholders,")
  319. print_info("And defines in a \"global\" sense key delay and hold delay.")
  320. print()
  321. exit()
  322. ty = Tyrell(",".join(argv[1:]))
  323. if len(ty.keybinds) == 0:
  324. print_err("Missing keybinds")
  325. print_info("(Might want some keybinds, place them in a 'keys' directory)")
  326. print_info(f"( Need an example? Look at '{join('_example', 'keys', 'example.toml')}')")
  327. exit()
  328. ty.enable(all_notickers=True)
  329. ty.disable()
  330. ty.print_help()
  331. if ty.name == "_example":
  332. print_warn("This is the " + style("example", fg="bright_yellow") + ", please define you're own profile")
  333. print_info("Please DO NOT EDIT this example profile")
  334. try:
  335. run(ty.mainloop())
  336. except KeyboardInterrupt:
  337. exit()