// ==UserScript==
// @name Hypersubs
// @version 0.2.2
// @namespace Hypersubs-For-Fantasy-League-Classic-by-J
// @description A better way to set your Supersubs in Fantasyleague.com's Classic Premier League fantasy football game.
// @match http://www.fantasyleague.com/Classic/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js
// @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.21/jquery-ui.min.js
// @icon http://img9.imageshack.us/img9/6439/hypersubs.png
// @grant GM_getValue
// @grant GM_setValue
// @downloadURL none
// ==/UserScript==
/////////////////////////////////////////////////////////////////////////////////////
//
// Hypersubs provides an alternative way to set your Supersubs in
// Fantasyleague.com's Classic Premier League fantasy football game.
//
// Hypersubs automatically makes clear-cut choices for you, like fielding all
// of your full backs when you have no more than two active in a set of fixtures.
// On those few occasions when you have to leave an active player out, Hypersubs
// lets you choose.
//
//
// WHAT YOU NEED
//
// To use this version of Hypersubs, you need either:
//
// Firefox v30.0 with the Greasemonkey extension v2.0+
// or
// Chrome v36.0 with the Tampermonkey extension v3.0+
//
// or newer compatible versions.
//
//
// LIMITATIONS AND DISCLAIMERS
//
// This version of Hypersubs is intended to be compatible with the Fantasyleague.com
// Classic Premier League competition as of August 2014. Other competitions are not
// supported.
//
// Hypersubs relies on the existing structure of the Fantasy League web site.
// It will stop working properly -- possibly without any warning! -- as soon as
// Fantasy League change how their Supersubs pages are structured.
//
// Use at your own risk. This program was written as a personal exercise to learn
// the basics of a few technologies. It may well have bugs. Always be sure to
// check the Fantasy League site or their confirmation email to be certain that
// everything went right!
//
//
// TECHNICAL NOTES
//
// The core of this program is the outcome of the first time I really programmed in
// JavaScript, the first time I wrote a user script, and the first time I used any of
// jQuery / jQuery UI / CSS3 (in 2012). It shows. Large parts of the program are also
// a bit weird because they have been translated semi-automatically from some Scala
// code that was experimental to begin with. There's a bit of spaghetti in there,
// probably some cargo-cult stuff, and possibly some memory leakage.
//
/////////////////////////////////////////////////////////////////////////////////////
////////// CHECK THAT THE BROWSER IS OK AND JQUERY IS AVAILABLE ///////////////////
var HYPERSUBS_VERSION = "v0.2.2";
var FIREFOX_THRESHOLD = 30;
var CHROME_THRESHOLD = 36;
var SITE_COMPATIBILITY_CHECKED_DATE = "August 2014";
function checkBrowser() {
function checkFor(browserInfo, browserName, majorVersionThreshold) {
var nameIndex = browserInfo.indexOf(browserName);
if (nameIndex >= 0) {
var version = browserInfo.substring(nameIndex + browserName.length + 1);
var semicolon = version.indexOf(";");
version = semicolon >= 0 ? version.substring(0, semicolon) : version;
var space = version.indexOf(" ");
version = parseInt(space >= 0 ? version.substring(0, space) : version, 10);
if (version >= 0 && version < majorVersionThreshold) {
alert("You appear to be running version " + version + " of " + browserName + ", which is not recognized by Hypersubs.\n\n" +
"To use Hypersubs, please install a new version of " + browserName + " (" + majorVersionThreshold + "+).");
}
return version;
} else {
return -1;
}
}
if (checkFor(navigator.userAgent, "Chrome", CHROME_THRESHOLD) < 0) {
if (checkFor(navigator.userAgent, "Firefox", FIREFOX_THRESHOLD) < 0) {
alert("You don't appear to be running Firefox or Chrome.\n\nHypersubs only works with Firefox " + FIREFOX_THRESHOLD + ".0+ (with the Greasemonkey extension) and " +
"Chrome " + CHROME_THRESHOLD + ".0+ (with the Tampermonkey extension).\n\nYour browser info was detected as:\n" + navigator.userAgent);
throw "Hypersubs: Incompatible browser.";
}
} else {
if (typeof(jQuery) == "undefined") {
alert("Failed to load the jQuery framework required by Hypersubs.\n\n" +
"One reason why this may happen in Chrome is that you have installed Hypersubs as a regular Chrome script rather than a Tampermonkey script.\n\n" +
"Make sure you have installed Hypersubs using the Tampermonkey extension (and remove any other installations of Hypersubs).");
throw "Hypersubs: Failed to load jQuery";
}
}
}
checkBrowser();
function httpOrHttps() {
var protocol = window.location.protocol;
if (protocol != "https:" && protocol != "http:") {
console.log("Hypersubs: Unexpected protocol: " + protocol);
return null;
} else {
return protocol;
}
}
(function ($) {
var currentPage;
if (/\/Supersubs\.aspx/i.exec(window.location.pathname) != null) {
currentPage = "supersubs page";
} else if (/\/SupersubsConfirmation\.aspx/i.exec(window.location.pathname) != null){
currentPage = "confirmation page";
} else if (/\/FriendsLeague\/View.aspx/i.exec(window.location.pathname) != null){
currentPage = "league page";
} else if (/\/PremierLeague\/Default.aspx/i.exec(window.location.pathname) != null){
currentPage = "league page";
} else {
currentPage = "miscellaneous page";
}
if ($("body div#mainContent").length != 1) {
// console.log("Hypersubs: Additional page load ignored.");
return;
}
if (currentPage == "supersubs page") {
try {
$("body").prepend("
\
HYPERSUBS
\
Will start in a moment.
");
} catch (error) {
console.log("Hypersubs: Failed to display splash screen.");
}
}
$(window).load(function () {
console.log("Hypersubs: DOM ready. Now on a " + currentPage + ": " + window.location.pathname);
////////// NON-SUPERSUBS PAGES: MISCELLANEOUS FEATURES /////////////////////////////
try {
function addHypersubsShortcuts() {
$("#userTeams table.myTeams tbody tr").each(function() {
if (isClassicPrem(this)) {
$(this).find("td:nth(2)").attr("width", 40);
$(this).find("td:nth(2)").append("H");
}
});
}
function getTeamID(teamTr) {
var teamURL = $(teamTr).find("td:nth(1) a").attr("href");
var idPartStart = teamURL.indexOf("teamid=");
if (idPartStart < 0) {
return null;
}
return teamURL.substring(idPartStart);
}
function isClassicPrem(teamTr) {
var teamURL = $(teamTr).find("td:nth(1) a").attr("href");
return /\/Classic\//i.exec(teamURL) != null;
}
function setDoneCount(count) {
GM_setValue("INTERNALfixturesJustSet", count);
}
function clearDoneCount() {
setDoneCount(-1);
}
function getDoneCount() {
return GM_getValue("INTERNALfixturesJustSet", -1);
}
function addTeamInfo(tableElem) {
var teamTrs = $("#userTeams table.myTeams tbody tr");
var error = { name: "ERROR", week: 0, live: true, total: 0, subsUntil: "", transfers: 0, position: 0 };
var protocol = httpOrHttps();
if (protocol == null) {
console.log("Hypersubs: Aborting team info retrieval due to invalid protocol.");
return;
}
function addTeamsFrom(teamIndex) {
if (teamIndex >= teamTrs.length) {
return;
}
if (isClassicPrem(teamTrs[teamIndex])) {
var url = protocol + "//www.fantasyleague.com/Classic/Team/Default.aspx?" + getTeamID(teamTrs[teamIndex]);
var dataWrapper = $("");
dataWrapper.load(url + " #wrapper", function(responseText, textStatus) {
var teamInfo = { };
var subs = dataWrapper.find("div.supersubs-set");
if (subs.length > 1) {
console.log("Hypersubs: More than one supersubs-set element: " + subs.length);
tableElem.append(formulateTeamInfo(error));
return;
}
if (subs.length == 0) {
teamInfo.subsUntil = "NOT SET";
} else {
teamInfo.subsUntil = subs.text().replace("Supersubs set until ", "").replace("View", "");
}
var mainInfo = dataWrapper.find("#mainInfo");
if (mainInfo.length != 1) {
console.log("Hypersubs: Unexpected number of mainInfo elements: " + mainInfo.length);
tableElem.append(formulateTeamInfo(error));
return;
}
var teamInfoPanel = mainInfo.find("table.general:nth(1)");
if (mainInfo.length != 1) {
console.log("Hypersubs: Couldn't find team info panel.");
tableElem.append(formulateTeamInfo(error));
return;
}
teamInfo.name = teamInfoPanel.find("thead tr th:nth(0)").text().replace("Team: ", "");
teamInfo.week = parseInt(teamInfoPanel.find("tbody tr:nth(0) td:nth(1)").text(), 10);
teamInfo.total = parseInt(teamInfoPanel.find("tbody tr:nth(2) td:nth(1)").text(), 10);
teamInfo.transfers = parseInt(teamInfoPanel.find("tbody tr:nth(2) td:nth(4)").text(), 10);
teamInfo.position = parseInt(teamInfoPanel.find("tbody tr:nth(3) td:nth(4)").text(), 10);
var package = teamInfoPanel.find("tbody tr:nth(4) td:nth(4)").text();
teamInfo.live = package == "Silver" || package == "Gold";
tableElem.append(formulateTeamInfo(teamInfo));
addTeamsFrom(teamIndex + 1);
});
} else {
addTeamsFrom(teamIndex + 1);
}
}
if (teamTrs.length == 0) {
tableElem.append(formulateTeamInfo({ name: "N/A", week: 0, live: true, total: 0, subsUntil: "N/A", transfers: 0, position: 0 }));
} else {
addTeamsFrom(0);
}
}
function formulateTeamInfo(info) {
return "
");
}
});
fixtureData.children("tbody").each(function() { // for each list of players within a fixture block
var count = 0;
var inNotSelectedPart = false;
$(this).children("tr").each(function() { // for each row in player list
var kids = $(this).find("td");
if (prune && kids.length == 3 && $(kids[1]).hasClass("text-right") && $(kids[1]).hasClass("highlight") && $.trim($(this).children("td:nth(2)").text()) == "") {
$(this).css('display', 'none'); // hide irrelevant players
} else if ($(this).find("td:contains('Playing but not selected')").length > 0) {
inNotSelectedPart = true;
count = 0;
} else if (prune) {
count++;
if (count % 2 == 0) {
$(this).addClass("on");
$(this).removeClass("off");
} else {
$(this).removeClass("on");
$(this).addClass("off");
}
}
if (inNotSelectedPart) {
$(this).find('td').css('color', '#ff0000');
}
});
});
}
// League Page
function augmentLeaguePage() {
var openTabs = $("#mainContent div.content ul.tabs li.tab-cur");
if ($.trim($(openTabs[0]).text()) != "League Table" || $.trim($(openTabs[1]).text()) != "Season") {
console.log("Hypersubs: Not in the League Table / Season tab; won't augment the page.");
return;
}
if (openTabs.length != 2) {
console.log("Hypersubs: Unexpected number of open tabs on league page: " + openTabs.length + ". Won't augment the page.");
return;
}
console.log("Hypersubs: Augmenting league page.");
createTransferColumn();
}
function createTransferColumn() {
var reportURL = $("#mainContent div.content ul.tabs li:contains('League Report') a").attr("href");
var reportWrapper = $("");
var protocol = httpOrHttps();
if (protocol == null) {
console.log("Hypersubs: Aborting league page augmentation due to invalid protocol.");
return;
}
reportWrapper.load(protocol + "//www.fantasyleague.com" + reportURL + " #mainContent div.content", function(responseText, textStatus) {
var report = reportWrapper.find("div.content");
if (report.length == 0) {
console.log("Hypersubs: Failed to load League Report data.");
return;
}
if (report.length > 1) {
console.log("Hypersubs: Unexpected number of content elements in the League Report page; a change/bug in the FL site?");
return;
}
var transfersMade = loadTransfersMade(report);
if (transfersMade != null) {
writeTransfers(transfersMade);
}
});
}
function loadTransfersMade(report) {
var transfersMade = { };
report.find("div.league-tab div table:not(.general) tbody").each(function() {
var teamName = $.trim($(this).find("tr:nth(0) th:nth(1)").text().substring("Team: ".length));
var transfers = parseInt($.trim($(this).find("tr:nth(0) th:nth(2)").text()).substring("Transfers Done: ".length), 10);
if (teamName == "" || isNaN(transfers)) {
console.log("Hypersubs: Unexpected structure in the League Report page; a change/bug in the FL site?");
return null;
}
if (transfersMade.hasOwnProperty(teamName)) {
console.log("Hypersubs: Team name " + teamName + " is not unique. ");
return null;
} else {
transfersMade[teamName] = transfers;
}
});
return transfersMade;
}
function writeTransfers(transfersMade) {
var dgpHeader = $("div.content table.general thead th:contains('DGP')");
var insertLocation = dgpHeader.index();
var teamLocation = $("div.content table.general thead th:contains('Team')").index();
var managerLocation = $("div.content table.general thead th:contains('Manager')").index();
if (insertLocation < 0 || teamLocation < 0 || managerLocation < 0) {
throw "Hypersubs: Couldn't find the appropriate kind of league table; aborting.";
}
dgpHeader.after("
" + (this.theoreticalOpponent != null ? ("game " + (this.atHome ? "at home to " : "away at ") + this.theoreticalOpponent.name) : "no game in this set of fixtures") + "
";
var status = (this.theoreticalOpponent != null && this.status != Status.values.READY) ? "
Use the buttons below to start over if you want to undo any selections or change how Hypersubs automatically selects players. Or click the question mark in the top right-hand corner for more help.
\
\
\
Animate selections\
\
\
Enable Smartfill for each new set of fixtures\
\
\
Always require me to click Next\
\
\
Show shortcuts to Hypersubs\
\
\
Hide inactive players on the confirmation page\
\
\
Show remaining transfers in leagues\
\
\
Show More Info button\
\
\
Clubs with a strong attack: \
\
\
Clubs with a strong defense: \
\
\
\
Really exit without saving any of your Hypersubs?
If you do, you can set your Supersubs normally or reload the page to start over from the first upcoming set of fixtures.
\
\
\
Hypersubs " + HYPERSUBS_VERSION + " Help
\
Instructions
Click on players to pick them for your team. The Reset button lets you undo your selections. Click Next to move to the \
next set of fixtures, and remember to click Finish to actually set your Supersubs on the Fantasy League site. Review your team on the site to make sure that everything went to plan.
\
If you have the \"Smartfill\" option enabled, Hypersubs will automatically pick players that seem like obvious selections (for instance, fielding active players rather than inactive ones). \
Often, all the choices will be clear-cut, and all you have to do is accept what Smartfill suggests by clicking Next.
\
For greater manual control, click Disable Smartfill. (Even with Smartfill off, Hypersubs will automatically make some truly trivial \"choices\" such as picking the only players you have available, and picking \"just anybody\" when everyone is idle.)
\
By default, Smartfill turns on each time you start working on a new set of fixtures. You can change this, and other things, through the Preferences button.
\
If you're wondering why a player has an unusual background color, hover the mouse over the player to find out.
\
Frequently Asked Questions
\
Hypersubs has selected a player who I want to leave on the bench. How do I unselect him? Answer: You can disable Smartfill (see above). Or you can go to Preferences \
and set the Strong Attacks and Strong Defenses to your liking so that the player will be highlighted as dangerous and won't be selected automatically. Or you can even hit \
Cancel and set Supersubs in the traditional way, if you really want.
\
How do I go back to a previous set of fixtures? Answer: Currently, the only way to do that is to close the Hypersubs window (e.g., with Cancel) and reload the page to start over.
\
Hypersubs works great, but clicking Next so many times seems unnecessary, don't you think? Answer: You can unselect \"Always require me to click Next\" in the Preferences to speed up you Hypersubs even more. \
If you do, make sure the Strong Attacks and Strong Defenses are to your liking and be extra sure to take a look at the confirmation page.
\
Isn't there any way of quickly telling if a player has a home or away fixture? Answer: In the main Hypersubs view, capital letters indicate a home fixture for the player and lowercase letters indicate away an away fixture. (Also, the mouseover text spells out the fixtures.)
\
The bonus features are nice. Could you also add a feature that automatically updates my teams' points in real time during the weekend? Answer: I won't, because Fantasy League want us to pay for that functionality with a Silver or Gold upgrade.
\
Limitations and Disclaimers
\
This version of Hypersubs is compatible with the Fantasyleague.com Classic Premier League competition as of " + SITE_COMPATIBILITY_CHECKED_DATE + ". Other competitions are not supported.
\
Hypersubs relies on the existing structure of the Fantasy League web site. It will stop working properly — possibly without any warning! — as soon as Fantasy League change how their Supersubs pages are structured.
\
Hypersubs only works in:
\
Chrome v" + CHROME_THRESHOLD + ".0+ with the Tampermonkey extension, and
Firefox v" + FIREFOX_THRESHOLD + ".0+ with the Greasemonkey extension.
\
Use at your own risk. This program was written as an exercise to learn the basics of a few technologies. It may well be buggy. \
Again, be sure to check the Fantasy League site or their confirmation email to be certain that everything went right!
\
";
/////////////////////////////// ADD THE GUI TO THE PAGE ////////////////////////////////////////////////
var THEME_PATH = "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.21/themes/le-frog";
$('head').append('');
$('head').append('');
$('body').append(HYPERSUBS_HTML);
/////////////////////////////// PLAYERBOX WIDGET ////////////////////////////////////////////////////////
$.widget("hypersubs.playerCircle", {
options: {
player: null,
colorExplanation: null
}, // default options
setPlayer: function (newPlayer) {
this.options = {
player: newPlayer
};
this._setInfo();
this.refresh();
},
getPlayer: function () {
return this.options.player;
},
getColorExplanation: function () {
return this.options.colorExplanation;
},
_setInfo: function () {
var p = this.options.player;
var metrics = NameBoxMetrics.getForName(p.name, MAX_PLAYER_NAME_FONT_SIZE, MIN_PLAYER_NAME_FONT_SIZE);
var displayName = metrics[0];
var fontSize = metrics[1];
var nameHeight = metrics[2] + 2;
var nameDiv = $(this.element).find(".name");
nameDiv.css("font-size", fontSize + "px");
nameDiv.css("line-height", fontSize + "px");
nameDiv.css("height", nameHeight + "px");
nameDiv.css("margin-top", (-(nameHeight / 2)) + "px");
nameDiv.html(displayName);
$(this.element).find(".club").html(p.club.name);
$(this.element).find(".position").html(p.position.name);
$(this.element).find(".lowerBit").html(p.activityHTML());
if (p.status == Status.values.UNKNOWN || p.status == Status.values.READY) {
$(this.element).find(".status").css('display', 'none');
} else {
$(this.element).find(".status").css('display', 'block');
$(this.element).find(".status").css('background-position', INJURY_ICON_OFFSETS[p.status.id]);
}
},
refresh: function () { // update those aspects of the view which may change without the player changing (currently: danger levels -> colors)
var colorName = this._determineColor();
var color = PLAYER_COLOR_SCHEME[colorName];
this.options.colorExplanation = "