// ==UserScript==
// @name strikeout.co scores
// @namespace http://your.homepage/
// @version 1.0
// @description inclues game info on strikeout.co main page
// @author muffleyd
// @match http://www.strikeout.co/
// @grant $
// ==/UserScript==
/** Copywrite David Muffley 2015-2016.
* Use however you like, with acknowledgement.
*/
(function () {
// TODO more styling for info text?
//remove crap at the top of the content
if ("#content #accordian") {
$("#content > *:not(:has(#accordion))").remove();
}
//just, stop
$('#toTop').remove();
//_main() won't run through if tab is hidden, this stores when that occurs so on tab visibility it can run again
var SKIPPED = false;
//_main() will always run the first time through even if the tab is hidden
var firstGet = true;
//completed inning value to display no hitter info (middle of previous inning for away hitters)
var NOHITATBATS = 6;
var addedClasses = ['_tmMLBFree', '_tmESPN'];
var selectorParent = '#content h1[league="Major League Baseball"]';
function parseTime(time) {
//time can be in format of "10:35" or timezone like "-03:30" or "+9:30"
var mod = 1;
if (time[0] == '-' || time[0] == '+') {
if (time[0] == '-') {
mod = -1;
}
time = time.substring(1);
}
time = time.split(':');
return mod * ((parseInt(time[0]) * 60) + parseInt(time[1]));
}
//set "mlbgday" attr to match mlb.com's scoreboard since "gday" attribute may not match mlb's date, based on timezone
(function () {
var timezoneMod = parseTime(timezone) + 240; // + 240 makes this effectively eastern time, close enough
$(selectorParent + ' .matchtime').each(function (index, el) {
var val = el.getAttribute('gday');
//even at -12:00 it can't be behind by a day, it can only ever be ahead by a day all the way to +13:00
if (parseTime(el.innerText) - timezoneMod < 0) {
var dateObj = new Date(val);
dateObj.setTime(dateObj.getTime() - (86400 * 1000));
val = dateObj.toJSON().slice(0, 10);
}
el.setAttribute('mlbgday', val);
})
})();
//pass an optional delay in
// when waking up from sleep the network connection is not immediately active, this allows you to compensate for that
function main(delay) {
if (!delay) {
delay = 0;
}
setTimeout(_main, delay * 1000);
}
// 5 hours in the past, for when it's after midnight, not sure if needed
function _main() {
//skip when tab is hidden or window is minimized, and set so event is later told to run main()
if (!firstGet && document.hidden) {
SKIPPED = true;
return;
}
firstGet = false;
var dateObj = new Date();
//offset the computer's timezone to get UTC, then go back 10 hours to guarantee you use the correct date for mlb.com
var date = new Date(dateObj.getTime() + (dateObj.getTimezoneOffset() * 60 * 1000) - (1000 * 60 * 60 * 10));
var year = date.getFullYear().toString();
//January is 0, etc, so add 1
var month = date.getMonth() + 1;
if (month < 10) {
month = "0" + month.toString();
} else {
month = month.toString();
}
var day = date.getDate();
if (day < 10) {
day = "0" + day.toString();
} else {
day = day.toString();
}
$.getJSON('http://gd2.mlb.com/components/game/mlb/year_' + year + '/month_' + month + '/day_' + day + '/miniscoreboard.json', function (resp, type) {
if (type == 'success') {
$(document).ready(function () {
//clear previously added classes to elements
addedClasses.forEach(function (item) {
$(selectorParent).removeClass(item);
});
var info = resp.data.games.game;
if (info.length === undefined) { //if only one game, it's not passed back as an array
info = [info];
}
var gameObj = {};
//on later runs, remove added text immediately
$(selectorParent + ' a ._tmInfo').remove();
//get all team names from today
$(selectorParent + ' a:has(span[mlbgday="' + year + '-' + month + '-' + day + '"])').each(function (index, el) {
var gameText = el.lastChild.textContent.trim();
//later regex now checks for hyphens, so deal with it in D-backs
gameText = gameText.replace('D-backs', 'Diamondbacks');
//Find games missing the colon before "Game #" and add it in
// But don't for ones using a hyphen
gameText = gameText.replace(/([^:\-])(\s+[Gg]ame\s+[0-9]+)$/, '$1:$2');
//Turn "Team vsTeam" into "Team vs Team". Not truely needed since only the team name, not city is used, but just in case
gameText = gameText.replace(/(\s+[Vv][Ss])([A-Z])/, '$1 $2');
//this match regex handles things like:
// Team vs Team
// Team vs Team: Game 2
// MLB Wildcard: Team vs Team
// ALDS: Team vs Team: Game 1
// Team vs Team - Game 2
//regex split is for occasional non-space blank character (tab has been there)
var txt = gameText.match(/([^:]+:|^)(.*?vs[^:\-$]+)(.*)/i);
if (txt === null) {
console.log('not a game', gameText);
//something like "MLB on Fox Sports 1 Pregame"
return;
}
var texts = txt[2].split(/\s+vs\s+/i);
texts.forEach(function (item) {
var splitItems = item.trim().split(/\s+/);
//strip out this special thing that has appeared during spring training
if (splitItems[splitItems.length-1] == '(ss)') {
splitItems.pop();
}
splitItems.forEach(function (item, index, arr) {
//combine all words after the first, getting smaller and smaller,
//because of things like "Chicago White Sox" while avoiding "Los Angeles Dodgers"
if (index == 0) {
return;
}
var key = arr.slice(index).join(' ');
if (!gameObj.hasOwnProperty(key)) {
gameObj[key] = [];
}
//sometimes a game appears as "A vs B - Game 2", so keep the spaces around the "-"
if (txt[3][0] == '-') {
var txt2end = txt[2].substr(-1);
if (txt2end.match(/\s/)) {
txt[3] = txt2end + txt[3];
txt[2] = txt[2].slice(0, -1);
}
}
//place at the head since the (reversed) game array goes from the beginning of gameObj[key]
//include the original text so extra stuff can stay when rewriting it later
gameObj[key].unshift([el, txt]);
});
});
});
//fix differences
if (gameObj.hasOwnProperty('Diamondbacks')) {
gameObj['D-backs'] = gameObj['Diamondbacks'];
}
console.log(gameObj);
//sort handles out of order games in the info list which happens sometimes with resumed games or double headers
info.sort(function (a, b) {
//test for NaN because sometimes "time" is like "Game 2"
var t1 = a.time.split(':');
t1 = (t1[0] * 60) + (1*t1[1]);
if (isNaN(t1)) {
return 0;
}
var t2 = b.time.split(':');
t2 = (t2[0] * 60) + (1*t2[1]);
if (isNaN(t2)) {
return 0;
}
//note that mlb.com returns all times as ET
//some games start at 12:35pm, and rarely in the 11am hour, while never being 11pm or later
//since there's no 24 hour time these need to be set as being before 1pm
if (t1 >= 11*60) {
t1 -= (12*60);
}
if (t2 >= 11*60) {
t2 -= (12*60);
}
//
if (t1 < t2) {
return -1;
}
if (t1 > t2) {
return 1;
}
return 0;
});
info.reverse();
info.forEach(function (item, index, arr) {
var unknownStatus = false;
var delayed = false;
var postponed = false;
var stop = false;
var media = true;
var final = false;
var checkAwayNoHit = false;
var checkHomeNoHit = false;
var includeReason = false;
if (item.status == 'Pre-Game' || item.status == 'Preview') {
//after removing the first item and managing media info this will return
stop = true;
} else if (item.status == 'In Progress' || item.status == 'Review' || item.status == 'Manager Challenge') {
//good
} else if (item.status == 'Game Over' || item.status == 'Final' || item.status == 'Completed Early') {
//good
if (item.status == 'Completed Early') {
includeReason = true;
}
media = false;
final = true;
} else if (item.status == 'Delayed Start' || item.status == 'Delayed') {
//delay, but good
includeReason = true;
delayed = true;
} else if (item.status == 'Warmup') {
//good
} else if (item.status == 'Postponed' || item.status == 'Suspended' || item.status == 'Cancelled') {
includeReason = true;
postponed = true;
} else {
unknownStatus = true;
console.log('other status:', item.status, item);
}
console.log(item.away_team_name, item.home_team_name);
if (!gameObj.hasOwnProperty(item.away_team_name) || !gameObj.hasOwnProperty(item.home_team_name)) {
return;
}
var gameEl = gameObj[item.away_team_name][0];
if (gameEl === undefined) {
//likely a double header's first game that's fallen off the top of the list
console.log('game gone', item);
return;
} else {
gameEl = gameEl[0]
}
var originalMainText = gameObj[item.away_team_name][0][1];
if (gameEl !== gameObj[item.home_team_name][0][0]) {
//cry
console.log('mismatch', item);
} else if (gameEl) {
//pop off the game values, in case of double header
gameObj[item.away_team_name].shift();
gameObj[item.home_team_name].shift();
console.log(item, gameEl);
if (media) {
handle_game_media(item, gameEl);
}
//for double headers, now that the game is popped off we can return
if (stop) {
return;
}
var text = '';
var cls = '';
if (item.status == 'Warmup') {
text = 'Warmup';
cls = 'warmup';
} else {
var status = item.status;
if (status != 'Game Over' && status != 'Final') {
if (postponed) {
cls = 'postponed';
}
//Top 3, Bottom 5, Middle 7, etc.
//also determine if no hitter worth mentioning
//if home team is ahead in the middle or later of inning 9 or greater, it's actually over
//same if score is different at end of inning 9 or greater
if (item.hasOwnProperty('inning')) {
cls += ' inprogress';
//check for no-hitter through NOHITATBATS number of at-bats for a team
if (item.inning > NOHITATBATS) {
checkAwayNoHit = true;
checkHomeNoHit = true;
}
if (item.top_inning == "Y") {
if (item.outs == 3) {
var topbottom = 'Middle';
if (item.inning >= item.scheduled_innings && item.away_team_runs < item.home_team_runs) {
final = true;
}
if (item.inning >= NOHITATBATS) {
checkAwayNoHit = true;
}
} else {
topbottom = 'Top';
}
} else {
if (item.outs == 3) {
topbottom = 'End';
if (item.inning >= item.scheduled_innings && item.away_team_runs != item.home_team_runs) {
final = true;
}
if (item.inning >= NOHITATBATS) {
checkAwayNoHit = true;
checkHomeNoHit = true;
}
} else {
topbottom = 'Bottom';
if (item.inning >= item.scheduled_innings && item.away_team_runs < item.home_team_runs) {
final = true;
}
if (item.inning >= NOHITATBATS) {
checkAwayNoHit = true;
}
}
}
if (!final) {
if (topbottom) {
text = topbottom + ' '
}
text += item.inning;
}
}
}
if (final) {
text = 'Final';
//usually extra innings, perhaps rain-shortened?
if (item.inning != 9) {
text += ' (' + item.inning + ')';
}
cls = 'final';
}
if ((checkHomeNoHit && item.home_team_hits == 0) ||
(checkAwayNoHit && item.away_team_hits == 0)) {
cls += ' noHitter';
}
if (item.hasOwnProperty('away_team_runs') && item.hasOwnProperty('home_team_runs')) {
text += ', ' + item.away_team_runs + '-' + item.home_team_runs;
var parens = true;
} else {
parens = false;
}
if (includeReason) {
text += ' ';
if (parens) {
text += '(';
}
text += item.status;
if (item.reason) {
text += ': ' + item.reason;
}
if (parens) {
text += ')';
}
}
}
if (unknownStatus) {
text += ' DEBUG:' + item.status;
}
//get original text surrounding the 'Team vs Team' to include along with the reordering of the Teams to Away vs Home
gameEl.lastChild.textContent = originalMainText[1] + ' ' + getTeamName(item, 'away') + ' vs ' + getTeamName(item, 'home') + originalMainText[3];
if (text) {
$('<span class="_tmInfo">, <span class="_tmText ' + cls + '">' + text + '</span></span>').appendTo(gameEl);
}
}
});
});
}
});
}
function handle_game_media(item, gameEl) {
if (item.game_media && item.game_media.media) {
var media = item.game_media.media;
if ($.isArray(item.game_media.media)) {
media = item.game_media.media[0];
}
if (media.free != "NO") {
if (media.free != "ALL") {
console.log('different media free info', media);
}
gameEl.parentElement.classList.add('_tmMLBFree');
}
}
if (item.tv_station) {
item.tv_station.split(', ').forEach(function (station) {
//TODO find ESPN more generally
if (station == 'ESPN' || station == 'ESPN2' || station == 'ESPN (out-of-market only)') {
gameEl.parentElement.classList.add('_tmESPN');
}
});
}
}
//example why this is needed:
//team_city: 'NY Yankees'
//team_name: 'Yankees'
//Should return 'NY Yankees' rather than 'NY Yankees Yankees'
function getTeamName(item, homeaway) {
var city = item[homeaway + '_team_city'];
var name = item[homeaway + '_team_name'];
if (city.slice(-name.length) == name) {
return city;
} else {
return city + ' ' + name;
}
}
var initial_delay = 6;
var constant_delay = 120;
main();
//this blob compensates for 'initial_delay'
// e.g. main loop runs every 2 minutes, with a 6 second initial delay,
// this will wait 114 seconds to run main(6) and register the Interval of 120 seconds at the same time to run main(6)
setTimeout(function () {
main(initial_delay);
setInterval(main, 1000 * constant_delay, initial_delay);
}, 1000 * (constant_delay - initial_delay));
//since we don't do anything when the tab is invisible, force an update when made visible if one was skipped
document.addEventListener('visibilitychange', function () {
if (!document.hidden) {
if (SKIPPED) {
SKIPPED = false;
main();
}
}
});
function addGlobalStyle(css) {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) { return; }
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
head.appendChild(style);
}
$(document).ready(function () {
//do an awful job of figuring out which theme is running
if ($('body').css('background-color').replace(/\s/g, '') == 'rgb(0,0,0)') {
//golden midnight
$('body').addClass('_tmDark');
addGlobalStyle('body._tmDark ._tmInfo ._tmText.inprogress { color: lightblue; }');
addGlobalStyle('body._tmDark ' + selectorParent + '._tmMLBFree { background: green; }');
addGlobalStyle('body._tmDark ' + selectorParent + '._tmESPN { background: rgba(255,7,7,.35); }');
addGlobalStyle('body._tmDark ._tmInfo ._tmText.noHitter { background: crimson; }');
} else {
//white magic or failure
}
addGlobalStyle('._tmInfo ._tmText.inprogress { color: red; }');
addGlobalStyle('._tmInfo ._tmText.warmup { color: goldenrod; }');
addGlobalStyle('._tmInfo ._tmText.final { color: grey; }');
addGlobalStyle('._tmInfo ._tmText.postponed { color: blue; }');
addGlobalStyle('._tmInfo ._tmText.noHitter { background: chartreuse; }');
addGlobalStyle(selectorParent + '._tmMLBFree { background: gold; }');
addGlobalStyle(selectorParent + '._tmESPN { background: rgba(255,7,7,.25); }');
});
})()