Last active
April 9, 2022 11:18
-
-
Save mscharley/2d73c81d1d70c504d58d to your computer and use it in GitHub Desktop.
Tampermonkey script - Adds keyboard shortcuts for easily navigating the Homestuck webcomic. May work for other MSPA comics.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Homestuck Keyboard Advancer | |
// @namespace http://matt.scharley.me/ | |
// @version 1.13 | |
// @updateURL https://gist.githubusercontent.com/mscharley/2d73c81d1d70c504d58d/raw/homestuck-keyboard.user.js | |
// @downloadURL https://gist.githubusercontent.com/mscharley/2d73c81d1d70c504d58d/raw/homestuck-keyboard.user.js | |
// @description Adds keyboard shortcuts for easily navigating the Homestuck webcomic. May work for other MSPA comics. | |
// @author Matthew Scharley | |
// @match *://www.homestuck.com/* | |
// @grant none | |
// @run-at document-end | |
// ==/UserScript== | |
// Click the raw button above to install this script. | |
(function() { | |
"use strict"; | |
var backLink = null, | |
nextLink = null; | |
var comicAreas = document.querySelectorAll( | |
// Regular coloring | |
'td[bgcolor="c6c6c6"],td[bgcolor="#c6c6c6"],' + | |
// Doc Scratch coloring (scratch.php) | |
'td[bgcolor="0E4603"],td[bgcolor="#0E4603"],' + | |
// Trickster coloring (trickster.php) | |
'td[bgcolor="FF73FD"],td[bgcolor="#FF73FD"],' + | |
// Act 6 Act 6 coloring (ACT6ACT6.php) | |
'td[bgcolor="073C00"],td[bgcolor="#073C00"],' + | |
// New website design | |
'div.row' | |
); | |
var links = nodeListQuerySelectorAll(comicAreas, 'a[href^="?s="],a[href^="/story/"]'); | |
for (var i in links) { | |
if (!links[i].href) { | |
continue; | |
} | |
var parent = links[i].parentNode; | |
if (parent.nodeName == 'FONT' && parent.size == '5') { | |
nextLink = links[i]; | |
} else if (parent.parentNode.classList.contains("o_story-nav")) { | |
nextLink = links[i]; | |
} else if (links[i].innerHTML == 'Go Back') { | |
backLink = links[i]; | |
} | |
} | |
if (nextLink == null) { | |
console.log("hka: unable to find a next link."); | |
} else { console.log("hka: next link", nextLink); } | |
if (backLink == null) { | |
console.log("hka: unable to find a back link."); | |
} else { console.log("hka: back link", backLink); } | |
document.addEventListener('keyup', function(event) { | |
var flash_elements = nodeListQuerySelectorAll(comicAreas, '[type="application/x-shockwave-flash"], canvas'); | |
var has_flash = flash_elements.length; | |
if (has_flash) { | |
console.log("hka: not adding listeners to this page because it looks like an [S]"); | |
return; | |
} | |
switch(event.code) { | |
case "ArrowRight": | |
event.preventDefault(); | |
console.log("hka: trying to click", nextLink); | |
if (nextLink) { | |
fireEvent(nextLink, 'click'); | |
} | |
break; | |
case "ArrowLeft": | |
event.preventDefault(); | |
console.log("hka: trying to click", backLink); | |
if (backLink) { | |
fireEvent(backLink, 'click'); | |
} | |
break; | |
case "KeyP": | |
// P | |
event.preventDefault(); | |
var spoilers = document.querySelectorAll('.o_chat-log-btn'); | |
for (var i in spoilers) { | |
fireEvent(spoilers[i], 'click'); | |
} | |
break; | |
} | |
}); | |
/** | |
* @param {NodeList} list A nodelist or array of Nodes to search | |
* @param {String} selector A selector to apply to all elements | |
*/ | |
function nodeListQuerySelectorAll(list, selector) { | |
var mappings = []; | |
for (var i = 0; i < list.length; i++) { | |
var sublisting = list[i].querySelectorAll(selector); | |
for (var j = 0; j < sublisting.length; j++) { | |
mappings.push(sublisting[j]); | |
} | |
} | |
return mappings; | |
} | |
/** | |
* Fire an event handler to the specified node. Event handlers can detect that the event was fired programatically | |
* by testing for a 'synthetic=true' property on the event object. | |
* | |
* @param {HTMLNode} node The node to fire the event handler on. | |
* @param {String} eventName The name of the event without the "on" (e.g., "focus") | |
* | |
* @see http://stackoverflow.com/a/2381862/15537 | |
*/ | |
function fireEvent(node, eventName) { | |
// Make sure we use the ownerDocument from the provided node to avoid cross-window problems | |
var doc; | |
if (node.ownerDocument) { | |
doc = node.ownerDocument; | |
} else if (node.nodeType == 9){ | |
// the node may be the document itself, nodeType 9 = DOCUMENT_NODE | |
doc = node; | |
} else { | |
throw new Error("Invalid node passed to fireEvent: " + node.id); | |
} | |
if (node.dispatchEvent) { | |
// Gecko-style approach (now the standard) takes more work | |
var eventClass = ""; | |
// Different events have different event classes. | |
// If this switch statement can't map an eventName to an eventClass, | |
// the event firing is going to fail. | |
switch (eventName) { | |
case "click": // Dispatching of 'click' appears to not work correctly in Safari. Use 'mousedown' or 'mouseup' instead. | |
case "mousedown": | |
case "mouseup": | |
eventClass = "MouseEvents"; | |
break; | |
case "focus": | |
case "change": | |
case "blur": | |
case "select": | |
eventClass = "HTMLEvents"; | |
break; | |
default: | |
throw "fireEvent: Couldn't find an event class for event '" + eventName + "'."; | |
} | |
var event = doc.createEvent(eventClass); | |
var bubbles = eventName == "change" ? false : true; | |
event.initEvent(eventName, bubbles, true); // All events created as bubbling and cancelable. | |
event.synthetic = true; // allow detection of synthetic events | |
// The second parameter says go ahead with the default action | |
node.dispatchEvent(event, true); | |
} else if (node.fireEvent) { | |
// IE-old school style | |
var event = doc.createEventObject(); | |
event.synthetic = true; // allow detection of synthetic events | |
node.fireEvent("on" + eventName, event); | |
} | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment