automatically suggest players to your table on BGA.
// ==UserScript==
// @name bga-auto-suggest
// @namespace https://github.com/nqztv/bga-auto-suggest
// @version 0.3.1
// @description automatically suggest players to your table on BGA.
// @include *.boardgamearena.com/*
// @grant none
// ==/UserScript==
//
// on boardgamearena.com, players will often not join your table unless they are
// suggested to that table. bga also limits you to 1 suggestion every 5 seconds.
// so manually suggesting players to your table can be a tedious process.
// insectman from the perudo community wrote an proof of concept script to
// automate this process. @nqztv rewrote his script to work as a user script on
// greasemonkey or tampermonkey while taking away dependencies and stuff.
// @naXa777 modified this script so it works endlessly until the user interrupts
// it by pressing STOP button.
//
// you can adjust the minimum Elo, add people to the white list, or add people
// to a blacklist below if you wish to.
// VARIABLES TO SET USER PREFERENCE
var minimumElo = 100;
var blackList = [];
var whiteList = ["nqztv", "insectman", "pnight"];
var autoStartGame = false; // start game when enough players?
// INTERNAL VARIABLES
var alreadySuggested = [];
var suggestPlayers = true;
var interrupted = false;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function init() {
console.log("Creating auto suggest button.");
await sleep(2000); // delay; the script is loaded faster then the BGA UI sometimes
var bgaButtonBarNode = document.querySelector(".bgabuttonbar");
if (!bgaButtonBarNode) {
console.error("Please reload the page.");
return;
}
var span = document.createElement("span");
span.innerHTML = "Auto Suggest";
var autoSuggestButton = document.createElement("div");
autoSuggestButton.append(span);
autoSuggestButton.setAttribute("id", "autosuggest");
autoSuggestButton.className = "bgabutton bgabutton_gray tableaction";
bgaButtonBarNode.appendChild(autoSuggestButton);
var span = document.createElement("span");
span.innerHTML = "Stop Auto Suggest";
var stopSuggestButton = document.createElement("div");
stopSuggestButton.append(span);
stopSuggestButton.setAttribute("id", "stopsuggest");
stopSuggestButton.setAttribute("style", "display: none;");
stopSuggestButton.className = "bgabutton bgabutton_gray tableaction";
bgaButtonBarNode.appendChild(stopSuggestButton);
document.getElementById("autosuggest").addEventListener("click", loop, false);
document.getElementById("stopsuggest").addEventListener("click", stopLoop, false);
}
async function stopLoop() {
suggestPlayers = false;
interrupted = true;
// hide Stop Auto Suggest button, show Auto Suggest button
var autoSuggestButton = document.getElementById("autosuggest");
if (autoSuggestButton)
autoSuggestButton.setAttribute("style", "display: inline;");
var stopSuggestButton = document.getElementById("stopsuggest");
if (stopSuggestButton)
stopSuggestButton.setAttribute("style", "display: none;");
}
async function loop() {
console.log("Beginning auto suggestions...");
if (autoStartGame) {
observeStartButton();
}
var availablePlayersNode = document.querySelector("#available_players_for_game");
var availablePlayers;
var tableID = window.location.href.split("table=")[1];
var index = 0;
var playerAttributes = [];
var playerName = "";
/**
* Caption: Suggestion sent!
*/
var playerStatus,
/**
* Button: Suggest joining table
*/
playerSuggestButton;
/**
* Player ELO
* @type {number}
*/
var playerElo = 0;
/**
* Player ID.
* Technically a number, but just using it to concatenate to a url
* @type {string}
*/
var playerID = "";
// only proceed if there is an available players node
if (availablePlayersNode) {
availablePlayers = availablePlayersNode.childNodes;
} else {
console.log("availablePlayersNode not found.");
return;
}
// show Stop Auto Suggest button, hide Auto Suggest button
var autoSuggestButton = document.getElementById("autosuggest");
if (autoSuggestButton)
autoSuggestButton.setAttribute("style", "display: none;");
var stopSuggestButton = document.getElementById("stopsuggest");
if (stopSuggestButton)
stopSuggestButton.setAttribute("style", "display: inline;");
suggestPlayers = true;
while (suggestPlayers) {
console.log("current index is " + index + " out of " + availablePlayers.length);
if (index >= availablePlayers.length) {
index = 0;
suggestPlayers = false;
console.log("Finished. The next iteration is scheduled in 12 seconds.");
interrupted = false;
await sleep(12000);
if (!interrupted) {
loop();
} else {
console.log("Auto suggestions process is interrupted by user.");
}
return;
}
// OLD WAY TO GET PLAYER ATTRIBUTES... playerID and playerName do not match sometimes with this method.
//playerAttributes = availablePlayers[index].innerText.split("\n").map(attribute => attribute.trim());
//playerAttributes = availablePlayers[index].innerHTML;
//playerName = playerAttributes[0];
//playerElo = parseInt(playerAttributes[1], 10);
//playerID = availablePlayers[1].id.split("_")[1];
var parser = new DOMParser();
var playerHTML = parser.parseFromString(availablePlayers[index].innerHTML, "text/html");
// Example of what playerHTML could be:
// <!DOCTYPE html>
// <html>
// <head>
// <title></title>
// </head>
// <body>
// <div class="\"availableplayername\"">
// <a href="/"/#!player?id=34570376/"">saltyk9</a>
// </div>
// <div class="\"availableplayerrank\"">
// <div class="\"gamerank">
// <span class="\"icon20"></span> <span class="\"gamerank_value\"">191</span>
// </div>
// <div class="\"imgtext" style="\"display:none\""></div> <i class="\"fa" id="\"sticky_avail_34570376\"" style="\"display:none\""></i>
// </div>
// <div class="\"availableplayertable\"" id="\"availableplayertable_34570376\"" style="\"display:">
// Seasons n°35897569
// </div>
// <div class="\"availableplayersuggest\"">
// <a class="\"bgabutton" href="/"#/"" id="\"suggestjoin_34570376\""><i class="\"fa"></i> Suggest to join table</a><span class="\"suggestsent\"" id="\"suggestsent_34570376\""><i class="\"fa"></i> Suggestion sent!</span>
// </div>
// </body>
// </html>
playerID = playerHTML.getElementsByTagName("a")[0].outerHTML.split("id=")[1].split('\">')[0];
playerName = playerHTML.getElementsByTagName("a")[0].innerText;
playerElo = playerHTML.querySelector(".gamerank_value").innerText;
playerStatus = document.getElementById("suggestsent_" + playerID);
playerSuggestButton = document.getElementById("suggestjoin_" + playerID);
// skip player if on the blacklist.
if (blackList.indexOf(playerName) != -1) {
console.log(playerName + " is skipped because player is on the blacklist.");
index++;
continue;
}
// skip player if the player has already been suggested to play on this table.
if (alreadySuggested.indexOf(playerName) != -1) {
console.log(playerName + " is skipped because player has already been suggested.");
console.log(alreadySuggested);
index++;
continue;
}
// skip player if the player has already been suggested (probably manually prior to running this script) to play on this table.
if (!isVisible(playerSuggestButton)) {
console.log(playerName + " is skipped because player has already been suggested.");
alreadySuggested.push(playerName);
++index;
continue;
}
// skip if player does not meet minimum Elo.
if (playerElo < minimumElo) {
console.log(playerName + " is skipped because player's Elo (" + playerElo + ") is less than " + minimumElo + ".");
index++;
continue;
}
console.log("Suggestion is being sent to " + playerName + " (" + playerElo + "): " + playerID + ".");
alreadySuggested.push(playerName);
suggestPlayer(tableID, playerID);
console.log("Waiting 5 seconds.");
await sleep(6000); // actually waiting 6 seconds because bga is still giving 5 second warning when set to 5001.
}
}
function suggestPlayer(tableID, playerID) {
var fetchRequest = new Request("https://en.boardgamearena.com/gamelobby/gamelobby/suggest.html?table=" + tableID + "&player=" + playerID);
var fetchInit = { method: 'GET', mode: 'cors', credentials: "include" }; // make sure to send credentials or you will get "you are not the admin" warnings.
fetch(fetchRequest, fetchInit)
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
if (data.status == "0") {
console.log("BGA Error: " + data.error);
}
})
.catch(function(err) {
console.log("Fetch Error: " + err);
});
// Example Responses:
// {"status":1,"data":"ok"}
// {"status":"0","error":"You've already sent a suggestion to that player.","expected":1,"code":100}
// {"status":"0","error":"You need to wait 5 seconds between two suggestions.","expected":1,"code":100}
}
function isVisible(elem) {
return elem && (elem.offsetWidth !== 0 || elem.offsetHeight !== 0);
}
async function observeStartButton() {
var startGameButton = document.getElementById("startgame");
if (!startGameButton)
return;
var enoughPlayers = false;
while (!enoughPlayers) {
await sleep(6000);
enoughPlayers = isVisible(startGameButton);
}
if (!interrupted) {
console.log("Automatically starting game.");
startGameButton.click();
}
}
window.onload = function() {
// tampermonkey doesn't support '#' in the url (see https://tampermonkey.net/documentation.php#_include), so check if on a table here before continuing.
if (window.location.href.includes("#!table?table=")) {
init();
}
};