dangerbob / NT Bot Detector

// ==UserScript==
// @name             NT Bot Detector
// @description      detects possible cheaters on nitrotype
// @match            *://www.nitrotype.com/race
// @match            *://www.nitrotype.com/race/*
// @grant            GM_getValue
// @grant            GM_setValue
// @author           dangerbobbad
// @run-at           document-start
// @version          1.0
// @license          MIT
// ==/UserScript==

console.info('[NT Bot Detector] Running!');
const clientUserID = JSON.parse(JSON.parse(localStorage.getItem('persist:nt')).user).userID;
var currentRacers;
var longestWordLength;

const originalSend = WebSocket.prototype.send;
window.sockets = [];
WebSocket.prototype.send = function(...args) {
  if (window.sockets.indexOf(this) === -1) {
      currentRacers = {};
      sockets.push(this);
      var ws = sockets[0];
      ws.addEventListener('message', function (event) {
          if (event.data.startsWith('4')) {
              var message = JSON.parse(event.data.substring(1));
              switch (message.msg) {
                  case 'setup':
                      message.payload.racers.forEach((racer) => {
                          if (!racer.robot) {
                              saveEncounter(racer.userID, racer.profile.username);
                              console.info(`[NT Bot Detector] Tracking racer: ${racer.profile.username} (${racer.userID})`);
                          }
                      });
                      break;
                  case 'joined':
                      if (!message.payload.robot && message.payload.userID != clientUserID) {
                          saveEncounter(message.payload.userID, message.payload.profile.username);
                          console.info(`[NT Bot Detector] Tracking racer: ${message.payload.profile.username} (${message.payload.userID})`);
                      }
                      break;
                  case 'status':
                      if (message.payload.status == 'countdown') {
                          longestWordLength = message.payload.l.split(' ').sort(function(a, b) { return b.length - a.length; })[0].length;
                      }
                      break;
                  case 'update':
                      message.payload.racers.forEach((racer) => {
                          if (racer.hasOwnProperty('s') && racer.u != clientUserID) {
                              if (racer.s == longestWordLength + 1) {
                                  console.info(`[NT Bot Detector] Racer ${racer.u} used nitro on the longest word`);
                                  saveNitroUsage(racer.u, true);
                              } else {
                                  console.info(`[NT Bot Detector] Racer ${racer.u} used nitro, but not on longest word`);
                                  saveNitroUsage(racer.u, false);
                              }
                          }
                      });
              }
          }
      });
      console.info('[NT Bot Detector] Socket connected');
  }
  return originalSend.call(this, ...args);
};

function saveEncounter(userID, username) {
    let savedData = GM_getValue(userID, `{ \"username\":"${username}", \"totalEncounters\":0, \"racesNitroUsed\":0, \"racesPerfectNitro\":0 }`);
    let parsedData = JSON.parse(savedData);
    parsedData.totalEncounters++;
    GM_setValue(userID, JSON.stringify(parsedData));
}

function saveNitroUsage(userID, perfectNitro) {
    let savedData = GM_getValue(userID);
    let parsedData = JSON.parse(savedData);
    parsedData.racesNitroUsed++;
    if (perfectNitro) {
        parsedData.racesPerfectNitro++;
    }
    GM_setValue(userID, JSON.stringify(parsedData));
}