5
\$\begingroup\$

This is a personal project which I created solely to torture my friends ;)

I'm pretty sure you've heard about typeracer.
And if you have friends like mine who are faster than you, you'd surely be frustrated.
This is a program that removes your frustration by automating it.

from time import sleep

from selenium import webdriver
from selenium.common.exceptions import UnexpectedAlertPresentException
from selenium.webdriver.chrome.options import Options


class TypeRacerBot:
    def __init__(self, driver, is_private, link, wpm=70):
        self.driver = driver
        self.is_private = is_private
        self.wpm = wpm

        self.link = link
        self.driver.get(self.link)

        sleep(2)

        if self.is_private:
            while not self.can_join_race():
                pass
            self.enter_private_race()

        else:
            self.enter_race()

        sleep(2)
        while not self.has_started():
            pass

        self.type_text(self.get_text())

    def enter_race(self):
        """ Click the link to enter a new race """
        self.driver.find_element_by_partial_link_text('Enter a typing race').click()

    def enter_private_race(self):
        """ Click the link to enter a new race """
        self.driver.find_element_by_partial_link_text('join race').click()

    def race_again(self):
        """ Click the link to race again """
        self.driver.find_element_by_partial_link_text('Race Again').click()

    def has_started(self):
        """ Returns whether the race has started or not """
        return self.driver.find_element_by_css_selector(
            'table > tbody > tr:nth-child(2) > td > table > tbody > tr:nth-child(2) > td > input'
        ).is_enabled()

    def can_join_race(self):
        """ Returns whether the cool-down between private races has ended """
        return len(self.driver.find_elements_by_partial_link_text('join race')) > 0

    def get_text(self):
        """ Returns the text you are supposed to type """
        return self.driver.find_elements_by_css_selector(
            'table > tbody > tr:nth-child(2) > td > table > tbody'
            ' > tr:nth-child(1) > td > table > tbody > tr:nth-child(1) > td > div > div'
        )[3].text

    def type_text(self, text):
        """ Types the text with an average WPM of the parameter words_per_minute. """

        textbox = self.driver.find_element_by_css_selector(
            'table > tbody > tr:nth-child(2) > td > table > tbody > tr:nth-child(2) > td > input'
        )

        words = text.split()
        pause_time = 60 / self.wpm if self.wpm else 0

        for word in words:
            textbox.send_keys(word + ' ')
            if pause_time:
                sleep(pause_time)


def main():
    is_private = input('Is the race private? (y/n): ').lower() == 'y'

    if is_private:
        link = input('Please enter the link of the race: ')
    else:
        link = 'https://play.typeracer.com'

    wpm = int(input('Please enter the WPM you would like (0 for max speed): '))

    options = Options()
    options.add_argument('--start-maximized')
    driver = webdriver.Chrome('chromedriver.exe', options=options)

    while True:
        try:
            TypeRacerBot(driver, is_private, link, wpm)
        except UnexpectedAlertPresentException:
            pass

        print()
        input('Press enter to start new race')
        wpm = int(input('Please enter the WPM you would like (0 for max speed): '))


if __name__ == '__main__':
    main()

You can participate in a public or private race without any issues.

With an average PC, the maximum speed clocks up to 500 WPM.
When setting a custom speed, the actual speed differs a lot from the target, due to selenium being a bit slow.

Right now, I don't think the code is very enjoyable to read.
How do I make it look better, and how do I increase the performance of the program?

Thanks a lot!

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I encourage you to examine the site in closer detail. So many instances of Selenium use are at the wrong layer of abstraction and this is no different.

The site communicates with XHR request; one example is:

POST https://play.typeracer.com/gameserv;jsessionid=B45A6C283A7C20091095F4BCD6DA1B42

Request:

7|1|6|https://play.typeracer.com/com.typeracer.guest.Guest/|5CBFBDCD9A4D280D027FF3A5E637DC0C|_|joinSinglePlayerGame|y|1w|1|2|3|4|1|5|5|0|1|0|6|crLHRPFB|

Response:

//OK[4,17,1.602780248399E12,0,-5,4000,3,16,15,14,0,13,4060062,12,11,10,9,8,7,0,0,0,6,"crLHRPFB",5,1,4,1.602780252399E12,1,3,487716,2,0,1,["1h","13","12","2w","1w","15","1i","B00WO1YUQS","Tame Impala","sleepyaf123","","https://data.typeracer.com/pit/profile?user\u003Dsleepyaf123","32","Let It Happen","All this running around. I can\u0027t fight it much longer. Something\u0027s trying to get out. And it\u0027s never been closer. If my ticker fails, make up some other story. But if I never come back, tell my mother I\u0027m sorry.","1j","27"],1,7]

The "stuff to type" is in there. It'll take a little more work to complete the rest of the necessary reverse-engineering, but in general this process is made simple by the developer tools of any modern browser. Once you have the adequate insight into how the application works, drop Selenium in favour of raw Requests.

\$\endgroup\$
1
  • \$\begingroup\$ +1 because "drop Selenium in favour of raw Request" \$\endgroup\$
    – hjpotter92
    Oct 15, 2020 at 17:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.