bga-auto-suggest

automatically suggest players to your table on BGA.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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();
	}
};