bga-auto-suggest

automatically suggest players to your table on BGA.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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="\&quot;availableplayername\&quot;">
		//       <a href="/&quot;/#!player?id=34570376/&quot;">saltyk9</a>
		//     </div>
		//     <div class="\&quot;availableplayerrank\&quot;">
		//     <div class="\&quot;gamerank">
		//       <span class="\&quot;icon20"></span> <span class="\&quot;gamerank_value\&quot;">191</span>
		//     </div>&nbsp;
		//     <div class="\&quot;imgtext" style="\&quot;display:none\&quot;"></div>&nbsp;<i class="\&quot;fa" id="\&quot;sticky_avail_34570376\&quot;" style="\&quot;display:none\&quot;"></i>
		//     </div>
		//     <div class="\&quot;availableplayertable\&quot;" id="\&quot;availableplayertable_34570376\&quot;" style="\&quot;display:">
		//       Seasons n°35897569
		//     </div>
		//     <div class="\&quot;availableplayersuggest\&quot;">
		//       <a class="\&quot;bgabutton" href="/&quot;#/&quot;" id="\&quot;suggestjoin_34570376\&quot;"><i class="\&quot;fa"></i>&nbsp;&nbsp;Suggest to join table</a><span class="\&quot;suggestsent\&quot;" id="\&quot;suggestsent_34570376\&quot;"><i class="\&quot;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();
	}
};