KeeganT

Private Racetrack Bot

Oct 27th, 2018
33
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        TypeRacer Private Racetrack Bot
  3. // @namespace   morinted
  4. // @description Preview texts, race alone, and keep a log of races by having this bot host a private racetrack.
  5. // @include     https://play.typeracer.com/
  6. // @version     5.3.2
  7. // @grant       none
  8. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
  9. // ==/UserScript==
  10. /* jshint asi:true */
  11.  
  12. var nbsp = ' '
  13.  
  14. function getNumberOfUsers() {
  15.   return $('.users-list .userNameLabel').length
  16. }
  17.  
  18. function isBotInChat() {
  19.   return !!$('.users-list .userNameLabel.userNameLabel-self').length
  20. }
  21.  
  22. // Check number of users in race, but only if they are not finished and haven't left.
  23. function getNumberOfRacingUsers() {
  24.   return $('.scoreboard > tbody > tr').map((index, row) => {
  25.     row = $(row)
  26.     // .avatar-dead means they've left, if rank is empty they haven't finished the race.
  27.     if (row.find('.avatar-dead').length || (row.find('div.rank')[0].textContent || '').trim()) {
  28.       return 0
  29.     }
  30.     return 1
  31.   }).get().reduce((result, x) => result + x)
  32. }
  33.  
  34. function capitalize(string) {
  35.     return string.charAt(0).toUpperCase() + string.slice(1)
  36. }
  37.  
  38. function postRaceResults() {
  39.     var raceResults = $('.scoreboard > tbody > tr').map((index, row) => {
  40.       row = $(row)
  41.       if ((row.find('div.rank')[0].textContent || '').trim()) {
  42.         var place = parseInt(row.find('div.rank')[0].textContent)
  43.         var wpm = parseInt(row.find('.rankPanelWpm')[0].textContent)
  44.         var username = (row.find('.lblUsername')[0].textContent || '').trim()
  45.         username = username ? username.slice(1, -1) : 'Guest'
  46.         return { place: place, wpm: wpm, username: username }
  47.       }
  48.       return null
  49.     }).get().filter(x => x).sort((a, b) => a.place - b.place)
  50.       .map((user, index) => {
  51.         const badge = index === 0 ? '?' : index === 1 ? '?' : index === 2 ? '?' : '?'
  52.         return [
  53.             badge,
  54.             user.username.split('_').map(x => capitalize(x)).join('_'),
  55.             user.wpm + 'wpm'
  56.         ].filter(x => x).join(' ')
  57.       }).join(', ')
  58.     if (raceResults) sendChatMessage(raceResults)
  59.     var quitters = $('.scoreboard > tbody > tr').map((index, row) => {
  60.       row = $(row)
  61.       if (!(row.find('div.rank')[0].textContent || '').trim() &&
  62.           row.find('.avatar-dead').length &&
  63.           !row.find('.avatar-self').length) {
  64.         var username = (row.find('.lblUsername')[0].textContent || '').trim()
  65.         username = username ? username.slice(1, -1) : 'Guest'
  66.         return username
  67.       }
  68.       return null
  69.     }).get().filter(x => x).join(', ')
  70.     if (quitters) sendChatMessage('? Quitters: ' + quitters)
  71. }
  72.  
  73. function inRace() {
  74.   // Check for presence and visibility of "(you)" label.
  75.   return !!document.querySelector('.lblUsername[style=""]')
  76. }
  77.  
  78. function getGameStatus() {
  79.   return ((document.getElementsByClassName('gameStatusLabel') || [])[0] || {}).innerHTML || ''
  80. }
  81.  
  82. function leaveRace() {
  83.   document.querySelector('table.navControls > tbody > tr > td > a:first-child').click()
  84. }
  85.  
  86. function fakeActivity() {
  87.   // Ted: Huge thank you to *Nimble* for figuring this out. Drove me crazy!
  88.   // Mouse move event clientX needs to be different from last event
  89.   // to work as an activity event.
  90.   // Emit 2 different events at once so this function can be stateless.
  91.   document.dispatchEvent(new MouseEvent("mousemove", {
  92.      'clientX': 0
  93.   }));
  94.   document.dispatchEvent(new MouseEvent("mousemove", {
  95.      'clientX': 1
  96.   }));
  97. }
  98.  
  99. function joinRace() {
  100.   (document.getElementsByClassName('raceAgainLink') || [])[0].click()
  101. }
  102.  
  103. function getQuoteText() {
  104.   return ((document.querySelector('table.inputPanel table tr:first-child') || {}).textContent || '').trim()
  105. }
  106.  
  107. function rejoinRacetrack() {
  108.   $('.lnkRejoin').click() // Rejoin
  109. }
  110.  
  111. function isOnHomePage() {
  112.   return !!(document.querySelector('.mainMenuItemText'))
  113. }
  114.  
  115. function raceYourFriends() {
  116.   return $(
  117.     'div.mainViewport > div > table > tbody > tr:nth-child(4) > td > table > tbody > tr > td:nth-child(2) > table > tbody > tr:nth-child(1) > td > a'
  118.   ).click()
  119. }
  120.  
  121. function leaveRacetrack() {
  122.   $('.roomSection > table > tbody > tr > td > a.gwt-Anchor').click()
  123. }
  124.  
  125. function status() {
  126.   var gameStatus = getGameStatus()
  127.   return {
  128.     noRace: gameStatus.startsWith('Join'),
  129.     waitingForOthers: gameStatus.startsWith('Waiting'),
  130.     countingDown: gameStatus.startsWith('The race is about to start'),
  131.     racing: gameStatus.startsWith('Go!') || gameStatus.startsWith('The race is on!'),
  132.     raceOver: gameStatus.startsWith('The race has ended')
  133.   }
  134. }
  135.  
  136. function sendChatMessage(message) {
  137.      var chatInput = $('input.txtChatMsgInput')
  138.      chatInput.click()
  139.      chatInput.val(message)
  140.      chatInput.focus()
  141.      var keyboardEvent = jQuery.Event('keydown')
  142.      keyboardEvent.which = 13
  143.      keyboardEvent.keyCode = 13
  144.      chatInput.trigger(keyboardEvent)
  145. }
  146.  
  147. function waitingForNewQuote() {
  148.      return !!$('.mainViewport .timeDisplay .caption').length
  149. }
  150.  
  151. function sendQuoteText() {
  152.    var quoteText = getQuoteText()
  153.    if (quoteText) {
  154.      log('Sending quote text')
  155.      log(quoteText)
  156.      sendChatMessage('“' + quoteText + '”')
  157.    }
  158. }
  159.  
  160. function tick() {
  161.     setTimeout(mainLoop, 500)
  162. }
  163.  
  164. lastMessage = ''
  165. function log(message) {
  166.     if (message != lastMessage) {
  167.       lastMessage = message
  168.       console.log('BOT:', message)
  169.     }
  170. }
  171.  
  172. function refreshPage() {
  173.  location.reload()
  174. }
  175.  
  176. function mainLoop() {
  177.     fakeActivity()
  178.     if (isOnHomePage()) {
  179.         log('on the homepage, going to private track')
  180.         raceYourFriends()
  181.         return tick()
  182.     }
  183.     if (!isBotInChat()) {
  184.         // Check again in a second to be sure.
  185.         setTimeout(function() {
  186.           if (!isBotInChat()) {
  187.             log('leaving the racetrack because it seems I am not in it anymore')
  188.             if (inRace()) leaveRace()
  189.               leaveRacetrack()
  190.           }
  191.           return tick()
  192.         }, 1000)
  193.         return
  194.     }
  195.     if (getNumberOfUsers() < 2) {
  196.         if (inRace()) {
  197.             log('alone, so leaving the current race')
  198.             leaveRace()
  199.             return tick()
  200.         }
  201.         log('alone on the private track')
  202.         // No one but the bot in the track.
  203.         return tick()
  204.     }
  205.     // We have another user in the track
  206.     var trackIs = status()
  207.     if (trackIs.noRace && !waitingForNewQuote()) {
  208.         log('Joining race')
  209.         // Give a little time just to try and avoid glitchy track issue.
  210.         setTimeout(joinRace, 500)
  211.         setTimeout(sendQuoteText, 700)
  212.         setTimeout(tick, 1000)
  213.         return
  214.     }
  215.     if (trackIs.waitingForOthers){
  216.         log('waiting for others...')
  217.         return tick()
  218.     }
  219.     if (trackIs.countingDown || trackIs.racing) {
  220.         if (inRace() && getNumberOfRacingUsers() < 2) {
  221.           log('leaving race because I am the only racer')
  222.           postRaceResults()
  223.           leaveRace()
  224.           // Delay after leaving to allow new quote to queue.
  225.           setTimeout(tick, 2000)
  226.           return
  227.         }
  228.         if (inRace()) {
  229.             log('in race, waiting for everyone to finish')
  230.         } else {
  231.             log('race is going, not in it...')
  232.         }
  233.         return tick()
  234.     }
  235.     if (trackIs.raceOver) {
  236.         log('race over...')
  237.         return tick()
  238.     }
  239.     log('unknown state')
  240.     return tick()
  241. }
  242.  
  243. function kickOffLoop() {
  244.   if ($('.mainMenuItem').is(':visible')) {
  245.       mainLoop()
  246.   } else {
  247.       setTimeout(kickOffLoop, 100)
  248.   }
  249. }
  250. kickOffLoop()
Advertisement
Add Comment
Please, Sign In to add comment