tyrell.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  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, Tuple
  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, KeyboardEvent, add_hotkey, hook as key_hook, write as key_write
  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. key_write(msg, delay=hold)
  29. sleep(delay/1000)
  30. def mouse_output(button: str, delay: int=50, hold: int=20):
  31. mouse_down(button=button)
  32. sleep(hold / 1000)
  33. mouse_up(button=button)
  34. sleep(hold / 1000)
  35. sleep(delay / 1000)
  36. platform: str = ""
  37. os_name = OS_NAME.lower()
  38. if "linux" in os_name or "posix" in os_name:
  39. platform = "LINUX"
  40. elif "windows" in os_name or "nt" in os_name:
  41. platform = "WINDOWS"
  42. elif "darwin" in os_name:
  43. platform = "MACOS"
  44. else:
  45. print_err("Platform 'Linux', 'Windows' or 'MacOS' expected")
  46. print_info(f"Platform: {OS_NAME}")
  47. exit()
  48. class ActionKind(int):
  49. """
  50. Actions can be defined to do different things
  51. Because of this complexity, Actions are separated into
  52. Mouse Actions:
  53. - Click (Press mouse button down, then release mouse button up)
  54. - Click Down (Press mouse button down, never releasing up)
  55. Keyboard Actions:
  56. - Key (Press key down, then release key up)
  57. - Key Down (Press key down, never releasing up)
  58. Mirror Actions will typically be one of the above (mouse or keyboard actions, typically Click or Key)
  59. They Mirror a key's state, if it's down: pressing or clicking down, if it's up: releasing up
  60. """
  61. NONE = 0
  62. """ Invalid Action state """
  63. CLICK = 1
  64. """ Mouse Action, Click (Press mouse button down, then release mouse button up) """
  65. CLICK_DOWN = 2
  66. """ Mouse Action, Click Down (Press mouse button down, never releasing up) """
  67. KEY = 3
  68. """ Keyboard Action, Key (Press key down, then release key up) """
  69. KEY_DOWN = 4
  70. """ Keyboard Action, Key Down (Press key down, never releasing up) """
  71. MIRROR = 5
  72. """ Mirror Actions will typically be one of the above (mouse or keyboard actions, typically Click or Key)
  73. They Mirror a key's state, if it's down: pressing or clicking down, if it's up: releasing up
  74. """
  75. def __init__(self, kind: str):
  76. # Unlike most __init__ methods, this converts a string into a ActionKind (returning the ActionKind)
  77. k = kind.lower().strip()
  78. if k == "click":
  79. return ActionKind.CLICK
  80. elif k == "click down":
  81. return ActionKind.CLICK_DOWN
  82. elif k == "key":
  83. return ActionKind.KEY
  84. elif k == "key down":
  85. return ActionKind.KEY_DOWN
  86. elif k == "mirror":
  87. return ActionKind.MIRROR
  88. else:
  89. return ActionKind.NONE
  90. def __str__(self) -> str:
  91. # Converts the ActionKind into a string, mostly for debugging
  92. if self == ActionKind.CLICK:
  93. return "click"
  94. elif self == ActionKind.CLICK_DOWN:
  95. return "click down"
  96. elif self == ActionKind.KEY:
  97. return "key"
  98. elif self == ActionKind.KEY_DOWN:
  99. return "key down"
  100. elif self == ActionKind.MIRROR:
  101. return "mirror"
  102. else:
  103. return f"<ActionKind {int(self)}>"
  104. def is_mouse(self) -> bool:
  105. """ Is this Action a Mouse Action
  106. Mouse Actions:
  107. - Click (Press mouse button down, then release mouse button up)
  108. - Click Down (Press mouse button down, never releasing up)
  109. """
  110. return self == ActionKind.CLICK or self == ActionKind.CLICK_DOWN
  111. def is_keyboard(self) -> bool:
  112. """ Is this Action a Keyboard Action
  113. Keyboard Actions:
  114. - Key (Press key down, then release key up)
  115. - Key Down (Press key down, never releasing up)
  116. """
  117. return self == ActionKind.KEY or self == ActionKind.KEY_DOWN
  118. class Action:
  119. name: str
  120. """ Action's can be named to describe and clarify what the Action should do
  121. i.e. 'auto click' for some Auto Clicker, 'shop' for something that opens a shop (such as '/shop')
  122. """
  123. kind: ActionKind
  124. """ The Kind of Action to do
  125. See tyrell.ActionKind
  126. """
  127. extra: Dict
  128. enabled: bool
  129. delay: int
  130. max_delay: int
  131. def __init__(self, name: str, kind: str, extra: Dict, on: bool=False, delay: int=0):
  132. self.name = name
  133. self.kind = ActionKind(kind)
  134. self.enabled = on
  135. self.delay = delay
  136. self.max_delay = delay
  137. def toggle(self) -> bool:
  138. self.enabled = not self.enabled
  139. return self.enabled
  140. def enable(self):
  141. self.enabled = True
  142. def disable(self):
  143. self.enabled = False
  144. def is_ticker(self) -> bool:
  145. """ Actions can be time based or not
  146. Tickers are: Actions time based will fire after a number of ticks (at a set delay, tick speed)
  147. All Actions not time based fire only when the selected "keybind" is pressed (where tickers use the "keybind" to toggle them on and off)
  148. """
  149. return self.max_delay != 0
  150. def is_mirror(self, key: str) -> bool:
  151. if self.kind != ActionKind.MIRROR:
  152. return False
  153. return key == self.extra["mirror"]
  154. def tick(self) -> bool:
  155. if not self.is_ticker():
  156. return False
  157. if not self.enabled:
  158. if self.delay != self.max_delay:
  159. self.delay = self.max_delay
  160. return False
  161. self.delay -= 1
  162. if self.delay <= 0:
  163. self.delay = self.max_delay
  164. return True
  165. return False
  166. def do(self, key: Tuple[str, int], delay: int=50, hold: int=20):
  167. if not self.enabled:
  168. return
  169. d = delay
  170. h = hold
  171. if "delay" in self.extra:
  172. d = self.extra["delay"]
  173. if "hold" in self.extra:
  174. h = self.extra["hold"]
  175. if self.kind == ActionKind.CLICK:
  176. mouse_output(self.extra["button"], d, h)
  177. elif self.kind == ActionKind.CLICK_DOWN:
  178. mouse_down(button=self.extra["button"])
  179. elif self.kind == ActionKind.KEY:
  180. keyboard_output(self.extra["write"], d, h)
  181. elif self.kind == ActionKind.KEY_DOWN:
  182. key_down(self.extra["write"])
  183. elif self.kind == ActionKind.MIRROR:
  184. if key[1] == 0: # Up
  185. if "write" in self.extra: # Key Mirror
  186. key_up(self.extra["write"])
  187. elif "button" in self.extra: # Mouse Mirror
  188. mouse_up(button=self.extra["button"])
  189. elif key[1] == 1: # Down
  190. if "write" in self.extra: # Key Mirror
  191. key_down(self.extra["write"])
  192. elif "button" in self.extra: # Mouse Mirror
  193. mouse_down(button=self.extra["button"])
  194. def do_tick(self, delay: int=50, hold: int=20):
  195. if self.tick():
  196. self.do(delay, hold)
  197. class Tyrell:
  198. pass
  199. class OldAction():
  200. name: str
  201. kind: str
  202. extra: Dict
  203. toggled: bool
  204. delay: int
  205. max_delay: int
  206. def __init__(self, name, kind, extra=None, toggled=False, delay=0):
  207. self.name = name
  208. self.kind = kind
  209. if extra is not None:
  210. self.extra = extra
  211. else:
  212. self.extra = {}
  213. self.toggled = toggled
  214. self.delay = delay
  215. self.max_delay = delay
  216. def toggle(self) -> bool:
  217. self.toggled = not self.toggled
  218. return self.toggled
  219. def tick(self) -> bool:
  220. if not self.toggled:
  221. if self.delay != self.max_delay:
  222. self.delay = self.max_delay
  223. return False
  224. if self.max_delay != 0:
  225. self.delay -= 1
  226. if self.delay <= 0:
  227. self.delay = self.max_delay
  228. return True
  229. return False
  230. def do(self, delay: int=50, hold: int=20):
  231. if not self.toggled:
  232. return
  233. d = delay
  234. h = hold
  235. if self.extra is not None:
  236. if "delay" in self.extra:
  237. d = self.extra["delay"]
  238. if "hold" in self.extra:
  239. h = self.extra["hold"]
  240. if self.kind == "click":
  241. mouse_output(self.extra["button"], d, h)
  242. elif self.kind == "click down":
  243. mouse_down(self.extra["button"])
  244. elif self.kind == "key":
  245. keyboard_output(self.extra["write"], d, h)
  246. elif self.kind == "key down":
  247. key_down(self.extra["write"])
  248. class OldTyrell():
  249. keybinds: Dict[str, OldAction]
  250. mirrors: Dict[str, str] # mirror key -> key in keybinds
  251. toggled: bool
  252. delay: int
  253. hold: int
  254. def __init__(self, profile_name: str):
  255. self.profile = {
  256. "activator": "`",
  257. "helper": "?",
  258. "delay": 50,
  259. "hold": 20,
  260. "tick": 50,
  261. "placeholder_name": False,
  262. }
  263. self.name = profile_name
  264. if profile_name.endswith(".toml"):
  265. self.name = profile_name.removesuffix(".toml")
  266. if not exists(self.name) or not isdir(self.name):
  267. print_err(f"Invalid profile '{self.name}'")
  268. print_info(f"This should be a directory")
  269. exit()
  270. profile_config = join(self.name, self.name+".toml")
  271. if exists(profile_config):
  272. with open(profile_config, "r") as f:
  273. try:
  274. t = toml_load(f)
  275. except Exception as err:
  276. print_err(f"Invalid profile '{self.name}'")
  277. print_info(f"Invalid '{self.name+'.toml'}' config")
  278. print(err)
  279. exit()
  280. self.profile = t
  281. if "activator" not in self.profile:
  282. self.profile["activator"] = "`"
  283. if "helper" not in self.profile:
  284. self.profile["helper"] = "?"
  285. if "hold" not in self.profile:
  286. self.profile["hold"] = 20
  287. if "delay" not in self.profile:
  288. self.profile["delay"] = 50
  289. if "tick" not in self.profile:
  290. self.profile["tick"] = 50
  291. if "placeholder_name" not in self.profile:
  292. self.profile["placeholder_name"] = False
  293. else:
  294. print_err(f"Invalid profile '{self.name}'")
  295. print_info(f"Missing '{self.name+'.toml'}' config")
  296. exit()
  297. self.delay = self.profile["delay"]
  298. self.hold = self.profile["hold"]
  299. self.keybinds = {}
  300. self.mirrors = {}
  301. self.toggled = False
  302. if not exists(join(self.name, 'keys')):
  303. print_err(f"Invalid profile '{self.name}'")
  304. print_info("Missing 'keys' directory (for keybinds)")
  305. exit()
  306. else:
  307. for ent in listdir(join(self.name, "keys")):
  308. if ent.startswith(".") or isdir(ent) or not ent.endswith(".toml"):
  309. continue
  310. with open(join(self.name, "keys", ent), "r") as f:
  311. t = toml_load(f)
  312. if t["keybind"] == "?" or t["keybind"] == "`":
  313. continue
  314. if "duration" in t:
  315. self.add_action(t["keybind"], OldAction(ent.removesuffix(".toml"), t["kind"], extra=t, delay=t["duration"]))
  316. else:
  317. self.add_action(t["keybind"], OldAction(ent.removesuffix(".toml"), t["kind"], extra=t))
  318. def toggle(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False) -> bool:
  319. self.toggled = not self.toggled
  320. if all:
  321. for key in self.keybinds:
  322. act = self.keybinds[key]
  323. act.toggle()
  324. elif all_tickers:
  325. for key in self.keybinds:
  326. act = self.keybinds[key]
  327. if act.max_delay != 0:
  328. act.toggle()
  329. elif all_notickers:
  330. for key in self.keybinds:
  331. act = self.keybinds[key]
  332. if act.max_delay == 0:
  333. act.toggle()
  334. return self.toggled
  335. def disable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  336. self.toggled = False
  337. if all:
  338. for key in self.keybinds:
  339. act = self.keybinds[key]
  340. act.toggled = False
  341. elif all_tickers:
  342. for key in self.keybinds:
  343. act = self.keybinds[key]
  344. if act.max_delay != 0:
  345. act.toggled = False
  346. elif all_notickers:
  347. for key in self.keybinds:
  348. act = self.keybinds[key]
  349. if act.max_delay == 0:
  350. act.toggled = False
  351. def enable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  352. self.toggled = True
  353. if all:
  354. for key in self.keybinds:
  355. act = self.keybinds[key]
  356. act.toggled = True
  357. elif all_tickers:
  358. for key in self.keybinds:
  359. act = self.keybinds[key]
  360. if act.max_delay != 0:
  361. act.toggled = True
  362. elif all_notickers:
  363. for key in self.keybinds:
  364. act = self.keybinds[key]
  365. if act.max_delay == 0:
  366. act.toggled = True
  367. def add_action(self, bind: str, act: OldAction):
  368. act.toggled = False
  369. if "placeholder_name" in self.profile:
  370. if self.profile["placeholder_name"] and "write" in act.extra:
  371. act.extra["write"] = act.extra["write"].replace("{name}", self.name)
  372. if act.kind == "mirror" and "mirror" in act.extra:
  373. self.mirrors[act.extra["mirror"]] = bind
  374. self.keybinds[bind] = act
  375. def remove_action(self, bind: str):
  376. self.keybinds[bind] = None
  377. def is_action(self, bind: str) -> bool:
  378. return self.keybinds[bind] is not None
  379. def print_help(self):
  380. print_ok(f"{int(1000 / self.profile['tick'])} ticks per second ({self.profile['tick']} ms per tick)")
  381. print_ok(f"Name placeholder: {self.profile['placeholder_name']}")
  382. print_warn(f"{self.profile['activator']} -> Activate/Deactivate")
  383. print_warn(f"{self.profile['helper']} -> Displays Help")
  384. for key in self.keybinds:
  385. act = self.keybinds[key]
  386. if act.max_delay == 0 and act.toggled:
  387. print_info(f"{key} -> {act.name}")
  388. else:
  389. print_info(f"{key} -> {act.name} = {act.toggled} ({act.max_delay} ticks)")
  390. print_ok("Please use " + style("CTRL+C", fg="bright_yellow") + " to stop")
  391. def tick(self):
  392. for key in self.keybinds:
  393. act = self.keybinds[key]
  394. if act.tick():
  395. act.do(self.delay, self.hold)
  396. # def callback(self, event: KeyboardEvent):
  397. # key_name = event.name
  398. # #if key_name in self.mirrors:
  399. # # # key mirrors currently lag
  400. # # act = self.keybinds[self.mirrors[key_name]]
  401. # # if act.toggled:
  402. # # if "write" in act.extra:
  403. # # if event.event_type == "down":
  404. # # key_down(act.extra["write"])
  405. # # else:
  406. # # key_up(act.extra["write"])
  407. # # elif "button" in act.extra:
  408. # # if event.event_type == "down":
  409. # # mouse_down(button=act.extra["button"])
  410. # # else:
  411. # # mouse_up(button=act.extra["button"])
  412. # #if event.event_type == "up":
  413. # if key_name == self.profile["activator"]:
  414. # if self.toggle():
  415. # print_ok("ON")
  416. # else:
  417. # print_ok("OFF")
  418. # elif self.toggled:
  419. # #print(f"Name: {event.name}")
  420. # #print(f"Code: {event.scan_code}")
  421. # #print(f"Modifiers: {event.modifiers}")
  422. # if self.profile["helper"] == "?" and "shift" in event.modifiers and event.name == "/" or key_name == self.profile["helper"]:
  423. # self.print_help()
  424. # elif key_name in self.keybinds:
  425. # act = self.keybinds[key_name]
  426. # if act.max_delay == 0 and act.toggled:
  427. # print_info(f"{key_name} -> {act.kind}")
  428. # act.do(self.delay, self.hold)
  429. # else:
  430. # print_info(f"{key_name} -> {act.kind} = {act.toggle()}")
  431. # self.toggle()
  432. # print_ok("OFF")
  433. def callback(self, bind, state=None, mirror_host=None):
  434. if bind == "__toggle__":
  435. if self.toggle():
  436. print_info(style("ON", fg="bright_green"))
  437. else:
  438. print_info(style("OFF", fg="bright_red"))
  439. return
  440. if state == "up" or state == "down":
  441. act = self.keybinds[mirror_host]
  442. if act.toggled:
  443. if act.extra["write"] != None or len(act.extra["write"]) != 0:
  444. if state == "up":
  445. print(f"'{bind}' -> {act.name} UP")
  446. key_up(act.extra["write"])
  447. elif state == "down":
  448. print(f"'{bind}' -> {act.name} DOWN")
  449. key_down(act.extra["write"])
  450. elif act.extra["button"] != None or len(act.extra["button"]) != 0:
  451. if state == "up":
  452. print(f"'{bind}' -> {act.name} UP")
  453. mouse_up(button=act.extra["button"])
  454. elif state == "down":
  455. print(f"'{bind}' -> {act.name} DOWN")
  456. mouse_down(button=act.extra["button"])
  457. return
  458. if not self.toggled:
  459. return
  460. if bind == "__help__":
  461. self.print_help()
  462. self.disable()
  463. print_info(style("OFF", fg="bright_red"))
  464. return
  465. act = self.keybinds[bind]
  466. if act.toggled:
  467. print(f"'{bind}' -> {act.name}")
  468. act.do(self.delay, self.hold)
  469. elif act.max_delay != 0 or act.kind == "mirror":
  470. if act.toggle():
  471. print(f"'{bind}' -> {act.name} " + style("ON", fg="bright_green"))
  472. else:
  473. print(f"'{bind}' -> {act.name} " + style("OFF", fg="bright_red"))
  474. self.disable()
  475. print_info(style("OFF", fg="bright_red"))
  476. async def background(self):
  477. while True:
  478. self.tick()
  479. await asleep(self.profile["tick"] / 1000) # 50 ms (20 per second, same as Minecraft)
  480. async def mainloop(self):
  481. #keyboard_hook(self.callback)
  482. add_hotkey(41, self.callback, args=["__toggle__"])
  483. add_hotkey("shift+?", self.callback, args=["__help__"])
  484. for bind in self.keybinds:
  485. act = self.keybinds[bind]
  486. if act.kind == "key" or act.kind == "key down":
  487. print(f"hotkey -=> {bind}")
  488. add_hotkey(bind, self.callback, args=[bind])
  489. elif act.kind == "mirror":
  490. print(f"hotkey -=> {bind} (mirror {act.extra['mirror']})")
  491. add_hotkey(bind, self.callback, args=[bind])
  492. add_hotkey(bind, self.callback, args=[act.extra["mirror"], "down", bind])
  493. add_hotkey(bind, self.callback, args=[act.extra["mirror"], "up", bind], trigger_on_release=True)
  494. await task_group(
  495. self.background()
  496. )
  497. if __name__ == "__main__":
  498. if len(argv) == 1:
  499. print_err("Missing profile filename")
  500. print_info("Example: 'tyrell.py Apollo' would look for 'Apollo.toml'")
  501. print()
  502. print_info("Profiles allow you to add placeholders,")
  503. print_info("And defines in a \"global\" sense key delay and hold delay.")
  504. print()
  505. exit()
  506. ty = OldTyrell(",".join(argv[1:]))
  507. if len(ty.keybinds) == 0:
  508. print_err("Missing keybinds")
  509. print_info("(Might want some keybinds, place them in a 'keys' directory)")
  510. print_info(f"( Need an example? Look at '{join('_example', 'keys', 'example.toml')}')")
  511. exit()
  512. ty.enable(all_notickers=True)
  513. ty.disable()
  514. ty.print_help()
  515. if ty.name == "_example":
  516. print_warn("This is the " + style("example", fg="bright_yellow") + ", please define you're own profile")
  517. print_info("Please DO NOT EDIT this example profile")
  518. try:
  519. run(ty.mainloop())
  520. except KeyboardInterrupt:
  521. exit()