/*
==Installation instructions==
===Firefox===
If you don't have it already, you'll need to install [https://addons.mozilla.org/en-US/firefox/addon/748 Greasemonkey], then restart Firefox and return to this page.
Then, just click on <span class="plainlinks">[{{fullurl:{{FULLPAGENAME}}|action=raw&ctype=text/javascript&cachedodge=4.4.101&fakeextension=.user.js}} this link]</span> to install the script.
To upgrade a new version when it's updated, just click the install link again – it'll automagically replace the old version. If the option is enabled, the script will automatically check for updates for you.
===Chrome===
This script can be installed as [https://chrome.google.com/webstore/detail/homestar-all-in-one/ekecfcebbojjfaiendgjgcadampmppdb an extension from the Chrome Web Store]. Chrome will then automatically keep it up-to-date for you via the normal update process.
==Script code==
<pre>*/
// Homestar All-In-One
// version 4.4
// 2018-03-11
// Copyright (c) Phillip Bradbury, Loafing
//
// --------------------------------------------------------------------
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name Homestar All-In-One
// @namespace http://www.hrwiki.org/
// @description Combination of many Homestar Runner scripts. Version 4.4.
// @version 4.4.101
// @icon http://www.hrwiki.org/w/images/thumb/1/1b/logo.png/32px-logo.png
// @match http://homestarrunner.com/*
// @match https://homestarrunner.com/*
// @match http://www.homestarrunner.com/*
// @match https://www.homestarrunner.com/*
// @match http://podstar.homestarrunner.com/*
// @match https://podstar.homestarrunner.com/*
// @match http://videlectrix.com/*
// @match https://videlectrix.com/*
// @match http://www.videlectrix.com/*
// @match https://www.videlectrix.com/*
// @match http://hrwiki.org/mirror/*
// @match https://hrwiki.org/mirror/*
// @match http://www.hrwiki.org/mirror/*
// @match https://www.hrwiki.org/mirror/*
// @match https://secure.homestarrunner.com/heythanks.html*
// @match http://www.homestarrunner.kitkorp.com/*
// @match http://thoraxcorp.com/*
// @match https://old.homestarrunner.com/*
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.xmlHttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
(async function(){
function Utils()
{
this.guessisplaying = {
lastframe: -1,
lastframeat: new Date(),
state: true
};
}
// Taken from http://diveintogreasemonkey.org/patterns/add-css.html
Utils.prototype.addGlobalStyle = function(css)
{
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) return;
style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
head.appendChild(style);
};
// Based on http://userscripts.org/topics/41177
Utils.prototype.useGMFunctions = async function useGMFunctions()
{
// We can't just test if GM_getValue exists, because in Chrome they do exist
// but they don't actually do anything, just report failure to console.log
// Have to do it like this instead of like "if(window.GM_getValue)"
// because apparently this function isn't actually on "window", and I don't
// know where it actually lives...
if (typeof(GM) == "object" && GM.getValue && await GM.getValue("this-value-doesn't-exist-I-promise", true))
return 2; // Use GM4 methods
else if (typeof(GM_getValue) == "function" && GM_getValue("this-value-doesn't-exist-I-promise", true))
return 1; // Use GM3 methods
else
return 0; // Use native methods
return gmstorage;
};
// Only really need to do this once...
Utils.prototype.useGMFunctions = await Utils.prototype.useGMFunctions();
Utils.prototype.getPref = async function getPref(key, def)
{
if (this.useGMFunctions == 2)
return await GM.getValue(key, def);
else if (this.useGMFunctions == 1)
return GM_getValue(key, def);
else if (window.localStorage)
{
var value = localStorage.getItem("hr-allinone-" + key);
if (value === null)
return def;
var type = value[0];
value = value.substring(1);
if (type == 'b')
return Number(value) != 0;
else if (type == 'n')
return Number(value);
else
return value;
}
else
{
alert("Homestar Runner All-in-one is not supported on this platform");
throw "Couldn't find a local storage provider";
}
};
Utils.prototype.setPref = function(key, value)
{
if (this.useGMFunctions == 2)
GM.setValue(key, value);
else if (this.useGMFunctions == 1)
GM_setValue(key, value);
else if (window.localStorage)
{
if (typeof(value) == "string")
localStorage.setItem("hr-allinone-" + key, "s" + value);
else if (typeof(value) == "number")
localStorage.setItem("hr-allinone-" + key, "n" + value);
else if (typeof(value) == "boolean")
localStorage.setItem("hr-allinone-" + key, "b" + (value ? 1 : 0));
else
throw "Unexpected type for storage: " + typeof(value);
}
else
{
alert("Homestar Runner All-in-one is not supported on this platform");
throw "Couldn't find a local storage provider";
}
};
Utils.prototype.downloadPage = function(url, method)
{
if (!method)
method = 'GET';
return new Promise((resolve, reject) => {
if (typeof(GM) == "object" && GM.xmlHttpRequest) {
GM.xmlHttpRequest({
method: method,
url: url,
onload: res => resolve({text: res.responseText, status: res.status, statusText: res.statusText, headers: res.responseHeaders}),
onerror: res => reject(`${res.status} ${res.statusText}`)
});
} else if (typeof(GM_xmlhttpRequest) == "function") {
GM_xmlhttpRequest({
method: method,
url: url,
onload: res => resolve({text: res.responseText, status: res.status, statusText: res.statusText, headers: res.responseHeaders}),
onerror: res => reject(`${res.status} ${res.statusText}`)
});
} else {
var xhr = new XMLHttpRequest();
xhr.onload = () => resolve({text: xhr.responseText, status: xhr.status, statusText: xhr.statusText, headers: xhr.getAllResponseHeaders()});
xhr.onerror = () => reject(`${xhr.status} ${xhr.statusText}`);
xhr.open(method, url);
xhr.send();
}
});
};
Utils.prototype.buildWikiUrl = function(page)
{
var url = escape(page.replace(/ /g, '_'));
return "http://www.hrwiki.org/w/index.php?title=" + url + "&action=raw&source=allinone&cachedodge=" + this.getPref('cachedodge', 0);
};
Utils.prototype.downloadWiki = async function downloadWiki(page)
{
for (var timesredirected = 0; timesredirected < 3; timesredirected++) {
var res = await this.downloadPage(this.buildWikiUrl(page));
// check for redirects
var matches = res.text.match(/^\s*#\s*REDIRECT\s*\[\[(.*)\]\]/i);
if (matches)
{
// Get the page name out of the redirect text
var text = matches[1];
if ((matches = text.match(/^(.*)\|/)))
text = matches[1];
if ((matches = text.match(/^(.*)\#/)))
text = matches[1];
page = text.replace(/^\s+|\s+$/g, '');
}
else
return res.text;
}
throw "Too many redirects";
};
Utils.prototype.parseWikiXML = function(text)
{
// strip various things - templates and <pre> tags for wiki formatting, and <noinclude> sections...
// <includeonly> tags are stripped (but their contents kept) for consistency.
text = text.replace(/{{.*?}}/g, "");
text = text.replace(/<\/?pre[^>]*>/g, "");
text = text.replace(/<noinclude[^>]*>.*?<\/noinclude[^>]*>/g, "");
text = text.replace(/<includeonly[^>]*>(.*?)<\/includeonly[^>]*>/g, "$1");
text = text.replace(/^\s+/g, "");
var parser = new DOMParser();
try
{
var doc = parser.parseFromString(text, "application/xml");
}
catch (e)
{
throw "Error in XML:\n" + e.toString();
}
// check if returned document is an error message
if (doc.getElementsByTagName('parsererror').length > 0)
{
var error = doc.getElementsByTagName('parsererror')[0];
if (error.firstChild.nodeType == doc.TEXT_NODE && error.lastChild.nodeType == doc.ELEMENT_NODE && error.lastChild.nodeName == "sourcetext")
{
// Firefox's errors look like this:
// <parsererror>Error details<sourcetext>Source text</sourcetext></parsererror>
throw (
error.firstChild.nodeValue.replace(/Location: .*\n/, "") + "\n" +
doc.documentElement.lastChild.textContent
);
}
else if (error.getElementsByTagName('div').length > 0)
{
// Chrome's errors look like this:
// <someRoot><parsererror style="..."><h3>Generic error message</h3><div style="...">Error details</div><h3>Generic footer</h3><attempted parsing of page/></someRoot>
throw (
"Error in XML:\n" +
error.getElementsByTagName('div')[0].textContent
);
}
else
{
// Try to at least return something
throw (
"Error in XML:\n" +
error.textContent
);
}
}
return doc;
};
Utils.prototype.currentFrame = async function currentFrame(flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
if (flashmovie === globals.flashmovie && globals.is_puppets)
{
var a = await playercomm.targetCurrentFrame(flashmovie, "/videoplayer");
// Keep track of whether the current frame is changing, for isPlaying()
// If we stay on the same frame for more than, say, a second, guess
// that we're paused.
if (a != this.guessisplaying.lastframe)
{
this.guessisplaying.lastframe = a;
this.guessisplaying.lastframeat = new Date();
this.guessisplaying.state = true;
}
else if (new Date() - this.guessisplaying.lastframeat > 1000)
{
this.guessisplaying.state = false;
}
return a;
}
else
{
return await playercomm.currentFrame(flashmovie)
}
};
Utils.prototype.totalFrames = async function totalFrames(flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
var a;
if (flashmovie === globals.flashmovie && globals.is_puppets)
return await playercomm.targetTotalFrames(flashmovie, "/videoplayer")
else
return await playercomm.totalFrames(flashmovie)
};
Utils.prototype.isPlaying = async function isPlaying(flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
if (flashmovie === globals.flashmovie && globals.is_puppets)
{
// There isn't a telltarget version of IsPlaying, there's no flag for it in
// TGetProperty, and it doesn't seem to be gettable via GetVariable (though
// it's possible I just haven't tried the right thing)...
// So, for puppet toons, we need to try to track whether it seems to be playing...
return this.guessisplaying.state;
}
else
{
return await playercomm.isPlaying(flashmovie);
}
};
Utils.prototype.framesLoaded = async function framesLoaded(flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
if (flashmovie === globals.flashmovie && globals.is_puppets)
return await playercomm.targetFramesLoaded(flashmovie, '/videoplayer')
else
return await playercomm.targetFramesLoaded(flashmovie, '/')
};
Utils.prototype.isLoaded = async function isLoaded(flashmovie)
{
var frame = await this.currentFrame(flashmovie);
return frame >= 0;
};
Utils.prototype.waitLoaded = function(flashmovie)
{
var useglobal = false;
if (!flashmovie) {
useglobal = true;
flashmovie = globals.flashmovie;
}
if (!flashmovie)
return new Promise((resolve, reject) => reject());
if (useglobal && this.loadedPromise)
return this.loadedPromise;
async function poll(resolve) {
if (await this.isLoaded(flashmovie))
resolve();
else
setTimeout(poll.bind(this, resolve), 100)
}
var promise = new Promise(poll.bind(this));
if (useglobal)
this.loadedPromise = promise;
return promise;
}
Utils.prototype.stop = async function stop(flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
if (flashmovie === globals.flashmovie && globals.is_puppets)
{
await playercomm.targetStop(flashmovie, "/videoplayer");
// make sure this.guessisplaying.lastframe is updated so that it doesn't
// go back to state=true
await this.currentFrame(flashmovie);
this.guessisplaying.state = false;
}
else
{
await playercomm.stop(flashmovie);
}
};
Utils.prototype.play = async function play(flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
if (flashmovie === globals.flashmovie && globals.is_puppets)
{
await playercomm.targetPlay(flashmovie, "/videoplayer");
this.guessisplaying.state = true;
this.guessisplaying.lastframeat = new Date();
}
else
{
await playercomm.play(flashmovie);
}
};
Utils.prototype.goto = async function goto(frame, flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
if (flashmovie === globals.flashmovie && globals.is_puppets)
{
await playercomm.targetGoto(flashmovie, "/videoplayer", frame);
// make sure this.guessisplaying.lastframe is updated so that it doesn't
// go back to state=true
await this.currentFrame(flashmovie);
this.guessisplaying.state = false;
}
else
{
await playercomm.goto(flashmovie, frame);
}
};
Utils.prototype.zoomOut = async function zoomOut(factor, flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
await playercomm.zoom(flashmovie, 100 * factor);
};
Utils.prototype.zoomIn = async function zoomIn(factor, flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
await playercomm.zoom(flashmovie, 100 / factor);
};
Utils.prototype.zoomReset = async function zoomReset(factor, flashmovie)
{
if (!flashmovie)
flashmovie = globals.flashmovie;
if (!flashmovie)
return;
await playercomm.zoom(flashmovie, 0);
};
Utils.prototype.insertAfter = function(newElement, referenceElement)
{
if(referenceElement.nextSibling)
referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
else
referenceElement.parentNode.appendChild(newElement);
};
function Globals()
{
this.whichsite = 0;
if (location.hostname.indexOf("podstar") >= 0) this.whichsite = 1;
if (location.hostname.indexOf("videlectrix") >= 0) this.whichsite = 2;
if (location.pathname.indexOf("/mirror/") >= 0) this.whichsite = 3;
// icons, as Base64-encoded PNG files.
this.images = {
close:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAm' +
'JLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfWBRkTNhxuPxLkAAAAHX' +
'RFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBUaGUgR0lNUO9kJW4AAAEKSURBVCjPhdGxSgNBFA' +
'XQMzpgYWwsLEQUDBJBQgqFIChZEPR7/DA/QCGQTgQtJE1ENoWohYUgbGKQyFjErNv52nObe1' +
'9wqGWg7z0l5YVgVdOu+wUt507tqIVQ4Zodp861ooELe15M5KFI6Zfr9u25MIj6Jl4cmSIPBW' +
'rq2o5cufO4aOJDYSozNTa2pK4t03PtwUdMKRRykAmW0dTRcyNXpBQpI8GJDTR050zkNzK0bM' +
'MZLvUNZ8yCfy6Wvbc1NVyi4dloXjqWvds6uvp41pFmpVOKJWd6bgwxkmTMIotWKpwrfBkZl7' +
'uMonUHf5wSlV2+fUZrjnXdzrmyy7djD8GWTW9e51z557o1Tz85FH/WkOkaHQAAAABJRU5Erk' +
'Jggg==',
ffwd:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABl' +
'BMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAA' +
'sTAQCanBgAAAAHdElNRQfeCgQNLh+v5c+DAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aC' +
'BHSU1QV4EOFwAAAC9JREFUCNcVisENAEAIwjo6ozmKI/j0YfS4hAeUIhFBJlV0M8Mudz8uno' +
'a+LFiTHqCuHAU1qtJ6AAAAAElFTkSuQmCC',
hrwiki:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGX' +
'RFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm1QTFRF////2wAzgZDJAiGNAB' +
'6Lenp6ABCEABKFAAyDjp3O8gAAipjLlaPPFUixAB6OAA6C/f//fY3JABaIhJXK///50gAn//' +
'/4CymXyQAaAA+DOFCm1QAmDiuX//zvnV2IfI3IQ0h7ABSFN0+qZXm9ABSG9PTxABiK2wAkuQ' +
'AdSWW5WGu4cILCgYy209PZGRdjABeH0AALDiyYASGOhJTL2bi8k5OTzgAj1QAdLkilAAiDAB' +
'iQIiCBzwAbyAAk//31ABSO0gAXDB95c5nZDAxeoRhHOVCp7u3lfx1W1LrCxQYtwwApQVitwA' +
'QpJj2bAAyFKSODfI3GYna86urqysfL9fT0NUyXMDGGNk6cxgASy9rkAB2OQ1qzTmOzu8Pa4d' +
'/b+v//58zRFEqw09XR25yrIjyh9P//g5PLAAN+foy/uRY92treh6neAASDXXC9jJvKjJvL6e' +
'npiJfKDzejNk6r2wE1N0+rABKEAB6KxAAn0tPWyQAZRFuvXXfB/f392AAgKiib2QAyABaJhJ' +
'TH2XeNEy+ZzgAwBiSRKUOlgI27urrP7t/iCghS0AAfk3SoyBc+iChf3vH1VWq426GvgI/Hiz' +
'duboDCPEOXABCDSmu/DyeD///6P2K4OUJ/HByRlKHOAB+O8AAA2QI1hZPHg5TI9PT0ABuJiZ' +
'jM1tbdf43CzgApAB2We4vD7e3rwgAseInHAyGWi5rOUme3hIuqFTGaWG25dojDd5LQ5Ki1AA' +
'yMASCNcYHEAyKOABqLACSWHDeR+vr6uwAiIyBjipnJ1AIyjZvMmJyaITylAByMAB2L5wAlHD' +
'eeCCaUcHCjWGy4wBQy/7AMAgAAARFJREFUeNpiONFeuLWjfL4RM1/R4tXyx5kZdrNHZ8za5D' +
'jT3n/KimUtLDsZhPbFJilY14d5cC3o1dRnaWPILaucozf3DAND3DmnxqWnmRjyd+046NzJwG' +
'B6dMJ6xVNHJjPIrGvq1mVgYGBlZQg9xjlxD4MKh2+wKgMILLET00mPYmCqFpctYIAAEamz3A' +
'zaG4TdgmohAllpgsoMbBw5y9fshwiEW0qyM7jGbIlMjWcQCOlKMKnR8rZgyDOe3e95oErCJq' +
'LOPDGQ8xBDCv8qF9tWRkb1SStPTvNTU2JgK83OrDjMaKbB0Gwgt23zdIap83h9vBZKJ4MMdZ' +
'/Bs5EhwHBvz9qSBoftDAx9olbFiwACDABkK1N43Z86KwAAAABJRU5ErkJggg==',
next:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABl' +
'BMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAA' +
'sTAQCanBgAAAAHdElNRQfeCgQNLSOrp+DHAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aC' +
'BHSU1QV4EOFwAAACtJREFUCNdjULBhMLBhsLBhsLFhsLNhsAeiPQz2f8BoD4hrB5ayACtTsA' +
'EA6J8JvyvoxNYAAAAASUVORK5CYII=',
pause:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABl' +
'BMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAA' +
'sTAQCanBgAAAAHdElNRQfeCgQNLS1MH83AAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aC' +
'BHSU1QV4EOFwAAAA5JREFUCNdjsLFhIAUBALQwB4FBHjsqAAAAAElFTkSuQmCC',
play:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABl' +
'BMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAA' +
'sTAQCanBgAAAAHdElNRQfeCgQNLjLqOpP2AAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aC' +
'BHSU1QV4EOFwAAACdJREFUCNcdirEJAAAMg/z/qpzUAwJpG3ARRTBgyCEyxCTFVX1yN7Ejqh' +
'alykITkQAAAABJRU5ErkJggg==',
prefs:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAll' +
'BMVEUAGQASEhIfHx8fJy8pKSk2NjZBQUFJR0ZQUE9RUVFSUlJNX3NoaGhsaWdramlycG1meY' +
'98fHx+fn5wgpV0iqKKh4R4jaR9jJx8kad9kad/mbONmaWEnrmEnrqkoZy3t7fIx8bKyMHT0c' +
'3S0dDU09DV1NPP1t3W1dXY2Njb2tfe29bf3tzj4uHr6+js6+r39/f5+PgAAABrL3yvAAAAAX' +
'RSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfWBR' +
'oFKh31UQ8DAAAAgUlEQVQY022OxxLCMAwFRSc4BEIPJZQQ08v+/8+RsTExDDpIe3ijfSJ/hx' +
'9g62Dt4GaAI+8YT0t27+BxxvvE/no5pYT10lGFrE34Ja40W3g1oMGmW7YZ6hnCYexKTPVkXi' +
'vuvWe1Cz1aKqPNI3N0slI2TNYZiARJX30qERc7wBPKC4WRDzWdWHfmAAAAAElFTkSuQmCC',
prev:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABl' +
'BMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAA' +
'sTAQCanBgAAAAHdElNRQfeCgQNLgFV6vLgAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aC' +
'BHSU1QV4EOFwAAACxJREFUCNdjsGFhsOFhsJFhsLFhsKlhsPnDYPuHwR6MgAwgFyRoA1YAVM' +
'YCABGLC3k4wQ8QAAAAAElFTkSuQmCC',
rewind:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABl' +
'BMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAA' +
'sTAQCanBgAAAAHdElNRQfeCgQNLhgxgVogAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aC' +
'BHSU1QV4EOFwAAAC9JREFUCNdjYGRkYGZmYGdn4OdnkJdnsLdnqK9n+P8fhIAMIBcoCJQCKg' +
'AqY2QEALxwB9ke+WHMAAAAAElFTkSuQmCC',
stop:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1' +
'BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9' +
'4KBA0uOX3oSn4AAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQV' +
'QI12NgIA0AAAAwAAHHqoWOAAAAAElFTkSuQmCC',
update:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAMAAABG8BK2AAAC8V' +
'BMVEUAAAD/AAD+AQH/AQH/AgL+AwP/AwP+BAT/BAT/BQX+Bgb/Bgb/Bwf+CAj/CAj/CQn/Cg' +
'r+Cwv/Cwv+DAz/DAz/DQ3/Dg7+Dw//Dw//EBD+ERH/ERH/EhL/ExP+FBT/FRX/Fhb/Fxf+GB' +
'j/GBj/GRn/Ghr/Gxv/HBz/HR3/Hh7/Hx//ICD+ISH/ISH/IiL/IyP/JCT/JSX/Jib/Jyf/KS' +
'n/Kyv/LCz/LS3/Ly//MDD/MTH+MjL/MjL/MzP/NDT/NTX/Njb+Nzf/Nzf/ODj+OTn/OTn/Oj' +
'r/PDz/Pj7/Pz//QUH/QkL+Q0P/RUX/Rkb/R0f/SEj/SUn/Skr/S0v/TEz/TU3/Tk7/T0//UF' +
'D/UVH/UlL/VFT/VVX/Vlb/WFj/WVn/Wlr/W1v/XFz/XV3/Xl7/X1//YGD/YWH/YmL/Y2P/ZW' +
'X/Zmb/Z2f/aGj/aWn/amr/a2v/bGz/bW3/bm7/b2//cHD/cXH/cnL/dHT/dnb/d3f/eHj/eX' +
'n/e3v/fX3/fn7/f3//gID/gYH/goL/g4P/hIT/hob/h4f/iIj/iYn/ior/i4v/jIz/jY3/jo' +
'7+kJD/kJD/kZH/kpL/lJT/lpb/l5f/mJj/mZn/mpr/m5v/nJz/nZ3/n5//oKD/oaH/oqL/o6' +
'P/pqb/p6f/qKj/qan/qqr/q6v/rKz/ra3/r6//sLD/sbH/srL/s7P/tLT/tbX/trb/t7f/uL' +
'j/urr/u7v/vLz/vb3/vr7/v7//wMD/wcH/wsL/w8P/xMT/xcX/xsb+x8f/x8f/yMj/ycn/ys' +
'r/y8v/zMz/zc3/zs7/z8//0ND/0dH/0tL/09P+1NT/1NT/1tb/19f+2Nj/2Nj/2dn/29v/3N' +
'z/3d3/39//4OD/4eH/4uL/4+P/5OT/5eX/5ub/5+f/6Oj/6en/6ur/6+v/7Oz/7e3/7u7/7+' +
'/+8PD/8fH/8vL/8/P/9PT/9fX/9vb/9/f/+Pj/+fn/+vr/+/v//Pz//f3+/v7//v7////+AA' +
'A5GkRyAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADzoAAA+IAUHKF/gAAA' +
'AHdElNRQfXCRYICgxGxxkcAAAEL0lEQVRYw63Xe1wURRwA8Pm1G0KcHdGBkKAYjxC0yLJITU' +
'l7cr7RUjAos4AuraCH2pWCVlZaRpD5AEXDwAemQRFdmgQeCgWUPKTk4JJHomAq5PBXu/fC2z' +
't2Z7fdf+Y38/nc9zPz+83M7iEQ9VBDjCNxStKGG5xJSBSjWPV+c3m0nxNFDEP/XBf3ZkPLuv' +
'GOigiG2oLrhyvVJX26abdzFXKGWtrUPRXA5aasRjyD5ijkzJjd/2aMNkXqhCiKoxAzU9bg3n' +
'mDdXe1V4iZJIzTBnvhH9xrpxAzKbj1cYDY2Ww8AMuOL7NTiBg6koZX2rruhFhjLJsVP5iv8b' +
'FTSBj6xxo/CHqxXftwYxFTKwhY/aj9iogYOgfrRwCM/vr0qXOmpUQ0pXAVYYZa19tuymc8xq' +
'vY1u0nnOXCUQQZ6vnf/p5jiibpqgOYxqcctwRwFUEmqrD/1VvMYWppjGrUE7/ghkAHRYhxy8' +
'QdG6x79u2DBbru/mLHuQgyr+H9HYatCkvv2U3Hdmv9nSgCzKyW/MnBpW1HvSz9gRHsMUAiGe' +
'/1OA5A9XlX/TQv7pkmZtzB/Y1UNvBMP2NIDOVTeJjpT49lJNOjXHHq/Mb7eRQe5pnavAm2W3' +
'jRt33Fjw2t8C3qG3z8AWvsOnFba6YbNZTCw9yYYsg2qkfabqpZPkPOhXc2ET2bk3FpAvDXSJ' +
'BxbSsZ29O1fz2BwrtvVlzSNb60vX5ruEJI4WVUxxoTISSp46hWJaA4MtSw2dlVRXlq5jy6H6' +
'5hRzw+XasSUBwYOu2rC4YO/bmWM0EesPRQsGnsZiGFy9AlVbmRzG9dQrMr1NSEE1OEs+uEoX' +
'bivUGW+EBrIGh3KYkUDuP7bu3JPZ7mOKSsgFr4ggeRwmE87/FfW9Pqbb74vqgOg3Ay5XqGmp' +
'Re9+U7vsvL/0oybZRE9rIhU65j6Az9tZL0ffn3jdtyadNdzEAaTiZVBhn6O9y+YBxAUw64fn' +
'R+hxoUVXg5qWJjqBzcFsbutYrDwwBWHvr9rUrc5E+q2JjQExceYduHruQqBgAe3NhvLBhDrN' +
'iYyD79agXzTtXg98xs9CIvcsXGRPzQc7F68R23NlxZQtk+pZEohnoyBuDuqI9P99Y244rhJP' +
'eLMyZQ90exJgyUU/dgfPEpKYp5UeHak83fT2Tf0pXX8hMlKMj6Znu57HIMcwjmZmCcI15BVI' +
'CvWfLK7ExmKnzbPH3fJ6IV9NzZLG/LKo4Y49kmOHKUaAVB2T8h1pzGGMeLrrSVmX71iPUzaO' +
'afMyRk15Lios4EixONl0hU2ErldW82O5rOORIVU8ELDZ8xDq2sPRsmUTHvm8LuyvjFr/+Kc3' +
'0kKpbtt6OuC+OefSOlKrYTHqf5MNVPsoLs/2QjGZj/oSB5FCSPguRRkDwKkkdB8ihIHgXJoy' +
'B5FCSPguRRkDzKf7Z6NUd33kmjAAAAAElFTkSuQmCC'
};
// find flash objects
var objs;
switch (this.whichsite)
{
case 0: // www.homestarrunner.com
objs = document.getElementsByTagName("EMBED");
if (objs && objs.length >= 2)
{
this.flashmovie = objs[0];
this.navbar = objs[1];
}
else if (objs && objs.length >= 1)
{
this.flashmovie = objs[0];
this.navbar = false;
}
else
{
this.flashmovie = false;
this.navbar = false;
}
if (!this.flashmovie)
{
objs = document.getElementsByTagName("OBJECT");
if (objs && objs.length >= 1)
this.flashmovie = objs[0];
}
break;
case 1: // podstar.homestarrunner.com
objs = document.getElementsByTagName("EMBED");
this.flashmovie = false;
if (objs && objs.length >= 1)
this.navbar = objs[0];
else
this.navbar = false;
break;
case 2: // videlectrix
objs = document.getElementsByTagName("EMBED");
this.navbar = false;
if (objs && objs.length >= 1)
this.flashmovie = objs[0];
else
this.flashmovie = false;
/*settings.navbar = false;*/
break;
case 3: // mirror
objs = document.getElementsByTagName("EMBED");
this.flashmovie = false;
if (objs && objs.length >= 1)
this.flashmovie = objs[0];
if (!this.flashmovie)
{
objs = document.getElementsByTagName("OBJECT");
if (objs && objs.length >= 1)
this.flashmovie = objs[0];
}
this.navbar = document.getElementById('navbar');
/*if (!this.navbar)
settings.navbar = false;*/
var flashcontainer = document.getElementById('flash');
if (flashcontainer)
flashcontainer.style.width = "auto";
break;
}
if (this.flashmovie)
{
//expose Flash plugin-added methods
if (this.flashmovie.wrappedJSObject)
this.flashmovie = this.flashmovie.wrappedJSObject;
// confirm that this is really a flash file
// and not (for example) the embedded background sound on SB's website
var src = this.flashmovie.getAttribute('src');
if (this.flashmovie.nodeName.toLowerCase() == "object")
{
if (src)
{
if (src.substring(src.length - 4).toLowerCase() != ".swf")
this.flashmovie = false;
}
else
{
var a = this.flashmovie.getElementsByTagName('param').namedItem("movie");
if (!a || a.value.substring(a.value.length - 4).toLowerCase() != ".swf")
this.flashmovie = false;
else
src = a.value;
}
}
else if (this.flashmovie.nodeName.toLowerCase() == "embed")
{
if (!src || src.substring(src.length - 4).toLowerCase() != ".swf")
this.flashmovie = false;
}
// puppet_background.swf is a wrapper around the puppet stuff popup toons
// This flag tells things like seekbar to control the wrapped movie clip
if (src)
this.is_puppets = src == "puppet_background.swf" || src.substring(src.length - 22) == "/puppet_background.swf";
}
// Don't run large flash objects inline (gets rid of some extra padding from
// having the movie sitting on the baseline)
if (this.flashmovie)
{
this.flashmovie.style.display = "block";
this.flashmovie.style.margin = "0 auto";
}
if (this.navbar)
{
this.navbar.style.display = "block";
this.navbar.style.margin = "0 auto";
}
this.filename = window.location.pathname.toLowerCase();
var i = this.filename.lastIndexOf('/');
if (i >= 0)
this.filename = this.filename.substr(i + 1);
i = this.filename.lastIndexOf('.');
if (i >= 0)
this.filename = this.filename.substr(0,i);
}
Globals.prototype.initModules = async function initModules()
{
this.modules = {};
this.modules.settingspane = new SettingsPane();
this.modules.fullscreen = new Fullscreen();
this.modules.seekbar = new Seekbar();
this.modules.wikilink = new WikiLink();
this.modules.nextprev = new NextPrev();
this.modules.navbar = new Navbar();
this.modules.subtitles = new Subtitles();
this.modules.updates = new Updates();
// Can load the preferences in each module in parallel
var start = new Date();
var loadpromises = []
for (var i in this.modules)
loadpromises.push(this.modules[i].load());
await Promise.all(loadpromises)
var end = new Date();
console.log(`Loaded prefs in ${end - start}ms`);
// Initialise each module in sequence
for (var i in this.modules)
await this.modules[i].init();
this.modules.settingspane.initComplete();
};
function PlayerComm()
{
}
PlayerComm.handlers = {};
PlayerComm.prototype.init = function()
{
var script = document.createElement("script");
script.appendChild(document.createTextNode("(" + this.inPageContext + ")();"));
document.body.appendChild(script);
this.origin = document.location.protocol + "//" + document.location.hostname;
var defaultport = '';
if (document.location.protocol == 'http:')
defaultport = '80';
else if (document.location.protocol == 'https:')
defaultport = '443';
if (document.location.port && document.location.port != defaultport)
this.origin += ":" + document.location.port;
this.callbacks = [];
this.id_count = 0;
window.addEventListener("message", this.receiveMessage.bind(this), false);
}
PlayerComm.prototype.inPageContext = function()
{
// This code is run in the page context (which in Chrome is the only one
// allowed to communicate with the Flash object) to communicate with the script
var origin = document.location.protocol + "//" + document.location.hostname;
var defaultport = '';
if (document.location.protocol == 'http:')
defaultport = '80';
else if (document.location.protocol == 'https:')
defaultport = '443';
if (document.location.port && document.location.port != defaultport)
origin += ":" + document.location.port;
var handlers = {}
function receiveMessage(event)
{
if (event.origin !== origin)
return;
if (event.data.message.substring(0, 8) !== 'aio_req_')
return;
var message = event.data.message.substring(8);
handlers[message](event.data);
}
window.addEventListener("message", receiveMessage, false);
// Documentation for the Flash interface is really lacking...
// Adobe removed the docs from their website.
// Luckily, the Wayback Machine captures all
// http://web.archive.org/web/20100710000820/http://www.adobe.com/support/flash/publishexport/scriptingwithflash/scriptingwithflash_03.html
// http://web.archive.org/web/20090210205955/http://www.adobe.com/support/flash/publishexport/scriptingwithflash/scriptingwithflash_04.html
handlers.currentFrame = function(data)
{
var elem = document.getElementById(data.id);
var a = elem.CurrentFrame;
if (typeof(a) == 'function')
a = elem.CurrentFrame();
if (typeof(a) !== 'number' || a < 0)
a = -1;
window.postMessage({
message: "aio_resp_paramCallback",
callback: data.callback,
val: a
}, origin);
}
handlers.targetCurrentFrame = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.TCurrentFrame) == 'function')
a = elem.TCurrentFrame(data.target);
else
a = -1;
window.postMessage({
message: "aio_resp_paramCallback",
callback: data.callback,
val: a
}, origin);
}
handlers.totalFrames = function(data)
{
var elem = document.getElementById(data.id);
var a = elem.TotalFrames;
if (typeof(a) == 'function')
a = elem.TotalFrames();
if (typeof(a) !== 'number' || a < 0)
a = -1;
window.postMessage({
message: "aio_resp_paramCallback",
callback: data.callback,
val: a
}, origin);
}
handlers.targetTotalFrames = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.TGetPropertyAsNumber) == 'function')
a = elem.TGetPropertyAsNumber(data.target, 5); // TOTAL_FRAMES
else
a = -1;
window.postMessage({
message: "aio_resp_paramCallback",
callback: data.callback,
val: a
}, origin);
}
handlers.isPlaying = function(data)
{
var elem = document.getElementById(data.id);
var a = elem.IsPlaying;
if (typeof(a) == 'function')
a = elem.IsPlaying();
if (typeof(a) == 'number')
a = (a != 0);
else if (typeof(a) != 'boolean')
a = false;
window.postMessage({
message: "aio_resp_paramCallback",
callback: data.callback,
val: a
}, origin);
}
handlers.targetFramesLoaded = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.TGetPropertyAsNumber) == 'function')
a = elem.TGetPropertyAsNumber(data.target, 12); // FRAMES_LOADED
else
a = -1;
window.postMessage({
message: "aio_resp_paramCallback",
callback: data.callback,
val: a
}, origin);
}
handlers.stop = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.StopPlay) == 'function')
elem.StopPlay();
window.postMessage({
message: "aio_resp_basicCallback",
callback: data.callback
}, origin);
}
handlers.targetStop = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.TStopPlay) == 'function')
elem.TStopPlay(data.target);
window.postMessage({
message: "aio_resp_basicCallback",
callback: data.callback
}, origin);
}
handlers.play = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.Play) == 'function')
elem.Play();
window.postMessage({
message: "aio_resp_basicCallback",
callback: data.callback
}, origin);
}
handlers.targetPlay = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.TPlay) == 'function')
elem.TPlay(data.target);
window.postMessage({
message: "aio_resp_basicCallback",
callback: data.callback
}, origin);
}
handlers.goto = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.GotoFrame) == 'function')
elem.GotoFrame(data.frame);
window.postMessage({
message: "aio_resp_basicCallback",
callback: data.callback
}, origin);
}
handlers.targetGoto = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.TGotoFrame) == 'function')
elem.TGotoFrame(data.target, data.frame);
window.postMessage({
message: "aio_resp_basicCallback",
callback: data.callback
}, origin);
}
handlers.zoom = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.Zoom) == 'function')
elem.Zoom(data.zoom);
window.postMessage({
message: "aio_resp_basicCallback",
callback: data.callback
}, origin);
}
handlers.setScaleMode = function(data)
{
var elem = document.getElementById(data.id);
if (typeof(elem.SetVariable) == 'function')
elem.SetVariable("Stage.scaleMode", data.scaleMode);
window.postMessage({
message: "aio_resp_basicCallback",
callback: data.callback
}, origin);
}
}
PlayerComm.prototype.currentFrame = function(elem)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_currentFrame",
callback: this.storeCallback(resolve),
id: this.getId(elem)
}, this.origin));
}
PlayerComm.prototype.targetCurrentFrame = function(elem, target)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_targetCurrentFrame",
callback: this.storeCallback(resolve),
id: this.getId(elem),
target: target
}, this.origin));
}
PlayerComm.prototype.totalFrames = function(elem)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_totalFrames",
callback: this.storeCallback(resolve),
id: this.getId(elem)
}, this.origin));
}
PlayerComm.prototype.targetTotalFrames = function(elem, target)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_targetTotalFrames",
callback: this.storeCallback(resolve),
id: this.getId(elem),
target: target
}, this.origin));
}
PlayerComm.prototype.isPlaying = function(elem)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_isPlaying",
callback: this.storeCallback(resolve),
id: this.getId(elem)
}, this.origin));
}
PlayerComm.prototype.targetFramesLoaded = function(elem, target)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_targetFramesLoaded",
callback: this.storeCallback(resolve),
id: this.getId(elem),
target: target
}, this.origin));
}
PlayerComm.prototype.stop = function(elem)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_stop",
callback: this.storeCallback(resolve),
id: this.getId(elem)
}, this.origin));
}
PlayerComm.prototype.targetStop = function(elem, target)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_targetStop",
callback: this.storeCallback(resolve),
id: this.getId(elem),
target: target
}, this.origin));
}
PlayerComm.prototype.play = function(elem)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_play",
callback: this.storeCallback(resolve),
id: this.getId(elem)
}, this.origin));
}
PlayerComm.prototype.targetPlay = function(elem, target)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_targetPlay",
callback: this.storeCallback(resolve),
id: this.getId(elem),
target: target
}, this.origin));
}
PlayerComm.prototype.goto = function(elem, frame)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_goto",
callback: this.storeCallback(resolve),
id: this.getId(elem),
frame: frame
}, this.origin));
}
PlayerComm.prototype.targetGoto = function(elem, target, frame)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_targetGoto",
callback: this.storeCallback(resolve),
id: this.getId(elem),
target: target,
frame: frame
}, this.origin));
}
PlayerComm.prototype.zoom = function(elem, zoom)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_zoom",
callback: this.storeCallback(resolve),
id: this.getId(elem),
zoom: zoom
}, this.origin));
}
PlayerComm.prototype.setScaleMode = function(elem, scaleMode)
{
return new Promise(resolve => window.postMessage({
message: "aio_req_setScaleMode",
callback: this.storeCallback(resolve),
id: this.getId(elem),
scaleMode: scaleMode
}, this.origin));
}
PlayerComm.prototype.receiveMessage = function(event)
{
if (event.origin !== this.origin)
return;
if (event.data.message.substring(0, 9) !== 'aio_resp_')
return;
var message = event.data.message.substring(9);
PlayerComm.handlers[message].call(this, event.data);
}
PlayerComm.handlers.basicCallback = function(data)
{
var callback = this.getCallback(data.callback);
if (callback)
callback();
}
PlayerComm.handlers.paramCallback = function(data)
{
var callback = this.getCallback(data.callback);
if (callback)
callback(data.val);
}
PlayerComm.prototype.storeCallback = function(callback)
{
if (!callback)
return -1;
var ix = 0;
while (this.callbacks[ix] !== undefined)
ix++;
this.callbacks[ix] = callback;
return ix;
}
PlayerComm.prototype.getCallback = function(ix)
{
if (ix < 0)
return undefined;
var callback = this.callbacks[ix];
this.callbacks[ix] = undefined;
return callback;
}
PlayerComm.prototype.getId = function(elem)
{
if (!elem.id)
{
this.id_count++;
elem.id = "aio_id_" + this.id_count;
}
return elem.id;
}
function SettingsPane()
{
}
SettingsPane.prototype.load = function()
{
}
SettingsPane.prototype.init = function()
{
utils.addGlobalStyle(
'#settingsbox, #settingslink\n' +
'{\n' +
'\tborder-right: 1px solid #666;\n' +
'\tborder-bottom: 1px solid #666;\n' +
'\tbackground: #EEE;\n' +
'\tcolor: #000;\n' +
'\tposition: fixed;\n' +
'\toverflow: auto;\n' +
'\tleft: 0;\n' +
'\ttop: 0;\n' +
'\tfont: 12px sans-serif;\n' +
'\ttext-align: left;\n' +
'\tz-index: 2;\n' +
'}\n' +
'#settingsbox\n' +
'{\n' +
'\twidth: 350px;\n' +
'}\n' +
'#settingstitlebar\n' +
'{\n' +
'\tfont-weight: bolder;\n' +
'\tbackground: #CCC;\n' +
'\tborder-bottom: 1px solid #666;\n' +
'\tpadding: 3px;\n' +
'}\n' +
'#settingstitlebar img\n' +
'{\n' +
'\tvertical-align: text-bottom;\n' +
'}\n' +
'#settingstitlebar .prefsicon\n' +
'{\n' +
'\tfloat: left;\n' +
'\tmargin-right: 0.5em;\n' +
'}\n' +
'#settingstitlebar .buttonimage, #settingslink .buttonimage\n' +
'{\n' +
'\tcursor: pointer;\n' +
'\tdisplay: block;\n' +
'}\n' +
'#settingstitlebar .buttonimage\n' +
'{\n' +
'\tfloat: right;\n' +
'}\n' +
'#settingsbox form\n' +
'{\n' +
'\tmargin: 0;\n' +
'\tpadding: 3px;\n' +
'}\n' +
'#settingsbox ul, #settingsbox li\n' +
'{\n' +
'\tlist-style: none;\n' +
'\tmargin: 0;\n' +
'\tpadding: 0;\n' +
'}\n' +
'#settingsbox ul ul\n' +
'{\n' +
'\tmargin-left: 2em;\n' +
'}\n' +
'#settingsbox input[type="checkbox"]\n' +
'{\n' +
'\tmargin-right: 0.25em;\n' +
'}\n' +
'#settingsbuttons\n' +
'{\n' +
'\ttext-align: center;\n' +
'}\n' +
'#settingslink\n' +
'{\n' +
'\tpadding: 3px;\n' +
'}\n' +
""
);
var settingsbox = document.createElement('div');
this.settingsbox = settingsbox;
settingsbox.id = 'settingsbox';
settingsbox.style.display = 'none';
document.body.appendChild(settingsbox);
var titlebar = document.createElement('div');
titlebar.id = 'settingstitlebar';
settingsbox.appendChild(titlebar);
var closebutton = document.createElement('img');
closebutton.src = globals.images.close;
closebutton.title = "Click to hide preferences";
closebutton.className = 'buttonimage';
closebutton.addEventListener('click', this.hidePane.bind(this), false);
titlebar.appendChild(closebutton);
var prefslogo = document.createElement('img');
prefslogo.src = globals.images.prefs;
prefslogo.className = 'prefsicon';
titlebar.appendChild(prefslogo);
titlebar.appendChild(document.createTextNode("Preferences"));
var settingsform = document.createElement('form');
settingsbox.appendChild(settingsform);
var settingslist = document.createElement('ul');
this.settingslist = settingslist;
var a = window.innerHeight - 75;
if (a < 40) a = 40;
settingslist.style.maxHeight = a + 'px';
settingslist.style.overflow = 'auto'; // vertical scrollbar if needed
window.addEventListener('resize', this.resizeWindow.bind(this), true);
settingsform.appendChild(settingslist);
var div = document.createElement('div');
div.id = 'settingsbuttons';
settingsform.appendChild(div);
var savebutton = document.createElement('input');
savebutton.type = "submit";
savebutton.value = "Save and Apply";
div.appendChild(savebutton);
var nocachebutton = document.createElement('input');
nocachebutton.type = "submit";
nocachebutton.value = "Clear subtitles cache";
nocachebutton.addEventListener("click", this.cacheDodge.bind(this), false);
div.appendChild(document.createTextNode(" "));
div.appendChild(nocachebutton);
settingsform.addEventListener("submit", this.saveSettings.bind(this), false);
var settingslink = document.createElement('div');
this.settingslink = settingslink;
settingslink.id = 'settingslink';
var settingslinkimage = document.createElement('img');
settingslinkimage.src = globals.images.prefs;
settingslinkimage.title = "Click to show preferences";
settingslinkimage.className = 'prefsicon buttonimage';
settingslinkimage.addEventListener('click', this.showPane.bind(this), false);
settingslink.appendChild(settingslinkimage);
document.body.appendChild(settingslink);
this.hidePanels = [];
};
SettingsPane.prototype.saveSettings = function(e)
{
// stop the form from actually being submitted
if (e && e.preventDefault)
e.preventDefault();
for (var i in globals.modules)
globals.modules[i].updateSettings();
return false;
};
SettingsPane.prototype.updateSettings = function(){};
SettingsPane.prototype.showPane = function()
{
this.settingsbox.style.display = "block";
this.settingslink.style.display = "none";
};
SettingsPane.prototype.hidePane = function()
{
this.settingsbox.style.display = "none";
this.settingslink.style.display = "block";
};
SettingsPane.prototype.resizeWindow = function()
{
var a = window.innerHeight - 75;
if (a < 40) a = 40;
this.settingslist.style.maxHeight = a + 'px';
};
SettingsPane.prototype.cacheDodge = function(e)
{
if (e && e.preventDefault)
e.preventDefault();
utils.setPref("cachedodge", Math.random().toString());
globals.modules.updates.cacheDodge();
};
SettingsPane.prototype.addSettingRow = function(parent)
{
if (!parent)
parent = this.settingslist;
else
{
var checkbox = undefined;
if (parent.tagName.toLowerCase() == "input")
{
checkbox = parent;
parent = parent.parentNode;
}
var ul = parent.getElementsByTagName("ul");
if (ul.length)
parent = ul[ul.length - 1];
else
{
ul = document.createElement("ul");
parent.appendChild(ul);
parent = ul;
if (checkbox)
{
this.hidePanels.push({checkbox: checkbox, panel: ul});
checkbox.addEventListener("click", this.showHidePanel.bind(this, checkbox, ul), false);
}
}
}
var settingrow = document.createElement('li');
parent.appendChild(settingrow);
return settingrow;
};
SettingsPane.prototype.addCheckbox = function(id, label, title, checked, parent)
{
var settingrow = this.addSettingRow(parent);
var settingcheckbox = document.createElement('input');
settingcheckbox.type = 'checkbox';
settingcheckbox.checked = checked;
settingcheckbox.title = title;
settingcheckbox.id = 'setting_' + id;
settingrow.appendChild(settingcheckbox);
var settinglabel = document.createElement('label');
settinglabel.htmlFor = 'setting_' + id;
settinglabel.appendChild(document.createTextNode(label));
settinglabel.title = settingcheckbox.title;
settingrow.appendChild(settinglabel);
return settingcheckbox;
};
SettingsPane.prototype.showHidePanel = function(checkbox, panel)
{
panel.style.display = checkbox.checked ? "" : "none";
};
SettingsPane.prototype.initComplete = function()
{
for (var i = 0; i < this.hidePanels.length; i++)
this.showHidePanel(this.hidePanels[i].checkbox, this.hidePanels[i].panel);
};
function Fullscreen()
{
}
Fullscreen.prototype.load = async function load()
{
this.shouldresize = await utils.getPref('resize', true);
this.noscale = await utils.getPref('noscale', false);
}
Fullscreen.prototype.init = async function init()
{
this.setting_main = globals.modules.settingspane.addCheckbox('resize', "Resize flash to full-screen", "Resizes the toon so it fills the entire window", this.shouldresize);
this.setting_noscale = globals.modules.settingspane.addCheckbox('noscale', "Show behind the black", "Lets you see what's happening beyond the frames", this.noscale, this.setting_main);
if (!globals.flashmovie)
return;
this.initwidth = globals.flashmovie.width;
this.initheight = globals.flashmovie.height;
if (this.initwidth.toString().indexOf('%') >= 0 || this.initwidth.toString().indexOf('%') >= 0)
{
this.isPercentage = true;
this.aspect = 1.0;
}
else
{
this.isPercentage = false;
this.aspect = this.initwidth / this.initheight;
}
window.addEventListener('resize', this.doResize.bind(this), true);
this.doResize();
if (this.noscale)
await this.setScaleMode("noScale");
};
Fullscreen.prototype.doResize = function()
{
if (!globals.flashmovie)
return;
if (!this.shouldresize)
{
globals.flashmovie.style.width = this.initwidth + "px";
globals.flashmovie.style.height = this.initheight + "px";
if (globals.modules.seekbar.seekbar)
globals.modules.seekbar.seekbar.style.width = Math.max(this.initwidth, 450) + "px";
return;
}
var dw = window.innerWidth;
var dh = window.innerHeight;
var a = document.defaultView.getComputedStyle(document.body, null);
// parseInt will take the number part at the start, turning eg "10px" into 10
dw -= parseInt(a.marginLeft,10);
dw -= parseInt(a.marginRight,10);
dh -= parseInt(a.marginTop,10);
dh -= parseInt(a.marginBottom,10);
if (globals.navbar)
{
a = document.defaultView.getComputedStyle(globals.navbar, null);
dh -= parseInt(a.height,10);
dh -= parseInt(a.marginTop,10);
dh -= parseInt(a.marginBottom,10);
}
if (globals.modules.seekbar.seekbar)
{
a = document.defaultView.getComputedStyle(globals.modules.seekbar.seekbar, null);
dh -= parseInt(a.height,10);
dh -= parseInt(a.marginTop,10);
dh -= parseInt(a.marginBottom,10);
}
if (globals.modules.subtitles.subtitleholder)
{
a = document.defaultView.getComputedStyle(globals.modules.subtitles.subtitleholder, null);
dh -= parseInt(a.height,10);
dh -= parseInt(a.marginTop,10);
dh -= parseInt(a.marginBottom,10);
}
if (globals.modules.subtitles.errorsholder)
{
a = document.defaultView.getComputedStyle(globals.modules.subtitles.errorsholder, null);
dh -= parseInt(a.height,10);
dh -= parseInt(a.marginTop,10);
dh -= parseInt(a.marginBottom,10);
}
// enforce a (rather small) minimum size, regardless of how much crap is squeezed below the frame
if (dw < 100) dw = 100;
if (dh < 100) dh = 100;
// if it was a percentage size, or we're looking outside the frame, just fill the whole window.
// otherwise, keep the aspect ratio correct... "touch inside" style.
if (!this.isPercentage && !this.noscale)
{
if(dw <= dh * this.aspect)
dh = Math.floor(dw / this.aspect);
else
dw = Math.floor(dh * this.aspect);
}
// set embed's size
globals.flashmovie.style.width = dw + "px";
globals.flashmovie.style.height = dh + "px";
if (globals.modules.seekbar.seekbar)
globals.modules.seekbar.seekbar.style.width = Math.max(dw, 450) + "px";
};
Fullscreen.prototype.setScaleMode = async function setScaleMode(scaleMode)
{
await utils.waitLoaded();
await playercomm.setScaleMode(globals.flashmovie, scaleMode);
};
Fullscreen.prototype.updateSettings = function()
{
this.shouldresize = this.setting_main.checked;
utils.setPref("resize", this.shouldresize);
var old_noscale = this.noscale;
this.noscale = this.setting_noscale.checked;
utils.setPref("noscale", this.noscale);
this.doResize();
if (this.noscale && !old_noscale)
this.setScaleMode("noScale");
else if (!this.noscale && old_noscale)
this.setScaleMode("showAll");
};
function Seekbar()
{
}
Seekbar.prototype.load = async function load() {
this.enabled = await utils.getPref('seekbar', true);
this.framecounter = await utils.getPref('frames', false);
this.zoom = await utils.getPref('zoom', false);
}
Seekbar.prototype.init = async function init() {
this.setting_enabled = globals.modules.settingspane.addCheckbox('seekbar', "Show seek bar", "Lets you fast forward and rewind", this.enabled);
this.setting_framecounter = globals.modules.settingspane.addCheckbox('framecounter', "Show frame counter on seek bar", "Shows you exactly where you are", this.framecounter, this.setting_enabled);
this.setting_zoom = globals.modules.settingspane.addCheckbox('zoom', "Show zooming controls", "Allows zooming in on the toon", this.zoom, this.setting_enabled);
if (!globals.flashmovie)
return;
if (this.enabled)
await this.addSeekbar();
this.dragging = false;
this.paused = !await utils.isPlaying();
document.addEventListener("mousemove", this.dragMousemove.bind(this), false);
document.addEventListener("mouseup", this.release.bind(this), false);
window.setInterval(this.update.bind(this), 50);
};
Seekbar.prototype.updateSettings = function()
{
if (this.enabled)
this.removeSeekbar();
this.enabled = this.setting_enabled.checked;
utils.setPref("seekbar", this.enabled);
this.framecounter = this.setting_framecounter.checked;
utils.setPref("frames", this.framecounter);
this.zoom = this.setting_zoom.checked;
utils.setPref("zoom", this.zoom);
if (this.enabled && globals.flashmovie)
this.addSeekbar();
};
Seekbar.prototype.addSeekbar = async function addSeekbar()
{
this.dragging = false;
this.paused = !await utils.isPlaying();
this.seekbar = document.createElement("div");
var where = globals.flashmovie;
while(where.parentNode.tagName.toLowerCase()=="object" || where.parentNode.tagName.toLowerCase()=="embed")
where=where.parentNode;
utils.insertAfter(this.seekbar, where);
this.seekbar.style.width = globals.flashmovie.width;
this.seekbar.style.margin = "0 auto";
var table=document.createElement("table");
table.style.width="100%";
this.seekbar.appendChild(table);
var row=table.insertRow();
this.pauseButton=document.createElement("button");
this.pauseButtonImg = document.createElement("img");
this.pauseButtonImg.src = globals.images.pause;
this.pauseButton.appendChild(this.pauseButtonImg);
var buttonCell=row.insertCell();
buttonCell.appendChild(this.pauseButton);
var rewindCell=row.insertCell();
this.rewindButton=document.createElement("button");
var img = document.createElement("img");
img.src = globals.images.rewind;
this.rewindButton.appendChild(img);
rewindCell.appendChild(this.rewindButton);
var prevCell=row.insertCell();
this.prevButton=document.createElement("button");
img = document.createElement("img");
img.src = globals.images.prev;
this.prevButton.appendChild(img);
prevCell.appendChild(this.prevButton);
this.slider=row.insertCell();
this.slider.width="100%";
var visibleSlider=document.createElement("div");
visibleSlider.style.position="relative";
visibleSlider.style.height="0.5em";
visibleSlider.style.width="100%";
visibleSlider.style.borderRadius="0.25em";
visibleSlider.style.background="#333";
this.slider.appendChild(visibleSlider);
this.loadmeter=document.createElement("div");
this.loadmeter.style.position="absolute";
this.loadmeter.style.top=this.loadmeter.style.left = "0";
this.loadmeter.style.height="0.5em";
this.loadmeter.style.width="0";
this.loadmeter.style.borderRadius="0.25em";
this.loadmeter.style.background="#aaa";
visibleSlider.appendChild(this.loadmeter);
this.thumb=document.createElement("div");
this.thumb.style.position="absolute";
this.thumb.style.height="1em";
this.thumb.style.width="0.5em";
this.thumb.style.top="-0.25em";
this.thumb.style.borderRadius="0.25em";
this.thumb.style.background="#666";
visibleSlider.appendChild(this.thumb);
var nextCell=row.insertCell();
this.nextButton=document.createElement("button");
img = document.createElement("img");
img.src = globals.images.next;
this.nextButton.appendChild(img);
nextCell.appendChild(this.nextButton);
var ffCell=row.insertCell();
this.ffButton=document.createElement("button");
img = document.createElement("img");
img.src = globals.images.ffwd;
this.ffButton.appendChild(img);
ffCell.appendChild(this.ffButton);
if (this.framecounter)
{
var frameCell=row.insertCell();
var framediv=document.createElement("div");
framediv.style.background="#ccc";
framediv.style.color="#000";
framediv.style.fontWeight="bold";
framediv.style.padding = "0 5px";
frameCell.appendChild(framediv);
this.framecountertext=document.createTextNode("");
framediv.appendChild(this.framecountertext);
}
else
this.framecountertext = false;
if (this.zoom && !globals.modules.fullscreen.noscale)
{
var zoomOutCell=row.insertCell();
this.zoomOutButton=document.createElement("button");
// \u2212 is −
this.zoomOutButton.appendChild(document.createTextNode("\u2212"));
zoomOutCell.appendChild(this.zoomOutButton);
var zoomNormalCell=row.insertCell();
this.zoomNormalButton=document.createElement("button");
this.zoomNormalButton.appendChild(document.createTextNode("0"));
zoomNormalCell.appendChild(this.zoomNormalButton);
var zoomInCell=row.insertCell();
this.zoomInButton=document.createElement("button");
this.zoomInButton.appendChild(document.createTextNode("+"));
zoomInCell.appendChild(this.zoomInButton);
}
else
{
this.zoomOutButton = false;
this.zoomNormalButton = false;
this.zoomInButton = false;
}
this.slider.addEventListener("mousedown", this.drag.bind(this), false);
this.pauseButton.addEventListener("click",this.pauseUnpause.bind(this),false);
this.rewindButton.addEventListener("click",this.rewind.bind(this),false);
this.prevButton.addEventListener("click",this.prevFrame.bind(this),false);
this.nextButton.addEventListener("click",this.nextFrame.bind(this),false);
this.ffButton.addEventListener("click",this.fastforward.bind(this),false);
if (this.zoomOutButton)
{
this.zoomOutButton.addEventListener("click",this.zoomOut.bind(this),false);
this.zoomNormalButton.addEventListener("click",this.zoomNormal.bind(this),false);
this.zoomInButton.addEventListener("click",this.zoomIn.bind(this),false);
}
globals.modules.fullscreen.doResize();
};
Seekbar.prototype.removeSeekbar = function()
{
if (!this.seekbar)
return;
this.seekbar.parentNode.removeChild(this.seekbar);
this.seekbar = undefined;
globals.modules.fullscreen.doResize();
};
Seekbar.prototype.update = async function update()
{
if (!this.seekbar)
return;
var fullSliderWidth = parseInt(document.defaultView.getComputedStyle(this.slider, null).width, 10);
var sliderWidth = fullSliderWidth - parseInt(document.defaultView.getComputedStyle(this.thumb, null).width, 10);
var tot = await utils.totalFrames();
if (tot > 0)
{
var frame = await utils.currentFrame();
if (frame < 0)
frame = 0;
if (this.framecountertext)
{
var a = tot.toString();
var b = (frame+1).toString();
while (b.length < a.length)
b = "\u2007" + b; // U+2007 FIGURE SPACE
this.framecountertext.nodeValue = b+"/"+a;
}
if(!this.dragging)
{
if (tot > 1)
this.thumb.style.left = (frame/(tot - 1)*sliderWidth)+"px";
else
this.thumb.style.left = "0";
this.paused = !await utils.isPlaying();
this.pauseButtonImg.src = this.paused ? globals.images.play : globals.images.pause;
}
var loaded = await utils.framesLoaded();
this.loadmeter.style.width = (loaded/tot*fullSliderWidth)+"px";
}
else if (this.framecountertext)
{
this.framecountertext.nodeValue = "Loading...";
}
};
Seekbar.prototype.pauseUnpause = async function pauseUnpause()
{
this.paused = await utils.isPlaying();
this.pauseButtonImg.src = this.paused ? globals.images.play : globals.images.pause;
if (this.paused)
await utils.stop();
else
await utils.play();
};
Seekbar.prototype.rewind = async function rewind()
{
await utils.goto(0);
await utils.play();
};
Seekbar.prototype.fastforward = async function fastforward()
{
var tot = await utils.totalFrames();
await utils.goto(tot - 1);
};
Seekbar.prototype.prevFrame = async function prevFrame()
{
var frame = await utils.currentFrame();
await utils.goto(frame - 1);
};
Seekbar.prototype.nextFrame = async function nextFrame()
{
var frame = await utils.currentFrame();
await utils.goto(frame + 1);
};
Seekbar.prototype.zoomIn = async function zoomIn()
{
await utils.zoomIn(1.5);
};
Seekbar.prototype.zoomOut = async function zoomOut()
{
await utils.zoomOut(1.5);
};
Seekbar.prototype.zoomNormal = async function zoomNormal()
{
await utils.zoomReset();
};
Seekbar.prototype.drag = function(e)
{
this.dragging=true;
this.dragMousemove(e);
e.preventDefault();
return false;
};
Seekbar.prototype.dragMousemove = async function dragMousemove(e)
{
if (!this.dragging) return;
var pageX = e.clientX + document.body.scrollLeft;
var rect = this.slider.getBoundingClientRect();
var thumbWidth = parseInt(document.defaultView.getComputedStyle(this.thumb, null).width, 10);
var width = rect.right - rect.left - thumbWidth;
var pos = (pageX - rect.left - thumbWidth/2) / width;
if (pos < 0)
pos = 0;
if (pos > 1)
pos = 1;
var t = await utils.totalFrames();
if (t > 1)
{
var frame = Math.round(t * pos);
await utils.goto(frame);
}
this.thumb.style.left = (pos * width) + "px";
};
Seekbar.prototype.release = function()
{
if (!this.dragging) return;
if (!this.paused)
utils.play();
this.dragging = false;
};
function WikiLink()
{
}
WikiLink.prototype.load = async function load() {
this.enabled = await utils.getPref('hrwiki', true);
}
WikiLink.prototype.init = function()
{
this.setting_enabled = globals.modules.settingspane.addCheckbox('hrwiki', "Add HRWiki link", "Adds a link to the appropriate page on the Homestar Runner Wiki", this.enabled);
this.buildWikiLink();
this.showWikiLink();
};
WikiLink.prototype.updateSettings = function()
{
this.enabled = this.setting_enabled.checked;
utils.setPref("hrwiki", this.enabled);
// This is called before Subtitles.updateSettings, so delay until after that happens
// so we can update the subtitles link as appropriate
window.setTimeout(this.showWikiLink.bind(this), 0);
};
WikiLink.prototype.buildWikiLink = function()
{
// many pages on the mirror have an "info" link in the navbar (thanks Tom!)... use that
if (globals.whichsite === 3)
{
var navbar;
if (globals.modules.navbar && globals.modules.navbar.originalnavbar)
navbar = globals.modules.navbar.originalnavbar;
else
navbar = globals.navbar;
if (navbar)
{
var a = navbar.getElementsByTagName("a");
for (var i = 0; i < a.length; i++)
{
if (a[i].firstChild.nodeType === 3 && a[i].firstChild.nodeValue === "info")
{
this.addHRWikiLink(a[i].href, true);
return;
}
}
}
}
// pull the filename from the url, use it as a link to HRWiki
// all the filenames except a couple of special-cases are
// redirects to their articles
// don't link to certain pages, they aren't redirects, but already existing pages
// also detect a 404 error and special-case Strong Sad's Lament
if (document.title === "Oops! You bwoke it.")
this.addHRWikiLink("404'd");
else if (globals.filename === "interview")
this.addHRWikiLink("The_Interview");
else if (globals.filename === "fhqwhgads")
this.addHRWikiLink("Everybody_to_the_Limit");
else if (globals.filename === "trogdor")
this.addHRWikiLink("TROGDOR!");
else if (globals.filename === "marshie")
this.addHRWikiLink("Meet_Marshie");
else if (globals.filename === "eggs")
this.addHRWikiLink("Eggs_(toon)");
else if (globals.filename === "fireworks")
this.addHRWikiLink("Happy_Fireworks");
else if (globals.filename === "sbemail100")
this.addHRWikiLink("Not_the_100th_Email!!!");
else if (globals.filename === "sbemail200")
this.addHRWikiLink("Page_Load_Error");
else if (globals.filename === "sbcg4ap")
this.addHRWikiLink("Strong_Bad's_Cool_Game_for_Attractive_People_Advertisement");
else if (globals.filename === "dangeresque")
this.addHRWikiLink("Dangeresque_Roomisode_1:_Behind_the_Dangerdesque");
else if (location.pathname.substr(0, 12) === "/sadjournal/" && globals.filename != "wonderyears" && globals.filename != "super8")
this.addHRWikiLink("Strong_Sad's_Lament");
else if (location.pathname.substr(0,5) === "/vii/" && (globals.filename === "" || globals.filename === "index"))
this.addHRWikiLink("Viidelectrix");
else if (globals.filename === "" || globals.filename === "index")
{
if (document.location.pathname === "/slash/slash/")
this.addHRWikiLink("Screenland_-_24_Apr_2017");
else if (globals.whichsite === 0)
this.addHRWikiLink("Index_Page");
else if (globals.whichsite === 1)
this.addHRWikiLink("Podstar_Runner");
else if (globals.whichsite === 2)
this.addHRWikiLink("Videlectrix");
//else if (globals.whichsite === 3)
// ; // this will be a 403 page - do nothing.
}
else
this.addHRWikiLink(globals.filename);
};
WikiLink.prototype.addHRWikiLink = function(pagename, isurl)
{
this.linkdiv = document.createElement("div");
this.linkdiv.style.borderLeft = this.linkdiv.style.borderBottom = '1px solid #666';
this.linkdiv.style.background = '#EEE';
this.linkdiv.style.position = "fixed";
this.linkdiv.style.overflow = 'auto';
this.linkdiv.style.right = "0px";
this.linkdiv.style.top = "0px";
this.linkdiv.style.padding = "3px";
var link = document.createElement("a");
if (isurl)
link.href = pagename;
else
link.href = "http://www.hrwiki.org/wiki/" + escape(pagename.replace(/ /g, '_'));
link.title = "See the HRWiki article for this page";
link.style.display = "block";
link.style.textDecoration = "none";
this.linkdiv.appendChild(link);
var img=document.createElement("img");
img.style.border="0px";
img.style.display="block";
img.src=globals.images.hrwiki;
link.appendChild(img);
this.sublink = document.createElement("a");
this.sublink.title = "See the HRWiki article for this page's subtitles";
this.sublink.style.display = "block";
this.sublink.style.textDecoration = "none";
this.sublink.style.textAlign = "center";
this.sublink.style.fontSize = this.sublink.style.lineHeight = "16px";
this.sublink.style.marginTop = "3px";
this.linkdiv.appendChild(this.sublink);
this.sublink.appendChild(document.createTextNode('S'));
document.body.appendChild(this.linkdiv);
};
WikiLink.prototype.showWikiLink = function()
{
if (this.enabled)
{
this.linkdiv.style.display = "block";
if (globals.modules.subtitles && globals.modules.subtitles.enabled)
{
this.sublink.style.display = "block";
this.sublink.href = "http://www.hrwiki.org/wiki/Subtitles:" + escape(globals.filename.replace(/ /g, '_')) + "/" + escape(globals.modules.subtitles.language);
}
else
this.sublink.style.display = "none";
}
else
this.linkdiv.style.display = "none";
};
function NextPrev()
{
}
NextPrev.prototype.load = async function load() {
this.enabled = await utils.getPref('prevnext', true);
this.docheck = await utils.getPref('checknext', true);
}
NextPrev.prototype.init = function()
{
this.setting_enabled = globals.modules.settingspane.addCheckbox('prevnext', "Show previous/next buttons", "Lets you easily move through SBEmails, TGS, etc", this.enabled);
this.setting_docheck = globals.modules.settingspane.addCheckbox('checknext', "Check if next exists", 'Doesn\'t add a "next" link on the latest SBEmail, etc', this.docheck, this.setting_enabled);
this.createPrevNext();
this.showPrevNext();
};
NextPrev.prototype.updateSettings = function()
{
this.enabled = this.setting_enabled.checked;
utils.setPref("prevnext", this.enabled);
this.docheck = this.setting_docheck.checked;
utils.setPref("checknext", this.docheck);
this.showPrevNext();
};
NextPrev.prototype.createPrevNext = function()
{
// this is coded like this instead of just looking for /(\d+)/ so that it
// doesn't find pages like commandos3 or xmas04
var result;
if ((result = globals.filename.match(/^(sbemail|tgs|answer|bizcasfri|puppetjam|main)(\d+)$/)))
{
// sbemail100 and sbemail200 aren't actually sbemails
if (!(result[1] == "sbemail" && (result[2] == "100" || result[2] == "200")))
this.addPrevNextlinks(result[1],parseInt(result[2],10));
}
else if (globals.filename == "sbemailahundred")
this.addPrevNextlinks("sbemail", 100);
else if (globals.filename == "kotpoptoon")
this.addPrevNextlinks("sbemail", 151);
else if (globals.filename == "sbemailtwohundred")
this.addPrevNextlinks("sbemail", 200);
else if (globals.filename == "hremail3184")
this.addPrevNextlinks("sbemail", 201);
else if (globals.filename == "dween_tgs")
this.addPrevNextlinks("tgs", 6);
};
NextPrev.prototype.addPrevNextlinks = function(series, num)
{
if (num > 1)
{
this.prevlink = document.createElement("a");
this.prevlink.href = this.makeLink(series, num - 1);
this.prevlink.style.position="fixed";
this.prevlink.style.left="0px";
this.prevlink.style.bottom="0px";
this.prevlink.style.padding="3px";
this.prevlink.style.background="white";
this.prevlink.style.border="1px solid black";
this.prevlink.style.textDecoration="none";
this.prevlink.style.display = "none";
var img = document.createElement("img");
img.style.border = "none";
img.src = globals.images.prev;
this.prevlink.appendChild(img);
document.body.appendChild(this.prevlink);
}
this.nextlink = document.createElement("a");
this.nextlink.href = this.makeLink(series, num + 1);
this.nextlink.style.position="fixed";
this.nextlink.style.right="0px";
this.nextlink.style.bottom="0px";
this.nextlink.style.padding="3px";
this.nextlink.style.background="white";
this.nextlink.style.border="1px solid black";
this.nextlink.style.textDecoration="none";
this.nextlink.style.display = "none";
img = document.createElement("img");
img.style.border = "none";
img.src = globals.images.next;
this.nextlink.appendChild(img);
document.body.appendChild(this.nextlink);
this.checkedNext = false;
};
NextPrev.prototype.makeLink = function(series, num)
{
if (series == "sbemail" && num == 100)
return "sbemailahundred.html";
else if (series == "sbemail" && num == 151)
return "kotpoptoon.html";
else if (series == "sbemail" && num == 200)
return "sbemailtwohundred.html";
else if (series == "sbemail" && num == 201)
return "hremail3184.html";
else
return series + num + ".html";
};
NextPrev.prototype.showPrevNext = function()
{
if (this.enabled)
{
if (this.prevlink)
this.prevlink.style.display = "block";
if (this.docheck && !this.checkedNext && this.nextlink)
/*no await*/ this.doCheckNext();
else if (this.nextlink)
this.nextlink.style.display = "block";
}
else
{
if (this.prevlink)
this.prevlink.style.display = "none";
if (this.nextlink)
this.nextlink.style.display = "none";
}
};
NextPrev.prototype.doCheckNext = async function doCheckNext()
{
try {
var res = await utils.downloadPage(this.nextlink.href + "?cachedodge=" + (await utils.getPref('cachedodge', 0)), "HEAD");
} catch (e) {
this.nextlink.parentNode.removeChild(this.nextlink);
this.nextlink = undefined;
return;
}
if (res.status == 200 && res.headers.indexOf("404error.html") < 0)
{
this.checkedNext = true;
this.showPrevNext();
}
else if (this.nextlink)
{
this.nextlink.parentNode.removeChild(this.nextlink);
this.nextlink = undefined;
}
};
NextPrev.prototype.onCheckError = function()
{
};
function Navbar()
{
}
Navbar.prototype.SECTIONS = {
t: "Big Toons",
sh: "Shorts",
ho: "Holday Toons",
p: "Puppet Stuff",
teh: "Powered by The Cheat",
sb: "Strong Bad Emails",
am: "Marzipan's Answering Machine",
tgs: "Teen Girl Squad"
};
Navbar.prototype.MAIN_COUNT = 26;
Navbar.prototype.load = async function load() {
this.enabled = await utils.getPref('navbar', false);
this.rando = {};
for (var i in this.SECTIONS)
this.rando[i] = await utils.getPref('rando' + i, true);
}
Navbar.prototype.init = function() {
utils.addGlobalStyle(
'#newnavbar\n' +
'{\n' +
'\tmargin: 0;\n' +
'\tpadding: 0;\n' +
'\ttext-align: center;\n' +
'\ttext-transform: lowercase;\n' +
'\theight: 10px;\n' +
'\tfont: 10px/10px sans-serif;\n' +
'}\n' +
'#newnavbar li\n' +
'{\n' +
'\tmargin: 0;\n' +
'\tpadding: 0;\n' +
'\tdisplay: inline;\n' +
'}\n' +
'#newnavbar :link, #newnavbar :visited\n' +
'{\n' +
'\tcolor: #666;\n' +
'\tfont-family: sans-serif;\n' +
'\ttext-decoration: none;\n' +
'\tpadding: 0 1em;\n' +
'}\n' +
'#newnavbar :link:hover, #newnavbar :visited:hover\n' +
'{\n' +
'\tcolor: #999;\n' +
'}\n' +
'\n' +
"/* for overriding podstar's settings: */\n" +
'#newnavbar :link, #newnavbar :visited\n' +
'{\n' +
'\tfont-weight: normal;\n' +
'}\n' +
'#newnavbar :link:hover, #newnavbar :visited:hover\n' +
'{\n' +
'\tbackground: transparent;\n' +
'\tfont-weight: normal;\n' +
'}\n' +
""
);
this.setting_enabled = globals.modules.settingspane.addCheckbox('navbar', "Plain HTML navbar", "Replaces the flash navbar with normal links, so you can open in tabs, etc", this.enabled);
this.setting_rando = {};
for (var i in this.SECTIONS)
this.setting_rando[i] = globals.modules.settingspane.addCheckbox('rando' + i, "Include " + this.SECTIONS[i] + " in rando", 'Limit the "rando" function to what you like to watch', this.rando[i], this.setting_enabled);
this.allrandourls = false;
this.randourls = false;
this.originalnavbar = globals.navbar;
this.newnavbar = this.buildNavbar(this.originalnavbar);
this.showNavbar();
};
Navbar.prototype.updateSettings = function()
{
this.enabled = this.setting_enabled.checked;
utils.setPref("navbar", this.enabled);
for (var i in this.SECTIONS)
{
this.rando[i] = this.setting_rando[i].checked;
utils.setPref("rando" + i, this.rando[i]);
}
this.filterRando();
this.showNavbar();
};
Navbar.prototype.showNavbar = function()
{
if (this.enabled)
{
if (this.originalnavbar)
this.originalnavbar.style.display = "none";
this.newnavbar.style.display = "";
this.newnavbar.style.marginTop = (globals.modules.seekbar.enabled ? "0" : "10px");
globals.navbar = this.newnavbar;
/*no await*/ this.loadRandoXML();
}
else
{
if (this.originalnavbar)
this.originalnavbar.style.display = "";
this.newnavbar.style.display = "none";
globals.navbar = this.originalnavbar;
}
globals.modules.fullscreen.doResize();
};
Navbar.prototype.buildNavbar = function(where)
{
var newnavbar = document.createElement("ul");
newnavbar.id = "newnavbar";
if (where)
{
while(where.parentNode.tagName.toLowerCase() == "object")
where = where.parentNode;
utils.insertAfter(newnavbar, where);
}
else
document.body.appendChild(newnavbar);
this.mainlink = this.addnavbarlink(newnavbar, "https://homestarrunner.com/main" + Math.floor(Math.random() * this.MAIN_COUNT + 1) + ".html", "Main");
// just for fun, re-randomise on each mouse-over (for the status bar)
this.mainlink.addEventListener("mouseout", this.newMainLink.bind(this), false);
this.addnavbarlink(newnavbar, "https://homestarrunner.com/toons.html", "Toons");
this.addnavbarlink(newnavbar, "https://homestarrunner.com/games.html", "Games");
this.addnavbarlink(newnavbar, "https://homestarrunner.com/characters2.html", "Characters");
this.addnavbarlink(newnavbar, "https://homestarrunner.com/homester.html", "Downloads");
this.addnavbarlink(newnavbar, "https://homestarrunner.com/store.html", "Store", "storelink");
this.addnavbarlink(newnavbar, "https://homestarrunner.com/sbemail.html", "SB Emails");
//this.addnavbarlink(newnavbar, "https://feeds.feedburner.com/HomestarRunner", "Subscribe");
this.addnavbarlink(newnavbar, "https://www.youtube.com/user/homestarrunnerdotcom", "YouTube");
this.addnavbarlink(newnavbar, "https://homestarrunner.com/email.html", "Contact");
//this.addnavbarlink(newnavbar, "https://podstar.homestarrunner.com/", "Podcast");
this.addnavbarlink(newnavbar, "https://homestarrunner.com/legal.html", "Legal");
this.randolink = this.addnavbarlink(newnavbar, "javascript:void(alert('rando.xml not loaded yet... be patient'))", "Rando");
this.randolink.addEventListener("mouseout", this.newRandoLink.bind(this), false);
return newnavbar;
};
Navbar.prototype.addnavbarlink = function(ul, href, title, extraclass)
{
var li = document.createElement("li");
var link = document.createElement("a");
link.href = href;
link.appendChild(document.createTextNode(title));
if (extraclass)
link.className = extraclass;
li.appendChild(link);
ul.appendChild(li);
return link;
};
Navbar.prototype.newMainLink = function()
{
this.mainlink.href="https://homestarrunner.com/main" + Math.floor(Math.random() * this.MAIN_COUNT + 1) + ".html";
};
Navbar.prototype.newRandoLink = function()
{
if (!this.randourls)
return;
if (this.randourls.length > 0)
{
var r = this.randourls[Math.floor(Math.random() * this.randourls.length)];
this.randolink.href = r.u;
this.randolink.title = r.n;
}
else
{
this.randolink.href = "javascript:void(alert('Nothing to choose from'))";
this.randolink.title = "Nothing to choose from";
}
};
Navbar.prototype.loadRandoXML = async function loadRandoXML()
{
// Only run this once
if (this.haveLoadedXML)
return;
this.haveLoadedXML = true;
try {
var res = await utils.downloadPage(
"https://homestarrunner.com/rando.xml?cachedodge=" + (await utils.getPref('cachedodge', 0))
);
var parser = new DOMParser();
// fix invalid XML...
// add missing root element
var doc = res.text.replace(/<\?xml.*?\?>/g, ""); // strip <?xml ?> tag
doc = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n<rando>" + doc + "</rando>";
// fix bad ampersands
doc = doc.replace(/&(?!\w*;)/g, "&");
doc = parser.parseFromString(doc, "application/xml");
var sbemailcounter = 0;
this.allrandourls = [];
for (var i = 0; i < doc.documentElement.childNodes.length; i++)
{
var node = doc.documentElement.childNodes[i];
if (node.nodeType == 1)
{
var type = node.nodeName.toLowerCase();
var u = node.getAttribute('u');
var n = node.getAttribute('n');
if (!n) n = "Untitled";
if (type == "sb")
{
sbemailcounter++;
n = "SBEmail: " + n;
}
if (u)
this.allrandourls.push({u: "https://homestarrunner.com/" + u, n: n, type: type});
else
this.allrandourls.push({u: "https://homestarrunner.com/sbemail" + sbemailcounter + ".html", n: n, type: type});
}
}
this.filterRando();
} catch (e) {
this.randolink.href = "javascript:void(alert('Error loading rando.xml... try refreshing'))";
}
};
Navbar.prototype.filterRando = function()
{
if (!this.allrandourls)
return;
this.randourls = [];
for (var i in this.allrandourls)
{
var r = this.allrandourls[i];
if (this.rando[r.type] === false) // === false so that it's considered "true" for undefined... if they add a new toon type
continue;
this.randourls.push(r);
}
this.newRandoLink();
};
function Subtitles()
{
}
Subtitles.prototype.DEFAULTXML = escape('<?xml version="1.0" encoding="utf-8"?>\n<transcript xml:lang="en-us">\n<line start="" end="" speaker=""></line>\n</transcript>');
Subtitles.prototype.NAMES_OPTS = ["Never", "Voiceovers", "Always"];
Subtitles.prototype.NO_SUBTITLES = document.createComment("");
Subtitles.prototype.load = async function load() {
this.enabled = await utils.getPref('subtitles', false);
this.captions = await utils.getPref('captions', true);
this.colours = await utils.getPref('colours', true);
this.testsubs = await utils.getPref('testsubs', false);
this.language = await utils.getPref('language', "en");
this.testsubsdata = unescape(await utils.getPref('testsubsdata', this.DEFAULTXML));
this.names = await utils.getPref('names', 0);
}
Subtitles.prototype.init = function()
{
utils.addGlobalStyle(
'.subtitles\n' +
'{\n' +
'\tbackground: black;\n' +
'\tcolor: white;\n' +
'\tfont: 20px/25px sans-serif;\n' +
'\theight: 100px;\n' +
'\ttext-align: center;\n' +
'}\n' +
'\n' +
'.subtitle_errors\n' +
'{\n' +
'\tbackground: black;\n' +
'\tcolor: red;\n' +
'\tfont: 12pt sans-serif;\n' +
'\ttext-align: left;\n' +
'\tmargin: 0.5em;\n' +
'}\n' +
'\n' +
'.subtitles .italic\n' +
'{\n' +
'\tfont-style: italic;\n' +
'}\n' +
'.subtitles .italic em, .subtitles .italic cite, .subtitles .italic i\n' +
'{\n' +
'\tfont-style: normal;\n' +
'}\n' +
""
);
this.setting_enabled = globals.modules.settingspane.addCheckbox('subtitles', "Show subtitles", "Shows subtitles or captions below the toon, if any are available", this.enabled);
var settingrow = globals.modules.settingspane.addSettingRow(this.setting_enabled);
var settinglabel = document.createElement('label');
settinglabel.htmlFor = "setting_language";
settinglabel.appendChild(document.createTextNode('Subtitle Language: '));
settinglabel.title = 'Display subtitles in this language, if any';
settingrow.appendChild(settinglabel);
this.setting_language = document.createElement('select');
this.setting_language.title = 'Display subtitles in this language, if any';
this.setting_language.id = "setting_language";
this.setting_language.disabled = true;
settingrow.appendChild(this.setting_language);
this.language_populated = false;
/*no await*/ this.populateLanguage();
this.setting_captions = globals.modules.settingspane.addCheckbox('captions', "Show captions", "Include sound effects in the subtitles", this.captions, this.setting_enabled);
this.setting_colours = globals.modules.settingspane.addCheckbox('colours', "Use colours", "Distinguish characters by colour effects (turn off if colourblind)", this.colours, this.setting_enabled);
settingrow = globals.modules.settingspane.addSettingRow(this.setting_enabled);
settinglabel = document.createElement('label');
settinglabel.htmlFor = "setting_names";
settinglabel.appendChild(document.createTextNode('Show speakers\' names: '));
settinglabel.title = 'Show the speakers\' names before their lines';
settingrow.appendChild(settinglabel);
this.setting_names = document.createElement('select');
this.setting_names.title = 'Show the speakers\' names before their lines';
this.setting_names.id = "setting_names";
settingrow.appendChild(this.setting_names);
for (var i = 0; i < this.NAMES_OPTS.length; i++)
{
var option = document.createElement('option');
option.value = i;
option.appendChild(document.createTextNode(this.NAMES_OPTS[i]));
if (this.names == i)
option.selected = true;
this.setting_names.appendChild(option);
}
this.setting_testsubs = globals.modules.settingspane.addCheckbox('testsubs', "Test subtitles script", "Use this to test a subtitles script (copy/paste into a text box)", this.testsubs, this.setting_enabled);
settingrow = globals.modules.settingspane.addSettingRow(this.setting_testsubs);
this.setting_testsubsdata = document.createElement('textarea');
this.setting_testsubsdata.title = 'Paste your XML data here';
this.setting_testsubsdata.id = "setting_testsubsdata";
this.setting_testsubsdata.style.width = "100%";
this.setting_testsubsdata.style.height = "10em";
this.setting_testsubsdata.style.fontSize = "8px";
this.setting_testsubsdata.style.textAlign = "left";
this.setting_testsubsdata.appendChild(document.createTextNode(this.testsubsdata));
settingrow.appendChild(this.setting_testsubsdata);
this.charsready = false;
this.subsready = false;
/*no await*/ this.setupSubtitles();
window.setInterval(this.update.bind(this), 50);
};
Subtitles.prototype.updateSettings = function()
{
this.enabled = this.setting_enabled.checked;
utils.setPref('subtitles', this.enabled);
if (this.language_populated)
{
this.language = this.setting_language.value;
utils.setPref('language', this.language);
}
this.captions = this.setting_captions.checked;
utils.setPref('captions', this.captions);
this.colours = this.setting_colours.checked;
utils.setPref('colours', this.colours);
this.names = this.setting_names.value;
utils.setPref('names', this.names);
this.testsubs = this.setting_testsubs.checked;
utils.setPref('testsubs', this.testsubs);
this.testsubsdata = this.setting_testsubsdata.value;
utils.setPref('testsubsdata', escape(this.testsubsdata));
/*no await*/ this.setupSubtitles();
};
Subtitles.prototype.populateLanguage = async function populateLanguage()
{
var option = document.createElement('option');
option.appendChild(document.createTextNode("Loading..."));
option.selected = true;
this.setting_language.appendChild(option);
try {
var xml = await utils.downloadWiki("Subtitles:Languages");
xml = utils.parseWikiXML(xml);
} catch (e) {
while (this.setting_language.firstChild)
this.setting_language.removeChild(this.setting_language.firstChild);
var option = document.createElement('option');
option.appendChild(document.createTextNode("Error loading languages"));
option.selected = true;
this.setting_language.appendChild(option);
return;
}
while (this.setting_language.firstChild)
this.setting_language.removeChild(this.setting_language.firstChild);
var languages = xml.getElementsByTagName('language');
for (var i = 0; i < languages.length; i++)
{
var node = languages[i];
// sanity-check the node
if (node.hasAttribute('xml:lang') && node.firstChild && (node.firstChild.nodeType == xml.TEXT_NODE || node.firstChild.nodeType == xml.CDATA_SECTION_NODE))
{
var option = document.createElement('option');
option.appendChild(document.createTextNode(node.firstChild.nodeValue));
option.lang = option.value = node.getAttribute('xml:lang');
if (option.lang == this.language)
option.selected = true;
option.dir = "ltr";
if (node.hasAttribute('dir'))
option.dir = node.getAttribute('dir');
this.setting_language.appendChild(option);
}
}
this.setting_language.disabled = false;
this.language_populated = true;
};
Subtitles.prototype.removeSubtitles = function()
{
if (this.subtitleholder)
{
this.subtitleholder.parentNode.removeChild(this.subtitleholder);
this.subtitleholder = undefined;
}
if (this.errorsholder)
{
this.errorsholder.parentNode.removeChild(this.errorsholder);
this.errorsholder = undefined;
}
globals.modules.fullscreen.doResize();
};
Subtitles.prototype.createSubtitleHolder = function()
{
this.subtitleholder = document.createElement('div');
this.subtitleholder.className = "subtitles";
var where = globals.flashmovie;
if (globals.modules.seekbar && globals.modules.seekbar.seekbar)
where = globals.modules.seekbar.seekbar;
while(where.parentNode.tagName.toLowerCase() == "object")
where = where.parentNode;
utils.insertAfter(this.subtitleholder, where);
this.subtitleholder.appendChild(this.NO_SUBTITLES);
this.currentsubtitles = this.NO_SUBTITLES;
globals.modules.fullscreen.doResize();
};
Subtitles.prototype.createErrorsHolder = function()
{
this.errorsholder = document.createElement('div');
this.errorsholder.className = "subtitle_errors";
var where = globals.flashmovie;
if (globals.modules.seekbar && globals.modules.seekbar.seekbar)
where = globals.modules.seekbar.seekbar;
while(where.parentNode.tagName.toLowerCase() == "object")
where = where.parentNode;
utils.insertAfter(this.errorsholder, where);
globals.modules.fullscreen.doResize();
};
Subtitles.prototype.transcriptError = function(message)
{
if (!this.errorsholder)
this.createErrorsHolder();
var pre = document.createElement("pre");
pre.appendChild(document.createTextNode(message));
this.errorsholder.appendChild(pre);
globals.modules.fullscreen.doResize();
};
Subtitles.prototype.setupSubtitles = async function setupSubtitles()
{
this.removeSubtitles();
if (!this.enabled)
return;
this.createSubtitleHolder();
this.setSubtitles(document.createTextNode("Loading subtitles..."));
try {
await this.loadCharacters();
await this.reloadSubs();
} catch (e) {
this.removeSubtitles();
if (this.testsubs)
this.transcriptError(e.toString());
}
};
Subtitles.prototype.loadCharacters = async function loadCharacters() {
if (this.charsready)
return;
var xml = await utils.downloadWiki('Subtitles:Characters');
xml = utils.parseWikiXML(xml);
this.characters = {
sfx: {
color: "#FFF",
sfx: true,
name: {en: ""}
}
};
var speakers = xml.getElementsByTagName("speaker");
for (var i = 0; i < speakers.length; i++)
{
var speakername = speakers[i].getAttribute("id");
this.characters[speakername] = {color: speakers[i].getAttribute("color"), sfx: speakers[i].hasAttribute("sfx"), name: {en: ""}};
var names = speakers[i].getElementsByTagName("name");
for (var j = 0; j < names.length; j++)
{
var lang = names[j].getAttribute("xml:lang");
if (names[j].firstChild && (names[j].firstChild.nodeType == xml.TEXT_NODE || names[j].firstChild.nodeType == xml.CDATA_SECTION_NODE))
this.characters[speakername].name[lang] = names[j].firstChild.nodeValue;
}
}
this.charsready = true;
}
Subtitles.prototype.reloadSubs = async function reloadSubs()
{
if (!this.charsready)
return;
this.subsready = false;
this.removeSubtitles();
this.createSubtitleHolder();
this.setSubtitles(document.createTextNode("Loading subtitles..."));
var xml;
if (!this.testsubs)
xml = await utils.downloadWiki('Subtitles:' + globals.filename + '/' + this.language);
else
xml = this.testsubsdata;
xml = utils.parseWikiXML(xml);
this.parseTranscript(xml);
this.subsready = true;
};
Subtitles.prototype.parseTranscript = function(xml)
{
// set some defaults
if (!xml.documentElement.getAttribute("xml:lang")) xml.documentElement.setAttribute("xml:lang", this.language);
if (!xml.documentElement.getAttribute("dir")) xml.documentElement.setAttribute("dir", "ltr");
// inherit languages to all subnodes
this.inheritLanguages(xml.documentElement);
// now parse the lines into divs and get start and end frames
var lines = xml.getElementsByTagName("line");
var previousEnd = NaN;
this.transcript = [];
for (var i = 0; i < lines.length; i++)
{
var line = {};
// ignore lines with missing start/end values
// so you can add all the lines and not worry about timing them until later
if (!lines[i].getAttribute("start") || !lines[i].getAttribute("end"))
continue;
line.start = parseInt(lines[i].getAttribute("start"), 10);
line.end = parseInt(lines[i].getAttribute("end"), 10);
if (this.testsubs)
{
if (isNaN(line.start))
this.transcriptError("Start value \"" + lines[i].getAttribute("start") + "\" is not a number");
if (isNaN(line.end))
this.transcriptError("End value \"" + lines[i].getAttribute("end") + "\" is not a number");
if (line.end < line.start)
this.transcriptError("Line beginning frame " + line.start + " ends before it begins.");
if (line.start < previousEnd)
this.transcriptError("Line beginning frame " + line.start + " starts before the previous frame ends.");
previousEnd = line.end;
}
line.text = this.importNodes(lines[i]);
this.transcript.push(line);
}
};
Subtitles.prototype.inheritLanguages = function(node)
{
for (var i = node.firstChild; i; i = i.nextSibling)
{
if (i.nodeType == i.ELEMENT_NODE)
{
if (!i.hasAttribute("xml:lang")) i.setAttribute("xml:lang", node.getAttribute("xml:lang"));
if (!i.hasAttribute("dir")) i.setAttribute("dir", node.getAttribute("dir"));
this.inheritLanguages(i);
}
}
};
Subtitles.prototype.importNodes = function(node)
{
var name = node.nodeName.toLowerCase();
if (this.characters[name])
{
node.setAttribute("speaker", name);
name = "speaker";
}
if (name == "line" || name == "speaker")
{
// format the speaker appropriately as a div
var speaker = node.getAttribute("speaker");
if (!this.captions && (speaker == "sfx" || node.hasAttribute("sfx")))
return document.createComment(""); // return nothing
newNode = document.createElement("div");
var char = this.characters[speaker];
if (!char)
{
if (this.testsubs && speaker)
{
var line = node;
while (line && line.nodeName != "line")
line = line.parentNode;
if (line)
this.transcriptError("Line beginning frame " + line.getAttribute("start") + " has an unrecognised speaker name \"" + speaker + '"');
}
char = {color: "#FFF", name: {en: ""}};
}
if (this.colours)
newNode.style.color = char.color;
if (node.hasAttribute("voiceover"))
newNode.className = "italic";
if (node.hasAttribute("volume"))
{
newNode.style.fontSize = (node.getAttribute("volume") * 100) + "%";
newNode.style.lineHeight = "1.25em";
}
newNode.lang = node.getAttribute("xml:lang");
newNode.dir = node.getAttribute("dir");
var hasSpeakerChildren = false;
for (var i = node.firstChild; i; i = i.nextSibling)
{
if (i.nodeType == i.ELEMENT_NODE)
{
newNode.appendChild(this.importNodes(i));
var a = i.nodeName.toLowerCase();
if (a == "line" || a == "speaker" || this.characters[a])
hasSpeakerChildren = true;
}
else if (i.nodeType == i.TEXT_NODE || i.nodeType == i.CDATA_SECTION_NODE)
newNode.appendChild(document.importNode(i, true));
}
if (!hasSpeakerChildren)
{
// this is a normal text node - do some extra text stuff
if (char.sfx || node.hasAttribute("sfx"))
{
newNode.insertBefore(document.createTextNode('('), newNode.firstChild);
newNode.appendChild(document.createTextNode(')'));
newNode.className = "italic";
}
if (this.names == 2 || (node.hasAttribute("voiceover") && this.names == 1))
{
// find the language with the longest prefix match
// fall back to "en" if none found
var bestmatch = "en";
var langbits = node.getAttribute("xml:lang").split("-");
for (i = langbits.length; i >= 1; i--)
{
var lang = langbits.slice(0, i).join("-");
if (char.name[lang])
{
bestmatch = lang;
break;
}
}
if (char.name[bestmatch] != '')
newNode.insertBefore(document.createTextNode(char.name[bestmatch] + ": "), newNode.firstChild);
}
}
return newNode;
}
else
{
// check element blacklist
if (name == "script" ||
name == "style" ||
name == "object" ||
name == "param" ||
name == "embed" ||
name == "a" ||
name == "img" ||
name == "applet" ||
name == "map" ||
name == "frame" ||
name == "iframe" ||
name == "meta" ||
name == "link" ||
name == "form" ||
name == "input")
{
if (this.testsubs)
this.transcriptError("Blacklisted element \"" + name + "\" stripped.");
return document.createComment(""); // return nothing
}
var newNode = document.createElement(name);
// copy across attributes
for (i = 0; i < node.attributes.length; i++)
{
name = node.attributes[i].nodeName.toLowerCase();
// check attribute blacklist
// javascript, and anything that might load stuff from offsite
if (name != "href" && name != "src" && name.substring(0, 2) != "on")
{
if (name == "style")
{
// regex taken from MediaWiki Sanitizer.php
if (!node.attributes[i].value.match(/(expression|tps*:\/\/|url\\s*\()/i))
newNode.setAttribute("style", node.attributes[i].value);
}
else if (name == "xml:lang")
{
newNode.lang = node.attributes[i].value;
}
else
newNode.setAttribute(node.attributes[i].nodeName, node.attributes[i].value);
}
else if (this.testsubs)
this.transcriptError("Blacklisted attribute \"" + name + "\" stripped.");
}
// copy across children
for (i = node.firstChild; i; i = i.nextSibling)
{
if (i.nodeType == i.ELEMENT_NODE)
newNode.appendChild(this.importNodes(i));
else if (i.nodeType == i.TEXT_NODE || i.nodeType == i.CDATA_SECTION_NODE)
newNode.appendChild(document.importNode(i, true));
}
return newNode;
}
return document.createComment(""); // fallthrough
};
Subtitles.prototype.update = async function update()
{
if (!this.enabled || !this.charsready || !this.subsready || !this.subtitleholder)
return;
var frame = await utils.currentFrame();
if (frame < 0)
return;
frame++; // Make 1-based
// binary search to find the right transcript line
var first = 0;
var last = this.transcript.length;
while(first < (last - 1))
{
var mid = (first + last) >> 1;
if (frame >= this.transcript[mid].start)
{
first = mid;
if (frame <= this.transcript[mid].end)
break;
}
else
last = mid;
}
// should we actually show the line?
if(this.transcript[first] && this.transcript[first].start <= frame && this.transcript[first].end >= frame)
this.setSubtitles(this.transcript[first].text);
else
this.setSubtitles(false);
};
Subtitles.prototype.setSubtitles = function(node)
{
if (!this.subtitleholder)
return;
if (!node)
node = this.NO_SUBTITLES;
if (this.currentsubtitles != node)
{
this.subtitleholder.replaceChild(node, this.subtitleholder.firstChild);
this.currentsubtitles = node;
}
};
// Returned by Special:Getversion
// <versionstring>4.4.101=http://www.hrwiki.org/w/index.php?title=User:Phlip/Greasemonkey&action=raw&ctype=text/javascript&fakeextension=.user.js</versionstring>
function Updates()
{
}
Updates.CURRENT_VERSION = [4, 4, 101];
Updates.CHECK_INTERVAL = 24*60*60*1000; // once per day
Updates.prototype.load = async function load() {
this.enabled = await utils.getPref('updates', true);
}
Updates.prototype.init = function()
{
// We don't need to do this update checking on Chrome - the Chrome Web Store
// will handle that for us
if (!utils.useGMFunctions)
{
delete globals.modules.updates;
return;
}
this.setting_enabled = globals.modules.settingspane.addCheckbox('updates', "Check for updates", "Regularly check for updates to the All-in-one script", this.enabled);
/*no await*/ this.doCheck();
};
Updates.prototype.updateSettings = function()
{
this.enabled = this.setting_enabled.checked;
utils.setPref("updates", this.enabled);
this.doCheck();
};
Updates.prototype.doCheck = async function doCheck()
{
if (this.updatelink) {
this.updatelink.parentNode.removeChild(this.updatelink);
this.updatelink = null;
}
if (!this.enabled)
return;
var str;
if (Date.now() - (await utils.getPref("lastchecktime", 0)) > Updates.CHECK_INTERVAL)
{
str = await utils.downloadPage("http://www.hrwiki.org/wiki/Special:Getversion/User:Phlip/Greasemonkey?cachedodge=" + Math.random());
str = str.text;
utils.setPref("lastchecktime", Date.now());
utils.setPref("lastcheckstring", str);
}
else
str = await utils.getPref("lastcheckstring", "");
var parts = str.split("@@");
for (var i = 0; i < parts.length; i++)
{
var matches = parts[i].match(/^(\d+)\.(\d+)\.(\d+)=(.*)$/);
if (!matches) continue;
if (matches[1] > Updates.CURRENT_VERSION[0] ||
(matches[1] == Updates.CURRENT_VERSION[0] && matches[2] > Updates.CURRENT_VERSION[1]) ||
(matches[1] == Updates.CURRENT_VERSION[0] && matches[2] == Updates.CURRENT_VERSION[1] && matches[3] > Updates.CURRENT_VERSION[2]))
{
var updatelink = document.createElement('a');
updatelink.href=matches[4];
updatelink.style.display = "block";
updatelink.style.position = 'fixed';
updatelink.style.left = '0px';
updatelink.style.top = '0px';
updatelink.style.border = 'none';
updatelink.style.zIndex = 1;
var updatelinkimage = document.createElement('img');
updatelinkimage.src = globals.images.update;
var oldversionstr = Updates.CURRENT_VERSION[0] + "." + Updates.CURRENT_VERSION[1] + "." + Updates.CURRENT_VERSION[2];
var newversionstr = matches[1] + "." + matches[2] + "." + matches[3];
updatelinkimage.title = "Click here to update from script version " + oldversionstr + " to " + newversionstr;
updatelinkimage.style.display = "block";
updatelinkimage.style.border = 'none';
updatelink.appendChild(updatelinkimage);
document.body.appendChild(updatelink);
this.updatelink = updatelink;
return;
}
}
};
Updates.prototype.cacheDodge = function()
{
utils.setPref("lastchecktime", 0);
/*no await*/ this.doCheck();
}
// Podstar/Videlectrix (stock IIS), HRWiki and stock Apache error pages, respectively. Don't do anything on those pages.
if (document.title == "The page cannot be found" || document.title == "Homestar Runner Wiki - 404 Not Found" || document.title == "404 Not Found")
return;
var utils = new Utils();
var globals = new Globals();
var playercomm = new PlayerComm();
playercomm.init();
await globals.initModules();
})();
/*</pre>*/