tyrell.py 22 KB

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