User:Phlip/Greasemonkey
From Homestar Runner Wiki
/*
Contents |
Installation instructions
Firefox
If you don't have it already, you'll need to install Greasemonkey, then restart Firefox and return to this page.
Then, just click on this link 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 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
*/ // 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 // @downloadURL http://www.hrwiki.org/w/index.php?title=User:Phlip/Greasemonkey&action=raw&ctype=text/javascriptfakeextension=.user.js // @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* // @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(); })(); /**/