NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Viagra for Anarchy Blood Bowl League // @namespace http://www.anarchy.bloodbowlleague.net/ // @version 0.21 // @description Convert onclick to anchor for bloodbowlleague.net // @license MIT // @copyright 2024, ketilkn (https://openuserjs.org/users/ketilkn) // @author Ketil Nordstad // @match http://*.bloodbowlleague.net/* // @match https://*.bloodbowlleague.net/* // @match http://www.*.bloodbowlleague.net/* // @match https://www.*.bloodbowlleague.net/* // @match https://www.*.bbleague.net/* // @match https://*.bbleague.net/* // @match http://www.*.bbleague.net/* // @match http://*.bbleague.net/* // @grant none // @updateURL https://openuserjs.org/meta/ketilkn/Viagra_for_Anarchy_Blood_Bowl_League.meta.js // @downloadURL https://openuserjs.org/src/scripts/ketilkn/Viagra_for_Anarchy_Blood_Bowl_League.user.js // ==/UserScript== // 0.1: Initial version. Replace onclick player, match with anchor. Remove timeout and add keep alive // 0.2: Replace tournament table onclick with anchor. Team name link to team. Team value link to roster. // 0.3: Replace menu onclick with a href // 0.4: Support all leagues at bloodbowlleague.com // 0.5: Added tooltip to league standings. Click on team value to open roster directly. // 0.6: Auto update test // 0.7: Searchable bounty selector, support for arosbb.dk. // 0.8: Improved bounty selector. Support for arrowkeys. Fixed empty search text bug. // 0.9: Added search to new match // 0.10: Added link to quickly go to league matches (and new match for semi pro) // 0.11: Added bloodbowlleauge.net // 0.12: Added https://www.anarchy.bloodbowlleauge.net // 0.13: Added Sums row to team roster // 0.14: Improved skill count. Added average for player characteristics // 0.15: Changed URL matchers to use bloodbowlleague.net // 0.16: Add link to show SPP details // 0.17: Add back url matchers working with http // 0.18: Add back url matchers working with http again // 0.19: Always show player statistics in the team roster // 0.20: Added MNG row and made rows selectable in the team roster // 0.21: Fix bug in team roster select all/none toggle (function() { const SELECTED_PLAYER_COLOR = "lightblue"; 'use strict'; //From: https://gist.github.com/niyazpk/f8ac616f181f6042d1e0 // Add / Update a key-value pair in the URL query parameters function updateUrlParameter(uri, key, value) { // remove the hash part before operating on the uri var i = uri.indexOf('#'); var hash = i === -1 ? '' : uri.substr(i); uri = i === -1 ? uri : uri.substr(0, i); var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i"); var separator = uri.indexOf('?') !== -1 ? "&" : "?"; if (uri.match(re)) { uri = uri.replace(re, '$1' + key + "=" + value + '$2'); } else { uri = uri + separator + key + "=" + value; } return uri + hash; // finally append the hash as well } function getStyle(el, styleProp) { var value, defaultView = (el.ownerDocument || document).defaultView; // W3C standard way: if (defaultView && defaultView.getComputedStyle) { // sanitize property name to css notation // (hypen separated words eg. font-Size) styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase(); return defaultView.getComputedStyle(el, null).getPropertyValue(styleProp); } else if (el.currentStyle) { // IE // sanitize property name to camelCase styleProp = styleProp.replace(/\-(\w)/g, function(str, letter) { return letter.toUpperCase(); }); value = el.currentStyle[styleProp]; // convert other units to pixels on IE if (/^\d+(em|pt|%|ex)?$/i.test(value)) { return (function(value) { var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left; el.runtimeStyle.left = el.currentStyle.left; el.style.left = value || 0; value = el.style.pixelLeft + "px"; el.style.left = oldLeft; el.runtimeStyle.left = oldRsLeft; return value; })(value); } return value; } } function hasClass( target, className ) { return new RegExp('(\\s|^)' + className + '(\\s|$)').test(target.className); } var heartbeat = function (){ var xmlHttp = new XMLHttpRequest(); xmlHttp.open( "GET", "http://www.anarchy.bloodbowlleague.com/default.asp?p=adm", false ); xmlHttp.send( null ); }; var wrapAnchor = function(el, link, title) { var tooltip = ""; if( title && title.length > 0 ) { tooltip = title; } var text = el.innerHTML; el.innerHTML = "<a class='player-link' style='text-decoration: none; cursor: pointer;' href='"+link+"' title='"+tooltip+"'>"+text+"</a>"; return el; }; var extractLink = function(el) { if (! el ) { return "#feilExtractLink"; } return el.onclick.toString().match(/\'.*.*\'/g).toString().slice(1,-1); }; var isPlayerTemporaryRetired = function(el) { const columns = el.children; if (columns[11].querySelector('img') && columns[11].querySelector('img').src.endsWith("retire.png")) { return true; } return false; }; var isPlayerMng = function(el) { const columns = el.children; return el.children[9].innerText == "M"; // && !isPlayerTemporaryRetired(el); }; var isPlayer = function(row) { return row.classList.containes("trlist"); }; var getPlayerName = function(row) { return row.children[2].children[1].childNodes[0].nodeValue; }; var getPlayerSkillsBasic = function(row) { const skillsNodes = row.children[8].childNodes[1].childNodes[0].nodeValue; if(skillsNodes) { return skillsNodes.split(',').map((s) => s.trim()).filter((v) => v); } return []; }; var getPlayerSkillsExtra = function(row) { return [...row.children[8].childNodes[1].children].map((s) => s.innerText.trim().split("(")[0]).filter((r) => r!="?"); }; var getPlayerSkillsImprovements = function(row) { if([...row.children[8].childNodes[1].children].map((s) => s.innerText.trim()).filter((r) => r=="?").length > 0) { return true; } return false; }; var getPlayerPosition = function(row) { return row.children[2].children[1].childNodes[row.children[2].children[1].childNodes.length-1].innerText; }; var getPlayerMa = function(row) { return parseInt(row.children[3].innerText); } var getPlayerSt = function(row) { return parseInt(row.children[4].innerText); } var getPlayerAg = function(row) { return parseInt(row.children[5].innerText); } var getPlayerPa = function(row) { let pa = parseInt(row.children[6].innerText); if(! pa ) { return 0; } return pa; } var getPlayerAv = function(row) { return parseInt(row.children[7].innerText); } var getPlayerNiggles = function(row) { var niggles = parseInt(row.children[10].innerText); if(niggles) { return niggles; } return 0; } var getPlayerSppTotal = function(row) { return parseInt(row.children[17].childNodes[1].childNodes[1].innerText.replace("(","").replace(")","").trim()) } var getPlayerSppUnspent = function(row) { return parseInt(row.children[17].childNodes[1].childNodes[0].nodeValue) } var parsePlayer = function(row) { const playerInfo = { number: parseInt(row.children[0].innerText), name: getPlayerName(row), url: row.querySelector('a').href, position: getPlayerPosition(row), ma: getPlayerMa(row), st: getPlayerSt(row), ag: getPlayerAg(row), pa: getPlayerPa(row), av: getPlayerAv(row), skills: getPlayerSkillsBasic(row) + getPlayerSkillsExtra(row), skillsBasic: getPlayerSkillsBasic(row), skillsExtra: getPlayerSkillsExtra(row), skillsImprovementAvailable: getPlayerSkillsImprovements(row), missNextGame: isPlayerMng(row), niggles: getPlayerNiggles(row), temporaryRetired: isPlayerTemporaryRetired(row), interceptions: ~~parseInt(row.children[12].textContent), completions: ~~parseInt(row.children[13].textContent), touchdowns: ~~parseInt(row.children[14].textContent), casualties: ~~parseInt(row.children[15].textContent), mvp: ~~parseInt(row.children[16].textContent), spp: getPlayerSppTotal(row), sppUnspent: getPlayerSppUnspent(row), currentValue: parseInt(row.querySelectorAll('td')[16].innerText), theRow: row }; return playerInfo; }; var countPlayerSkills = function(players) { var extraSkills = players.map(p => p.skillImprovements); var extraSkillCount = {}; players.forEach((player) => { player.skillsExtra.forEach((skill) => { if(! extraSkillCount[skill]) { extraSkillCount[skill] = 1; } else { extraSkillCount[skill] = extraSkillCount[skill] + 1; } }); }); return extraSkillCount; }; var processTdOnClick = function(td) { if(! td.hasAttribute("onclick")) { alert("I find your lack of onclick disturbing"); } var onclick = td.getAttribute("onclick").toString(); if(onclick.indexOf("gototeam") > -1 ) { if( (td.style.color=="rgb(32, 48, 64)" || td.style.color=="rgb(96, 96, 96)")) { var teamId = extractLink(td); var innerHtml = td.innerHTML; td.innerHTML = ""; var a = document.createElement("a"); td.appendChild(a); var tooltip ="open team info"; var whereTo = "tm"; if(hasClass(td,"td10")) { whereTo = "ro"; tooltip = "open roster"; } a.setAttribute("href", "/default.asp?p="+whereTo+"&t=" + teamId); if(tooltip.length > 0 ) { a.setAttribute("title", tooltip); } a.innerHTML = innerHtml; a.style.cursor="pointer"; td.appendChild(a); } td.removeAttribute("onclick"); td.style.cursor = "default"; } return ""; }; var processTrOnClick = function(el) { var link = extractLink(el); var td = el.querySelectorAll("td"); for(var j = 0; j < td.length; j++) { wrapAnchor(td[j], link); wrapAnchor(td[j], link); } if(td.length > 3 ) { wrapAnchor(td[2], link+"#2"); } }; var addLinkToParent = function(el, linkText) { var a = document.createElement("a"); a.innerText=linkText; a.href = el.href; el.parentNode.appendChild(a); return a; }; var processMenuTd = function(el) { //alert(el.getAttribute("onclick")); var link = extractLink(el); //alert(link); wrapAnchor(el, link); el.setAttribute("onclick", ""); var leagueLink = el.querySelector('a'); if(el.querySelector('a').href.indexOf('&s=') >= 0){ var matchLink = addLinkToParent(leagueLink, '[m]'); matchLink.href = updateUrlParameter(matchLink.href, 'p', 'ma'); matchLink.href = updateUrlParameter(matchLink.href, 'so', 's'); matchLink.title = 'Show matches'; if(leagueLink.innerText.indexOf('Semi Pro') == 0) { var newLink = addLinkToParent(leagueLink, '[+]'); newLink.href = updateUrlParameter(newLink.href, 'p', 'am'); newLink.title = 'Create matches'; } } }; // CONVERT onclicks to link var tr_onclicks = document.querySelectorAll("tr[onclick]"); for(var i = 0; i < tr_onclicks.length; i++) { processTrOnClick(tr_onclicks[i]); tr_onclicks[i].onclick=""; tr_onclicks[i].style.cursor="default"; } var td_onclicks = document.querySelectorAll("td[onclick]"); for(var q = 0; q < td_onclicks.length; q++) { processTdOnClick(td_onclicks[q]); } td_onclicks = document.querySelectorAll("td.menu"); for(q = 0; q < td_onclicks.length; q++) { var menuTd = td_onclicks[q]; if(menuTd.hasAttribute("onclick")) { processMenuTd(menuTd); } } var applyDropdownFilter = function (element) { element.dropdown.innerHTML = ""; for(var j = 0; j < element.options.length; j++) { //console.log(this.options[j].name); if((element.value.length == 0) || element.options[j].name.search(new RegExp(element.value,"i")) >=0) { var foo = document.createElement("option"); foo.value = element.options[j].id; foo.innerHTML = element.options[j].name; element.dropdown.appendChild(foo); } } }; var updateDropdown = function(event, el) { var time = new Date().getTime(); console.log("Søk:"+this.value + ":" +this.dropdown.options.length); if(event.keyCode == 38 ) { //up var index = this.dropdown.selectedIndex; if(index > 0 ) { this.dropdown.selectedIndex = index -1; } }else if (event.keyCode == 40 ) { //down var idx = this.dropdown.selectedIndex; if(idx < this.dropdown.length -1 ) { this.dropdown.selectedIndex =idx +1; } }else { applyDropdownFilter(this); } }; var updateRosterSums = function (players, sums, sumAllPlayers) { let sumRow = document.querySelector("tr.totalSums"); if(sums) { sumRow = sums; } var selectedPlayers = players.filter((p)=>!p.missNextGame); if(sumAllPlayers) { selectedPlayers = players; } var activePlayerCount = selectedPlayers.length; var playerCount = players.length; var meanMovement = selectedPlayers.map((p)=>p.ma).reduce((a, b) => a + b, 0) / selectedPlayers.length; var meanStrength = selectedPlayers.map((p)=>p.st).reduce((a, b) => a + b, 0) / selectedPlayers.length; var meanAgility = selectedPlayers.map((p)=>p.ag).reduce((a, b) => a + b, 0) / selectedPlayers.length; var meanPassing = selectedPlayers.map((p)=>p.pa).reduce((a, b) => a + b, 0) / selectedPlayers.length; var meanAv = selectedPlayers.map((p)=>p.av).reduce((a, b) => a + b, 0) / selectedPlayers.length; var playerPrices = selectedPlayers.map(p=>p.currentValue); var playersPriceTotal = playerPrices.reduce((totalValue, playerValue) => { return totalValue + playerValue}, 0); var playersSpp = selectedPlayers.map(p=>p.spp).reduce((totalSpp, playerSpp) => { return totalSpp + playerSpp}, 0); var skillCount = selectedPlayers.map((p)=>p.skillsExtra.length).reduce((totalSkills, playerSkills) => { return totalSkills + playerSkills}, 0) + " extra skills"; var skillsImprovementCount = selectedPlayers.map(p=>p.skillsImprovementAvailable).reduce((totalImprovementsAvailable, playerImprovement) => { return totalImprovementsAvailable + playerImprovement}, 0); //skillCount = ""; //var counts = countPlayerSkills(selectedPlayers).map((s)=> s[0] + "(" + s[1] + ")"); //skillCount = counts.join(', '); var skills = Object.entries(countPlayerSkills(selectedPlayers)) skills.sort(function(a, b) { return b[1] - a[1]; }); var skillList = skills.map((s)=> s[0] + "(" + s[1] + ")").join(", "); if(skillsImprovementCount > 0) { skillList = skillList + " +available\u00a0("+ skillsImprovementCount + ")"; } var mngCount = selectedPlayers.filter((p)=>p.missNextGame).length; var niggleSum = selectedPlayers.map((p)=>p.niggles).reduce((totalNiggle, playerNiggle) => { return totalNiggle + playerNiggle}, 0); var tempRetireCount = selectedPlayers.map((p)=>p.temporaryRetired?1:0).reduce((totalTemp, playerTemp) => { return totalTemp + playerTemp}, 0); var interceptionsSum = selectedPlayers.map((p)=>p.interceptions).reduce((totalInterceptions, playerInterceptions) => { return totalInterceptions + playerInterceptions}, 0); var completionsSum = selectedPlayers.map((p)=>p.completions).reduce((totalCompletions, playerCompletions) => { return totalCompletions + playerCompletions}, 0); var touchdownsSum = selectedPlayers.map((p)=>p.touchdowns).reduce((totalTouchdowns, playerTouchdowns) => { return totalTouchdowns + playerTouchdowns}, 0); var casualtiesSum = selectedPlayers.map((p)=>p.casualties).reduce((totalCasualties, playerCasualties) => { return totalCasualties + playerCasualties}, 0); var mvpSum = selectedPlayers.map((p)=>p.mvp).reduce((totalMvp, playerMvp) => { return totalMvp + playerMvp}, 0); //var borderRow = document.querySelector('tr.trborder:nth-child(18)'); var borderRow = document.querySelector('table.tblist tr.trborder'); var sumLabel = selectedPlayers.length +" "+sumAllPlayers; if(!sumAllPlayers) { sumLabel = activePlayerCount + " of " + playerCount + " ready"; } //var sumRow = document.createElement("tr"); //sumRow.innerHTML = playerRowHtml; //sumRow.className = "trlist sums"; sumRow.children[0].align="center"; sumRow.children[1].innerText=""; sumRow.children[2].innerText= sumLabel; sumRow.children[2].align = "center"; sumRow.children[3].innerText = Math.round(meanMovement*100)/100; sumRow.children[4].innerText = Math.round(meanStrength*100)/100; sumRow.children[5].innerText = Math.round(meanAgility*100)/100; sumRow.children[6].innerText = Math.round(meanPassing*100)/100; sumRow.children[7].innerText = Math.round(meanAv*100)/100; //sumRow.children[4].innerText=sumRow.children[5].innerText=sumRow.children[6].innerText=sumRow.children[7].innerText=""; sumRow.children[8].textContent = skillCount; sumRow.children[8].id = 'SumOfSkills'; sumRow.children[8].onclick = function(){ if(sumRow.children[8].textContent == skillCount) { sumRow.children[8].textContent = skillList; } else { sumRow.children[8].textContent = skillCount; }}; sumRow.children[9].innerText = mngCount; sumRow.children[10].innerText = niggleSum; sumRow.children[11].innerText = tempRetireCount; sumRow.children[12].innerText = interceptionsSum; sumRow.children[13].innerText =completionsSum; sumRow.children[14].innerText =touchdownsSum; sumRow.children[15].textContent=casualtiesSum; sumRow.children[16].textContent=mvpSum; sumRow.children[17].innerText =playersSpp; sumRow.children[18].innerText = playersPriceTotal + " k "; borderRow.parentNode.insertBefore(sumRow, borderRow); }; var addRosterSums = function(players, sumAllPlayers) { var playerRowHtml = players[players.length-1].theRow.innerHTML; var sumRow = document.createElement("tr"); sumRow.innerHTML = playerRowHtml; sumRow.className = "trlist sums totalSums"; sumRow.children[0].innerText="sum"; updateRosterSums(players, sumRow, sumAllPlayers); return sumRow; }; var addRosterSumsReady = function(players) { var playerRowHtml = players[players.length-1].theRow.innerHTML; var totalSumRow = addRosterSums(players); totalSumRow.className = "trlist sums totalSums"; totalSumRow.children[2].onclick = function() { let selectedPlayers = players.filter((player) => player.theRow.style.backgroundColor && player.theRow.style.backgroundColor == SELECTED_PLAYER_COLOR); if(selectedPlayers.length < 1 ) { players.forEach((playerRow) => {playerRow.theRow.click();}); } else { selectedPlayers.forEach((playerRow) => { playerRow.theRow.click()}); } }; //totalSumRow.children[2].innerText="aaa"; return totalSumRow; }; var addRosterSumsMng = function(players) { var playerRowHtml = players[players.length-1].theRow.innerHTML; var mngPlayers = players.filter((p)=>p.missNextGame); if( mngPlayers.length < 1 ) { return; } var mngRow = addRosterSums(mngPlayers, " missing"); mngRow.className = "trlist sums mngSums"; mngRow.children[0].innerText="mng"; return mngRow; }; var addDropdownSearch = function(name) { var targets = document.getElementsByName(name); for(var i=0; i < targets.length; i++) { var target = targets[i]; var targetWidth = getStyle(target, "width"); var dropdownSearch = document.createElement("input"); //target.setAttribute("onchange", ""); dropdownSearch.dropdown = target; dropdownSearch.setAttribute("class", "dropdown-search"); target.width = targetWidth; dropdownSearch.style.width = targetWidth; dropdownSearch.type="text"; dropdownSearch.addEventListener("keyup", updateDropdown); dropdownSearch.options = []; var previousId = 0; for(var j=0; j < target.childNodes.length; j++) { var option = target.childNodes[j]; //console.log(option); if("OPTION" == target.childNodes[j].tagName && target.childNodes[j].value !== previousId) { //console.log(target.childNodes[j].tagName + "::" + target.childNodes[j].value+ "::" + previousId); previousId = target.childNodes[j].value; var nam = option.textContent || target.childNodes[j].innerText; var player = {"id": target.childNodes[j].value, "name":nam}; dropdownSearch.options.push( player ); } } target.parentNode.insertBefore(dropdownSearch, target); applyDropdownFilter(dropdownSearch); console.log("//onchange:"+target.getAttribute("onchange")); } }; var fixLastTdColspan = function (roster) { for(let i = roster.querySelectorAll('tr').length - 8; i < roster.querySelectorAll('tr').length; i++) { let row = roster.querySelectorAll('tr')[i]; if(row.children.length < 19) { let colSpan = row.children.length - 18; row.children[row.children.length-1].colSpan = 6; } } } var toggleRosterStats = function () { var roster = document.querySelector(".tblist"); var rosterHeadingRow = document.querySelector(".tblist .trlisthead"); var rosterRows = [...document.querySelectorAll(".tblist tr")].filter((row) => !row.classList.contains("trborder")); var teamBadgeColumn = document.querySelector(".trborder .esmall9"); rosterRows.forEach((row) => { row.children[10].style.display="table-cell"; row.children[11].style.display="table-cell"; row.children[12].style.display="table-cell"; row.children[13].style.display="table-cell"; row.children[14].style.display="table-cell"; if(!row.classList.contains(".trlist") && row.children.length == 17) { let missing1 = document.createElement("td"); missing1.className = row.children[14].className; missing1.style = row.children[14].style.cssText; row.insertBefore(missing1, row.children[15]); let missing2 = document.createElement("td"); missing2.className = row.children[14].className; missing2.style = row.children[14].style.cssText; row.insertBefore(missing2, row.children[15]); } row.children[15].style.display="table-cell"; row.children[16].style.display="table-cell"; row.children[17].style.display="table-cell"; row.children[18].style.display="table-cell"; }); fixLastTdColspan(roster); return true; }; var addStatsToggle = function (roster) { const statsToggle = document.createElement('a'); statsToggle.textContent = "Show SPP details"; statsToggle.href="#"; statsToggle.onclick = function() { toggleRosterStats(); statsToggle.style.display="none"; return false;}; roster.after(statsToggle); }; var addPlayerSkillFunctionsToDocument = function(playerValues) { document.countPlayerSkills = countPlayerSkills; document.toogleRosterStats = toggleRosterStats; document.playerValues = playerValues; }; var selectPlayerRow = function(row, rosterRows) { if(!row.style.backgroundColor || row.style.backgroundColor !== SELECTED_PLAYER_COLOR) { row.oldBackgroundColor = row.style.backgroundColor; row.style.backgroundColor = SELECTED_PLAYER_COLOR; } else if ( row.oldBackgroundColor ) { row.style.backgroundColor = row.oldBackgroundColor; } else { row.style.backgroundColor = null; } var selectedPlayers = rosterRows.filter((player) => player.theRow.style.backgroundColor && player.theRow.style.backgroundColor == SELECTED_PLAYER_COLOR); var selectedPlayersCount = selectedPlayers.length; document.selectedPlayers = selectedPlayers; console.log(selectedPlayers); console.log("Clicked row"); console.log(row); console.log("There are " + selectedPlayersCount + " rows selected"); if ( selectedPlayersCount > 0 ) { updateRosterSums(selectedPlayers, document.querySelector("tr.totalSums"), "selected"); } else { updateRosterSums(rosterRows, document.querySelector("tr.totalSums"), false); } }; var makeRosterRowsClickable = function(rosterRows) { rosterRows.forEach((player) => { let row = player.theRow; row.style.cursor = "crosshair"; row.onclick = function() {selectPlayerRow(row, rosterRows)}; }); }; addDropdownSearch("bountyspiller"); addDropdownSearch("m0team1"); addDropdownSearch("m0team2"); if( document.URL.indexOf("default.asp?p=ro") > 0 ) { const roster = document.querySelector(".tblist"); const players = [...document.querySelectorAll(".tblist tr")].filter((row) => {return row.classList.contains("trlist");}); const playerValues = players.map((row) => parsePlayer(row)); makeRosterRowsClickable(playerValues); addPlayerSkillFunctionsToDocument(playerValues); addRosterSumsMng(playerValues); addRosterSumsReady(playerValues); toggleRosterStats(); } else { document.playerValues = {no_players: true}; } //Remove javascript log out var timer_id = window.setTimeout(function() {}, 0); while (timer_id--) { window.clearTimeout(timer_id); // will do nothing if no timeout with id is present } //Keep alive window.setInterval(heartbeat, 600000); })();