Last active
February 17, 2020 01:52
-
-
Save hldh214/24971ca19d6095f14231d12d39893c6f to your computer and use it in GitHub Desktop.
gtav-casino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
import pytesseract | |
import pywinauto | |
# Code by Daniel Kukiela (https://twitter.com/daniel_kukiela) | |
import ctypes | |
from threading import Thread | |
from time import sleep | |
from queue import Queue | |
import win32gui | |
def make_relative_rect(source, diff): | |
return list(map(lambda x: x[0] + x[1], zip(source, diff))) | |
# main keys class | |
class Keys(object): | |
common = None | |
standalone = False | |
# instance of worker class | |
keys_worker = None | |
keys_process = None | |
# key constants | |
direct_keys = 0x0008 | |
virtual_keys = 0x0000 | |
key_press = 0x0000 | |
key_release = 0x0002 | |
# mouse constants | |
mouse_move = 0x0001 | |
mouse_lb_press = 0x0002 | |
mouse_lb_release = 0x0004 | |
mouse_rb_press = 0x0008 | |
mouse_rb_release = 0x0010 | |
mouse_mb_press = 0x0020 | |
mouse_mb_release = 0x0040 | |
# direct keys | |
dk = { | |
"1": 0x02, | |
"2": 0x03, | |
"3": 0x04, | |
"4": 0x05, | |
"5": 0x06, | |
"6": 0x07, | |
"7": 0x08, | |
"8": 0x09, | |
"9": 0x0A, | |
"0": 0x0B, | |
"NUMPAD1": 0x4F, "NP1": 0x4F, | |
"NUMPAD2": 0x50, "NP2": 0x50, | |
"NUMPAD3": 0x51, "NP3": 0x51, | |
"NUMPAD4": 0x4B, "NP4": 0x4B, | |
"NUMPAD5": 0x4C, "NP5": 0x4C, | |
"NUMPAD6": 0x4D, "NP6": 0x4D, | |
"NUMPAD7": 0x47, "NP7": 0x47, | |
"NUMPAD8": 0x48, "NP8": 0x48, | |
"NUMPAD9": 0x49, "NP9": 0x49, | |
"NUMPAD0": 0x52, "NP0": 0x52, | |
"DIVIDE": 0xB5, "NPDV": 0xB5, | |
"MULTIPLY": 0x37, "NPM": 0x37, | |
"SUBSTRACT": 0x4A, "NPS": 0x4A, | |
"ADD": 0x4E, "NPA": 0x4E, | |
"DECIMAL": 0x53, "NPDC": 0x53, | |
"NUMPADENTER": 0x9C, "NPE": 0x9C, | |
"A": 0x1E, | |
"B": 0x30, | |
"C": 0x2E, | |
"D": 0x20, | |
"E": 0x12, | |
"F": 0x21, | |
"G": 0x22, | |
"H": 0x23, | |
"I": 0x17, | |
"J": 0x24, | |
"K": 0x25, | |
"L": 0x26, | |
"M": 0x32, | |
"N": 0x31, | |
"O": 0x18, | |
"P": 0x19, | |
"Q": 0x10, | |
"R": 0x13, | |
"S": 0x1F, | |
"T": 0x14, | |
"U": 0x16, | |
"V": 0x2F, | |
"W": 0x11, | |
"X": 0x2D, | |
"Y": 0x15, | |
"Z": 0x2C, | |
"F1": 0x3B, | |
"F2": 0x3C, | |
"F3": 0x3D, | |
"F4": 0x3E, | |
"F5": 0x3F, | |
"F6": 0x40, | |
"F7": 0x41, | |
"F8": 0x42, | |
"F9": 0x43, | |
"F10": 0x44, | |
"F11": 0x57, | |
"F12": 0x58, | |
"UP": 0xC8, | |
"LEFT": 0xCB, | |
"RIGHT": 0xCD, | |
"DOWN": 0xD0, | |
"ESC": 0x01, | |
"SPACE": 0x39, "SPC": 0x39, | |
"RETURN": 0x1C, "ENT": 0x1C, | |
"INSERT": 0xD2, "INS": 0xD2, | |
"DELETE": 0xD3, "DEL": 0xD3, | |
"HOME": 0xC7, | |
"END": 0xCF, | |
"PRIOR": 0xC9, "PGUP": 0xC9, | |
"NEXT": 0xD1, "PGDN": 0xD1, | |
"BACK": 0x0E, | |
"TAB": 0x0F, | |
"LCONTROL": 0x1D, "LCTRL": 0x1D, | |
"RCONTROL": 0x9D, "RCTRL": 0x9D, | |
"LSHIFT": 0x2A, "LSH": 0x2A, | |
"RSHIFT": 0x36, "RSH": 0x36, | |
"LMENU": 0x38, "LALT": 0x38, | |
"RMENU": 0xB8, "RALT": 0xB8, | |
"LWIN": 0xDB, | |
"RWIN": 0xDC, | |
"APPS": 0xDD, | |
"CAPITAL": 0x3A, "CAPS": 0x3A, | |
"NUMLOCK": 0x45, "NUM": 0x45, | |
"SCROLL": 0x46, "SCR": 0x46, | |
"MINUS": 0x0C, "MIN": 0x0C, | |
"LBRACKET": 0x1A, "LBR": 0x1A, | |
"RBRACKET": 0x1B, "RBR": 0x1B, | |
"SEMICOLON": 0x27, "SEM": 0x27, | |
"APOSTROPHE": 0x28, "APO": 0x28, | |
"GRAVE": 0x29, "GRA": 0x29, | |
"BACKSLASH": 0x2B, "BSL": 0x2B, | |
"COMMA": 0x33, "COM": 0x33, | |
"PERIOD": 0x34, "PER": 0x34, | |
"SLASH": 0x35, "SLA": 0x35, | |
} | |
# virtual keys | |
vk = { | |
"1": 0x31, | |
"2": 0x32, | |
"3": 0x33, | |
"4": 0x34, | |
"5": 0x35, | |
"6": 0x36, | |
"7": 0x37, | |
"8": 0x38, | |
"9": 0x39, | |
"0": 0x30, | |
"NUMPAD1": 0x61, "NP1": 0x61, | |
"NUMPAD2": 0x62, "NP2": 0x62, | |
"NUMPAD3": 0x63, "NP3": 0x63, | |
"NUMPAD4": 0x64, "NP4": 0x64, | |
"NUMPAD5": 0x65, "NP5": 0x65, | |
"NUMPAD6": 0x66, "NP6": 0x66, | |
"NUMPAD7": 0x67, "NP7": 0x67, | |
"NUMPAD8": 0x68, "NP8": 0x68, | |
"NUMPAD9": 0x69, "NP9": 0x69, | |
"NUMPAD0": 0x60, "NP0": 0x60, | |
"DIVIDE": 0x6F, "NPDV": 0x6F, | |
"MULTIPLY": 0x6A, "NPM": 0x6A, | |
"SUBSTRACT": 0x6D, "NPS": 0x6D, | |
"ADD": 0x6B, "NPA": 0x6B, | |
"DECIMAL": 0x6E, "NPDC": 0x6E, | |
"NUMPADENTER": 0x0D, "NPE": 0x0D, | |
"A": 0x41, | |
"B": 0x42, | |
"C": 0x43, | |
"D": 0x44, | |
"E": 0x45, | |
"F": 0x46, | |
"G": 0x47, | |
"H": 0x48, | |
"I": 0x49, | |
"J": 0x4A, | |
"K": 0x4B, | |
"L": 0x4C, | |
"M": 0x4D, | |
"N": 0x4E, | |
"O": 0x4F, | |
"P": 0x50, | |
"Q": 0x51, | |
"R": 0x52, | |
"S": 0x53, | |
"T": 0x54, | |
"U": 0x55, | |
"V": 0x56, | |
"W": 0x57, | |
"X": 0x58, | |
"Y": 0x59, | |
"Z": 0x5A, | |
"F1": 0x70, | |
"F2": 0x71, | |
"F3": 0x72, | |
"F4": 0x73, | |
"F5": 0x74, | |
"F6": 0x75, | |
"F7": 0x76, | |
"F8": 0x77, | |
"F9": 0x78, | |
"F10": 0x79, | |
"F11": 0x7A, | |
"F12": 0x7B, | |
"UP": 0x26, | |
"LEFT": 0x25, | |
"RIGHT": 0x27, | |
"DOWN": 0x28, | |
"ESC": 0x1B, | |
"SPACE": 0x20, "SPC": 0x20, | |
"RETURN": 0x0D, "ENT": 0x0D, | |
"INSERT": 0x2D, "INS": 0x2D, | |
"DELETE": 0x2E, "DEL": 0x2E, | |
"HOME": 0x24, | |
"END": 0x23, | |
"PRIOR": 0x21, "PGUP": 0x21, | |
"NEXT": 0x22, "PGDN": 0x22, | |
"BACK": 0x08, | |
"TAB": 0x09, | |
"LCONTROL": 0xA2, "LCTRL": 0xA2, | |
"RCONTROL": 0xA3, "RCTRL": 0xA3, | |
"LSHIFT": 0xA0, "LSH": 0xA0, | |
"RSHIFT": 0xA1, "RSH": 0xA1, | |
"LMENU": 0xA4, "LALT": 0xA4, | |
"RMENU": 0xA5, "RALT": 0xA5, | |
"LWIN": 0x5B, | |
"RWIN": 0x5C, | |
"APPS": 0x5D, | |
"CAPITAL": 0x14, "CAPS": 0x14, | |
"NUMLOCK": 0x90, "NUM": 0x90, | |
"SCROLL": 0x91, "SCR": 0x91, | |
"MINUS": 0xBD, "MIN": 0xBD, | |
"LBRACKET": 0xDB, "LBR": 0xDB, | |
"RBRACKET": 0xDD, "RBR": 0xDD, | |
"SEMICOLON": 0xBA, "SEM": 0xBA, | |
"APOSTROPHE": 0xDE, "APO": 0xDE, | |
"GRAVE": 0xC0, "GRA": 0xC0, | |
"BACKSLASH": 0xDC, "BSL": 0xDC, | |
"COMMA": 0xBC, "COM": 0xBC, | |
"PERIOD": 0xBE, "PER": 0xBE, | |
"SLASH": 0xBF, "SLA": 0xBF, | |
} | |
# setup object | |
def __init__(self, common=None): | |
self.keys_worker = KeysWorker(self) | |
# Thread(target=self.keys_worker.processQueue).start() | |
self.common = common | |
if common is None: | |
self.standalone = True | |
# parses keys string and adds keys to the queue | |
def parse_key_string(self, string): | |
# print keys | |
if not self.standalone: | |
self.common.info("Processing keys: %s" % string) | |
key_queue = [] | |
errors = [] | |
# defaults to direct keys | |
key_type = self.direct_keys | |
# split by comma | |
keys = string.upper().split(",") | |
# translate | |
for key in keys: | |
# up, down or stroke? | |
up = True | |
down = True | |
direction = key.split("_") | |
subkey = direction[0] | |
if len(direction) >= 2: | |
if direction[1] == 'UP': | |
down = False | |
else: | |
up = False | |
# switch to virtual keys | |
if subkey == "VK": | |
key_type = self.virtual_keys | |
# switch to direct keys | |
elif subkey == "DK": | |
key_type = self.direct_keys | |
# key code | |
elif subkey.startswith("0x"): | |
subkey = int(subkey, 16) | |
if subkey > 0 and subkey < 256: | |
key_queue.append({ | |
"key": int(subkey), | |
"okey": subkey, | |
"time": 0, | |
"up": up, | |
"down": down, | |
"type": key_type, | |
}) | |
else: | |
errors.append(key) | |
# pause | |
elif subkey.startswith("-"): | |
time = float(subkey.replace("-", "")) / 1000 | |
if time > 0 and time <= 10: | |
key_queue.append({ | |
"key": None, | |
"okey": "", | |
"time": time, | |
"up": False, | |
"down": False, | |
"type": None, | |
}) | |
else: | |
errors.append(key) | |
# direct key | |
elif key_type == self.direct_keys and subkey in self.dk: | |
key_queue.append({ | |
"key": self.dk[subkey], | |
"okey": subkey, | |
"time": 0, | |
"up": up, | |
"down": down, | |
"type": key_type, | |
}) | |
# virtual key | |
elif key_type == self.virtual_keys and subkey in self.vk: | |
key_queue.append({ | |
"key": self.vk[subkey], | |
"okey": subkey, | |
"time": 0, | |
"up": up, | |
"down": down, | |
"type": key_type, | |
}) | |
# no match? | |
else: | |
errors.append(key) | |
# if there are errors, do not process keys | |
if len(errors): | |
return errors | |
# create new thread if there is no active one | |
if self.keys_process is None or not self.keys_process.is_alive(): | |
self.keys_process = Thread(target=self.keys_worker.processQueue) | |
self.keys_process.start() | |
# add keys to queue | |
for i in key_queue: | |
self.keys_worker.key_queue.put(i) | |
self.keys_worker.key_queue.put(None) | |
return True | |
# direct key press | |
def directKey(self, key, direction=None, type=None): | |
if type is None: | |
type = self.direct_keys | |
if direction is None: | |
direction = self.key_press | |
if key.startswith("0x"): | |
key = int(key, 16) | |
else: | |
key = key.upper() | |
lookup_table = self.dk if type == self.direct_keys else self.vk | |
key = lookup_table[key] if key in lookup_table else 0x0000 | |
self.keys_worker.sendKey(key, direction | type) | |
# direct mouse move or button press | |
def directMouse(self, dx=0, dy=0, buttons=0): | |
self.keys_worker.sendMouse(dx, dy, buttons) | |
def click(self, button='left', repeat=1, repeat_sleep_time=0.1, sleep_time=0.05): | |
for _ in range(repeat): | |
sleep(repeat_sleep_time) | |
self.directMouse(buttons=self.mouse_lb_press if button == 'left' else self.mouse_rb_press) | |
sleep(sleep_time) | |
self.directMouse(buttons=self.mouse_lb_release if button == 'left' else self.mouse_rb_release) | |
def relative_move(self, x, y, sleep_time=0.2): | |
self.directMouse(-999, -999) | |
sleep(sleep_time) | |
self.directMouse(x, y) | |
# threaded sending keys class | |
class KeysWorker(): | |
# keys object | |
keys = None | |
# queue of keys | |
key_queue = Queue() | |
# init | |
def __init__(self, keys): | |
self.keys = keys | |
# main function, process key's queue in loop | |
def processQueue(self): | |
# endless loop | |
while True: | |
# get one key | |
key = self.key_queue.get() | |
# terminate process if queue is empty | |
if key is None: | |
self.key_queue.task_done() | |
if self.key_queue.empty(): | |
return | |
continue | |
# print key | |
elif not self.keys.standalone: | |
self.keys.common.info( | |
"Key: \033[1;35m%s/%s\033[0;37m, duration: \033[1;35m%f\033[0;37m, direction: \033[1;35m%s\033[0;37m, type: \033[1;35m%s" % ( | |
key["okey"] if key["okey"] else "None", | |
key["key"], key["time"], | |
"UP" if key["up"] and not key["down"] else "DOWN" if not key["up"] and key[ | |
"down"] else "BOTH" if key["up"] and key["down"] else "NONE", | |
"None" if key["type"] is None else "DK" if key["type"] == self.keys.direct_keys else "VK"), | |
"\033[0;35mKEY: \033[0;37m" | |
) | |
# if it's a key | |
if key["key"]: | |
# press | |
if key["down"]: | |
self.sendKey(key["key"], self.keys.key_press | key["type"]) | |
# wait | |
sleep(key["time"]) | |
# and release | |
if key["up"]: | |
self.sendKey(key["key"], self.keys.key_release | key["type"]) | |
# not an actual key, just pause | |
else: | |
sleep(key["time"]) | |
# mark as done (decrement internal queue counter) | |
self.key_queue.task_done() | |
# send key | |
def sendKey(self, key, type): | |
self.SendInput(self.Keyboard(key, type)) | |
# send mouse | |
def sendMouse(self, dx, dy, buttons): | |
if dx != 0 or dy != 0: | |
buttons |= self.keys.mouse_move | |
self.SendInput(self.Mouse(buttons, dx, dy)) | |
# send input | |
def SendInput(self, *inputs): | |
nInputs = len(inputs) | |
LPINPUT = INPUT * nInputs | |
pInputs = LPINPUT(*inputs) | |
cbSize = ctypes.c_int(ctypes.sizeof(INPUT)) | |
return ctypes.windll.user32.SendInput(nInputs, pInputs, cbSize) | |
# get input object | |
def Input(self, structure): | |
if isinstance(structure, MOUSEINPUT): | |
return INPUT(0, _INPUTunion(mi=structure)) | |
if isinstance(structure, KEYBDINPUT): | |
return INPUT(1, _INPUTunion(ki=structure)) | |
if isinstance(structure, HARDWAREINPUT): | |
return INPUT(2, _INPUTunion(hi=structure)) | |
raise TypeError('Cannot create INPUT structure!') | |
# mouse input | |
def MouseInput(self, flags, x, y, data): | |
return MOUSEINPUT(x, y, data, flags, 0, None) | |
# keyboard input | |
def KeybdInput(self, code, flags): | |
return KEYBDINPUT(code, code, flags, 0, None) | |
# hardware input | |
def HardwareInput(self, message, parameter): | |
return HARDWAREINPUT(message & 0xFFFFFFFF, | |
parameter & 0xFFFF, | |
parameter >> 16 & 0xFFFF) | |
# mouse object | |
def Mouse(self, flags, x=0, y=0, data=0): | |
return self.Input(self.MouseInput(flags, x, y, data)) | |
# keyboard object | |
def Keyboard(self, code, flags=0): | |
return self.Input(self.KeybdInput(code, flags)) | |
# hardware object | |
def Hardware(self, message, parameter=0): | |
return self.Input(self.HardwareInput(message, parameter)) | |
# types | |
LONG = ctypes.c_long | |
DWORD = ctypes.c_ulong | |
ULONG_PTR = ctypes.POINTER(DWORD) | |
WORD = ctypes.c_ushort | |
class MOUSEINPUT(ctypes.Structure): | |
_fields_ = (('dx', LONG), | |
('dy', LONG), | |
('mouseData', DWORD), | |
('dwFlags', DWORD), | |
('time', DWORD), | |
('dwExtraInfo', ULONG_PTR)) | |
class KEYBDINPUT(ctypes.Structure): | |
_fields_ = (('wVk', WORD), | |
('wScan', WORD), | |
('dwFlags', DWORD), | |
('time', DWORD), | |
('dwExtraInfo', ULONG_PTR)) | |
class HARDWAREINPUT(ctypes.Structure): | |
_fields_ = (('uMsg', DWORD), | |
('wParamL', WORD), | |
('wParamH', WORD)) | |
class _INPUTunion(ctypes.Union): | |
_fields_ = (('mi', MOUSEINPUT), | |
('ki', KEYBDINPUT), | |
('hi', HARDWAREINPUT)) | |
class INPUT(ctypes.Structure): | |
_fields_ = (('type', DWORD), | |
('union', _INPUTunion)) | |
# create logger | |
logger = logging.getLogger() | |
logger.setLevel(logging.DEBUG) | |
# create console handler and set level to debug | |
ch = logging.StreamHandler() | |
ch.setLevel(logging.DEBUG) | |
# create formatter | |
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
# add formatter to ch | |
ch.setFormatter(formatter) | |
# add ch to logger | |
logger.addHandler(ch) | |
# 0 for borderless mode | |
# 45 for 4k resolution's windowed mode | |
height_offset = 45 | |
pytesseract.pytesseract.tesseract_cmd = 'C:/Program Files/Tesseract-OCR/tesseract' | |
hwnd = win32gui.FindWindow(None, 'Grand Theft Auto V') | |
win32gui.SetForegroundWindow(hwnd) | |
pwa_app = pywinauto.application.Application() | |
gtav_app = pwa_app.connect(handle=hwnd) | |
gtav_window = gtav_app.window() | |
keys_obj = Keys() | |
payout_coords = (1315, 645 + height_offset, 1440, 695 + height_offset) | |
horse_coords = ( | |
(300, 350 + height_offset), | |
(300, 460 + height_offset), | |
(300, 570 + height_offset), | |
(300, 680 + height_offset), | |
(300, 790 + height_offset), | |
(300, 900 + height_offset), | |
) | |
def left_click(coords, repeat=1): | |
gtav_window.click_input(coords=coords) | |
keys_obj.click('left', repeat) | |
while True: | |
sleep(1) | |
# move to single match's bet button and click it | |
left_click((1440, 900 + height_offset)) | |
# get min payout horse | |
payouts = [] | |
for horse_no, horse_coord in enumerate(horse_coords): | |
left_click(horse_coord) | |
full_screenshot = gtav_window.capture_as_image() | |
payout_part = full_screenshot.crop(( | |
1320, 645 + height_offset, 1440, 695 + height_offset | |
)) | |
payout = pytesseract.image_to_string(payout_part) | |
try: | |
payouts.append(int(payout)) | |
except ValueError: | |
payout_part.show() | |
raise | |
min_payout_index = payouts.index(min(payouts)) | |
min_payout_horse_coord = horse_coords[min_payout_index] | |
logger.info('{}[{}] = {}'.format(payouts, min_payout_index, min(payouts))) | |
# choose min paid horse | |
left_click(min_payout_horse_coord) | |
sleep(1) | |
# make a bet | |
left_click((1525, 520 + height_offset), 27) | |
sleep(1) | |
# start | |
left_click((1280, 800 + height_offset)) | |
# wait for the match complete | |
sleep(40) | |
# leave result page (or press ESC) | |
keys_obj.click('right') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Code by Daniel Kukiela (https://twitter.com/daniel_kukiela) | |
import ctypes | |
from threading import Thread | |
from time import sleep | |
from queue import Queue | |
import win32gui | |
def make_relative_rect(source, diff): | |
return list(map(lambda x: x[0] + x[1], zip(source, diff))) | |
# main keys class | |
class Keys(object): | |
common = None | |
standalone = False | |
# instance of worker class | |
keys_worker = None | |
keys_process = None | |
# key constants | |
direct_keys = 0x0008 | |
virtual_keys = 0x0000 | |
key_press = 0x0000 | |
key_release = 0x0002 | |
# mouse constants | |
mouse_move = 0x0001 | |
mouse_lb_press = 0x0002 | |
mouse_lb_release = 0x0004 | |
mouse_rb_press = 0x0008 | |
mouse_rb_release = 0x0010 | |
mouse_mb_press = 0x0020 | |
mouse_mb_release = 0x0040 | |
# direct keys | |
dk = { | |
"1": 0x02, | |
"2": 0x03, | |
"3": 0x04, | |
"4": 0x05, | |
"5": 0x06, | |
"6": 0x07, | |
"7": 0x08, | |
"8": 0x09, | |
"9": 0x0A, | |
"0": 0x0B, | |
"NUMPAD1": 0x4F, "NP1": 0x4F, | |
"NUMPAD2": 0x50, "NP2": 0x50, | |
"NUMPAD3": 0x51, "NP3": 0x51, | |
"NUMPAD4": 0x4B, "NP4": 0x4B, | |
"NUMPAD5": 0x4C, "NP5": 0x4C, | |
"NUMPAD6": 0x4D, "NP6": 0x4D, | |
"NUMPAD7": 0x47, "NP7": 0x47, | |
"NUMPAD8": 0x48, "NP8": 0x48, | |
"NUMPAD9": 0x49, "NP9": 0x49, | |
"NUMPAD0": 0x52, "NP0": 0x52, | |
"DIVIDE": 0xB5, "NPDV": 0xB5, | |
"MULTIPLY": 0x37, "NPM": 0x37, | |
"SUBSTRACT": 0x4A, "NPS": 0x4A, | |
"ADD": 0x4E, "NPA": 0x4E, | |
"DECIMAL": 0x53, "NPDC": 0x53, | |
"NUMPADENTER": 0x9C, "NPE": 0x9C, | |
"A": 0x1E, | |
"B": 0x30, | |
"C": 0x2E, | |
"D": 0x20, | |
"E": 0x12, | |
"F": 0x21, | |
"G": 0x22, | |
"H": 0x23, | |
"I": 0x17, | |
"J": 0x24, | |
"K": 0x25, | |
"L": 0x26, | |
"M": 0x32, | |
"N": 0x31, | |
"O": 0x18, | |
"P": 0x19, | |
"Q": 0x10, | |
"R": 0x13, | |
"S": 0x1F, | |
"T": 0x14, | |
"U": 0x16, | |
"V": 0x2F, | |
"W": 0x11, | |
"X": 0x2D, | |
"Y": 0x15, | |
"Z": 0x2C, | |
"F1": 0x3B, | |
"F2": 0x3C, | |
"F3": 0x3D, | |
"F4": 0x3E, | |
"F5": 0x3F, | |
"F6": 0x40, | |
"F7": 0x41, | |
"F8": 0x42, | |
"F9": 0x43, | |
"F10": 0x44, | |
"F11": 0x57, | |
"F12": 0x58, | |
"UP": 0xC8, | |
"LEFT": 0xCB, | |
"RIGHT": 0xCD, | |
"DOWN": 0xD0, | |
"ESC": 0x01, | |
"SPACE": 0x39, "SPC": 0x39, | |
"RETURN": 0x1C, "ENT": 0x1C, | |
"INSERT": 0xD2, "INS": 0xD2, | |
"DELETE": 0xD3, "DEL": 0xD3, | |
"HOME": 0xC7, | |
"END": 0xCF, | |
"PRIOR": 0xC9, "PGUP": 0xC9, | |
"NEXT": 0xD1, "PGDN": 0xD1, | |
"BACK": 0x0E, | |
"TAB": 0x0F, | |
"LCONTROL": 0x1D, "LCTRL": 0x1D, | |
"RCONTROL": 0x9D, "RCTRL": 0x9D, | |
"LSHIFT": 0x2A, "LSH": 0x2A, | |
"RSHIFT": 0x36, "RSH": 0x36, | |
"LMENU": 0x38, "LALT": 0x38, | |
"RMENU": 0xB8, "RALT": 0xB8, | |
"LWIN": 0xDB, | |
"RWIN": 0xDC, | |
"APPS": 0xDD, | |
"CAPITAL": 0x3A, "CAPS": 0x3A, | |
"NUMLOCK": 0x45, "NUM": 0x45, | |
"SCROLL": 0x46, "SCR": 0x46, | |
"MINUS": 0x0C, "MIN": 0x0C, | |
"LBRACKET": 0x1A, "LBR": 0x1A, | |
"RBRACKET": 0x1B, "RBR": 0x1B, | |
"SEMICOLON": 0x27, "SEM": 0x27, | |
"APOSTROPHE": 0x28, "APO": 0x28, | |
"GRAVE": 0x29, "GRA": 0x29, | |
"BACKSLASH": 0x2B, "BSL": 0x2B, | |
"COMMA": 0x33, "COM": 0x33, | |
"PERIOD": 0x34, "PER": 0x34, | |
"SLASH": 0x35, "SLA": 0x35, | |
} | |
# virtual keys | |
vk = { | |
"1": 0x31, | |
"2": 0x32, | |
"3": 0x33, | |
"4": 0x34, | |
"5": 0x35, | |
"6": 0x36, | |
"7": 0x37, | |
"8": 0x38, | |
"9": 0x39, | |
"0": 0x30, | |
"NUMPAD1": 0x61, "NP1": 0x61, | |
"NUMPAD2": 0x62, "NP2": 0x62, | |
"NUMPAD3": 0x63, "NP3": 0x63, | |
"NUMPAD4": 0x64, "NP4": 0x64, | |
"NUMPAD5": 0x65, "NP5": 0x65, | |
"NUMPAD6": 0x66, "NP6": 0x66, | |
"NUMPAD7": 0x67, "NP7": 0x67, | |
"NUMPAD8": 0x68, "NP8": 0x68, | |
"NUMPAD9": 0x69, "NP9": 0x69, | |
"NUMPAD0": 0x60, "NP0": 0x60, | |
"DIVIDE": 0x6F, "NPDV": 0x6F, | |
"MULTIPLY": 0x6A, "NPM": 0x6A, | |
"SUBSTRACT": 0x6D, "NPS": 0x6D, | |
"ADD": 0x6B, "NPA": 0x6B, | |
"DECIMAL": 0x6E, "NPDC": 0x6E, | |
"NUMPADENTER": 0x0D, "NPE": 0x0D, | |
"A": 0x41, | |
"B": 0x42, | |
"C": 0x43, | |
"D": 0x44, | |
"E": 0x45, | |
"F": 0x46, | |
"G": 0x47, | |
"H": 0x48, | |
"I": 0x49, | |
"J": 0x4A, | |
"K": 0x4B, | |
"L": 0x4C, | |
"M": 0x4D, | |
"N": 0x4E, | |
"O": 0x4F, | |
"P": 0x50, | |
"Q": 0x51, | |
"R": 0x52, | |
"S": 0x53, | |
"T": 0x54, | |
"U": 0x55, | |
"V": 0x56, | |
"W": 0x57, | |
"X": 0x58, | |
"Y": 0x59, | |
"Z": 0x5A, | |
"F1": 0x70, | |
"F2": 0x71, | |
"F3": 0x72, | |
"F4": 0x73, | |
"F5": 0x74, | |
"F6": 0x75, | |
"F7": 0x76, | |
"F8": 0x77, | |
"F9": 0x78, | |
"F10": 0x79, | |
"F11": 0x7A, | |
"F12": 0x7B, | |
"UP": 0x26, | |
"LEFT": 0x25, | |
"RIGHT": 0x27, | |
"DOWN": 0x28, | |
"ESC": 0x1B, | |
"SPACE": 0x20, "SPC": 0x20, | |
"RETURN": 0x0D, "ENT": 0x0D, | |
"INSERT": 0x2D, "INS": 0x2D, | |
"DELETE": 0x2E, "DEL": 0x2E, | |
"HOME": 0x24, | |
"END": 0x23, | |
"PRIOR": 0x21, "PGUP": 0x21, | |
"NEXT": 0x22, "PGDN": 0x22, | |
"BACK": 0x08, | |
"TAB": 0x09, | |
"LCONTROL": 0xA2, "LCTRL": 0xA2, | |
"RCONTROL": 0xA3, "RCTRL": 0xA3, | |
"LSHIFT": 0xA0, "LSH": 0xA0, | |
"RSHIFT": 0xA1, "RSH": 0xA1, | |
"LMENU": 0xA4, "LALT": 0xA4, | |
"RMENU": 0xA5, "RALT": 0xA5, | |
"LWIN": 0x5B, | |
"RWIN": 0x5C, | |
"APPS": 0x5D, | |
"CAPITAL": 0x14, "CAPS": 0x14, | |
"NUMLOCK": 0x90, "NUM": 0x90, | |
"SCROLL": 0x91, "SCR": 0x91, | |
"MINUS": 0xBD, "MIN": 0xBD, | |
"LBRACKET": 0xDB, "LBR": 0xDB, | |
"RBRACKET": 0xDD, "RBR": 0xDD, | |
"SEMICOLON": 0xBA, "SEM": 0xBA, | |
"APOSTROPHE": 0xDE, "APO": 0xDE, | |
"GRAVE": 0xC0, "GRA": 0xC0, | |
"BACKSLASH": 0xDC, "BSL": 0xDC, | |
"COMMA": 0xBC, "COM": 0xBC, | |
"PERIOD": 0xBE, "PER": 0xBE, | |
"SLASH": 0xBF, "SLA": 0xBF, | |
} | |
# setup object | |
def __init__(self, common=None): | |
self.keys_worker = KeysWorker(self) | |
# Thread(target=self.keys_worker.processQueue).start() | |
self.common = common | |
if common is None: | |
self.standalone = True | |
# parses keys string and adds keys to the queue | |
def parseKeyString(self, string): | |
# print keys | |
if not self.standalone: | |
self.common.info("Processing keys: %s" % string) | |
key_queue = [] | |
errors = [] | |
# defaults to direct keys | |
key_type = self.direct_keys | |
# split by comma | |
keys = string.upper().split(",") | |
# translate | |
for key in keys: | |
# up, down or stroke? | |
up = True | |
down = True | |
direction = key.split("_") | |
subkey = direction[0] | |
if len(direction) >= 2: | |
if direction[1] == 'UP': | |
down = False | |
else: | |
up = False | |
# switch to virtual keys | |
if subkey == "VK": | |
key_type = self.virtual_keys | |
# switch to direct keys | |
elif subkey == "DK": | |
key_type = self.direct_keys | |
# key code | |
elif subkey.startswith("0x"): | |
subkey = int(subkey, 16) | |
if subkey > 0 and subkey < 256: | |
key_queue.append({ | |
"key": int(subkey), | |
"okey": subkey, | |
"time": 0, | |
"up": up, | |
"down": down, | |
"type": key_type, | |
}) | |
else: | |
errors.append(key) | |
# pause | |
elif subkey.startswith("-"): | |
time = float(subkey.replace("-", "")) / 1000 | |
if time > 0 and time <= 10: | |
key_queue.append({ | |
"key": None, | |
"okey": "", | |
"time": time, | |
"up": False, | |
"down": False, | |
"type": None, | |
}) | |
else: | |
errors.append(key) | |
# direct key | |
elif key_type == self.direct_keys and subkey in self.dk: | |
key_queue.append({ | |
"key": self.dk[subkey], | |
"okey": subkey, | |
"time": 0, | |
"up": up, | |
"down": down, | |
"type": key_type, | |
}) | |
# virtual key | |
elif key_type == self.virtual_keys and subkey in self.vk: | |
key_queue.append({ | |
"key": self.vk[subkey], | |
"okey": subkey, | |
"time": 0, | |
"up": up, | |
"down": down, | |
"type": key_type, | |
}) | |
# no match? | |
else: | |
errors.append(key) | |
# if there are errors, do not process keys | |
if len(errors): | |
return errors | |
# create new thread if there is no active one | |
if self.keys_process is None or not self.keys_process.isAlive(): | |
self.keys_process = Thread(target=self.keys_worker.processQueue) | |
self.keys_process.start() | |
# add keys to queue | |
for i in key_queue: | |
self.keys_worker.key_queue.put(i) | |
self.keys_worker.key_queue.put(None) | |
return True | |
# direct key press | |
def directKey(self, key, direction=None, type=None): | |
if type is None: | |
type = self.direct_keys | |
if direction is None: | |
direction = self.key_press | |
if key.startswith("0x"): | |
key = int(key, 16) | |
else: | |
key = key.upper() | |
lookup_table = self.dk if type == self.direct_keys else self.vk | |
key = lookup_table[key] if key in lookup_table else 0x0000 | |
self.keys_worker.sendKey(key, direction | type) | |
# direct mouse move or button press | |
def directMouse(self, dx=0, dy=0, buttons=0): | |
self.keys_worker.sendMouse(dx, dy, buttons) | |
def click(self, button='left', repeat=1, repeat_sleep_time=0.1, sleep_time=0.05): | |
for _ in range(repeat): | |
sleep(repeat_sleep_time) | |
self.directMouse(buttons=self.mouse_lb_press if button == 'left' else self.mouse_rb_press) | |
sleep(sleep_time) | |
self.directMouse(buttons=self.mouse_lb_release if button == 'left' else self.mouse_rb_release) | |
def relative_move(self, x, y, sleep_time=0.2): | |
self.directMouse(-999, -999) | |
sleep(sleep_time) | |
self.directMouse(x, y) | |
# threaded sending keys class | |
class KeysWorker(): | |
# keys object | |
keys = None | |
# queue of keys | |
key_queue = Queue() | |
# init | |
def __init__(self, keys): | |
self.keys = keys | |
# main function, process key's queue in loop | |
def processQueue(self): | |
# endless loop | |
while True: | |
# get one key | |
key = self.key_queue.get() | |
# terminate process if queue is empty | |
if key is None: | |
self.key_queue.task_done() | |
if self.key_queue.empty(): | |
return | |
continue | |
# print key | |
elif not self.keys.standalone: | |
self.keys.common.info( | |
"Key: \033[1;35m%s/%s\033[0;37m, duration: \033[1;35m%f\033[0;37m, direction: \033[1;35m%s\033[0;37m, type: \033[1;35m%s" % ( | |
key["okey"] if key["okey"] else "None", | |
key["key"], key["time"], | |
"UP" if key["up"] and not key["down"] else "DOWN" if not key["up"] and key[ | |
"down"] else "BOTH" if key["up"] and key["down"] else "NONE", | |
"None" if key["type"] is None else "DK" if key["type"] == self.keys.direct_keys else "VK"), | |
"\033[0;35mKEY: \033[0;37m" | |
) | |
# if it's a key | |
if key["key"]: | |
# press | |
if key["down"]: | |
self.sendKey(key["key"], self.keys.key_press | key["type"]) | |
# wait | |
sleep(key["time"]) | |
# and release | |
if key["up"]: | |
self.sendKey(key["key"], self.keys.key_release | key["type"]) | |
# not an actual key, just pause | |
else: | |
sleep(key["time"]) | |
# mark as done (decrement internal queue counter) | |
self.key_queue.task_done() | |
# send key | |
def sendKey(self, key, type): | |
self.SendInput(self.Keyboard(key, type)) | |
# send mouse | |
def sendMouse(self, dx, dy, buttons): | |
if dx != 0 or dy != 0: | |
buttons |= self.keys.mouse_move | |
self.SendInput(self.Mouse(buttons, dx, dy)) | |
# send input | |
def SendInput(self, *inputs): | |
nInputs = len(inputs) | |
LPINPUT = INPUT * nInputs | |
pInputs = LPINPUT(*inputs) | |
cbSize = ctypes.c_int(ctypes.sizeof(INPUT)) | |
return ctypes.windll.user32.SendInput(nInputs, pInputs, cbSize) | |
# get input object | |
def Input(self, structure): | |
if isinstance(structure, MOUSEINPUT): | |
return INPUT(0, _INPUTunion(mi=structure)) | |
if isinstance(structure, KEYBDINPUT): | |
return INPUT(1, _INPUTunion(ki=structure)) | |
if isinstance(structure, HARDWAREINPUT): | |
return INPUT(2, _INPUTunion(hi=structure)) | |
raise TypeError('Cannot create INPUT structure!') | |
# mouse input | |
def MouseInput(self, flags, x, y, data): | |
return MOUSEINPUT(x, y, data, flags, 0, None) | |
# keyboard input | |
def KeybdInput(self, code, flags): | |
return KEYBDINPUT(code, code, flags, 0, None) | |
# hardware input | |
def HardwareInput(self, message, parameter): | |
return HARDWAREINPUT(message & 0xFFFFFFFF, | |
parameter & 0xFFFF, | |
parameter >> 16 & 0xFFFF) | |
# mouse object | |
def Mouse(self, flags, x=0, y=0, data=0): | |
return self.Input(self.MouseInput(flags, x, y, data)) | |
# keyboard object | |
def Keyboard(self, code, flags=0): | |
return self.Input(self.KeybdInput(code, flags)) | |
# hardware object | |
def Hardware(self, message, parameter=0): | |
return self.Input(self.HardwareInput(message, parameter)) | |
# types | |
LONG = ctypes.c_long | |
DWORD = ctypes.c_ulong | |
ULONG_PTR = ctypes.POINTER(DWORD) | |
WORD = ctypes.c_ushort | |
class MOUSEINPUT(ctypes.Structure): | |
_fields_ = (('dx', LONG), | |
('dy', LONG), | |
('mouseData', DWORD), | |
('dwFlags', DWORD), | |
('time', DWORD), | |
('dwExtraInfo', ULONG_PTR)) | |
class KEYBDINPUT(ctypes.Structure): | |
_fields_ = (('wVk', WORD), | |
('wScan', WORD), | |
('dwFlags', DWORD), | |
('time', DWORD), | |
('dwExtraInfo', ULONG_PTR)) | |
class HARDWAREINPUT(ctypes.Structure): | |
_fields_ = (('uMsg', DWORD), | |
('wParamL', WORD), | |
('wParamH', WORD)) | |
class _INPUTunion(ctypes.Union): | |
_fields_ = (('mi', MOUSEINPUT), | |
('ki', KEYBDINPUT), | |
('hi', HARDWAREINPUT)) | |
class INPUT(ctypes.Structure): | |
_fields_ = (('type', DWORD), | |
('union', _INPUTunion)) | |
# example: | |
if __name__ == '__main__': | |
hwnd = win32gui.FindWindow(None, 'Grand Theft Auto V') | |
win32gui.SetForegroundWindow(hwnd) | |
keys = Keys() | |
while True: | |
sleep(1) | |
# move to single match's bet button and click it | |
keys.relative_move(600, 480) | |
keys.click() | |
sleep(1) | |
# select first horse | |
keys.relative_move(200, 190) | |
keys.click() | |
sleep(1) | |
# make a bet | |
keys.relative_move(600, 290) | |
keys.click('left', 28) | |
sleep(1) | |
# start | |
keys.relative_move(550, 440) | |
keys.click() | |
# wait for the match complete | |
sleep(40) | |
# leave result page (or press ESC) | |
keys.click('right') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment