Skip to content

Instantly share code, notes, and snippets.

@logan-mcgee
Last active August 22, 2021 18:19
Show Gist options
  • Save logan-mcgee/83c0effe25741607ccfc73ca83335c59 to your computer and use it in GitHub Desktop.
Save logan-mcgee/83c0effe25741607ccfc73ca83335c59 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name RC TTools
// @namespace RC TTools
// @match https://www.rockwelltransport.com/home/ttools/biz*
// @grant GM_xmlhttpRequest
// @version 1.0
// @author logan
// @downloadURL https://gist.github.com/sadboilogan/83c0effe25741607ccfc73ca83335c59/raw/ttools.user.js
// @description Extends TTools site
// ==/UserScript==
(async () => {
async function request(url) {
return new Promise(resolve => {
GM_xmlhttpRequest({ url: url, responseType: 'json', onload: (data) => resolve(data.response), onerror: () => resolve(false), ontimeout: () => resolve(false) });
});
}
async function runCommand(command, data) {
const res = await sendData('Runtime.evaluate', {
expression: `new Promise((resolve) => fetch("https://remote_connector/recvData", {method: "POST", body: '{"type": "${command}", "data": ${data ? JSON.stringify(data) : '""'} }' }).then((res) => res.text()).then((res) => resolve(res)))`,
contextId: mainScriptCtx,
awaitPromise: true
});
return res;
}
let sendData;
let cdpId = 0;
let mainScriptCtx = null;
let isConnected = false;
//! MAIN CODE
const navbar = document.querySelector('div.navbar-nav');
if (!navbar) return;
// ? hook the getJSON jQuery function so we can intercept the business request, saves us making our own request after
const jqueryJSON = $.getJSON;
$.getJSON = (url, cb) => {
if (url === './businesses.json') {
const oldCb = cb;
cb = (data) => {
console.log('[tt-remote] biz fetched, stored data');
mainCode(data);
oldCb(data);
};
}
return jqueryJSON(url, cb);
};
const navbarInner = document.createElement('a');
navbarInner.className = 'nav-link';
navbar.appendChild(navbarInner);
const nuiState = await request('http://localhost:13172/json/list');
if (!nuiState) {
navbarInner.innerHTML = 'TT-Remote: No response';
return;
}
let wsUrl = nuiState.find((data) => data.title === 'CitizenFX root UI' && data.url === 'nui://game/ui/root.html').webSocketDebuggerUrl;
if (!wsUrl) {
navbarInner.innerHTML = 'TT-Remote: WS not found';
return;
}
async function onScriptCtxSet() {
navbarInner.innerHTML = 'TT-Remote: Connected';
isConnected = true;
}
const ws = new WebSocket(wsUrl);
await new Promise(resolve => ws.addEventListener('open', resolve, { once: true }));
sendData = (type, params) => {
const id = cdpId++;
ws.send(JSON.stringify({
id,
method: type,
params: params ?? {}
}));
return new Promise(resolve => {
ws.addEventListener('message', ({ data }) => {
const response = JSON.parse(data);
if (response?.id === id) {
ws.removeEventListener('message', arguments.callee);
resolve(response);
}
});
});
};
ws.addEventListener('message', ({ data: msgData }) => {
const response = JSON.parse(msgData);
if (response?.id) return;
if (response.method === 'Debugger.scriptParsed') {
if (response.params.url === 'nui://remote_connector/nui/main.js') {
mainScriptCtx = response.params.executionContextId;
onScriptCtxSet();
}
}
});
await sendData('Runtime.enable');
await sendData('Debugger.enable');
async function mainCode(businesses) {
// ? wait for the business timer to be "in the range" of what RC wants, that way we know exactly when the table is being populated
await new Promise(resolve => {
let timer = setInterval(() => {
if (localStorage.getItem('biz-time') && parseInt(localStorage.getItem('biz-time')) > Date.now() - (1 * 60000)) {
clearInterval(timer);
resolve();
}
}, 200);
});
await new Promise(resolve => setTimeout(resolve, 100));
const table = $('#business-table').DataTable();
table.rows().every(function (rowIdx, tableLoop, rowLoop) {
const data = this.data();
const bizId = /href=".*biz=(.*?)"/g.exec(data[8])[1];
const bizData = businesses.find((biz) => biz.id === bizId);
data[8] += `<button type="button" class="btn btn-primary" id="waypointButton" xPos="${bizData.position.x}" yPos="${bizData.position.y}" >Waypoint</button>`;
this.data(data);
});
document.addEventListener('click', async (e) => {
if (e.target.id !== 'waypointButton') return;
const xPos = e.target.getAttribute('xPos');
const yPos = e.target.getAttribute('yPos');
await runCommand('setWaypoint', { x: parseFloat(xPos), y: parseFloat(yPos) });
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment