// ==UserScript==
// @name RichDarts - Remote input connection hub for Autodarts
// @namespace https://greatest.deepsurf.us/en/users/913506-dotty-dev
// @match *://lidarts.org/game/*
// @match *://play.autodarts.io/*
// @match *://login.autodarts.io/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addValueChangeListener
// @grant GM_removeValueChangeListener
// @grant GM_openInTab
// @grant GM_getTab
// @grant GM_saveTab
// @grant GM_notification
// @version 2.0
// @author dotty-dev
// @license MIT
// @description Connect a lidarts.org game with autodarts.io to automatically enter thrown scores into lidarts. This is a proof of concept and may break or don't function as expected, if you need to correct scores you will have to use autodarts to create the correct score. Manual score input is not possible while the script is running. Lidarts example can be used to implement other sites.
// ==/UserScript==
/*jshint esversion: 11 */
/*jshint laxbreak:true */
let websocketListenerActive = false;
function listen(fn) {
fn = fn || console.log;
let property = Object.getOwnPropertyDescriptor(
MessageEvent.prototype,
"data"
);
const data = property.get;
// wrapper that replaces getter
function lookAtMessage() {
let socket = this.currentTarget instanceof WebSocket;
if (!socket) {
return data.call(this);
}
let msg = data.call(this);
Object.defineProperty(this, "data", { value: msg }); //anti-loop
fn({ data: msg, socket: this.currentTarget, event: this });
return msg;
}
property.get = lookAtMessage;
Object.defineProperty(MessageEvent.prototype, "data", property);
}
function tryParseJSONObject(jsonString) {
try {
var o = JSON.parse(jsonString);
// Handle non-exception-throwing cases:
// Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
// but... JSON.parse(null) returns null, and typeof null === "object",
// so we must check for that, too. Thankfully, null is falsey, so this suffices:
if (o && typeof o === "object") {
return o;
}
} catch (e) {}
return jsonString;
}
function injectWebsocketListener() {
if (websocketListenerActive === false) {
websocketListenerActive = true;
let observer = new MutationObserver(function () {
if (document.head) {
observer.disconnect();
listen(({ data }) => {
const adObj = tryParseJSONObject(data);
console.log(adObj);
const score = adObj?.data?.turnScore;
if (typeof score === "number") {
GM_setValue("adScore", score);
console.log("score: " + score);
}
});
}
});
observer.observe(document, { subtree: true, childList: true });
}
}
(async function () {
const site = location.host;
let lidartLegs = 0;
let gameSettingsAutodarts = {
baseScore: 501,
inMode: "Straight",
outMode: "Double",
maxRounds: 80,
bullMode: "25/50",
bullOff: "Off",
matchMode: "Off",
lobby: "Private",
};
let connectionEstablished = false;
GM_deleteValue("autodartsHeartbeat");
GM_setValue("startNewGame", false);
GM_setValue("abortGame", false);
async function lidartsInit() {
console.info("RichDarts: we're on lidarts");
const callerMuter = setInterval(() => {
let muteLink = document.getElementById("mute");
muteLink.click();
if (muteLink.style.display == "none") {
clearInterval(callerMuter);
}
}, 500);
const gameUrl = location.href;
const apiUrl = gameUrl.replace("/game/", "/api/game/");
let gameData = await fetch(apiUrl).then((resp) => resp.json());
gameSettingsAutodarts.baseScore = gameData.type;
gameSettingsAutodarts.inMode =
gameData.in_mode === "si"
? "Straight"
: gameData.in_mode === "di"
? "Double"
: "Master";
gameSettingsAutodarts.outMode =
gameData.out_mode === "so"
? "Straight"
: gameData.out_mode === "do"
? "Double"
: "Master";
GM_setValue("lobbySettings", JSON.stringify(gameSettingsAutodarts));
const bullNotificationElement = document.querySelector(
"#closest_to_bull_notification_div"
);
const waitForBullOff = setInterval(() => {
if (
!bullNotificationElement ||
bullNotificationElement.style.display == "none"
) {
insertRichDartsButton();
clearInterval(waitForBullOff);
}
}, 500);
}
function startLidartsLoop(event) {
event.preventDefault();
console.warn(
`autodartsHeartbeat: ${GM_getValue("autodartsHeartbeat")}`
);
if (connectionEstablished) {
GM_notification("RichDarts is already connected");
return;
} else {
GM_setValue("isConnectRichDarts", true);
}
if (
parseInt(document.querySelector(".p1_score").textContent) !=
gameSettingsAutodarts.baseScore &&
parseInt(document.querySelector(".p2_score").textContent) !=
gameSettingsAutodarts.baseScore
) {
GM_notification(
"Score's don't match base score, please make sure to correct autodarts score!"
);
}
const scoreChangeListener = GM_addValueChangeListener(
"adScore",
(key, oldValue, newValue, remote) => {
const adScore = newValue;
console.log(
`Received ${key}: ${newValue}, old value: ${oldValue}, changed by remote: ${remote}`
);
document.querySelector("#score_value").value = adScore;
}
);
GM_notification("Starting Autodarts Match, please wait");
document.querySelector("#score_value").focus();
openAutodartsTab();
const richDartsButtons = document.querySelectorAll(".richdarts-connect");
const adHeartbeatCheck = setInterval(() => {
let heartbeat = GM_getValue("autodartsHeartbeat");
if (Date.now() - heartbeat < 20000) {
if (connectionEstablished === false) {
connectionEstablished = true;
richDartsButtons.forEach((el) => {
let crosshairsClassList =
el.querySelector(".fa-crosshairs").classList;
crosshairsClassList.add("text-success");
crosshairsClassList.remove("text-warning");
el.querySelector(".richdarts-status").innerText =
"RichDarts Connected";
});
}
} else if (
Date.now() - heartbeat > 20000 ||
!!GM_getValue("autodartsHeartbeat")
) {
console.info("RichDarts: Autodarts connection lost. Stopping loop.");
connectionEstablished = false;
clearInterval(adHeartbeatCheck);
richDartsButtons.forEach((el) => {
el.disabled = false;
let crosshairsClassList =
el.querySelector(".fa-crosshairs").classList;
crosshairsClassList.remove("text-success");
crosshairsClassList.add("text-danger");
el.querySelector(".richdarts-status").innerText =
"RichDarts disconnected";
});
} else {
richDartsButtons.forEach((el) => {
el.disabled = true;
let crosshairsClassList =
el.querySelector(".fa-crosshairs").classList;
crosshairsClassList.add("text-warning");
crosshairsClassList.remove("text-danger");
el.querySelector(".richdarts-status").innerText =
"RichDarts Connecting...";
});
}
let gameCompleted =
document.querySelector("#confirm_completion").style.display != "none";
let gameAborted =
document.querySelector("#game-aborted").style.display != "none";
if (gameCompleted || gameAborted) {
GM_setValue("abortGame", true);
GM_removeValueChangeListener(scoreChangeListener);
console.log("aborting autodarts game");
}
let currentLegsPlayed =
parseInt(document.querySelector(".p1_legs").textContent) +
parseInt(document.querySelector(".p2_legs").textContent);
if (currentLegsPlayed > lidartLegs && !(gameCompleted || gameAborted)) {
lidartLegs = currentLegsPlayed;
console.info("RichDarts: starting new Autodarts round");
GM_setValue("startNewRound", true);
}
}, 2500);
}
function insertRichDartsButton() {
const richDartsButtonDummy = document.createElement("div");
richDartsButtonDummy.innerHTML = /*html*/ `
<button class="btn btn-primary btn-sm richdarts-connect btn-block mb-3 px-0">
<span class="fa-stack fa-lg">
<i class="fas fa-square fa-stack-2x"></i>>
<i class="fas fa-crosshairs fa-stack-1x text-danger"></i>
</span>
<span class="richdarts-status">
Connect RichDarts
</span>
</button>
`;
document
.querySelectorAll(".score_input input.score_value")
.forEach((el) => {
el.parentElement.insertAdjacentElement(
"afterend",
richDartsButtonDummy.querySelector("button").cloneNode(1)
);
});
document.querySelectorAll(".richdarts-connect").forEach((el) => {
el.addEventListener("click", startLidartsLoop);
});
}
function autodartsApplySettings(buttons) {
let settingsClicker = setInterval(() => {
console.info("RichDarts: trying to apply settings");
setTimeout(() => {
buttons?.baseScore.click();
}, 0);
setTimeout(() => {
buttons?.inMode.click();
}, 50);
setTimeout(() => {
buttons?.outMode.click();
}, 100);
setTimeout(() => {
buttons?.maxRounds.click();
}, 150);
setTimeout(() => {
buttons?.bullMode.click();
}, 200);
setTimeout(() => {
buttons?.bullOff.click();
}, 250);
setTimeout(() => {
buttons?.matchMode.click();
}, 300);
setTimeout(() => {
buttons?.lobby.click();
}, 350);
if (Object.values(buttons).every((el) => el?.dataset.active === "")) {
console.info("RichDarts: all settings applied, stopping loop");
clearInterval(settingsClicker);
buttons.openButton.click();
let gameStarter = setInterval(() => {
console.info("RichDarts: looking for game start button");
document.querySelectorAll("button").forEach((el) => {
if (el.innerText === "Start game") {
if (!el.disabled) {
el.click();
clearInterval(gameStarter);
const gameLoadedCheck = setInterval(() => {
if (
document.getElementById("ad-ext-game-variant")?.innerText ==
"X01"
) {
clearInterval(gameLoadedCheck);
autodartsSendScores();
}
}, 250);
}
}
});
}, 250);
}
}, 250);
}
function autodartsSendScores() {
let abortButton;
document
.querySelector("#ad-ext-game-variant")
.closest(".css-0")
.querySelectorAll("button")
.forEach((el) => {
if (el.innerText === "Abort") {
abortButton = el;
}
});
let newGame = false;
GM_deleteValue("isConnectRichDarts");
console.info("RichDarts: init sending scores...");
console.info(
`autodartsHeartbeat is ${!!GM_getValue("autodartsHeartbeat")}`
);
injectWebsocketListener();
const sendHeartbeat = setInterval(() => {
GM_setValue("autodartsHeartbeat", Date.now());
}, 2500);
const startNewRoundListener = GM_addValueChangeListener(
"startNewRound",
(key, oldValue, newValue, remote) => {
if (newValue === true) {
console.log("starting new round dude");
GM_setValue("startNewRound", false);
GM_setValue("isConnectRichDarts", true);
clearInterval(sendHeartbeat);
newGame = true;
let finishButton;
document
.querySelector("#ad-ext-turn + div")
.querySelectorAll("button")
.forEach((el) => {
if (el.innerText === "Finish") {
finishButton = el;
}
});
if (finishButton) {
finishButton.click();
} else {
let i = 0;
const abortGame = setInterval(() => {
abortButton.click();
i++;
if (i === 3) {
clearInterval(abortGame);
}
}, 500);
}
restartOrClose();
}
}
);
const abortGameListener = GM_addValueChangeListener(
"abortGame",
(key, oldValue, newValue, remote) => {
console.log(`${key}: ${oldValue} => ${newValue}`);
if (newValue === true) {
console.log("aborting game dude");
GM_setValue("abortGame",false);
clearInterval(sendHeartbeat);
let finishButton;
document
.querySelector("#ad-ext-turn + div")
.querySelectorAll("button")
.forEach((el) => {
if (el.innerText === "Finish") {
finishButton = el;
}
});
if (finishButton) {
finishButton.click();
restartOrClose();
} else {
let i = 0;
const abortGame = setInterval(() => {
abortButton.click();
i++;
if (i === 3) {
clearInterval(abortGame);
restartOrClose();
}
}, 500);
}
}
}
);
function restartOrClose() {
clearInterval(sendHeartbeat);
console.log("we're here my dude");
GM_removeValueChangeListener(abortGameListener);
GM_removeValueChangeListener(startNewRoundListener);
if (newGame) {
console.info("RichDarts: Started new round");
// document.querySelector('a').click();
setTimeout(() => autodartsInit(), 2000);
} else {
GM_notification(
"Autodarts tabs that were opened by RichDarts will be closed"
);
setTimeout(() => {
window.close();
}, 2000);
}
}
}
async function autodartsInit() {
GM_setValue("abortGame",false);
GM_setValue("startNewRound",false);
const isConnect = GM_getValue("isConnectRichDarts");
if (isConnect) {
gameSettingsAutodarts = JSON.parse(GM_getValue("lobbySettings"));
let loopInterval = null;
console.info("RichDarts: we're on autodarts");
const autodartsNewLobbyButtons = {
baseScore: undefined,
inMode: undefined,
outMode: undefined,
maxRounds: undefined,
bullMode: undefined,
bullOff: undefined,
matchMode: undefined,
lobby: undefined,
openButton: undefined,
};
const x01clicker = () => {
console.info("RichDarts: x01Clicker interval running");
const x01Button = document.querySelector('[href="/lobbies/new/x01"]');
if (x01Button) {
x01Button.click();
clearInterval(loopInterval);
}
if (location.href === "https://play.autodarts.io/lobbies/new/x01") {
clearInterval(loopInterval);
}
};
setTimeout(() => {
if (location.href === "https://play.autodarts.io/") {
loopInterval = setInterval(x01clicker, 50);
}
},500);
let settingsElsGetter = setInterval(() => {
if (
!Object.values(autodartsNewLobbyButtons).some(
(val) => val === undefined
)
) {
clearInterval(settingsElsGetter);
console.info("RichDarts: all Settings loaded");
console.info(gameSettingsAutodarts);
console.info(autodartsNewLobbyButtons);
autodartsApplySettings(autodartsNewLobbyButtons);
} else {
console.info("RichDarts: runningElementGetter");
document.querySelectorAll(".chakra-text").forEach((el) => {
switch (el.innerText) {
case "Base score":
el.nextSibling.querySelectorAll("button").forEach((el) => {
if (el.innerText == gameSettingsAutodarts.baseScore) {
autodartsNewLobbyButtons.baseScore = el;
}
});
break;
case "In mode":
el.nextSibling.querySelectorAll("button").forEach((el) => {
if (el.innerText == gameSettingsAutodarts.inMode) {
autodartsNewLobbyButtons.inMode = el;
}
});
break;
case "Out mode":
el.nextSibling.querySelectorAll("button").forEach((el) => {
if (el.innerText == gameSettingsAutodarts.outMode) {
autodartsNewLobbyButtons.outMode = el;
}
});
break;
case "Max rounds":
el.nextSibling.querySelectorAll("button").forEach((el) => {
if (el.innerText == gameSettingsAutodarts.maxRounds) {
autodartsNewLobbyButtons.maxRounds = el;
}
});
break;
case "Bull mode":
el.nextSibling.querySelectorAll("button").forEach((el) => {
if (el.innerText == gameSettingsAutodarts.bullMode) {
autodartsNewLobbyButtons.bullMode = el;
}
});
break;
case "Bull-off":
el.nextSibling.querySelectorAll("button").forEach((el) => {
if (el.innerText == gameSettingsAutodarts.bullOff) {
autodartsNewLobbyButtons.bullOff = el;
}
});
break;
case "Match mode":
el.nextSibling.querySelectorAll("button").forEach((el) => {
if (el.innerText == gameSettingsAutodarts.matchMode) {
autodartsNewLobbyButtons.matchMode = el;
}
});
break;
case "Lobby":
el.nextSibling.querySelectorAll("button").forEach((el) => {
if (el.innerText == gameSettingsAutodarts.lobby) {
autodartsNewLobbyButtons.lobby = el;
}
});
break;
default:
break;
}
});
document.querySelectorAll("button.chakra-button").forEach((el) => {
if (el.innerText === "Open Lobby") {
autodartsNewLobbyButtons.openButton = el;
el.dataset.active = "";
}
});
}
}, 200);
}
}
function openAutodartsTab() {
console.info("RichDarts: trying to open a new tab");
GM_openInTab("https://play.autodarts.io", true);
}
switch (site) {
case "play.autodarts.io":
autodartsInit();
break;
case "login.autodarts.io":
alert(
"You need to log in to Autodarts for RichDarts to connect your games."
);
break;
case "lidarts.org":
if (location.pathname !== "/game/create") {
lidartsInit();
}
break;
}
})();