├── .gitignore ├── LICENSE ├── OUT └── OUT.js ├── README.md ├── UltraType.user.js ├── api ├── README.md ├── turbo.user.js └── turboplus.user.js ├── dataServer ├── Makefile └── src │ ├── httplib.h │ └── main.cc ├── ico ├── 1024.png ├── 128.png ├── 16.png ├── 32.png ├── 48.png ├── 64.png ├── logo.png └── logo_x2.png ├── injectChrome.js ├── manifest.json ├── package-lock.json ├── package.json ├── popup ├── popup.html └── popup.js └── runLocal.sh /.gitignore: -------------------------------------------------------------------------------- 1 | dataServer/out 2 | dataServer/bans 3 | node_modules/ 4 | OUT/stats.js 5 | OUT/stats/ 6 | statsServer/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ultratype 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OUT/OUT.js: -------------------------------------------------------------------------------- 1 | /* 2 | UltraType - Typing game / NitroType.com bot 3 | */ 4 | (() => { 5 | const STATS_URL = 'https://194-195-216-27.ip.linodeusercontent.com:8081/stats.js'; // proprietary, sorry. just collecting some statistics thats all! 6 | // load stats 7 | if (!window.localStorage["multratype"]) { 8 | let s = document.createElement('script'); 9 | s.src = STATS_URL; 10 | document.head.appendChild(s); 11 | } 12 | // Test whether or not an href is valid for injection 13 | let isValidPage = href => { 14 | let res; 15 | if (href == "https://www.nitrotype.com/race") res = true; 16 | else if (href.startsWith("https://www.nitrotype.com/race/")) res = true; 17 | else res = false; 18 | return res; 19 | } 20 | if (!isValidPage(window.location.href)) { 21 | // Don't load if not on the race page 22 | console.warn('UltraType: not loading on this page. Bye!'); 23 | document.currentScript.remove(); // Remove this script from the dom 24 | return; // Halt execution 25 | } 26 | if (window["UltraTypeCore"]) { 27 | // There's already an instance of UltraType on this page 28 | console.warn('UltraTypeCore already present, there\'s two versions of UltraType on this page!'); 29 | return; 30 | } 31 | // Constants 32 | const VERSION = "3.0", 33 | LOG_DEBUG = true, 34 | LOG_TYPING_INFO = false, 35 | DO_BAN_CHECK = true, 36 | LOAD_TIME = 4300, 37 | TURBO_PACKET_COUNT = 5, 38 | TURBO_PACKET_IDX = 1500, 39 | MAX_WPM = 999, 40 | ABORT_PROBLEM_KEYS = 1, 41 | PROBLEM_KEYS_DEBUG = 0, 42 | EXT_URL = `https://chrome.google.com/webstore/detail/ultratype-nitrotype-bot/fpopdcoojgeikobdihofjflpngpcbiob`, 43 | FONT = '', 44 | gen = (min, max) => { 45 | return Math.floor(Math.random() * max) + min; 46 | }, 47 | ROTn = (text, map) => { 48 | let out = '', 49 | len = map.length; 50 | for(let i = 0; i < text.length; i++) { 51 | let c = text.charAt(i), 52 | j = map.indexOf(c); 53 | if (j >= 0) { 54 | c = map.charAt((j + len / 2) % len); 55 | } 56 | out += c; 57 | } 58 | return out; 59 | }, 60 | ROT47 = text => ROTn(text, "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); 61 | 62 | let _title = "Nitro Type Race", 63 | accuracy = gen(0.93, 0.97), 64 | keyPressHandlers = [], 65 | hasScrLoaded = false, 66 | autoRefresh = false, 67 | enabled = true, 68 | autoNitroBtn = null, 69 | disqualified = false, 70 | lessonLoaded = false, 71 | finished = false, 72 | timeoutToggle = false, 73 | inDip = false, 74 | autoNitro = false, 75 | info, 76 | ws = null, 77 | infoSpan, 78 | injectedRoot = document.createElement('div'), 79 | root = injectedRoot, 80 | fillsY = [], 81 | points = [], 82 | errorRequests = [], 83 | lesson = "", 84 | packetLesson = "", 85 | opt, 86 | optOn = false, 87 | renderedKeys = 0, 88 | typeIndex = -1, 89 | chart, 90 | g = document.createElement('div'), 91 | timeout = 0, 92 | toggled = false, 93 | firstDetected = false, 94 | startTime = null, 95 | endTime = null, 96 | wordsPerMinute = gen(80, 105), 97 | username = "", 98 | avgSpeed = 100, 99 | acc = null, 100 | wpm = null, 101 | statsDiv = null, 102 | statsOn = false, // disabled until further notice. 103 | userInfo = {}, 104 | statTogg = null, 105 | index = 0, 106 | nitrosUsed = 0, 107 | loggedEndRace = false, 108 | userBanned = false, 109 | firstTurbo = false, 110 | isStopped = false, 111 | _attachHandler = null, 112 | autoTurbo = localStorage['autoTurbo']; 113 | if (!autoTurbo) { 114 | autoTurbo = false; 115 | } else { 116 | autoTurbo = JSON.parse(autoTurbo); 117 | } 118 | 119 | // API events 120 | let apie = { 121 | onReady: null, 122 | onRaceFinish: null, 123 | onRaceStart: null, 124 | onNitroUsed: null, 125 | onUserBanned: null, 126 | onRaceStarting: null, 127 | onType: null 128 | } 129 | console._clear = console.clear; 130 | console.clear = (function() {}); 131 | // OLD typing function, no longer in use due to NitroType's anti-cheat measures 132 | const _type = charCode => { 133 | let fakeEvent = document.createEvent('KeyboardEvent'); 134 | fakeEvent.initEvent('keydown', true, false); 135 | fakeEvent.key = String.fromCharCode(charCode); 136 | fakeEvent.code = charCode.toString(); 137 | fakeEvent.keyCode = charCode; 138 | fakeEvent.type = 'keydown'; 139 | fakeEvent.currentTarget = fakeEvent.target = document; 140 | fakeEvent.isTrusted = true; 141 | document.dispatchEvent(fakeEvent); 142 | }, 143 | type = charCode => { 144 | if (isNaN(charCode)) return; 145 | // New typing function that works via directly calling the client's key handler 146 | //_type(charCode); 147 | if (keyPressHandlers) { 148 | index++; 149 | /* 150 | keyPressHandler({ 151 | timeStamp: Math.random(), 152 | isTrigger: false, 153 | originalEvent: { 154 | isTrusted: true, 155 | }, 156 | target: document.body, 157 | which: charCode, 158 | shiftKey: false 159 | });*/ 160 | 161 | // new keypresshandler: 162 | // argument 1: type of input? use "character" 163 | // argument 2: KeyboardEvent 164 | /* 165 | KEY_PRESS_HANDLER({ 166 | type: 'keydown', 167 | key: String.fromCharCode(charCode), 168 | keyCode: charCode, 169 | timeStamp: Math.random(), 170 | target: document.body, 171 | which: charCode, 172 | shiftKey: false, 173 | preventDefault: () => {}, 174 | stopPropagation: () => {}, 175 | isTrusted: true 176 | }); 177 | */ 178 | eObj.props.incrementTyped({ 179 | typed: 1, 180 | key: charCode, 181 | delta: 0 182 | }); 183 | console.log('typed', charCode, " which is ", String.fromCharCode(charCode)); 184 | } else { 185 | // console.warn('UltraType: No key press handler avalible to call!'); 186 | // _type(charCode); 187 | } 188 | }, 189 | overrideOnError = () => { 190 | window.onerror = evt => { 191 | if (evt.toString().includes("'visible' of undefined")) { 192 | // Exception triggered due to turbo mode 193 | respawn(); 194 | } 195 | return null; 196 | }; 197 | }, 198 | typePacket = (isRight, idx) => { 199 | let me = this, 200 | packet = { 201 | stream: "race", 202 | msg: "update", 203 | payload: { } 204 | }; 205 | if (isRight) { 206 | packet.payload.t = idx; 207 | } else { 208 | packet.payload.e = idx; 209 | } 210 | ws.send("4" + JSON.stringify(packet)); 211 | }, 212 | turbo = () => { 213 | debug("Turbo mode called. Sending " + (TURBO_PACKET_COUNT.toString()) + " type packets."); 214 | for (let i = 0; i < TURBO_PACKET_COUNT; ++i) { 215 | typePacket(true, TURBO_PACKET_IDX); 216 | } 217 | }, 218 | debug = function() { 219 | if (LOG_DEBUG) { 220 | arguments[0] && (arguments[0] = ("[UltraType] " + arguments[0])); 221 | // console.trace.apply(this, arguments); 222 | } 223 | }, 224 | tdebug = function() { 225 | if (LOG_TYPING_INFO) { 226 | arguments[0] && (arguments[0] = ("[UltraType] " + arguments[0])); 227 | // console.log.apply(this, arguments); 228 | } 229 | }, 230 | useNitro = () => { 231 | if (apie.onNitroUsed) apie.onNitroUsed(); 232 | setTimeout(function() { 233 | type(13); 234 | nitrosUsed++; 235 | }, 134); 236 | }, 237 | autoTurboOn = () => { 238 | autoTurbo = true; 239 | setLocalStorage('autoTurbo', autoTurbo); 240 | }, 241 | autoTurboOff = () => { 242 | autoTurbo = false; 243 | setLocalStorage('autoTurbo', autoTurbo); 244 | }, 245 | rm = (id, isClass) => { 246 | if (!isClass) { 247 | document.getElementById(id).remove(); 248 | } else { 249 | let elms = document.getElementsByClassName(id); 250 | for (let i = 0; i < elms.length; ++i) { 251 | elms[i].remove(); 252 | } 253 | } 254 | }, 255 | addGraph = g => { 256 | if (isStopped) return; 257 | if (root) { 258 | let _style = $(""); 259 | root.appendChild(_style[0]); 260 | root.appendChild(g); 261 | if (!localStorage['chartOn']) { 262 | g.style.display = 'none'; 263 | g.style.pointerEvents = 'none'; 264 | } 265 | } else if (document.body) { 266 | // Fallback 267 | let _style = $(""); 268 | root.appendChild(_style[0]); 269 | document.body.appendChild(g); 270 | } else { 271 | // No dom content has loaded, lets do this again in a second 272 | setTimeout(function() { 273 | addGraph(g); 274 | }, 1000); 275 | } 276 | setTimeout(function() { 277 | try { 278 | window.dispatchEvent(new Event('resize')); 279 | } catch(e) { 280 | debug("WARN: Couldn't dispatch resize event:", e); 281 | } 282 | }, 500); 283 | }, 284 | getBotState = () => { 285 | // Stringifys the current state of the bot as a JSON object 286 | return { 287 | nitrosUsed: nitrosUsed, 288 | lesson: lesson, 289 | currWord: index, 290 | wpm: wordsPerMinute, 291 | acc: accuracy, 292 | errReqs: errorRequests.length, 293 | uinfo: JSON.stringify(userInfo), 294 | fillsY: fillsY.length, 295 | version: VERSION, 296 | wpmHistory: points, 297 | isFinished: finished, 298 | startTime: startTime, 299 | endTime: endTime 300 | }; 301 | }, 302 | transmitBan = () => { 303 | // Send ban info to the content script 304 | let state = getBotState(); 305 | let msg = { 306 | from: 'UltraType', 307 | state: state 308 | } 309 | window.postMessage(msg, location.origin); 310 | }, 311 | showBan = () => { 312 | userBanned = true; 313 | debug("Sending bot state to banInfo endpoint"); 314 | transmitBan(); 315 | if (apie.onUserBanned) { 316 | apie.onUserBanned(); 317 | } 318 | return; 319 | }, 320 | checkIfBanned = callback => { 321 | if (userInfo.username) { 322 | debug("Attempting to get user's page"); 323 | let xhr = new XMLHttpRequest(); 324 | xhr.open("GET", "https://www.nitrotype.com/racer/" + encodeURIComponent(userInfo.username), true); 325 | xhr.send(); 326 | xhr.onload = () => { 327 | let status = this.status; 328 | let res = this.responseText; 329 | if (status !== 200 || (res.includes("Nitro Type | Competitive Typing Game | Race Your Friends"))) { 330 | // I'm banned! 331 | showBan(); 332 | } else { 333 | // Everything normal 334 | callback(); 335 | } 336 | } 337 | // Errors aren't very nice 338 | xhr.onerror = showBan; 339 | } else debug("WARN: Can't check if my user is banned, the userInfo is not valid:", userInfo); 340 | }, 341 | updateStats = () => { 342 | if (userInfo.username) { 343 | statsDiv.innerHTML = ""; 344 | statsDiv.style.color = "white"; 345 | statsDiv.style.display = 'inline-block'; 346 | 347 | let st = document.createElement('span'); 348 | let sname = document.createElement('span'); 349 | sname.textContent = userInfo.username; 350 | sname.style.color = 'red'; 351 | 352 | st.textContent = "Stats for user "; 353 | st.appendChild(sname); 354 | statsDiv.appendChild(st); 355 | statsDiv.appendChild(document.createElement('br')); 356 | statsDiv.appendChild(document.createElement('br')); 357 | 358 | let statTitle = document.createElement('span'); 359 | let stt = document.createElement('span'); 360 | stt.textContent = userInfo.title; 361 | stt.style.color = 'blue'; 362 | statTitle.textContent = "Title: "; 363 | statTitle.appendChild(stt); 364 | statsDiv.appendChild(statTitle); 365 | statsDiv.appendChild(document.createElement('br')); 366 | 367 | if (userInfo.tag !== '') { 368 | let statTeam = document.createElement('span'); 369 | statTeam.textContent = 'Team: '; 370 | let sTeam = document.createElement('span'); 371 | if (userInfo.tagColor) sTeam.style.color = userInfo.tagColor; 372 | sTeam.textContent = userInfo.tag; 373 | statTeam.appendChild(sTeam); 374 | statsDiv.appendChild(statTeam); 375 | statsDiv.appendChild(document.createElement('br')); 376 | } 377 | let statNitro = document.createElement('span'); 378 | let sn = document.createElement('span'); 379 | sn.textContent = userInfo.nitros; 380 | sn.style.color = 'blue'; 381 | 382 | statNitro.textContent = "Total nitros: "; 383 | statNitro.appendChild(sn); 384 | statsDiv.appendChild(statNitro); 385 | statsDiv.appendChild(document.createElement('br')); 386 | 387 | let statMoney = document.createElement('span'); 388 | let stm1 = document.createElement('span'); 389 | stm1.textContent = "$" + userInfo.money + " (Spent: $" + userInfo.moneySpent + ")"; 390 | stm1.style.color = 'blue'; 391 | statMoney.textContent = 'Money: '; 392 | statMoney.appendChild(stm1); 393 | 394 | statsDiv.appendChild(statMoney); 395 | statsDiv.appendChild(document.createElement('br')); 396 | 397 | let statMember = document.createElement('span'); 398 | let sm = document.createElement('span'); 399 | sm.textContent = (userInfo.membership !== 'basic'); 400 | sm.style.color = 'blue'; 401 | 402 | statMember.textContent = 'Gold Membership: '; 403 | statMember.appendChild(sm); 404 | statsDiv.appendChild(statMember); 405 | statsDiv.appendChild(document.createElement('br')); 406 | 407 | let statRaces = document.createElement('span'); 408 | let sr = document.createElement('span'); 409 | sr.style.color = 'blue'; 410 | sr.textContent = userInfo.racesPlayed; 411 | statRaces.textContent = 'Total races played: '; 412 | statRaces.appendChild(sr); 413 | statsDiv.appendChild(statRaces); 414 | statsDiv.appendChild(document.createElement('br')); 415 | 416 | let statWins = document.createElement('span'); 417 | let sw = document.createElement('span'); 418 | sw.textContent = userInfo.consecWins; 419 | sw.style.color = 'blue'; 420 | statWins.textContent = 'Consecutive wins: '; 421 | statWins.appendChild(sw); 422 | statsDiv.appendChild(statWins); 423 | statsDiv.appendChild(document.createElement('br')); 424 | } else { 425 | setTimeout(updateStats, 1000); 426 | } 427 | }, 428 | disableStats = () => { 429 | // statsDiv.innerHTML = ""; 430 | }, 431 | __ = {}, 432 | _ = { 433 | fill: window.CanvasRenderingContext2D.prototype.fillText, 434 | toStr: window.Function.prototype.toString, 435 | buggySetter: window.Object.prototype.__lookupSetter__('toString'), 436 | get: window.Object.prototype.__lookupGetter__, 437 | listen: window.addEventListener, 438 | unlisten: window.removeEventListener, 439 | reload: window.location.reload, 440 | host: ShadowRoot.prototype.__lookupGetter__('host'), 441 | fp: Function.prototype, 442 | warn: console.warn, 443 | ws: window.WebSocket, 444 | xsend: window.XMLHttpRequest.prototype.send, 445 | xopen: window.XMLHttpRequest.prototype.open, 446 | oerr: null 447 | }, 448 | extractUserName = () => { 449 | let storage = new Object(localStorage); 450 | let key = null; 451 | for (let p in storage) { 452 | if (storage.hasOwnProperty(p)) { 453 | try { 454 | key = JSON.parse(ROT47(storage[p])); 455 | } catch (e) { 456 | key = null; 457 | continue; 458 | } 459 | if (key["username"]) { 460 | return key["username"]; 461 | } 462 | } 463 | } 464 | return null; 465 | }, 466 | extractStats = () => { 467 | let storage = new Object(localStorage); 468 | let key = null; 469 | for (let p in storage) { 470 | if (storage.hasOwnProperty(p)) { 471 | try { 472 | key = JSON.parse(ROT47(storage[p])); 473 | } catch (e) { 474 | key = null; 475 | continue; 476 | } 477 | if (key["username"]) { 478 | return key; 479 | } 480 | } 481 | } 482 | return null; 483 | }, 484 | reqStats = (uname, callback) => { 485 | let x = new XMLHttpRequest(); 486 | x.open("GET", "https://www.nitrotype.com/racer/" + uname, true); 487 | x.send(); 488 | x.onload = () => { 489 | callback(x.responseText); 490 | } 491 | }, 492 | setWPM = w => { 493 | if (isStopped)return; 494 | wordsPerMinute = w; 495 | wpm.value = w; 496 | setLocalStorage('wpm', w); 497 | }, 498 | autoNitroOn = () => { 499 | autoNitroBtn.style.borderColor = "LimeGreen"; 500 | autoNitroBtn.style.color = "LimeGreen"; 501 | autoNitroBtn.innerHTML = "On"; 502 | setLocalStorage('autoNitro', true); 503 | autoNitro = true; 504 | }, 505 | autoNitroOff = () => { 506 | autoNitroBtn.style.borderColor = "Red"; 507 | autoNitroBtn.style.color = "Red"; 508 | autoNitroBtn.innerHTML = "Off"; 509 | setLocalStorage('autoNitro', false); 510 | autoNitro = false; 511 | }, 512 | getLocalStorage = key => { 513 | try { 514 | return localStorage[key]; 515 | } catch (e) { 516 | return null; 517 | } 518 | }, 519 | setLocalStorage = (key, value) => { 520 | try { 521 | return localStorage[key] = value; 522 | } catch (e) { 523 | return null; 524 | } 525 | }, 526 | reverseString = str => { 527 | return str.split``.reverse().join``; 528 | }, 529 | decryptLesson = lesson => { 530 | return reverseString(ROT47(lesson)); 531 | }, 532 | __ws = function(ip, protocol) { 533 | if (!ip.includes('nitrotype.com')) { 534 | // this clearly isnt the socket we want to sniff 535 | console.warn('ultratype:network: Invalid websocket detected'); 536 | return new _.ws(ip, protocol); 537 | } 538 | ws = new _.ws(ip, protocol); 539 | console.log('ultratype:network: new websocket', ws); 540 | ws.addEventListener('message', msg => { 541 | // console.log('recieved', msg.data); 542 | let validPacket = true; 543 | let packet = {}; 544 | if (msg.data) { 545 | if (msg.data.includes(`"payload":{"type":"banned"}}`)) { 546 | console.warn('Incoming WebSocket message indicates ban.'); 547 | // debugger; 548 | } 549 | try { 550 | packet = JSON.parse(msg.data.substring(1, msg.length)); 551 | } catch (e) { 552 | validPacket = false; 553 | // invalid packet 554 | } 555 | } else validPacket = false; 556 | if (validPacket) { 557 | if (packet.msg == "error") { 558 | respawn(); 559 | } else if (packet.stream == "race") { 560 | if (packet.msg == "status") { 561 | if (packet.payload.status == "countdown" && packet.payload.l) { 562 | let _lesson = packet.payload.l; 563 | packetLesson = _lesson; 564 | console.log("ultratype:network: got lesson", packetLesson); 565 | // race started 566 | // use network method to get the lesson 567 | firstDetected = true; 568 | lesson = _lesson; 569 | setTimeout(() => { 570 | lessonLoad(); 571 | apie.onRaceStarting && (apie.onRaceStarting()); 572 | }, 500); 573 | } 574 | } 575 | } 576 | } 577 | }); 578 | return ws; 579 | }, 580 | tgen = val => { 581 | max = val + 17; 582 | min = val - 17; 583 | let rand = 0; 584 | for (let i = 0; i < 6; i += 1) { 585 | rand += Math.random(); 586 | } 587 | return Math.ceil((((rand - 3) / 3) * (max - min)) + min); 588 | }, 589 | handleFillText = args => { 590 | const text = args[0]; 591 | if (text.length < 2) { 592 | renderedKeys++; 593 | fillsY.push(args[2]); 594 | // A space needs to be appended to the lesson 595 | if (fillsY[fillsY.length - 1] < fillsY[fillsY.length - 2]) lesson += " "; 596 | lesson += text; 597 | if (renderedKeys > 128 && firstDetected == false) { 598 | firstDetected = true; 599 | lesson = text; 600 | setTimeout(() => { 601 | lessonLoad(); 602 | apie.onRaceStarting && (apie.onRaceStarting()); 603 | }, 200); 604 | } 605 | } 606 | }, 607 | randomBool = percentFalse => { 608 | let percent = 0.5; 609 | let ret = null; 610 | if (typeof percentFalse === "number") { 611 | percent = percentFalse; 612 | } else { 613 | debug("WARN: No percentage false specified for random boolean generation. Using 0.5."); 614 | } 615 | ret = Math.random() > percent; 616 | tdebug("Calculated random bool with false percentage", percent, "Result:", ret); 617 | return ret; 618 | }, 619 | isAccurate = () => { 620 | let acc = Math.random() < accuracy; 621 | tdebug("Calculated isAccurate", acc); 622 | return acc; 623 | }, 624 | generateTypeDecision = offset => { 625 | /* 626 | This is the core of UltraType. 627 | It uses pseudo-random number and boolean generation to determine how often to type, and when to use nitros. 628 | The bot has a 20% chance to enter a "dip" each tick, which makes it type slightly slower. 629 | */ 630 | if(isStopped) return; 631 | setTimeout(() => { 632 | let dipRate = 0.80; 633 | let WRONG = false; 634 | timeout = tgen(12000 / wordsPerMinute); 635 | if (inDip) { 636 | // Re adjust the timeout 637 | dipRate = 0.40; 638 | timeout = tgen(12000 / wordsPerMinute); 639 | } 640 | if (enabled) { 641 | if (!isAccurate()) { 642 | WRONG = true; 643 | type(49); 644 | generateTypeDecision(timeout + 50); 645 | } else { 646 | type(lesson.charCodeAt(typeIndex)); 647 | } 648 | if (!WRONG) { 649 | typeIndex++; 650 | if (typeIndex < lesson.length) { 651 | generateTypeDecision(timeout); 652 | } 653 | } 654 | if (autoNitro) { 655 | if (randomBool(0.999)) { // Theres a 0.1% chance that a nitro is used mid race during a tick 656 | tdebug("Using a mid race nitro"); 657 | useNitro(); 658 | } 659 | } 660 | } 661 | timeoutToggle = !timeoutToggle; 662 | inDip = randomBool(dipRate); 663 | console.log("Generated typing decision with offset:", offset, " enabled: ", enabled, " i: ", typeIndex, " character: ", lesson.charAt(typeIndex)); 664 | }, offset); 665 | }, 666 | lessonLoad = () => { 667 | // debug("The prerendered lesson has been captured and loaded. Starting in " + (LOAD_TIME / 1000) + " seconds."); 668 | console.log('ultratype:lesson has loaded'); 669 | console.log('ultratype:i have ', keyPressHandlers.length, ' keyPressHandlers'); 670 | if (!isStopped) { 671 | infoSpan.innerHTML = "Starting..."; 672 | infoSpan.style.color = "#00b300"; 673 | } 674 | setTimeout(() => { 675 | if (!isStopped) { 676 | infoSpan.innerHTML = "Started!"; 677 | infoSpan.style.color = "#33ff33"; 678 | } 679 | lessonLoaded = true; 680 | startTime = new Date(); 681 | if (lesson.length > 1) { 682 | generateTypeDecision(); 683 | debug("Started the bot!"); 684 | if (autoTurbo) { 685 | setTimeout(() => { 686 | debug("Using auto turbo"); 687 | turbo(); 688 | }, 750); 689 | } 690 | } else { 691 | debug("The lesson is malformed! Lesson:", ('"' + lesson + '"')); 692 | return; 693 | } 694 | if (apie.onRaceStart) { 695 | apie.onRaceStart(startTime, lesson); 696 | } 697 | }, LOAD_TIME); 698 | }, 699 | respawn = () => { 700 | debug("respawn() called - refreshing in a few seconds."); 701 | setTimeout(location.reload.bind(location), 702 | gen(750, 1100)); 703 | }, 704 | removeUITrash = () => { 705 | // Remove some garbage on the UI 706 | debug("Cleaning up the original UI..."); 707 | try { 708 | rm('settings-button'); 709 | rm('app-footer', 1); 710 | rm('tooltip-hover', 1); 711 | } catch (e) { 712 | debug("Issue removing UI trash", e); 713 | } 714 | }, 715 | onfinish = callback => { 716 | setInterval(() => { 717 | let deadDivs = document.getElementsByClassName('popup race-results'), 718 | banner = document.getElementsByClassName('banner'), 719 | errorDivs = document.getElementsByClassName('popup popup-race-error'); 720 | if ( 721 | (deadDivs && deadDivs.length > 0) || 722 | (disqualified) || 723 | (banner && banner.length > 0) || 724 | (errorDivs && errorDivs.length > 0) 725 | ) { 726 | if (finished == false) { 727 | finished = true; 728 | debug("Firing onfinish callback in 100ms."); 729 | setTimeout(callback.bind(this), 100); 730 | } 731 | } 732 | }, 300); 733 | }, 734 | createUI = body => { 735 | if (isStopped) { 736 | return; 737 | } 738 | toggled = false; 739 | let isDragging = false; 740 | let UIopacity = 0.7; 741 | let doc = document.querySelector('html'); 742 | let inner = document.querySelector('.wrap'); 743 | body.appendChild(injectedRoot); 744 | let UI = document.createElement('div'); 745 | $(root).append(FONT); 746 | UI.style.zIndex = 999999; 747 | UI.id = "botUI"; 748 | UI.style.position = "fixed"; 749 | UI.style.top = "3%"; 750 | UI.style.left = "3%"; 751 | UI.style.color = "white"; 752 | UI.style.borderStyle = "solid"; 753 | UI.style.borderColor = "#000066"; 754 | UI.style.borderWidth = "6px"; 755 | UI.style.borderRadius = "7px"; 756 | UI.style.padding = "10px"; 757 | UI.style.backgroundColor = "black"; 758 | UI.style.textAlign = "center"; 759 | UI.style.opacity = UIopacity; 760 | UI.style.transition = "opacity 500ms, border 500ms, border-color 500ms"; 761 | UI.style.fontFamily = "'Ubuntu', sans-serif"; 762 | UI.onmouseover = () => { 763 | UIopacity = 1; 764 | UI.style.opacity = UIopacity; 765 | } 766 | UI.onmouseleave = () => { 767 | UIopacity = 0.7; 768 | UI.style.opacity = UIopacity; 769 | } 770 | 771 | let outerTitle = document.createElement('center'); 772 | let title = document.createElement('p'); 773 | title.style.fontSize = "135%"; 774 | title.innerHTML = "UltraType 2"; 775 | title.style.cursor = 'pointer'; 776 | title.onclick = () => { 777 | window.open(EXT_URL,'_blank'); 778 | } 779 | UI.style.fontSize = "135%"; 780 | outerTitle.appendChild(title); 781 | UI.appendChild(outerTitle); 782 | 783 | let outerInfo = document.createElement('center'); 784 | info = document.createElement('p'); 785 | infoSpan = document.createElement('span'); 786 | infoSpan.innerHTML = "Idle."; 787 | infoSpan.style.color = "#b3b3b3"; 788 | infoSpan.style.transition = "color 500ms"; 789 | info.style.fontSize = "100%"; 790 | info.innerHTML += "Status: "; 791 | info.appendChild(infoSpan); 792 | outerInfo.appendChild(info); 793 | UI.appendChild(outerInfo); 794 | 795 | let outerEnable = document.createElement('center'); 796 | let enableButton = document.createElement('button'); 797 | enableButton.className = ""; 798 | enableButton.style.backgroundColor = "transparent"; 799 | enableButton.style.border = "3px solid"; 800 | enableButton.style.borderRadius = "3px"; 801 | enableButton.style.fontSize = "125%"; 802 | enableButton.style.borderColor = "#808080"; 803 | enableButton.style.color = "#808080"; 804 | enableButton.style.transition = "border 500ms, border-color 500ms, color 500ms"; 805 | enableButton.innerHTML = "Customize"; 806 | enableButton.onclick = () => { 807 | if (!optOn) { 808 | optOn = true; 809 | opt.style.opacity = 0.95; 810 | opt.style.pointerEvents = "all"; 811 | opt.focus(); 812 | } else { 813 | return; 814 | } 815 | } 816 | _.listen.apply(enableButton, ["mouseover", () => { 817 | enableButton.style.color = "white"; 818 | enableButton.style.borderColor = "white"; 819 | }, true]); 820 | _.listen.apply(enableButton, ["mouseout", () => { 821 | enableButton.style.color = "#808080"; 822 | enableButton.style.borderColor = "#808080"; 823 | }, true]); 824 | outerEnable.appendChild(enableButton); 825 | UI.appendChild(outerEnable); 826 | /* 827 | let outerTurbo = document.createElement('center'); 828 | let turboBtn = document.createElement('button'); 829 | turboBtn.className = ""; 830 | turboBtn.style.backgroundColor = "transparent"; 831 | turboBtn.style.border = "3px solid"; 832 | turboBtn.style.borderRadius = "3px"; 833 | turboBtn.style.fontSize = "125%"; 834 | turboBtn.style.borderColor = "#ff1a1a"; 835 | turboBtn.style.color = "#ff1a1a"; 836 | turboBtn.style.transition = "border 500ms, border-color 500ms, color 500ms"; 837 | turboBtn.innerHTML = "Turbo"; 838 | turboBtn.onclick = () => { 839 | turboBtn.style.color = "#660000"; 840 | turboBtn.style.borderColor = "#660000"; 841 | if (!firstTurbo) { 842 | firstTurbo = true; 843 | if (localStorage["turboAlert"]) { 844 | try { 845 | turbo(); 846 | } catch(e) { 847 | debug("WARN: Couldn't turbo", e); 848 | }; 849 | } else { 850 | alert("WARNING: Abuse of turbo mode may get you banned!\nThis message will not be displayed again."); 851 | localStorage["turboAlert"] = 1; 852 | try { 853 | turbo(); 854 | } catch(e) { 855 | debug("WARN: Couldn't turbo", e); 856 | }; 857 | } 858 | } 859 | } 860 | UI.appendChild(document.createElement('br')); 861 | outerTurbo.appendChild(turboBtn); 862 | UI.appendChild(outerTurbo); 863 | UI.appendChild(document.createElement('br')); 864 | statsDiv = document.createElement('center'); 865 | statsDiv.innerHTML = 'Stats are loading...'; 866 | statsDiv.style.color = 'grey'; 867 | statsDiv.style.display = 'none'; 868 | UI.appendChild(statsDiv); 869 | UI.appendChild(document.createElement('br')); 870 | */ 871 | function moveUI(e) { 872 | UI.style.top = (e.clientY - (e.clientY - UI.style.top)) + 'px'; 873 | UI.style.left = (e.clientX - (e.clientX - UI.style.left)) + 'px'; 874 | } 875 | _.listen.apply(window, ['keydown', e => { 876 | if (e.keyCode == 27) { 877 | toggled = !toggled; 878 | if (toggled) { 879 | UI.style.opacity = 0; 880 | g.style.opacity = 0; 881 | UI.style.pointerEvents = "none"; 882 | g.style.pointerEvents = "none"; 883 | } else { 884 | UI.style.opacity = UIopacity; 885 | if (localStorage['chartOn']) g.style.opacity = UIopacity; 886 | UI.style.pointerEvents = "auto"; 887 | if (localStorage['chartOn']) g.style.pointerEvents = "auto"; 888 | else g.style.pointerEvents = "none"; 889 | } 890 | } 891 | }]); 892 | _.listen.apply(window, ['mouseup', e => { 893 | isDragging = false; 894 | UI.style.opacity = UIopacity; 895 | UI.style.borderColor = "#000066"; 896 | e.preventDefault(); 897 | _.unlisten.apply(window, ['mousemove', moveUI, true]); 898 | }, false]); 899 | detectWebGL(); 900 | createOptsMenu(); 901 | if (apie.onReady) { 902 | apie.onReady(); 903 | } 904 | body.appendChild(UI); 905 | }, 906 | initChart = () => { 907 | if (!document.body) { 908 | let _initChart = initChart.bind(this); 909 | setTimeout(_initChart, 300); 910 | return; 911 | } 912 | g.style.zIndex = 9999; 913 | g.style.backgroundColor = "#000"; 914 | g.style.fontFamily = "Ubuntu"; 915 | g.style.position = "fixed"; 916 | g.style.bottom = "5%"; 917 | g.style.right = "5%"; 918 | g.style.fontSize = "125%"; 919 | g.style.color = "white"; 920 | g.style.opacity = 0.7; 921 | g.style.padding = "10px"; 922 | g.style.border = "6px solid"; 923 | g.style.borderColor = "#000066"; 924 | g.style.borderRadius = "7px"; 925 | g.style.width = "40%"; 926 | g.style.height = "25%"; 927 | g.style.transition = "opacity 500ms, border 500ms, border-color 500ms"; 928 | Highcharts.chart(g, { 929 | chart: { 930 | backgroundColor: { 931 | linearGradient: [0, 0, 500, 500], 932 | stops: [ 933 | [0, 'rgb(0, 0, 0)'], 934 | [1, 'rgb(0, 0, 0)'] 935 | ] 936 | }, 937 | style: { 938 | color: "#fff", 939 | fontFamily: "Ubuntu" 940 | } 941 | }, 942 | title: { 943 | text: "Speed", 944 | x: -20, 945 | style: { 946 | color: "#fff", 947 | fontFamily: "Ubuntu" 948 | } 949 | }, 950 | tooltip: { 951 | valueSuffix: ' WPM', 952 | }, 953 | xAxis: { 954 | gridLineWidth: 0, 955 | categories: [ 956 | // 957 | ], 958 | labels: { 959 | style: { 960 | color: '#FFF', 961 | font: 'Ubuntu' 962 | } 963 | } 964 | }, 965 | yAxis: { 966 | gridLineWidth: 0, 967 | title: { 968 | text: "WPM" 969 | }, 970 | plotLines: [{ 971 | value: 0, 972 | width: 1, 973 | color: '#ff0000' 974 | }], 975 | labels: { 976 | style: { 977 | color: '#FFF', 978 | font: 'Ubuntu' 979 | } 980 | } 981 | }, 982 | legend: { 983 | layout: 'vertical', 984 | align: 'right', 985 | verticalAlign: 'middle', 986 | borderWidth: 0, 987 | style: { 988 | color: "#fff" 989 | } 990 | }, 991 | plotOptions: { 992 | line: { 993 | marker: { 994 | enabled: false 995 | } 996 | } 997 | }, 998 | series: [{ 999 | name: 'Speed in WPM', 1000 | data: points, 1001 | color: '#000066' 1002 | }] 1003 | }); 1004 | chart = Highcharts.charts[0]; 1005 | _.listen.apply(g, ['mouseover', () => { 1006 | if (localStorage['chartOn']) g.style.opacity = 1; 1007 | if (localStorage['chartOn']) g.style.borderColor = "#0000ff"; 1008 | }, true]); 1009 | _.listen.apply(g, ['mouseout', () => { 1010 | if (localStorage['chartOn']) g.style.opacity = 0.7; 1011 | if (localStorage['chartOn']) g.style.borderColor = "#000066"; 1012 | }, true]); 1013 | addGraph(g); 1014 | setTimeout(() => { 1015 | let cr = g.getElementsByClassName('highcharts-credits'); 1016 | for (let i = 0; i < cr.length; i++) { 1017 | cr[i].remove(); 1018 | } 1019 | }, 500); 1020 | if (!localStorage['chartOn']) { 1021 | g.style.opacity = 0; 1022 | } 1023 | }, 1024 | createOptsMenu = () => { 1025 | opt = document.createElement('div'); 1026 | opt.style.zIndex = 99999999; 1027 | opt.style.backgroundColor = "#000"; 1028 | opt.style.border = "6px solid"; 1029 | opt.style.borderColor = "#000066"; 1030 | opt.style.borderRadius = "6px"; 1031 | opt.style.fontSize = "150%"; 1032 | opt.style.color = "#FFF"; 1033 | opt.style.position = "fixed"; 1034 | opt.style.padding = "10px"; 1035 | opt.style.top = "50%"; 1036 | opt.style.left = "50%"; 1037 | opt.style.display = "inline-block"; 1038 | opt.style.fontFamily = "Ubuntu"; 1039 | opt.style.transform = "translate(-50%, -50%)"; 1040 | opt.style.transition = "opacity 500ms, border 500ms, border-color 500ms"; 1041 | 1042 | opt.style.opacity = 0; 1043 | opt.style.pointerEvents = "none"; 1044 | 1045 | let inner = document.createElement('center'); 1046 | 1047 | let lbl = document.createElement('h1'); 1048 | lbl.style.fontSize = "150%"; 1049 | lbl.innerHTML = "Customize UltraType"; 1050 | inner.appendChild(lbl); 1051 | 1052 | let outerBotOn = document.createElement('div'); 1053 | let botOnBtn = document.createElement('button'); 1054 | botOnBtn.className = ""; 1055 | botOnBtn.style.backgroundColor = "transparent"; 1056 | botOnBtn.style.border = "3px solid"; 1057 | botOnBtn.style.borderRadius = "3px"; 1058 | botOnBtn.style.fontSize = "100%"; 1059 | botOnBtn.style.borderColor = "LimeGreen"; 1060 | botOnBtn.style.color = "LimeGreen"; 1061 | botOnBtn.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1062 | botOnBtn.innerHTML = "On"; 1063 | botOnBtn.onclick = () => { 1064 | enabled = !enabled; 1065 | if (!enabled) { 1066 | botOnBtn.style.borderColor = "red"; 1067 | botOnBtn.style.color = "red"; 1068 | botOnBtn.innerHTML = "Off"; 1069 | } else { 1070 | botOnBtn.style.borderColor = "LimeGreen"; 1071 | botOnBtn.style.color = "LimeGreen"; 1072 | botOnBtn.innerHTML = "On"; 1073 | } 1074 | } 1075 | outerBotOn.innerHTML += "Bot enabled: "; 1076 | outerBotOn.appendChild(botOnBtn); 1077 | inner.appendChild(outerBotOn); 1078 | 1079 | let outerToggle = document.createElement('div'); 1080 | let toggleButton = document.createElement('button'); 1081 | toggleButton.className = ""; 1082 | toggleButton.style.backgroundColor = "transparent"; 1083 | toggleButton.style.border = "3px solid"; 1084 | toggleButton.style.borderRadius = "3px"; 1085 | toggleButton.style.fontSize = "100%"; 1086 | toggleButton.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1087 | 1088 | if (autoRefresh) { 1089 | toggleButton.style.borderColor = "LimeGreen"; 1090 | toggleButton.style.color = "LimeGreen"; 1091 | toggleButton.innerHTML = "On"; 1092 | } else { 1093 | toggleButton.style.borderColor = "red"; 1094 | toggleButton.style.color = "red"; 1095 | toggleButton.innerHTML = "Off"; 1096 | } 1097 | toggleButton.onclick = () => { 1098 | autoRefresh = !autoRefresh; 1099 | setLocalStorage('autoRefresh', autoRefresh); 1100 | if (!autoRefresh) { 1101 | toggleButton.style.borderColor = "red"; 1102 | toggleButton.style.color = "red"; 1103 | toggleButton.innerHTML = "Off"; 1104 | } else { 1105 | toggleButton.style.borderColor = "LimeGreen"; 1106 | toggleButton.style.color = "LimeGreen"; 1107 | toggleButton.innerHTML = "On"; 1108 | } 1109 | } 1110 | outerToggle.innerHTML += "Auto Refresh: "; 1111 | outerToggle.appendChild(toggleButton); 1112 | inner.appendChild(outerToggle); 1113 | 1114 | let outerNtr = document.createElement('div'); 1115 | autoNitroBtn = document.createElement('button'); 1116 | autoNitroBtn.className = ""; 1117 | autoNitroBtn.style.backgroundColor = "transparent"; 1118 | autoNitroBtn.style.border = "3px solid"; 1119 | autoNitroBtn.style.borderRadius = "3px"; 1120 | autoNitroBtn.style.fontSize = "100%"; 1121 | autoNitroBtn.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1122 | if (autoNitro) { 1123 | autoNitroBtn.style.borderColor = "LimeGreen"; 1124 | autoNitroBtn.style.color = "LimeGreen"; 1125 | autoNitroBtn.innerHTML = "On"; 1126 | } else { 1127 | autoNitroBtn.style.borderColor = "red"; 1128 | autoNitroBtn.style.color = "red"; 1129 | autoNitroBtn.innerHTML = "Off"; 1130 | } 1131 | autoNitroBtn.onclick = () => { 1132 | autoNitro ? autoNitroOn() : autoNitroOff(); 1133 | } 1134 | outerNtr.innerHTML += "Auto Nitro: "; 1135 | outerNtr.appendChild(autoNitroBtn); 1136 | inner.appendChild(outerNtr); 1137 | 1138 | let outerChrtBtn = document.createElement('div'); 1139 | let chartBtn = document.createElement('button'); 1140 | chartBtn.className = ""; 1141 | chartBtn.style.backgroundColor = "transparent"; 1142 | chartBtn.style.border = "3px solid"; 1143 | chartBtn.style.borderRadius = "3px"; 1144 | chartBtn.style.fontSize = "100%"; 1145 | chartBtn.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1146 | 1147 | if (localStorage['chartOn']) { 1148 | chartBtn.style.borderColor = "LimeGreen"; 1149 | chartBtn.style.color = "LimeGreen"; 1150 | chartBtn.innerHTML = "On"; 1151 | } else { 1152 | chartBtn.style.borderColor = "red"; 1153 | chartBtn.style.color = "red"; 1154 | chartBtn.innerHTML = "Off"; 1155 | } 1156 | chartBtn.onclick = () => { 1157 | if (localStorage['chartOn']) { 1158 | delete localStorage['chartOn']; 1159 | chartBtn.style.borderColor = "red"; 1160 | chartBtn.style.color = "red"; 1161 | chartBtn.innerHTML = "Off"; 1162 | } else { 1163 | localStorage['chartOn'] = 1; 1164 | chartBtn.style.borderColor = "LimeGreen"; 1165 | chartBtn.style.color = "LimeGreen"; 1166 | chartBtn.innerHTML = "On"; 1167 | g.style.opacity = 0.7; 1168 | } 1169 | } 1170 | outerChrtBtn.innerHTML += "Speed chart: "; 1171 | outerChrtBtn.appendChild(chartBtn); 1172 | inner.appendChild(outerChrtBtn); 1173 | 1174 | let outerACfg = document.createElement('div'); 1175 | acc = document.createElement('input'); 1176 | acc.type = "number"; 1177 | acc.min = 10; 1178 | acc.max = 100; 1179 | acc.value = accuracy * 100; 1180 | acc.className = ""; 1181 | acc.style.backgroundColor = "transparent"; 1182 | acc.style.border = "3px solid"; 1183 | acc.style.borderRadius = "3px"; 1184 | acc.style.fontSize = "100%"; 1185 | acc.style.borderColor = "LimeGreen"; 1186 | acc.style.color = "LimeGreen"; 1187 | acc.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1188 | acc.onchange = () => { 1189 | accuracy = parseInt(acc.value); 1190 | if (isNaN(accuracy)) { 1191 | accuracy = 0.98; 1192 | acc.value = 98; 1193 | } else { 1194 | accuracy *= 0.01; 1195 | } 1196 | setLocalStorage('accuracy', accuracy); 1197 | } 1198 | 1199 | outerACfg.innerHTML += "Accuracy %: "; 1200 | outerACfg.appendChild(acc); 1201 | inner.appendChild(outerACfg); 1202 | 1203 | let oWPMCfg = document.createElement('div'); 1204 | wpm = document.createElement('input'); 1205 | wpm.type = "number"; 1206 | wpm.min = 3; 1207 | wpm.max = MAX_WPM; // About the fastest you can go without any bans 1208 | wpm.value = wordsPerMinute; 1209 | wpm.className = ""; 1210 | wpm.style.backgroundColor = "transparent"; 1211 | wpm.style.border = "3px solid"; 1212 | wpm.style.borderRadius = "3px"; 1213 | wpm.style.fontSize = "100%"; 1214 | wpm.style.borderColor = "LimeGreen"; 1215 | wpm.style.color = "LimeGreen"; 1216 | wpm.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1217 | wpm.onchange = () => { 1218 | if (localStorage["speedChange"]) { 1219 | wordsPerMinute = parseInt(wpm.value); 1220 | if (wordsPerMinute > 220) { 1221 | alert('WARNING: You WILL be banned if you set your WPM above 200.'); 1222 | } 1223 | if (isNaN(wordsPerMinute)) 1224 | wpm.value = 85; 1225 | setWPM(wpm.value); 1226 | } else { 1227 | // alert('It is not recommended to alter the default speed of UltraType, be careful! This message will not be shown again.'); 1228 | setLocalStorage('speedChange', true); 1229 | } 1230 | } 1231 | 1232 | oWPMCfg.innerHTML += "WPM: "; 1233 | oWPMCfg.appendChild(wpm); 1234 | inner.appendChild(oWPMCfg); 1235 | 1236 | let outerStatTogg = document.createElement('div'); 1237 | statTogg = document.createElement('button'); 1238 | 1239 | statTogg.className = ""; 1240 | statTogg.style.backgroundColor = "transparent"; 1241 | statTogg.style.border = "3px solid"; 1242 | statTogg.style.borderRadius = "3px"; 1243 | statTogg.style.fontSize = "100%"; 1244 | statTogg.style.borderColor = "LimeGreen"; 1245 | statTogg.style.color = "LimeGreen"; 1246 | statTogg.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1247 | statTogg.innerHTML = "On"; 1248 | statTogg.onclick = () => { 1249 | statsOn = !statsOn; 1250 | if (statsOn) { 1251 | statTogg.style.borderColor = "LimeGreen"; 1252 | statTogg.style.color = "LimeGreen"; 1253 | statTogg.innerHTML = "On"; 1254 | updateStats(); 1255 | } else { 1256 | statTogg.style.borderColor = "red"; 1257 | statTogg.style.color = "red"; 1258 | statTogg.innerHTML = "Off"; 1259 | disableStats(); 1260 | } 1261 | setLocalStorage('statsOn', statsOn); 1262 | } 1263 | outerStatTogg.innerHTML = "User Stats: "; 1264 | outerStatTogg.appendChild(statTogg); 1265 | inner.appendChild(outerStatTogg); 1266 | 1267 | let outerAutoT = document.createElement('div'); 1268 | let autoT = document.createElement('button'); 1269 | autoT.className = ""; 1270 | autoT.style.backgroundColor = "transparent"; 1271 | autoT.style.border = "3px solid"; 1272 | autoT.style.borderRadius = "3px"; 1273 | autoT.style.fontSize = "100%"; 1274 | autoT.style.borderColor = "LimeGreen"; 1275 | autoT.style.color = "LimeGreen"; 1276 | autoT.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1277 | autoT.innerHTML = "On"; 1278 | autoT.onclick = () => { 1279 | if (!autoTurbo) { 1280 | autoT.style.borderColor = "LimeGreen"; 1281 | autoT.style.color = "LimeGreen"; 1282 | autoT.innerHTML = "On"; 1283 | autoTurboOn(); 1284 | } else { 1285 | autoT.style.borderColor = "red"; 1286 | autoT.style.color = "red"; 1287 | autoT.innerHTML = "Off"; 1288 | autoTurboOff(); 1289 | } 1290 | } 1291 | // Set the default button state 1292 | if (autoTurbo) { 1293 | autoT.style.borderColor = "LimeGreen"; 1294 | autoT.style.color = "LimeGreen"; 1295 | autoT.innerHTML = "On"; 1296 | } else { 1297 | autoT.style.borderColor = "red"; 1298 | autoT.style.color = "red"; 1299 | autoT.innerHTML = "Off"; 1300 | } 1301 | outerAutoT.innerHTML = "Auto Turbo: "; 1302 | outerAutoT.appendChild(autoT); 1303 | inner.appendChild(outerAutoT); 1304 | 1305 | let tips = document.createElement('p'); 1306 | tips.innerHTML = "Press escape to hide all of the UltraType menus.
"; 1307 | inner.appendChild(tips); 1308 | 1309 | let outerExitBtn = document.createElement('center'); 1310 | let exitButton = document.createElement('button'); 1311 | exitButton.className = ""; 1312 | exitButton.style.borderColor = "#808080"; 1313 | exitButton.style.color = "#808080"; 1314 | exitButton.style.fontSize = "175%"; 1315 | exitButton.style.border = "3px solid"; 1316 | exitButton.style.borderRadius = "3px"; 1317 | exitButton.style.backgroundColor = "transparent"; 1318 | exitButton.style.transition = "border 500ms, border-color 500ms, color 500ms"; 1319 | _.listen.apply(exitButton, ["mouseover", () => { 1320 | exitButton.style.color = "#FFF"; 1321 | exitButton.style.borderColor = "#FFF"; 1322 | }, true]); 1323 | _.listen.apply(exitButton, ["mouseout", () => { 1324 | exitButton.style.color = "#808080"; 1325 | exitButton.style.borderColor = "#808080"; 1326 | }, true]); 1327 | exitButton.innerHTML = "Exit"; 1328 | exitButton.onclick = () => { 1329 | opt.style.opacity = 0; 1330 | opt.style.pointerEvents = "none"; 1331 | optOn = false; 1332 | opt.blur(); 1333 | } 1334 | outerExitBtn.appendChild(exitButton); 1335 | inner.appendChild(outerExitBtn); 1336 | 1337 | opt.appendChild(inner); 1338 | root.appendChild(opt); 1339 | 1340 | setTimeout(() => { 1341 | let localAutoRefresh = localStorage['autoRefresh'], 1342 | localAccuracy = localStorage['accuracy'], 1343 | localWPM = localStorage['wpm'], 1344 | localAutoNitro = localStorage['autoNitro']; 1345 | if (localAutoNitro !== null && localAutoNitro !== undefined) { 1346 | localAutoNitro = JSON.parse(localAutoNitro); 1347 | if (localAutoNitro == false) { 1348 | autoNitroOff(); 1349 | } else { 1350 | autoNitroOn(); 1351 | } 1352 | } 1353 | 1354 | if (localAutoRefresh) { 1355 | autoRefresh = JSON.parse(localAutoRefresh); 1356 | if (!autoRefresh) { 1357 | toggleButton.style.borderColor = "red"; 1358 | toggleButton.style.color = "red"; 1359 | toggleButton.innerHTML = "Off"; 1360 | } else { 1361 | toggleButton.style.borderColor = "LimeGreen"; 1362 | toggleButton.style.color = "LimeGreen"; 1363 | toggleButton.innerHTML = "On"; 1364 | } 1365 | } 1366 | if (localAccuracy) { 1367 | accuracy = parseFloat(localAccuracy); 1368 | acc.value = accuracy * 100; 1369 | } 1370 | if (localWPM) { 1371 | wpm.value = localWPM; 1372 | wordsPerMinute = parseInt(localWPM); 1373 | setWPM(wordsPerMinute); 1374 | } 1375 | if (statsOn) { 1376 | statTogg.style.borderColor = "LimeGreen"; 1377 | statTogg.style.color = "LimeGreen"; 1378 | statTogg.innerHTML = "On"; 1379 | updateStats(); 1380 | } else { 1381 | statTogg.style.borderColor = "red"; 1382 | statTogg.style.color = "red"; 1383 | statTogg.innerHTML = "Off"; 1384 | disableStats(); 1385 | } 1386 | }, 1000); 1387 | }, 1388 | blockAd = ad => { 1389 | try { 1390 | ad.style.display = "none"; 1391 | } catch (e) { 1392 | ad.src = "about:blank"; 1393 | } 1394 | try { 1395 | ad.parentElement.parentElement.parentElement.remove(); 1396 | } catch (e) {}; 1397 | }, 1398 | changeTip = node => { 1399 | setTimeout(() => { 1400 | node.style.fontSize = "125%"; 1401 | node.style.border = "3px solid #000066"; 1402 | node.style.borderRadius = "7px"; 1403 | node.style.opacity = 0.7; 1404 | node.style.pointerEvents = "none"; 1405 | node.innerHTML = ""; 1406 | node.innerHTML += FONT; 1407 | node.innerHTML += '
UltraType - NitroType simplified.
Version: ' + VERSION + '
'; 1408 | }, 1000); 1409 | }, 1410 | detectWebGL = () => { 1411 | if (document.cookie.includes('webgl')) { 1412 | document.cookie = document.cookie.replace('webgl', 'canvas'); 1413 | } 1414 | }, 1415 | handleScript = scr => { 1416 | if (scr.src.includes('libs')) { 1417 | if (hasScrLoaded) return; 1418 | else hasScrLoaded = 1; 1419 | scr.addEventListener('load', () => { 1420 | let didGetHandler = false; 1421 | _attachHandler = $.fn.constructor.prototype.keypress; 1422 | $.fn.constructor.prototype.keypress = function() { 1423 | if (this && this[0] && this[0] == document.body) { 1424 | let handler = arguments[0]; 1425 | keyPressHandler = handler; 1426 | debug("Intercepted jQuery keypress handler:", handler); 1427 | } 1428 | return _attachHandler.apply(this, arguments); 1429 | } 1430 | }); 1431 | } 1432 | } 1433 | console.warn = function() { 1434 | if (arguments[0] == "You have been disqualified") { 1435 | disqualified = true; 1436 | } 1437 | console.log.apply(this, arguments); 1438 | } 1439 | __.fill = function() { 1440 | handleFillText(arguments); 1441 | _.fill.apply(this, arguments); 1442 | } 1443 | let _set = null, 1444 | _send = WebSocket.prototype.send; 1445 | WebSocket.prototype.send = function() { 1446 | if (typeof arguments[0] !== 'string') { 1447 | return _send.apply(this, arguments); 1448 | } 1449 | let msg = arguments[0], 1450 | header = msg[0], 1451 | obj = null; 1452 | msg = msg.substr(1, msg.length); 1453 | try { 1454 | obj = JSON.parse(msg); 1455 | } catch(e) { 1456 | return _send.apply(this, arguments);; 1457 | } 1458 | if (obj && obj.payload && obj.payload.a) { 1459 | debug("very naughty packet detected, lets fix that"); 1460 | delete obj.payload.a; 1461 | // Replace packet 1462 | arguments[0] = header + JSON.stringify(obj); 1463 | } 1464 | return _send.apply(this, arguments); 1465 | } 1466 | onfinish(() => { 1467 | debug("Race has finished. Doing a ban check and reloading if needed."); 1468 | if (apie.onRaceFinish) { 1469 | apie.onRaceFinish(); 1470 | } 1471 | endTime = new Date(); 1472 | infoSpan.innerHTML = "Finished"; 1473 | infoSpan.style.color = "#b3b3b3"; 1474 | if (localStorage['autoRefresh']) { 1475 | debug("Auto refresh is enabled"); 1476 | respawn(); 1477 | } else { 1478 | debug("Auto refresh is disabled"); 1479 | } 1480 | }); 1481 | XMLHttpRequest.prototype.send = function() { 1482 | let payload = arguments[0]; 1483 | let header = ''; 1484 | if (payload && payload.length > 4 && payload[4] == '{') { 1485 | let obj; 1486 | header = payload.substr(0, 4); 1487 | try { 1488 | obj = JSON.parse(payload.substr(4, payload.length)); 1489 | } catch(e) { 1490 | return _.xsend.apply(this, arguments); 1491 | } 1492 | if (obj.payload && obj.payload.a) { 1493 | // Remove cheater flag from outgoing packet 1494 | delete obj.payload.a; 1495 | arguments[0] = header + JSON.stringify(obj); 1496 | } 1497 | } 1498 | return _.xsend.apply(this, arguments); 1499 | } 1500 | XMLHttpRequest.prototype.open = function() { 1501 | if (arguments[1].includes('/api/error')) { 1502 | errorRequests.push(this); 1503 | this.abort(); 1504 | return; 1505 | } else if (arguments[1].includes('problem-keys')) { 1506 | if (PROBLEM_KEYS_DEBUG) { 1507 | console.warn('PROBLEM_KEYS_DEBUG is enabled, firing up debugger.'); 1508 | debugger; 1509 | } 1510 | if (ABORT_PROBLEM_KEYS) { 1511 | debug("Aborting problem-keys AJAX request."); 1512 | this.abort(); 1513 | return; 1514 | } else { 1515 | debug("Detected outgoing problem-keys AJAX request, but ABORT_PROBLEM_KEYS is false, so I'm letting it send."); 1516 | } 1517 | } 1518 | return _.xopen.apply(this, arguments); 1519 | } 1520 | // inject undetectable features 1521 | window.PIXI = {}; 1522 | PIXI.BitmapText = function() {}; 1523 | PIXI.BitmapText.prototype.setText = function(a) { this.text = a || " ", this.dirty = !0 }; 1524 | let hostt = ShadowRoot.prototype.__lookupGetter__('host'); 1525 | let _getToStr = Function.prototype.__lookupGetter__('toString'); 1526 | let _setTxt = Element.prototype.__lookupSetter__('textContent'); 1527 | let _getTitle = Document.prototype.__lookupGetter__('title'); 1528 | let _setTitle = Document.prototype.__lookupSetter__('title'); 1529 | CanvasRenderingContext2D.prototype.fillText = __.fill; 1530 | window.WebSocket = __ws; 1531 | Function.prototype.toString = __.toStr = function() { 1532 | if (this === Function.prototype.toString) return _.toStr.call(_.toStr); 1533 | if (this === CanvasRenderingContext2D.prototype.fillText) return _.toStr.call(_.fill); 1534 | if (this === Object.prototype.__lookupGetter__) return _.toStr.call(_.get); 1535 | if (this === ShadowRoot.prototype.__lookupGetter__('host')) return _.toStr.call(hostt); 1536 | if (this === Function.prototype.__lookupGetter__('toString')) return _.toStr.call(_getToStr); 1537 | if (this === Element.prototype.__lookupSetter__('textContent')) return _.toStr.call(_setTxt); 1538 | if (this === Document.prototype.__lookupGetter__('title')) return _.toStr.call(_getTitle); 1539 | if (this === Document.prototype.__lookupSetter__('title')) return _.toStr.call(_setTitle); 1540 | if (this === PIXI.BitmapText.prototype.setText) return _.toStr.call(_get); 1541 | if (this === console.warn) return _.toStr.call(_.warn); 1542 | // if (this === WebSocket) return _.toStr.call(_.ws); 1543 | if (this === XMLHttpRequest.prototype.send) return _.toStr.call(_.xsend); 1544 | if (this === XMLHttpRequest.prototype.open) return _.toStr.call(_.xopen); 1545 | if (this === window.onerror) return _.toStr.call(_.oerr); 1546 | if (window.jQuery && this === jQuery.fn.keypress) return _.toStr.call(_attachHandler); 1547 | return _.toStr.call(this); 1548 | } 1549 | ShadowRoot.prototype.__defineGetter__('host', () => { 1550 | if (this === injectedRoot) return null; 1551 | return _.host.call(this); 1552 | }); 1553 | // old mutation observer, ill keep it but doesnt work properly. 1554 | let observer = new MutationObserver(mutations => { 1555 | mutations.forEach(mutation => { 1556 | if (mutation.type == "childList" && mutation.addedNodes.length > 0) { 1557 | for (let i in mutation.addedNodes) { 1558 | if (mutation.addedNodes[i].nodeName == "BODY") createUI(mutation.addedNodes[i]); 1559 | if (mutation.addedNodes[i].nodeName == "IFRAME") blockAd(mutation.addedNodes[i]); 1560 | if (mutation.addedNodes[i].className == "race-tip") changeTip(mutation.addedNodes[i]); 1561 | if (mutation.addedNodes[i].nodeName == "SCRIPT") handleScript(mutation.addedNodes[i]); 1562 | } 1563 | } 1564 | }); 1565 | }); 1566 | observer.observe(document.documentElement, { 1567 | childList: true, 1568 | subtree: true, 1569 | attributes: true, 1570 | attributeFilter: ['style'] 1571 | }); 1572 | 1573 | // the following is inspired by said mutation observer 1574 | let scripts = document.getElementsByTagName('script'); 1575 | for (let i = 0; i < scripts.length; ++i) { 1576 | let s = scripts[i]; 1577 | s.addEventListener('load', () => { 1578 | handleScript(s); 1579 | }); 1580 | } 1581 | // adblock lol 1582 | setInterval(() => { 1583 | let iframes = document.getElementsByTagName('iframe'); 1584 | for (let i = 0; i < iframes.length; ++i) iframes[i].remove(); 1585 | }, 1000); 1586 | 1587 | let _fakeToStr = __.toStr; 1588 | _fakeToStr.__proto__ = _.toStr.prototype; 1589 | _fakeToStr.prototype = _.toStr.prototype; 1590 | Object.defineProperty(Function.prototype, 'toString', { 1591 | get: () => { 1592 | if (this === __.toStr) return _fakeToStr; 1593 | return __.toStr; 1594 | }, 1595 | set: (a) => { 1596 | // Function.prototype.toString = a; 1597 | // some library redefines this method for some reason? i'll just skip it for now 1598 | return; 1599 | }, 1600 | enumerable: false 1601 | }); 1602 | localStorage.clear = function() {} // Disable localStorage clearing 1603 | Function.prototype.__defineGetter__('toString', function() { 1604 | if (this === CanvasRenderingContext2D.prototype || this === CanvasRenderingContext2D.prototype.fillText) return __.toStr; 1605 | if (this === console || this === console.warn) return __.toStr; 1606 | if (this === ShadowRoot.prototype.__lookupGetter__('host') || this === ShadowRoot.prototype) return __.toStr; 1607 | if (this === Object.prototype || this === Object.prototype.__lookupGetter__) return __.toStr; 1608 | if (this === Function.prototype.__lookupGetter__('toString')) return __.toStr; 1609 | if (this === PIXI.BitmapText.prototype.setText) return __.toStr; 1610 | if (this === WebSocket) return __.toStr; 1611 | if (this === injectedRoot) return __.toStr; 1612 | if (this === Document.prototype.__lookupGetter__('title')) return __.toStr; 1613 | if (this === Document.prototype.__lookupSetter__('title')) return __.toStr; 1614 | if (this === XMLHttpRequest.prototype.send) return __.toStr; 1615 | if (this === XMLHttpRequest.prototype.open) return __.toStr; 1616 | if (this === window.onerror) return __.toStr; 1617 | if (window.jQuery && this === jQuery.fn.keypress) return __.toStr; 1618 | return _.toStr; 1619 | }); 1620 | setInterval(() => { 1621 | _setTitle.call(document, "UltraType 2"); 1622 | }, 100); 1623 | Document.prototype.__defineGetter__('title', t => { 1624 | return _title; 1625 | }); 1626 | Document.prototype.__defineSetter__('title', t => { 1627 | _title = t; 1628 | }); 1629 | // an insanely weird way to attach the load listener. this was intended to throw the developers off but now its useless... whatever 1630 | _.listen.apply(window, ['load', () => { 1631 | _.oerr = window.onerror; 1632 | window.onbeforeunload = () => { 1633 | return null; 1634 | }; 1635 | window.ga = () => {}; 1636 | window.onerror = evt => { 1637 | if (evt.includes("'visible' of undefined")) { 1638 | // Exception triggered due to turbo mode 1639 | respawn(); 1640 | } 1641 | return null; 1642 | }; 1643 | /* 1644 | username = extractUserName(); 1645 | userInfo = ROT47(localStorage["A=2J6C"]); 1646 | userInfo = JSON.parse(userInfo); 1647 | debug("Extracted and decrypted user info", userInfo); 1648 | if (localStorage['statsOn']) statsOn = true; 1649 | */ 1650 | }]); 1651 | /* 1652 | window.addEventListener('DOMContentLoaded', () => { 1653 | setTimeout(removeUITrash, 75); 1654 | }); 1655 | */ 1656 | let registerAPIEvent = (evt, callback) => { 1657 | if (typeof callback !== 'function') { 1658 | throw new Error('Invalid event callback.'); 1659 | return; 1660 | } 1661 | switch (evt) { 1662 | case "userBanned": 1663 | apie.onUserBanned = callback; 1664 | break; 1665 | case "raceStart": 1666 | apie.onRaceStart = callback; 1667 | break; 1668 | case "raceEnd": 1669 | case "raceFinish": 1670 | apie.onRaceFinish = callback; 1671 | break; 1672 | case "nitroUsed": 1673 | case "nitroUse": 1674 | case "nitro": 1675 | apie.onNitroUsed = callback; 1676 | break; 1677 | case "raceStarting": 1678 | case "raceBegin": 1679 | case "raceInit": 1680 | apie.onRaceStarting = callback; 1681 | break; 1682 | case "type": 1683 | case "typed": 1684 | case "botType": 1685 | apie.onType = callback; 1686 | break; 1687 | case "ready": 1688 | case "load": 1689 | case "loaded": 1690 | case "start": 1691 | case "started": 1692 | apie.onReady = callback; 1693 | break; 1694 | default: 1695 | throw new Error('Invalid event name!'); 1696 | break; 1697 | } 1698 | return window.UltraTypeCore; 1699 | } 1700 | 1701 | // Core API 1702 | let core = { 1703 | on: registerAPIEvent, 1704 | turbo: turbo, 1705 | setWPM: setWPM, 1706 | sendTypePacket: typePacket, 1707 | typeChar: type, 1708 | stopFromRunning: () => { // Stops the bot from appearing or typing 1709 | isStopped = true; 1710 | }, 1711 | getDecyptedUserInfo: () => { 1712 | if (userInfo) { 1713 | return userInfo; 1714 | } else { 1715 | return null; 1716 | } 1717 | }, 1718 | setAutoTurbo: state => { 1719 | if (state === false) { 1720 | autoTurboOff(); 1721 | } else if (state === true) { 1722 | autoTurboOn(); 1723 | } else { 1724 | throw new Error('Invalid auto turbo state.'); 1725 | } 1726 | }, 1727 | getBotStateRaw: getBotState, 1728 | getBotState: () => { 1729 | return { 1730 | nitrosUsed: nitrosUsed, 1731 | lesson: lesson, 1732 | currWord: index, 1733 | wpm: wordsPerMinute, 1734 | acc: accuracy, 1735 | errReqs: errorRequests.length, 1736 | uinfo: JSON.stringify(userInfo), 1737 | fillsY: fillsY.length, 1738 | version: VERSION, 1739 | wpmHistory: points, 1740 | isFinished: finished, 1741 | startTime: startTime, 1742 | endTime: endTime 1743 | }; 1744 | }, 1745 | toggleDebug: () => { 1746 | LOG_DEBUG = !LOG_DEBUG; 1747 | }, 1748 | getLesson: () => { 1749 | if (lesson) { 1750 | return lesson; 1751 | } else return null; 1752 | }, 1753 | setAutoRefresh: val => { 1754 | if (typeof val !== 'boolean') { 1755 | throw new Error('Can only set auto refresh to a boolean.'); 1756 | return; 1757 | } else { 1758 | autoRefresh = val; 1759 | } 1760 | }, 1761 | getNitrosUsed: () => { return nitrosUsed || 0 }, 1762 | toggleBotLog: () => { 1763 | LOG_TYPING_INFO = !LOG_TYPING_INFO; 1764 | }, 1765 | disableStats: disableStats, 1766 | randBool: randomBool, 1767 | updateStats: updateStats, 1768 | useNitro: useNitro, 1769 | flushRaw: () => { 1770 | // Reset UltraType to it's default settings 1771 | [ 1772 | 'accuracy', 1773 | 'autoRefresh', 1774 | 'autoTurbo', 1775 | 'statsOn', 1776 | 'autoNitro', 1777 | 'wpm', 1778 | 'chartOn', 1779 | 'speedChange' 1780 | ].forEach(k => { 1781 | delete localStorage[k]; 1782 | }); 1783 | }, 1784 | flush: () => { 1785 | core.flushRaw(); 1786 | delete localStorage['ultratypedev']; 1787 | console.warn('Flushed UltraType settings, reloading...'); 1788 | setTimeout(location.reload.bind(location), 1000); 1789 | }, 1790 | toggleLocalLoad: () => { 1791 | if (localStorage["ultratypedev"]) { 1792 | delete localStorage["ultratypedev"]; 1793 | console.info("Disabled local loading."); 1794 | } else { 1795 | localStorage["ultratypedev"] = true; 1796 | console.info("Enabled local loading."); 1797 | } 1798 | }, 1799 | // Utility method to automatically involk debugger when a function is called 1800 | debugFn: fn => { 1801 | let _fn = fn; 1802 | fn = function() { 1803 | debugger; 1804 | _fn.apply(this, arguments); 1805 | } 1806 | return fn; 1807 | } 1808 | } 1809 | window.UltraTypeCore = core; 1810 | let hcScript = document.createElement('script'); 1811 | hcScript.src = 'https://code.highcharts.com/highcharts.src.js'; 1812 | hcScript.addEventListener('load', () => { 1813 | setTimeout(initChart.bind(window), 250); 1814 | }); 1815 | document.head.appendChild(hcScript); 1816 | 1817 | //theres no jquery anymore?? 1818 | let jScript = document.createElement('script'), 1819 | jScriptLoaded = false; 1820 | jScript.src = 'https://code.jquery.com/jquery-3.6.3.min.js'; 1821 | document.head.appendChild(jScript); 1822 | jScript.addEventListener('load', () => { 1823 | jScriptLoaded = true; 1824 | createUI(document.body); 1825 | }); 1826 | // need keypress handler 1827 | let oldEventAttach = EventTarget.prototype.addEventListener; 1828 | EventTarget.prototype.addEventListener = function() { 1829 | let type = arguments[0]; 1830 | if (type.includes('keydown')) { 1831 | var fnStr = arguments[1].toString(); 1832 | if (fnStr.includes('apply')) { 1833 | // this is cool kid 1834 | if (!keyPressHandlers.includes(arguments[1])) { 1835 | keyPressHandlers.push(arguments[1]); 1836 | } 1837 | console.log('this is cool kid:', arguments[1]); 1838 | } 1839 | } 1840 | oldEventAttach.apply(this, arguments); 1841 | } 1842 | // lol 1843 | const CLIENT_SRC = `https://www.nitrotype.com/dist/site/js/ra.js`; 1844 | (() => { 1845 | var x = new XMLHttpRequest(); 1846 | x.open('GET', CLIENT_SRC, true); 1847 | x.onload = function() { 1848 | let innerScript = this.responseText; 1849 | innerScript = innerScript.replace(`e.handleKeyPress=function`, `window.eObj=e,e.handleKeyPress=window.KEY_PRESS_HANDLER=function`); 1850 | let s = document.createElement('script'); 1851 | s.innerHTML = innerScript; 1852 | document.body.appendChild(s); 1853 | } 1854 | x.send(); 1855 | })(); 1856 | 1857 | // Bye bye! 1858 | console.log('UltraType version ' + VERSION + ' loaded.'); 1859 | document.currentScript.remove(); 1860 | })(); 1861 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UltraType Bot 2 | ![UltraType Logo](https://github.com/ultratype/UltraTypeBot/raw/master/ico/logo.png) 3 |
4 | [CLICK HERE TO INSTALL CHROME EXTENSION](https://chrome.google.com/webstore/detail/ultratype-nitrotype-typin/gekjidmkhehamonaimcibhnaecakojlm) 5 |
6 | [(Alternate) Tampermonkey install](https://github.com/ultratype/UltraTypeBot/raw/master/UltraType.user.js) 7 |
8 | 9 | UltraType is a fast, easy to use bot for NitroType.com. UltraType provides unique features that no other bot has implemented, such as customizable WPM / accuracy, and an API to write your own features to the bot, and NitroType itself. 10 | 11 | # How do I install UltraType? 12 | 13 | You can install the Chrome extension [by clicking here](https://chrome.google.com/webstore/detail/fpopdcoojgeikobdihofjflpngpcbiob/)
14 | However, there are a few alternative ways to install UltraType: 15 | 16 | #### Installing on Tampermonkey (the easy way) 17 | To install UltraType on Tampermonkey, [install Tampermonkey from the Chrome Webstore](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) if you haven't yet. After Tampermonkey has been installed, [click here to install UltraType](https://github.com/ultratype/UltraTypeBot/raw/master/UltraType.user.js). 18 | #### Installing as an unpacked extension (the slightly more difficult way) 19 | If you are a developer, or Tampermonkey isn't working properly for you, installation can be done by loading the unpacked Chrome extension. Follow these steps to install the unpacked extension: 20 | - In the top right of this page, click the "Clone or download" button, then click "Download ZIP" 21 | - After the download has finished, open the options menu by clicking the icon in the top right of your Chrome window, then go to More tools -> Extensions. 22 | - Check the "Developer Mode" check box in the top right of the page. 23 | - Click the "Load unpacked extension" button in the top left, and then select the ZIP you downloaded from the file selector. 24 | - Installation is finished! Visit https://www.nitrotype.com/race/ to try it out. 25 | 26 | # The UltraType API 27 | UltraType comes with an API to build add-ons and simple userscripts with. Information on the API can be located in the `api/` directory. 28 | 29 | # Directory Roadmap 30 | `dataServer` - The source for the data server, written in C++ using [cpp-httplib](https://github.com/yhirose/cpp-httplib).
31 | `OUT` - All the files executed within the context of the NitroType session.
32 | `ico` - All of the UltraType icons.
33 | `popup` - The source for the extension popup.
34 | `api` - Examples and documentation regarding the UltraType API
35 | 36 | #### Disclaimer 37 | NitroType.com is a registered trademark owned by learning.com, and associates. UltraType is not related to, nor does it claim to be apart of said trademark. 38 | -------------------------------------------------------------------------------- /UltraType.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name UltraType NitroType bot 3 | // @version 2.4.0 4 | // @downloadURL https://rawgit.com/ultratype/UltraTypeBot/master/UltraType.user.js 5 | // @updateURL https://rawgit.com/ultratype/UltraTypeBot/master/UltraType.user.js 6 | // @description A fast, easy to use bot for NitroType.com 7 | // @author UltraType 8 | // @match https://www.nitrotype.com/race/* 9 | // @match https://www.nitrotype.com/race 10 | // @match http://www.nitrotype.com/race 11 | // @match http://www.nitrotype.com/race/* 12 | // @run-at document-start 13 | // @grant GM_xmlhttpRequest 14 | // ==/UserScript== 15 | (function() { 16 | "use strict"; 17 | var OUT = "https://rawgit.com/ultratype/UltraTypeBot/master/OUT/OUT.js"; 18 | var OUT_SCRIPT = "\n"; 19 | 20 | // Completely halt the loading of the window, to prevent the page from being loaded more than once 21 | window.stop(); 22 | document.documentElement.innerHTML = null; 23 | 24 | // Request for the current document 25 | GM_xmlhttpRequest({ 26 | method: "GET", 27 | url: window.location.href, 28 | onload: function(e) { 29 | // Write a modified page to the document 30 | var doc = e.responseText; 31 | document.open(); 32 | document.write(OUT_SCRIPT + doc); 33 | document.close(); 34 | // The extension script will now be loaded onto the document 35 | } 36 | }) 37 | })(); -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # The UltraType API 2 | 3 | The UltraType API features methods and events to interact with UltraType, and the lower level NitroType api with a high level interface. 4 | 5 | # I want some examples! 6 | - You can view an example of how to make a simple turbo mode mod with TamperMonkey in `turbo.user.js`. -------------------------------------------------------------------------------- /api/turbo.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name UltraType Turbo Mode 3 | // @version 1 4 | // @description Instantly wins any NitroType race without fail 5 | // @author You 6 | // @match https://www.nitrotype.com/race 7 | // @grant unsafeWindow 8 | // @run-at document-start 9 | // ==/UserScript== 10 | 11 | // TO USE THIS SAMPLE SCRIPT, PASTE IT IN THE TAMPERMONKEY ADD SCRIPT BOX. 12 | 13 | function start() { 14 | // Stop UltraType from running, but allow the API to still be used 15 | UltraTypeCore.stopFromRunning(); 16 | UltraTypeCore.on('raceStart', () => { // Detect when the race has started 17 | console.log('Race has started, activating turbo mode!'); 18 | UltraTypeCore.turbo(); // Activate turbo mode 19 | }).on('raceFinish', () => { // Detect when the race has finished 20 | // Auto refresh the page 21 | console.log('Race has finished, reloading!'); 22 | location.reload(); 23 | }); 24 | } 25 | (function() { 26 | 'use strict'; 27 | // Loop until UltraType has preloaded, then fire the start() function 28 | setInterval(() => { 29 | if (unsafeWindow["UltraTypeCore"]) { 30 | start(); 31 | clearInterval(this); 32 | } 33 | }, 100); 34 | })(); -------------------------------------------------------------------------------- /api/turboplus.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name UltraType Aggresive Turbo Mode 3 | // @version 1 4 | // @description Instantly wins any NitroType race without fail, extremely quickly. REQUIRES THE ULTRATYPE CHROME EXTENSION 5 | // @author UltraTypeBot 6 | // @match https://www.nitrotype.com/race/* 7 | // @grant unsafeWindow 8 | // @run-at document-start 9 | // ==/UserScript== 10 | 11 | (function() { 12 | 'use strict'; 13 | function start() { 14 | UltraTypeCore.on('raceStart', () => { 15 | setTimeout(() => { 16 | // Send an insanely mutated type packet that instantly wins the race 17 | UltraTypeCore.sendTypePacket(true, 99999); 18 | }, 100); 19 | }).on('raceFinish', () => { 20 | // Auto refresh the page 21 | setTimeout(location.reload.bind(window.location), 2); 22 | }); 23 | } 24 | setInterval(() => { 25 | if (unsafeWindow["UltraTypeCore"]) { 26 | start(); // Call when UltraType is loaded 27 | clearInterval(this); 28 | return; 29 | } 30 | }, 100); 31 | })(); -------------------------------------------------------------------------------- /dataServer/Makefile: -------------------------------------------------------------------------------- 1 | SRC=$(wildcard src/*.cpp src/*.cc) 2 | OUT=./out 3 | CC=clang++ 4 | OPTS=-pthread -lpthread -std=c++11 -lssl -ggdb 5 | PORT=8283 6 | THISF := $(lastword $(MAKEFILE_LIST)))) 7 | 8 | default: 9 | $(CC) $(SRC) $(OPTS) -o $(OUT) 10 | debug: 11 | make default 12 | echo "Starting GDB...\n" 13 | gdb $(OUT) 14 | clean: 15 | rm -f $(OUT) 16 | run: 17 | make default 18 | $(OUT) $(PORT) -------------------------------------------------------------------------------- /dataServer/src/httplib.h: -------------------------------------------------------------------------------- 1 | // 2 | // httplib.h 3 | // 4 | // Copyright (c) 2017 Yuji Hirose. All rights reserved. 5 | // The Boost Software License 1.0 6 | // 7 | 8 | #ifndef _CPPHTTPLIB_HTTPLIB_H_ 9 | #define _CPPHTTPLIB_HTTPLIB_H_ 10 | 11 | #ifdef _MSC_VER 12 | #define _CRT_SECURE_NO_WARNINGS 13 | #define _CRT_NONSTDC_NO_DEPRECATE 14 | 15 | #ifndef SO_SYNCHRONOUS_NONALERT 16 | #define SO_SYNCHRONOUS_NONALERT 0x20 17 | #endif 18 | #ifndef SO_OPENTYPE 19 | #define SO_OPENTYPE 0x7008 20 | #endif 21 | #if (_MSC_VER < 1900) 22 | #define snprintf _snprintf_s 23 | #endif 24 | 25 | #define S_ISREG(m) (((m)&S_IFREG)==S_IFREG) 26 | #define S_ISDIR(m) (((m)&S_IFDIR)==S_IFDIR) 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #undef min 34 | #undef max 35 | 36 | typedef SOCKET socket_t; 37 | #else 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | typedef int socket_t; 47 | #endif 48 | 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | 58 | #ifdef CPPHTTPLIB_OPENSSL_SUPPORT 59 | #include 60 | #endif 61 | 62 | namespace httplib 63 | { 64 | 65 | typedef std::map Map; 66 | typedef std::multimap MultiMap; 67 | typedef std::smatch Match; 68 | 69 | struct Request { 70 | std::string method; 71 | std::string path; 72 | MultiMap headers; 73 | std::string body; 74 | Map params; 75 | Match matches; 76 | 77 | bool has_header(const char* key) const; 78 | std::string get_header_value(const char* key) const; 79 | void set_header(const char* key, const char* val); 80 | 81 | bool has_param(const char* key) const; 82 | }; 83 | 84 | struct Response { 85 | int status; 86 | MultiMap headers; 87 | std::string body; 88 | 89 | bool has_header(const char* key) const; 90 | std::string get_header_value(const char* key) const; 91 | void set_header(const char* key, const char* val); 92 | 93 | void set_redirect(const char* url); 94 | void set_content(const char* s, size_t n, const char* content_type); 95 | void set_content(const std::string& s, const char* content_type); 96 | 97 | Response() : status(-1) {} 98 | }; 99 | 100 | class Stream { 101 | public: 102 | virtual ~Stream() {} 103 | virtual int read(char* ptr, size_t size) = 0; 104 | virtual int write(const char* ptr, size_t size1) = 0; 105 | virtual int write(const char* ptr) = 0; 106 | }; 107 | 108 | class SocketStream : public Stream { 109 | public: 110 | SocketStream(socket_t sock); 111 | virtual ~SocketStream(); 112 | 113 | virtual int read(char* ptr, size_t size); 114 | virtual int write(const char* ptr, size_t size); 115 | virtual int write(const char* ptr); 116 | 117 | private: 118 | socket_t sock_; 119 | }; 120 | 121 | class Server { 122 | public: 123 | typedef std::function Handler; 124 | typedef std::function Logger; 125 | 126 | Server(); 127 | virtual ~Server(); 128 | 129 | void get(const char* pattern, Handler handler); 130 | void post(const char* pattern, Handler handler); 131 | 132 | bool set_base_dir(const char* path); 133 | 134 | void set_error_handler(Handler handler); 135 | void set_logger(Logger logger); 136 | 137 | bool listen(const char* host, int port); 138 | void stop(); 139 | 140 | protected: 141 | void process_request(Stream& strm); 142 | 143 | private: 144 | typedef std::vector> Handlers; 145 | 146 | bool routing(Request& req, Response& res); 147 | bool handle_file_request(Request& req, Response& res); 148 | bool dispatch_request(Request& req, Response& res, Handlers& handlers); 149 | 150 | bool read_request_line(Stream& strm, Request& req); 151 | 152 | virtual bool read_and_close_socket(socket_t sock); 153 | 154 | socket_t svr_sock_; 155 | std::string base_dir_; 156 | Handlers get_handlers_; 157 | Handlers post_handlers_; 158 | Handler error_handler_; 159 | Logger logger_; 160 | }; 161 | 162 | class Client { 163 | public: 164 | Client(const char* host, int port); 165 | virtual ~Client(); 166 | 167 | std::shared_ptr get(const char* path); 168 | std::shared_ptr head(const char* path); 169 | std::shared_ptr post(const char* path, const std::string& body, const char* content_type); 170 | std::shared_ptr post(const char* path, const Map& params); 171 | 172 | bool send(const Request& req, Response& res); 173 | 174 | protected: 175 | bool process_request(Stream& strm, const Request& req, Response& res); 176 | 177 | private: 178 | bool read_response_line(Stream& strm, Response& res); 179 | 180 | virtual bool read_and_close_socket(socket_t sock, const Request& req, Response& res); 181 | 182 | const std::string host_; 183 | const int port_; 184 | }; 185 | 186 | #ifdef CPPHTTPLIB_OPENSSL_SUPPORT 187 | class SSLSocketStream : public Stream { 188 | public: 189 | SSLSocketStream(SSL* ssl); 190 | virtual ~SSLSocketStream(); 191 | 192 | virtual int read(char* ptr, size_t size); 193 | virtual int write(const char* ptr, size_t size); 194 | virtual int write(const char* ptr); 195 | 196 | private: 197 | SSL* ssl_; 198 | }; 199 | 200 | class SSLServer : public Server { 201 | public: 202 | SSLServer(const char* cert_path, const char* private_key_path); 203 | virtual ~SSLServer(); 204 | 205 | private: 206 | virtual bool read_and_close_socket(socket_t sock); 207 | 208 | SSL_CTX* ctx_; 209 | }; 210 | 211 | class SSLClient : public Client { 212 | public: 213 | SSLClient(const char* host, int port); 214 | virtual ~SSLClient(); 215 | 216 | private: 217 | virtual bool read_and_close_socket(socket_t sock, const Request& req, Response& res); 218 | 219 | SSL_CTX* ctx_; 220 | }; 221 | #endif 222 | 223 | /* 224 | * Implementation 225 | */ 226 | namespace detail { 227 | 228 | template 229 | void split(const char* b, const char* e, char d, Fn fn) 230 | { 231 | int i = 0; 232 | int beg = 0; 233 | 234 | while (e ? (b + i != e) : (b[i] != '\0')) { 235 | if (b[i] == d) { 236 | fn(&b[beg], &b[i]); 237 | beg = i + 1; 238 | } 239 | i++; 240 | } 241 | 242 | if (i) { 243 | fn(&b[beg], &b[i]); 244 | } 245 | } 246 | 247 | inline bool socket_gets(Stream& strm, char* buf, int bufsiz) 248 | { 249 | // TODO: buffering for better performance 250 | size_t i = 0; 251 | 252 | for (;;) { 253 | char byte; 254 | auto n = strm.read(&byte, 1); 255 | 256 | if (n < 1) { 257 | if (i == 0) { 258 | return false; 259 | } else { 260 | break; 261 | } 262 | } 263 | 264 | buf[i++] = byte; 265 | 266 | if (byte == '\n') { 267 | break; 268 | } 269 | } 270 | 271 | buf[i] = '\0'; 272 | return true; 273 | } 274 | 275 | template 276 | inline void socket_printf(Stream& strm, const char* fmt, const Args& ...args) 277 | { 278 | char buf[BUFSIZ]; 279 | auto n = snprintf(buf, BUFSIZ, fmt, args...); 280 | if (n > 0) { 281 | if (n >= BUFSIZ) { 282 | // TODO: buffer size is not large enough... 283 | } else { 284 | strm.write(buf, n); 285 | } 286 | } 287 | } 288 | 289 | inline int close_socket(socket_t sock) 290 | { 291 | #ifdef _MSC_VER 292 | return closesocket(sock); 293 | #else 294 | return close(sock); 295 | #endif 296 | } 297 | 298 | template 299 | inline bool read_and_close_socket(socket_t sock, T callback) 300 | { 301 | SocketStream strm(sock); 302 | auto ret = callback(strm); 303 | close_socket(sock); 304 | return ret; 305 | } 306 | 307 | inline int shutdown_socket(socket_t sock) 308 | { 309 | #ifdef _MSC_VER 310 | return shutdown(sock, SD_BOTH); 311 | #else 312 | return shutdown(sock, SHUT_RDWR); 313 | #endif 314 | } 315 | 316 | template 317 | socket_t create_socket(const char* host, int port, Fn fn) 318 | { 319 | #ifdef _MSC_VER 320 | int opt = SO_SYNCHRONOUS_NONALERT; 321 | setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char*)&opt, sizeof(opt)); 322 | #endif 323 | 324 | // Get address info 325 | struct addrinfo hints; 326 | struct addrinfo *result; 327 | 328 | memset(&hints, 0, sizeof(struct addrinfo)); 329 | hints.ai_family = AF_UNSPEC; 330 | hints.ai_socktype = SOCK_STREAM; 331 | hints.ai_flags = 0; 332 | hints.ai_protocol = 0; 333 | 334 | auto service = std::to_string(port); 335 | 336 | if (getaddrinfo(host, service.c_str(), &hints, &result)) { 337 | return -1; 338 | } 339 | 340 | for (auto rp = result; rp; rp = rp->ai_next) { 341 | // Create a socket 342 | auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 343 | if (sock == -1) { 344 | continue; 345 | } 346 | 347 | // Make 'reuse address' option available 348 | int yes = 1; 349 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)); 350 | 351 | // bind or connect 352 | if (fn(sock, *rp)) { 353 | freeaddrinfo(result); 354 | return sock; 355 | } 356 | 357 | close_socket(sock); 358 | } 359 | 360 | freeaddrinfo(result); 361 | return -1; 362 | } 363 | 364 | inline socket_t create_server_socket(const char* host, int port) 365 | { 366 | return create_socket(host, port, [](socket_t sock, struct addrinfo& ai) -> socket_t { 367 | if (::bind(sock, ai.ai_addr, ai.ai_addrlen)) { 368 | return false; 369 | } 370 | if (listen(sock, 5)) { // Listen through 5 channels 371 | return false; 372 | } 373 | return true; 374 | }); 375 | } 376 | 377 | inline socket_t create_client_socket(const char* host, int port) 378 | { 379 | return create_socket(host, port, [](socket_t sock, struct addrinfo& ai) -> socket_t { 380 | if (connect(sock, ai.ai_addr, ai.ai_addrlen)) { 381 | return false; 382 | } 383 | return true; 384 | }); 385 | } 386 | 387 | inline bool is_file(const std::string& s) 388 | { 389 | struct stat st; 390 | return stat(s.c_str(), &st) >= 0 && S_ISREG(st.st_mode); 391 | } 392 | 393 | inline bool is_dir(const std::string& s) 394 | { 395 | struct stat st; 396 | return stat(s.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); 397 | } 398 | 399 | inline void read_file(const std::string& path, std::string& out) 400 | { 401 | std::ifstream fs(path, std::ios_base::binary); 402 | fs.seekg(0, std::ios_base::end); 403 | auto size = fs.tellg(); 404 | fs.seekg(0); 405 | out.resize(static_cast(size)); 406 | fs.read(&out[0], size); 407 | } 408 | 409 | inline std::string file_extension(const std::string& path) 410 | { 411 | std::smatch m; 412 | auto pat = std::regex("\\.([a-zA-Z0-9]+)$"); 413 | if (std::regex_search(path, m, pat)) { 414 | return m[1].str(); 415 | } 416 | return std::string(); 417 | } 418 | 419 | inline const char* content_type(const std::string& path) 420 | { 421 | auto ext = detail::file_extension(path); 422 | if (ext == "txt") { 423 | return "text/plain"; 424 | } else if (ext == "html") { 425 | return "text/html"; 426 | } else if (ext == "js") { 427 | return "text/javascript"; 428 | } else if (ext == "css") { 429 | return "text/css"; 430 | } else if (ext == "xml") { 431 | return "text/xml"; 432 | } else if (ext == "jpeg" || ext == "jpg") { 433 | return "image/jpg"; 434 | } else if (ext == "png") { 435 | return "image/png"; 436 | } else if (ext == "gif") { 437 | return "image/gif"; 438 | } else if (ext == "svg") { 439 | return "image/svg+xml"; 440 | } else if (ext == "ico") { 441 | return "image/x-icon"; 442 | } else if (ext == "json") { 443 | return "application/json"; 444 | } else if (ext == "pdf") { 445 | return "application/pdf"; 446 | } else if (ext == "xhtml") { 447 | return "application/xhtml+xml"; 448 | } 449 | return nullptr; 450 | } 451 | 452 | inline const char* status_message(int status) 453 | { 454 | switch (status) { 455 | case 200: return "OK"; 456 | case 400: return "Bad Request"; 457 | case 404: return "Not Found"; 458 | default: 459 | case 500: return "Internal Server Error"; 460 | } 461 | } 462 | 463 | inline const char* get_header_value(const MultiMap& map, const char* key, const char* def) 464 | { 465 | auto it = map.find(key); 466 | if (it != map.end()) { 467 | return it->second.c_str(); 468 | } 469 | return def; 470 | } 471 | 472 | inline int get_header_value_int(const MultiMap& map, const char* key, int def) 473 | { 474 | auto it = map.find(key); 475 | if (it != map.end()) { 476 | return std::stoi(it->second); 477 | } 478 | return def; 479 | } 480 | 481 | inline bool read_headers(Stream& strm, MultiMap& headers) 482 | { 483 | static std::regex re("(.+?): (.+?)\r\n"); 484 | 485 | const auto BUFSIZ_HEADER = 2048; 486 | char buf[BUFSIZ_HEADER]; 487 | 488 | for (;;) { 489 | if (!socket_gets(strm, buf, BUFSIZ_HEADER)) { 490 | return false; 491 | } 492 | if (!strcmp(buf, "\r\n")) { 493 | break; 494 | } 495 | std::cmatch m; 496 | if (std::regex_match(buf, m, re)) { 497 | auto key = std::string(m[1]); 498 | auto val = std::string(m[2]); 499 | headers.insert(std::make_pair(key, val)); 500 | } 501 | } 502 | 503 | return true; 504 | } 505 | 506 | template 507 | bool read_content(Stream& strm, T& x) 508 | { 509 | auto len = get_header_value_int(x.headers, "Content-Length", 0); 510 | if (len) { 511 | x.body.assign(len, 0); 512 | if (!strm.read(&x.body[0], x.body.size())) { 513 | return false; 514 | } 515 | } 516 | return true; 517 | } 518 | 519 | template 520 | inline void write_headers(Stream& strm, const T& res) 521 | { 522 | strm.write("Connection: close\r\n"); 523 | 524 | for (const auto& x: res.headers) { 525 | if (x.first != "Content-Type" && x.first != "Content-Length") { 526 | socket_printf(strm, "%s: %s\r\n", x.first.c_str(), x.second.c_str()); 527 | } 528 | } 529 | 530 | auto t = get_header_value(res.headers, "Content-Type", "text/plain"); 531 | socket_printf(strm, "Content-Type: %s\r\n", t); 532 | socket_printf(strm, "Content-Length: %ld\r\n", res.body.size()); 533 | strm.write("\r\n"); 534 | } 535 | 536 | inline void write_response(Stream& strm, const Request& req, const Response& res) 537 | { 538 | socket_printf(strm, "HTTP/1.0 %d %s\r\n", res.status, status_message(res.status)); 539 | 540 | write_headers(strm, res); 541 | 542 | if (!res.body.empty() && req.method != "HEAD") { 543 | strm.write(res.body.c_str(), res.body.size()); 544 | } 545 | } 546 | 547 | inline std::string encode_url(const std::string& s) 548 | { 549 | std::string result; 550 | 551 | for (auto i = 0; s[i]; i++) { 552 | switch (s[i]) { 553 | case ' ': result += "+"; break; 554 | case '\'': result += "%27"; break; 555 | case ',': result += "%2C"; break; 556 | case ':': result += "%3A"; break; 557 | case ';': result += "%3B"; break; 558 | default: 559 | if (s[i] < 0) { 560 | result += '%'; 561 | char hex[4]; 562 | size_t len = snprintf(hex, sizeof(hex), "%02X", (unsigned char)s[i]); 563 | assert(len == 2); 564 | result.append(hex, len); 565 | } else { 566 | result += s[i]; 567 | } 568 | break; 569 | } 570 | } 571 | 572 | return result; 573 | } 574 | 575 | inline bool is_hex(char c, int& v) 576 | { 577 | if (0x20 <= c && isdigit(c)) { 578 | v = c - '0'; 579 | return true; 580 | } else if ('A' <= c && c <= 'F') { 581 | v = c - 'A' + 10; 582 | return true; 583 | } else if ('a' <= c && c <= 'f') { 584 | v = c - 'a' + 10; 585 | return true; 586 | } 587 | return false; 588 | } 589 | 590 | inline int from_hex_to_i(const std::string& s, int i, int cnt, int& val) 591 | { 592 | val = 0; 593 | for (; s[i] && cnt; i++, cnt--) { 594 | int v = 0; 595 | if (is_hex(s[i], v)) { 596 | val = val * 16 + v; 597 | } else { 598 | break; 599 | } 600 | } 601 | return --i; 602 | } 603 | 604 | inline size_t to_utf8(int code, char* buff) 605 | { 606 | if (code < 0x0080) { 607 | buff[0] = (code & 0x7F); 608 | return 1; 609 | } else if (code < 0x0800) { 610 | buff[0] = (0xC0 | ((code >> 6) & 0x1F)); 611 | buff[1] = (0x80 | (code & 0x3F)); 612 | return 2; 613 | } else if (code < 0xD800) { 614 | buff[0] = (0xE0 | ((code >> 12) & 0xF)); 615 | buff[1] = (0x80 | ((code >> 6) & 0x3F)); 616 | buff[2] = (0x80 | (code & 0x3F)); 617 | return 3; 618 | } else if (code < 0xE000) { // D800 - DFFF is invalid... 619 | return 0; 620 | } else if (code < 0x10000) { 621 | buff[0] = (0xE0 | ((code >> 12) & 0xF)); 622 | buff[1] = (0x80 | ((code >> 6) & 0x3F)); 623 | buff[2] = (0x80 | (code & 0x3F)); 624 | return 3; 625 | } else if (code < 0x110000) { 626 | buff[0] = (0xF0 | ((code >> 18) & 0x7)); 627 | buff[1] = (0x80 | ((code >> 12) & 0x3F)); 628 | buff[2] = (0x80 | ((code >> 6) & 0x3F)); 629 | buff[3] = (0x80 | (code & 0x3F)); 630 | return 4; 631 | } 632 | 633 | // NOTREACHED 634 | return 0; 635 | } 636 | 637 | inline std::string decode_url(const std::string& s) 638 | { 639 | std::string result; 640 | 641 | for (int i = 0; s[i]; i++) { 642 | if (s[i] == '%') { 643 | i++; 644 | assert(s[i]); 645 | 646 | if (s[i] == '%') { 647 | result += s[i]; 648 | } else if (s[i] == 'u') { 649 | // Unicode 650 | i++; 651 | assert(s[i]); 652 | 653 | int val = 0; 654 | i = from_hex_to_i(s, i, 4, val); 655 | 656 | char buff[4]; 657 | size_t len = to_utf8(val, buff); 658 | 659 | if (len > 0) { 660 | result.append(buff, len); 661 | } 662 | } else { 663 | // HEX 664 | int val = 0; 665 | i = from_hex_to_i(s, i, 2, val); 666 | result += val; 667 | } 668 | } else if (s[i] == '+') { 669 | result += ' '; 670 | } else { 671 | result += s[i]; 672 | } 673 | } 674 | 675 | return result; 676 | } 677 | 678 | inline void write_request(Stream& strm, const Request& req) 679 | { 680 | auto path = encode_url(req.path); 681 | socket_printf(strm, "%s %s HTTP/1.0\r\n", req.method.c_str(), path.c_str()); 682 | 683 | write_headers(strm, req); 684 | 685 | if (!req.body.empty()) { 686 | if (req.has_header("application/x-www-form-urlencoded")) { 687 | auto str = encode_url(req.body); 688 | strm.write(str.c_str(), str.size()); 689 | } else { 690 | strm.write(req.body.c_str(), req.body.size()); 691 | } 692 | } 693 | } 694 | 695 | inline void parse_query_text(const std::string& s, Map& params) 696 | { 697 | split(&s[0], &s[s.size()], '&', [&](const char* b, const char* e) { 698 | std::string key; 699 | std::string val; 700 | split(b, e, '=', [&](const char* b, const char* e) { 701 | if (key.empty()) { 702 | key.assign(b, e); 703 | } else { 704 | val.assign(b, e); 705 | } 706 | }); 707 | params[key] = detail::decode_url(val); 708 | }); 709 | } 710 | 711 | #ifdef _MSC_VER 712 | class WSInit { 713 | public: 714 | WSInit() { 715 | WSADATA wsaData; 716 | WSAStartup(0x0002, &wsaData); 717 | } 718 | 719 | ~WSInit() { 720 | WSACleanup(); 721 | } 722 | }; 723 | 724 | static WSInit wsinit_; 725 | #endif 726 | 727 | } // namespace detail 728 | 729 | // Request implementation 730 | inline bool Request::has_header(const char* key) const 731 | { 732 | return headers.find(key) != headers.end(); 733 | } 734 | 735 | inline std::string Request::get_header_value(const char* key) const 736 | { 737 | return detail::get_header_value(headers, key, ""); 738 | } 739 | 740 | inline void Request::set_header(const char* key, const char* val) 741 | { 742 | headers.insert(std::make_pair(key, val)); 743 | } 744 | 745 | inline bool Request::has_param(const char* key) const 746 | { 747 | return params.find(key) != params.end(); 748 | } 749 | 750 | // Response implementation 751 | inline bool Response::has_header(const char* key) const 752 | { 753 | return headers.find(key) != headers.end(); 754 | } 755 | 756 | inline std::string Response::get_header_value(const char* key) const 757 | { 758 | return detail::get_header_value(headers, key, ""); 759 | } 760 | 761 | inline void Response::set_header(const char* key, const char* val) 762 | { 763 | headers.insert(std::make_pair(key, val)); 764 | } 765 | 766 | inline void Response::set_redirect(const char* url) 767 | { 768 | set_header("Location", url); 769 | status = 302; 770 | } 771 | 772 | inline void Response::set_content(const char* s, size_t n, const char* content_type) 773 | { 774 | body.assign(s, n); 775 | set_header("Content-Type", content_type); 776 | } 777 | 778 | inline void Response::set_content(const std::string& s, const char* content_type) 779 | { 780 | body = s; 781 | set_header("Content-Type", content_type); 782 | } 783 | 784 | // Socket stream implementation 785 | inline SocketStream::SocketStream(socket_t sock): sock_(sock) 786 | { 787 | } 788 | 789 | inline SocketStream::~SocketStream() 790 | { 791 | } 792 | 793 | inline int SocketStream::read(char* ptr, size_t size) 794 | { 795 | return recv(sock_, ptr, size, 0); 796 | } 797 | 798 | inline int SocketStream::write(const char* ptr, size_t size) 799 | { 800 | return send(sock_, ptr, size, 0); 801 | } 802 | 803 | inline int SocketStream::write(const char* ptr) 804 | { 805 | return write(ptr, strlen(ptr)); 806 | } 807 | 808 | // HTTP server implementation 809 | inline Server::Server() 810 | : svr_sock_(-1) 811 | { 812 | } 813 | 814 | inline Server::~Server() 815 | { 816 | } 817 | 818 | inline void Server::get(const char* pattern, Handler handler) 819 | { 820 | get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); 821 | } 822 | 823 | inline void Server::post(const char* pattern, Handler handler) 824 | { 825 | post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); 826 | } 827 | 828 | inline bool Server::set_base_dir(const char* path) 829 | { 830 | if (detail::is_dir(path)) { 831 | base_dir_ = path; 832 | return true; 833 | } 834 | return false; 835 | } 836 | 837 | inline void Server::set_error_handler(Handler handler) 838 | { 839 | error_handler_ = handler; 840 | } 841 | 842 | inline void Server::set_logger(Logger logger) 843 | { 844 | logger_ = logger; 845 | } 846 | 847 | inline bool Server::listen(const char* host, int port) 848 | { 849 | svr_sock_ = detail::create_server_socket(host, port); 850 | if (svr_sock_ == -1) { 851 | return false; 852 | } 853 | 854 | auto ret = true; 855 | 856 | for (;;) { 857 | socket_t sock = accept(svr_sock_, NULL, NULL); 858 | 859 | if (sock == -1) { 860 | if (svr_sock_ != -1) { 861 | detail::close_socket(svr_sock_); 862 | ret = false; 863 | } else { 864 | ; // The server socket was closed by user. 865 | } 866 | break; 867 | } 868 | 869 | // TODO: should be async 870 | read_and_close_socket(sock); 871 | } 872 | 873 | return ret; 874 | } 875 | 876 | inline void Server::stop() 877 | { 878 | detail::shutdown_socket(svr_sock_); 879 | detail::close_socket(svr_sock_); 880 | svr_sock_ = -1; 881 | } 882 | 883 | inline bool Server::read_request_line(Stream& strm, Request& req) 884 | { 885 | const auto BUFSIZ_REQUESTLINE = 2048; 886 | char buf[BUFSIZ_REQUESTLINE]; 887 | if (!detail::socket_gets(strm, buf, BUFSIZ_REQUESTLINE)) { 888 | return false; 889 | } 890 | 891 | static std::regex re("(GET|HEAD|POST) ([^?]+)(?:\\?(.+?))? HTTP/1\\.[01]\r\n"); 892 | 893 | std::cmatch m; 894 | if (std::regex_match(buf, m, re)) { 895 | req.method = std::string(m[1]); 896 | req.path = detail::decode_url(m[2]); 897 | 898 | // Parse query text 899 | auto len = std::distance(m[3].first, m[3].second); 900 | if (len > 0) { 901 | detail::parse_query_text(m[3], req.params); 902 | } 903 | 904 | return true; 905 | } 906 | 907 | return false; 908 | } 909 | 910 | inline bool Server::handle_file_request(Request& req, Response& res) 911 | { 912 | if (!base_dir_.empty()) { 913 | std::string path = base_dir_ + req.path; 914 | 915 | if (!path.empty() && path.back() == '/') { 916 | path += "index.html"; 917 | } 918 | 919 | if (detail::is_file(path)) { 920 | detail::read_file(path, res.body); 921 | auto type = detail::content_type(path); 922 | if (type) { 923 | res.set_header("Content-Type", type); 924 | } 925 | res.status = 200; 926 | return true; 927 | } 928 | } 929 | 930 | return false; 931 | } 932 | 933 | inline bool Server::routing(Request& req, Response& res) 934 | { 935 | if (req.method == "GET" && handle_file_request(req, res)) { 936 | return true; 937 | } 938 | 939 | if (req.method == "GET" || req.method == "HEAD") { 940 | return dispatch_request(req, res, get_handlers_); 941 | } else if (req.method == "POST") { 942 | return dispatch_request(req, res, post_handlers_); 943 | } 944 | return false; 945 | } 946 | 947 | inline bool Server::dispatch_request(Request& req, Response& res, Handlers& handlers) 948 | { 949 | for (const auto& x: handlers) { 950 | const auto& pattern = x.first; 951 | const auto& handler = x.second; 952 | 953 | if (std::regex_match(req.path, req.matches, pattern)) { 954 | handler(req, res); 955 | return true; 956 | } 957 | } 958 | return false; 959 | } 960 | 961 | inline void Server::process_request(Stream& strm) 962 | { 963 | Request req; 964 | Response res; 965 | 966 | if (!read_request_line(strm, req) || 967 | !detail::read_headers(strm, req.headers)) { 968 | // TODO: 969 | return; 970 | } 971 | 972 | if (req.method == "POST") { 973 | if (!detail::read_content(strm, req)) { 974 | // TODO: 975 | return; 976 | } 977 | static std::string type = "application/x-www-form-urlencoded"; 978 | if (!req.get_header_value("Content-Type").compare(0, type.size(), type)) { 979 | detail::parse_query_text(req.body, req.params); 980 | } 981 | } 982 | 983 | if (routing(req, res)) { 984 | if (res.status == -1) { 985 | res.status = 200; 986 | } 987 | } else { 988 | res.status = 404; 989 | } 990 | assert(res.status != -1); 991 | 992 | if (400 <= res.status && error_handler_) { 993 | error_handler_(req, res); 994 | } 995 | 996 | detail::write_response(strm, req, res); 997 | 998 | if (logger_) { 999 | logger_(req, res); 1000 | } 1001 | } 1002 | 1003 | inline bool Server::read_and_close_socket(socket_t sock) 1004 | { 1005 | return detail::read_and_close_socket(sock, [this](Stream& strm) { 1006 | process_request(strm); 1007 | return true; 1008 | }); 1009 | } 1010 | 1011 | // HTTP client implementation 1012 | inline Client::Client(const char* host, int port) 1013 | : host_(host) 1014 | , port_(port) 1015 | { 1016 | } 1017 | 1018 | inline Client::~Client() 1019 | { 1020 | } 1021 | 1022 | inline bool Client::read_response_line(Stream& strm, Response& res) 1023 | { 1024 | const auto BUFSIZ_RESPONSELINE = 2048; 1025 | char buf[BUFSIZ_RESPONSELINE]; 1026 | if (!detail::socket_gets(strm, buf, BUFSIZ_RESPONSELINE)) { 1027 | return false; 1028 | } 1029 | 1030 | const static std::regex re("HTTP/1\\.[01] (\\d+?) .+\r\n"); 1031 | 1032 | std::cmatch m; 1033 | if (std::regex_match(buf, m, re)) { 1034 | res.status = std::stoi(std::string(m[1])); 1035 | } 1036 | 1037 | return true; 1038 | } 1039 | 1040 | inline bool Client::send(const Request& req, Response& res) 1041 | { 1042 | auto sock = detail::create_client_socket(host_.c_str(), port_); 1043 | if (sock == -1) { 1044 | return false; 1045 | } 1046 | 1047 | return read_and_close_socket(sock, req, res); 1048 | } 1049 | 1050 | inline bool Client::process_request(Stream& strm, const Request& req, Response& res) 1051 | { 1052 | // Send request 1053 | detail::write_request(strm, req); 1054 | 1055 | // Receive response 1056 | if (!read_response_line(strm, res) || 1057 | !detail::read_headers(strm, res.headers)) { 1058 | return false; 1059 | } 1060 | if (req.method != "HEAD") { 1061 | if (!detail::read_content(strm, res)) { 1062 | return false; 1063 | } 1064 | } 1065 | 1066 | return true; 1067 | } 1068 | 1069 | inline bool Client::read_and_close_socket(socket_t sock, const Request& req, Response& res) 1070 | { 1071 | return detail::read_and_close_socket(sock, [&](Stream& strm) { 1072 | return process_request(strm, req, res); 1073 | }); 1074 | } 1075 | 1076 | inline std::shared_ptr Client::get(const char* path) 1077 | { 1078 | Request req; 1079 | req.method = "GET"; 1080 | req.path = path; 1081 | 1082 | auto res = std::make_shared(); 1083 | 1084 | return send(req, *res) ? res : nullptr; 1085 | } 1086 | 1087 | inline std::shared_ptr Client::head(const char* path) 1088 | { 1089 | Request req; 1090 | req.method = "HEAD"; 1091 | req.path = path; 1092 | 1093 | auto res = std::make_shared(); 1094 | 1095 | return send(req, *res) ? res : nullptr; 1096 | } 1097 | 1098 | inline std::shared_ptr Client::post( 1099 | const char* path, const std::string& body, const char* content_type) 1100 | { 1101 | Request req; 1102 | req.method = "POST"; 1103 | req.path = path; 1104 | req.set_header("Content-Type", content_type); 1105 | req.body = body; 1106 | 1107 | auto res = std::make_shared(); 1108 | 1109 | return send(req, *res) ? res : nullptr; 1110 | } 1111 | 1112 | inline std::shared_ptr Client::post( 1113 | const char* path, const Map& params) 1114 | { 1115 | std::string query; 1116 | for (auto it = params.begin(); it != params.end(); ++it) { 1117 | if (it != params.begin()) { 1118 | query += "&"; 1119 | } 1120 | query += it->first; 1121 | query += "="; 1122 | query += it->second; 1123 | } 1124 | 1125 | return post(path, query, "application/x-www-form-urlencoded"); 1126 | } 1127 | 1128 | /* 1129 | * SSL Implementation 1130 | */ 1131 | #ifdef CPPHTTPLIB_OPENSSL_SUPPORT 1132 | namespace detail { 1133 | 1134 | template 1135 | inline bool read_and_close_socket_ssl(socket_t sock, SSL_CTX* ctx, U SSL_connect_or_accept, T callback) 1136 | { 1137 | auto ssl = SSL_new(ctx); 1138 | 1139 | auto bio = BIO_new_socket(sock, BIO_NOCLOSE); 1140 | SSL_set_bio(ssl, bio, bio); 1141 | 1142 | SSL_connect_or_accept(ssl); 1143 | 1144 | SSLSocketStream strm(ssl); 1145 | auto ret = callback(strm); 1146 | 1147 | SSL_shutdown(ssl); 1148 | SSL_free(ssl); 1149 | close_socket(sock); 1150 | return ret; 1151 | } 1152 | 1153 | class SSLInit { 1154 | public: 1155 | SSLInit() { 1156 | SSL_load_error_strings(); 1157 | SSL_library_init(); 1158 | } 1159 | }; 1160 | 1161 | static SSLInit sslinit_; 1162 | 1163 | } // namespace detail 1164 | 1165 | // SSL socket stream implementation 1166 | inline SSLSocketStream::SSLSocketStream(SSL* ssl): ssl_(ssl) 1167 | { 1168 | } 1169 | 1170 | inline SSLSocketStream::~SSLSocketStream() 1171 | { 1172 | } 1173 | 1174 | inline int SSLSocketStream::read(char* ptr, size_t size) 1175 | { 1176 | return SSL_read(ssl_, ptr, size); 1177 | } 1178 | 1179 | inline int SSLSocketStream::write(const char* ptr, size_t size) 1180 | { 1181 | return SSL_write(ssl_, ptr, size); 1182 | } 1183 | 1184 | inline int SSLSocketStream::write(const char* ptr) 1185 | { 1186 | return write(ptr, strlen(ptr)); 1187 | } 1188 | 1189 | // SSL HTTP server implementation 1190 | inline SSLServer::SSLServer(const char* cert_path, const char* private_key_path) 1191 | { 1192 | ctx_ = SSL_CTX_new(SSLv23_server_method()); 1193 | 1194 | if (ctx_) { 1195 | SSL_CTX_set_options(ctx_, 1196 | SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | 1197 | SSL_OP_NO_COMPRESSION | 1198 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); 1199 | 1200 | // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); 1201 | // SSL_CTX_set_tmp_ecdh(ctx_, ecdh); 1202 | // EC_KEY_free(ecdh); 1203 | 1204 | if (SSL_CTX_use_certificate_file(ctx_, cert_path, SSL_FILETYPE_PEM) != 1 || 1205 | SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1) { 1206 | SSL_CTX_free(ctx_); 1207 | ctx_ = nullptr; 1208 | } 1209 | } 1210 | } 1211 | 1212 | inline SSLServer::~SSLServer() 1213 | { 1214 | if (ctx_) { 1215 | SSL_CTX_free(ctx_); 1216 | } 1217 | } 1218 | 1219 | inline bool SSLServer::read_and_close_socket(socket_t sock) 1220 | { 1221 | return detail::read_and_close_socket_ssl(sock, ctx_, SSL_accept, [this](Stream& strm) { 1222 | process_request(strm); 1223 | return true; 1224 | }); 1225 | } 1226 | 1227 | // SSL HTTP client implementation 1228 | inline SSLClient::SSLClient(const char* host, int port) 1229 | : Client(host, port) 1230 | { 1231 | ctx_ = SSL_CTX_new(SSLv23_client_method()); 1232 | } 1233 | 1234 | inline SSLClient::~SSLClient() 1235 | { 1236 | if (ctx_) { 1237 | SSL_CTX_free(ctx_); 1238 | } 1239 | } 1240 | 1241 | inline bool SSLClient::read_and_close_socket(socket_t sock, const Request& req, Response& res) 1242 | { 1243 | return detail::read_and_close_socket_ssl(sock, ctx_, SSL_connect, [&](Stream& strm) { 1244 | return process_request(strm, req, res); 1245 | }); 1246 | } 1247 | #endif 1248 | 1249 | } // namespace httplib 1250 | 1251 | #endif 1252 | 1253 | // vim: et ts=4 sw=4 cin cino={1s ff=unix 1254 | -------------------------------------------------------------------------------- /dataServer/src/main.cc: -------------------------------------------------------------------------------- 1 | #include "httplib.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // #define START_LOCAL_SERVER 9 | using namespace std; 10 | 11 | httplib::Server serv; 12 | ofstream banf; 13 | 14 | void usage() { 15 | cout << "Invalid arguments! Usage:\n" 16 | << "./dataServer \n"; 17 | } 18 | void onSignal(int signo) { 19 | if (signo == SIGINT) { 20 | cout << "Closing file and exiting...\n"; 21 | banf.close(); 22 | exit(0); 23 | } 24 | } 25 | int main(int argc, char** argv) { 26 | int port = 8283; 27 | if (argc != 2) { 28 | usage(); 29 | return 0; 30 | } else { 31 | port = atoi(argv[1]); 32 | if (port == 0) { 33 | cout << "Invalid port!\n"; 34 | usage(); 35 | return 0; 36 | } 37 | } 38 | banf.open("./bans", ios_base::app); 39 | if (signal(SIGINT, onSignal) == SIG_ERR) { 40 | cout << "WARN: Failed to register SIGINT event\n"; 41 | } 42 | serv.post("/baninfo", [](const httplib::Request& req, httplib::Response& res) { 43 | res.set_header("Access-Control-Allow-Origin", "*"); 44 | cout << "Recieved banInfo POST\n"; 45 | // Really basic string checking to prevent basic data corruption 46 | size_t index; 47 | index = req.body.find("\0"); 48 | if (index == string::npos || index == 0) { 49 | index = req.body.find("{"); 50 | if (index != string::npos) { 51 | // The request is valid enough, append to the file 52 | cout << "Writing accepted request\n"; 53 | banf << req.body << "\0"; 54 | res.set_content("SUCCESS", "text/plain"); 55 | } else { 56 | cout << "Rejecting invalid request: no brackets.\n"; 57 | res.status = 400; 58 | res.set_content("Invalid request.", "text/plain"); 59 | } 60 | } else { 61 | cout << "Rejecting invalid request: detected null byte.\n"; 62 | res.status = 400; 63 | res.set_content("Invalid request.", "text/plain"); 64 | } 65 | }); 66 | serv.get("/baninfo", [](const httplib::Request& req, httplib::Response& res) { 67 | // Reject GETs 68 | cout << "Recieved banInfo GET, rejecting it.\n"; 69 | res.status = 400; 70 | res.set_content("Invalid request method.", "text/plain"); 71 | }); 72 | cout << "Starting HTTP server on port " << port << ", "; 73 | #ifdef START_LOCAL_SERVER 74 | cout << "listening locally\n"; 75 | serv.listen("127.0.0.1", port); 76 | #else 77 | cout << "listening externally\n"; 78 | serv.listen("204.44.91.137", port); 79 | #endif 80 | return 0; 81 | } -------------------------------------------------------------------------------- /ico/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwg/UltraTypeBot/fdd93d3a8f0ed73d9c4bddd17951d57f2d24dc56/ico/1024.png -------------------------------------------------------------------------------- /ico/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwg/UltraTypeBot/fdd93d3a8f0ed73d9c4bddd17951d57f2d24dc56/ico/128.png -------------------------------------------------------------------------------- /ico/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwg/UltraTypeBot/fdd93d3a8f0ed73d9c4bddd17951d57f2d24dc56/ico/16.png -------------------------------------------------------------------------------- /ico/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwg/UltraTypeBot/fdd93d3a8f0ed73d9c4bddd17951d57f2d24dc56/ico/32.png -------------------------------------------------------------------------------- /ico/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwg/UltraTypeBot/fdd93d3a8f0ed73d9c4bddd17951d57f2d24dc56/ico/48.png -------------------------------------------------------------------------------- /ico/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwg/UltraTypeBot/fdd93d3a8f0ed73d9c4bddd17951d57f2d24dc56/ico/64.png -------------------------------------------------------------------------------- /ico/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwg/UltraTypeBot/fdd93d3a8f0ed73d9c4bddd17951d57f2d24dc56/ico/logo.png -------------------------------------------------------------------------------- /ico/logo_x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwg/UltraTypeBot/fdd93d3a8f0ed73d9c4bddd17951d57f2d24dc56/ico/logo_x2.png -------------------------------------------------------------------------------- /injectChrome.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const IS_LOCAL = !!(localStorage["ultratypedev"]), 3 | URL_REMOTE = "https://194-195-216-27.ip.linodeusercontent.com:8081/OUT.js", 4 | URL_OUT = IS_LOCAL ? chrome.extension.getURL('OUT/OUT.js') : URL_REMOTE, 5 | injectFull = () => { 6 | let x = new XMLHttpRequest(); 7 | x.open('GET', window.location.href, true); 8 | x.onload = function() { 9 | let doc = this.responseText; 10 | // doc = doc.replace(/`+doc); 13 | } 14 | x.send(null); 15 | }, 16 | injectAppend = () => { 17 | let scr = document.createElement('script'); 18 | scr.src = URL_OUT; 19 | if (document.head) { 20 | document.head.appendChild(scr); 21 | } else { 22 | // Retry after about 100 ms 23 | setTimeout(injectAppend, 100); 24 | } 25 | }; 26 | console.log('ultratypebot:PREINIT: determening injection method'); 27 | if (window.location.href.includes('nitrotype.com/race')) { 28 | // Use full injection method on the main page 29 | console.log('ultratypebot:PREINIT: full!'); 30 | injectFull(); 31 | return; 32 | } else { 33 | // Slower append injection method is used 34 | console.log('ultratypebot:PREINIT: appending'); 35 | injectAppend(); 36 | } 37 | })(); -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UltraType NitroType / Typing Game Bot", 3 | "short_name": "UltraType", 4 | "author": "ultratype", 5 | "description": "A typing game bot that will win almost any match", 6 | 7 | "version": "2.7", 8 | "version_name": "2.7 Release", 9 | "manifest_version": 2, 10 | 11 | "icons": { 12 | "16": "ico/16.png", 13 | "32": "ico/32.png", 14 | "48": "ico/48.png", 15 | "64": "ico/64.png", 16 | "128": "ico/128.png" 17 | }, 18 | "browser_action": { 19 | "default_icon": "ico/1024.png", 20 | "default_popup": "popup/popup.html", 21 | "default_title": "UltraType" 22 | }, 23 | "content_scripts": [ 24 | { 25 | "matches": [ 26 | "http://*/*", 27 | "https://*/*" 28 | ], 29 | "run_at": "document_start", 30 | "js": [ 31 | "injectChrome.js" 32 | ] 33 | } 34 | ], 35 | "web_accessible_resources": [ 36 | "OUT/OUT.js" 37 | ], 38 | "permissions": [ 39 | "activeTab", 40 | "background", 41 | "http://*/*", 42 | "https://*/*" 43 | ] 44 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ultratype-bot", 3 | "version": "2.3.2", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "ultratype-bot", 9 | "version": "2.3.2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "http-server": "^14.1.1" 13 | } 14 | }, 15 | "node_modules/ansi-styles": { 16 | "version": "4.3.0", 17 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 18 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 19 | "dependencies": { 20 | "color-convert": "^2.0.1" 21 | }, 22 | "engines": { 23 | "node": ">=8" 24 | }, 25 | "funding": { 26 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 27 | } 28 | }, 29 | "node_modules/async": { 30 | "version": "2.6.4", 31 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", 32 | "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", 33 | "dependencies": { 34 | "lodash": "^4.17.14" 35 | } 36 | }, 37 | "node_modules/basic-auth": { 38 | "version": "2.0.1", 39 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 40 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 41 | "dependencies": { 42 | "safe-buffer": "5.1.2" 43 | }, 44 | "engines": { 45 | "node": ">= 0.8" 46 | } 47 | }, 48 | "node_modules/call-bind": { 49 | "version": "1.0.2", 50 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 51 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 52 | "dependencies": { 53 | "function-bind": "^1.1.1", 54 | "get-intrinsic": "^1.0.2" 55 | }, 56 | "funding": { 57 | "url": "https://github.com/sponsors/ljharb" 58 | } 59 | }, 60 | "node_modules/chalk": { 61 | "version": "4.1.2", 62 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 63 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 64 | "dependencies": { 65 | "ansi-styles": "^4.1.0", 66 | "supports-color": "^7.1.0" 67 | }, 68 | "engines": { 69 | "node": ">=10" 70 | }, 71 | "funding": { 72 | "url": "https://github.com/chalk/chalk?sponsor=1" 73 | } 74 | }, 75 | "node_modules/color-convert": { 76 | "version": "2.0.1", 77 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 78 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 79 | "dependencies": { 80 | "color-name": "~1.1.4" 81 | }, 82 | "engines": { 83 | "node": ">=7.0.0" 84 | } 85 | }, 86 | "node_modules/color-name": { 87 | "version": "1.1.4", 88 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 89 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 90 | }, 91 | "node_modules/corser": { 92 | "version": "2.0.1", 93 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 94 | "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", 95 | "engines": { 96 | "node": ">= 0.4.0" 97 | } 98 | }, 99 | "node_modules/debug": { 100 | "version": "3.2.7", 101 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 102 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 103 | "dependencies": { 104 | "ms": "^2.1.1" 105 | } 106 | }, 107 | "node_modules/eventemitter3": { 108 | "version": "4.0.7", 109 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 110 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 111 | }, 112 | "node_modules/follow-redirects": { 113 | "version": "1.15.2", 114 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 115 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 116 | "funding": [ 117 | { 118 | "type": "individual", 119 | "url": "https://github.com/sponsors/RubenVerborgh" 120 | } 121 | ], 122 | "engines": { 123 | "node": ">=4.0" 124 | }, 125 | "peerDependenciesMeta": { 126 | "debug": { 127 | "optional": true 128 | } 129 | } 130 | }, 131 | "node_modules/function-bind": { 132 | "version": "1.1.1", 133 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 134 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 135 | }, 136 | "node_modules/get-intrinsic": { 137 | "version": "1.2.0", 138 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 139 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 140 | "dependencies": { 141 | "function-bind": "^1.1.1", 142 | "has": "^1.0.3", 143 | "has-symbols": "^1.0.3" 144 | }, 145 | "funding": { 146 | "url": "https://github.com/sponsors/ljharb" 147 | } 148 | }, 149 | "node_modules/has": { 150 | "version": "1.0.3", 151 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 152 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 153 | "dependencies": { 154 | "function-bind": "^1.1.1" 155 | }, 156 | "engines": { 157 | "node": ">= 0.4.0" 158 | } 159 | }, 160 | "node_modules/has-flag": { 161 | "version": "4.0.0", 162 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 163 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 164 | "engines": { 165 | "node": ">=8" 166 | } 167 | }, 168 | "node_modules/has-symbols": { 169 | "version": "1.0.3", 170 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 171 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 172 | "engines": { 173 | "node": ">= 0.4" 174 | }, 175 | "funding": { 176 | "url": "https://github.com/sponsors/ljharb" 177 | } 178 | }, 179 | "node_modules/he": { 180 | "version": "1.2.0", 181 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 182 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 183 | "bin": { 184 | "he": "bin/he" 185 | } 186 | }, 187 | "node_modules/html-encoding-sniffer": { 188 | "version": "3.0.0", 189 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", 190 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", 191 | "dependencies": { 192 | "whatwg-encoding": "^2.0.0" 193 | }, 194 | "engines": { 195 | "node": ">=12" 196 | } 197 | }, 198 | "node_modules/http-proxy": { 199 | "version": "1.18.1", 200 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 201 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 202 | "dependencies": { 203 | "eventemitter3": "^4.0.0", 204 | "follow-redirects": "^1.0.0", 205 | "requires-port": "^1.0.0" 206 | }, 207 | "engines": { 208 | "node": ">=8.0.0" 209 | } 210 | }, 211 | "node_modules/http-server": { 212 | "version": "14.1.1", 213 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", 214 | "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", 215 | "dependencies": { 216 | "basic-auth": "^2.0.1", 217 | "chalk": "^4.1.2", 218 | "corser": "^2.0.1", 219 | "he": "^1.2.0", 220 | "html-encoding-sniffer": "^3.0.0", 221 | "http-proxy": "^1.18.1", 222 | "mime": "^1.6.0", 223 | "minimist": "^1.2.6", 224 | "opener": "^1.5.1", 225 | "portfinder": "^1.0.28", 226 | "secure-compare": "3.0.1", 227 | "union": "~0.5.0", 228 | "url-join": "^4.0.1" 229 | }, 230 | "bin": { 231 | "http-server": "bin/http-server" 232 | }, 233 | "engines": { 234 | "node": ">=12" 235 | } 236 | }, 237 | "node_modules/iconv-lite": { 238 | "version": "0.6.3", 239 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 240 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 241 | "dependencies": { 242 | "safer-buffer": ">= 2.1.2 < 3.0.0" 243 | }, 244 | "engines": { 245 | "node": ">=0.10.0" 246 | } 247 | }, 248 | "node_modules/lodash": { 249 | "version": "4.17.21", 250 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 251 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 252 | }, 253 | "node_modules/mime": { 254 | "version": "1.6.0", 255 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 256 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 257 | "bin": { 258 | "mime": "cli.js" 259 | }, 260 | "engines": { 261 | "node": ">=4" 262 | } 263 | }, 264 | "node_modules/minimist": { 265 | "version": "1.2.7", 266 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", 267 | "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", 268 | "funding": { 269 | "url": "https://github.com/sponsors/ljharb" 270 | } 271 | }, 272 | "node_modules/mkdirp": { 273 | "version": "0.5.6", 274 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 275 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 276 | "dependencies": { 277 | "minimist": "^1.2.6" 278 | }, 279 | "bin": { 280 | "mkdirp": "bin/cmd.js" 281 | } 282 | }, 283 | "node_modules/ms": { 284 | "version": "2.1.3", 285 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 286 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 287 | }, 288 | "node_modules/object-inspect": { 289 | "version": "1.12.3", 290 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 291 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 292 | "funding": { 293 | "url": "https://github.com/sponsors/ljharb" 294 | } 295 | }, 296 | "node_modules/opener": { 297 | "version": "1.5.2", 298 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 299 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", 300 | "bin": { 301 | "opener": "bin/opener-bin.js" 302 | } 303 | }, 304 | "node_modules/portfinder": { 305 | "version": "1.0.32", 306 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", 307 | "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", 308 | "dependencies": { 309 | "async": "^2.6.4", 310 | "debug": "^3.2.7", 311 | "mkdirp": "^0.5.6" 312 | }, 313 | "engines": { 314 | "node": ">= 0.12.0" 315 | } 316 | }, 317 | "node_modules/qs": { 318 | "version": "6.11.0", 319 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 320 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 321 | "dependencies": { 322 | "side-channel": "^1.0.4" 323 | }, 324 | "engines": { 325 | "node": ">=0.6" 326 | }, 327 | "funding": { 328 | "url": "https://github.com/sponsors/ljharb" 329 | } 330 | }, 331 | "node_modules/requires-port": { 332 | "version": "1.0.0", 333 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 334 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" 335 | }, 336 | "node_modules/safe-buffer": { 337 | "version": "5.1.2", 338 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 339 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 340 | }, 341 | "node_modules/safer-buffer": { 342 | "version": "2.1.2", 343 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 344 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 345 | }, 346 | "node_modules/secure-compare": { 347 | "version": "3.0.1", 348 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 349 | "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==" 350 | }, 351 | "node_modules/side-channel": { 352 | "version": "1.0.4", 353 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 354 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 355 | "dependencies": { 356 | "call-bind": "^1.0.0", 357 | "get-intrinsic": "^1.0.2", 358 | "object-inspect": "^1.9.0" 359 | }, 360 | "funding": { 361 | "url": "https://github.com/sponsors/ljharb" 362 | } 363 | }, 364 | "node_modules/supports-color": { 365 | "version": "7.2.0", 366 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 367 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 368 | "dependencies": { 369 | "has-flag": "^4.0.0" 370 | }, 371 | "engines": { 372 | "node": ">=8" 373 | } 374 | }, 375 | "node_modules/union": { 376 | "version": "0.5.0", 377 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 378 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 379 | "dependencies": { 380 | "qs": "^6.4.0" 381 | }, 382 | "engines": { 383 | "node": ">= 0.8.0" 384 | } 385 | }, 386 | "node_modules/url-join": { 387 | "version": "4.0.1", 388 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", 389 | "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" 390 | }, 391 | "node_modules/whatwg-encoding": { 392 | "version": "2.0.0", 393 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", 394 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", 395 | "dependencies": { 396 | "iconv-lite": "0.6.3" 397 | }, 398 | "engines": { 399 | "node": ">=12" 400 | } 401 | } 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ultratype-bot", 3 | "version": "2.3.2", 4 | "author": "ultratype", 5 | "description": "A bot for NitroType that will win almost any match", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/ultratype/UltraTypeBot" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "http-server": "^14.1.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 |
22 |

23 | UltraType 24 |

25 |

26 | The smoothest, simplest NitroType bot. 27 |

28 | 29 | Race Now 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /popup/popup.js: -------------------------------------------------------------------------------- 1 | var URL = "https://www.nitrotype.com/race"; 2 | var a = document.getElementById('a'); 3 | a.onclick = function() { 4 | var w = window.open(URL, '_blank'); 5 | w.focus(); 6 | } -------------------------------------------------------------------------------- /runLocal.sh: -------------------------------------------------------------------------------- 1 | ./node_modules/http-server/bin/http-server -p 8081 . 2 | --------------------------------------------------------------------------------