RichDarts - Remote input connection hub for Autodarts

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.

Ekde 2024/09/15. Vidu La ĝisdata versio.

// ==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_openInTab
// @grant       GM_getTab
// @grant       GM_saveTab
// @grant       GM_notification
// @version     1.2
// @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 */

(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("autodartsLastLifesign");
  GM_deleteValue("startNewGame");
  GM_deleteValue("abortGame");

  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(
      `autodartsLastLifesign: ${GM_getValue("autodartsLastLifesign")}`
    );
    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!"
      );
    }

    GM_notification("Starting Autodarts Match, please wait");
    document.querySelector("#score_value").focus();
    openAutodartsTab();
    const richDartsButtons = document.querySelectorAll(".richdarts-connect");
    const scoreLoop = setInterval(() => {
      let lastLifeSign = GM_getValue("autodartsLastLifesign");
      if (Date.now() - lastLifeSign < 20000) {
        document.querySelector("#score_value").value = GM_getValue(
          "autodartsThrownScore"
        );
        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() - lastLifeSign > 20000 ||
        !!GM_getValue("autodartsLastLifesign")
      ) {
        console.info("RichDarts: Autodarts connection lost. Stopping loop.");
        connectionEstablished = false;
        clearInterval(scoreLoop);
        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);
      }

      let currentLegsPlayed =
        parseInt(document.querySelector(".p1_legs").textContent) +
        parseInt(document.querySelector(".p2_legs").textContent);
      if (currentLegsPlayed > lidartLegs && !(gameCompleted || gameAborted)) {
        lidartLegs = currentLegsPlayed;
        console.info("RhichDarts: starting new Autodarts round");
        GM_setValue("startNewRound", true);
      }
    }, 1000);
  }

  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();
      }, 250);
      setTimeout(() => {
        buttons?.outMode.click();
      }, 500);
      setTimeout(() => {
        buttons?.maxRounds.click();
      }, 750);
      setTimeout(() => {
        buttons?.bullMode.click();
      }, 1000);
      setTimeout(() => {
        buttons?.bullOff.click();
      }, 1250);
      setTimeout(() => {
        buttons?.matchMode.click();
      }, 1500);
      setTimeout(() => {
        buttons?.lobby.click();
      }, 1750);

      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();
                  }
                }, 500);
              }
            }
          });
        }, 500);
      }
    }, 500);
  }

  function autodartsSendScores() {
    const abortButton = document
      .querySelector("#ad-ext-game-variant")
      .closest(".css-0")
      .querySelector("div.chakra-stack>.chakra-stack>button");
    let newGame = false;
    GM_deleteValue("isConnectRichDarts");
    console.info("RichDarts: init sending scores...");
    console.info(
      `autodartsLastLifesign is ${!!GM_getValue("autodartsLastLifesign")}`
    );
    const sendScoreAndLifesign = setInterval(() => {
      GM_setValue("autodartsLastLifesign", Date.now());
      let turnPointsElement = document.querySelector(".ad-ext-turn-points");
      if (turnPointsElement) {
        let points =
          turnPointsElement.innerText == "BUST"
            ? 0
            : turnPointsElement.innerText;
        GM_setValue("autodartsThrownScore", points);
      } else {
        GM_deleteValue("autodartsLastLifesign");
        GM_deleteValue("autodartsThrownScore");
        clearInterval(sendScoreAndLifesign);
        return;
      }

      if (GM_getValue("startNewRound")) {
        GM_deleteValue("startNewRound");
        GM_setValue("isConnectRichDarts", true);
        clearInterval(sendScoreAndLifesign);
        newGame = true;
        abortButton.click();
      }

      if (GM_getValue("abortGame")) {
        GM_deleteValue("abortGame");
        clearInterval(sendScoreAndLifesign);
        abortButton.click();
      }
    }, 500);

    abortButton.addEventListener("click", () => {
      clearInterval(sendScoreAndLifesign);
      if (newGame) {
        console.info("RichDarts: Started new round");
        setTimeout(() => autodartsInit(), 2000);
      } else {
        GM_notification(
          "Autodarts tabs that were opened by RichDarts will be closed"
        );
        setTimeout(() => {
          window.close();
        }, 2000);
      }
    });
  }

  async function autodartsInit() {
    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);
        }
      };
      if (location.href === "https://play.autodarts.io/") {
        loopInterval = setInterval(x01clicker, 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 = "";
            }
          });
        }
      }, 2000);
    }
  }

  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;
  }
})();