NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name FA Hotkeys // @namespace Artex // @description hotkeys for furaffinity // @author Artex // @homepageURL http://www.furaffinity.net/user/artex./ // @homeURL https://openuserjs.org/scripts/Artex/FA_Hotkeys // @icon data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAABkAAD/4QNvaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NzgwMTgzNzU4MTkyRTUxMUI3MTk4QTE4RDY1NTFCMEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MEVGNTc3NDI5RkUwMTFFNUEwNUE5OEMyQjZGOTlFMzEiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MEVGNTc3NDE5RkUwMTFFNUEwNUE5OEMyQjZGOTlFMzEiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3OTAxODM3NTgxOTJFNTExQjcxOThBMThENjU1MUIwRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODAxODM3NTgxOTJFNTExQjcxOThBMThENjU1MUIwRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6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 // @include http://www.furaffinity.net/* // @include https://www.furaffinity.net/* // @run-at document-end // @version 1.6.4 // @grant none // ==/UserScript== /*jshint esnext: true */ var settingsOpen = false; var settingsMenu = null; var hotkeys = new Map(); var storage = window.localStorage; var isResized = false; //for fit to screen var smallImage = ""; //small submission view var debugEnabled = false; function log() { if (debugEnabled === true) { console.log.apply(console, arguments); } } //get the name of the key that was pressed function keyFromKeyboardEvent(event) { var key = null; if (event.key) { key = event.key; } else { var identifier = event.keyIdentifier; if (identifier.search(/^U\+/) === -1) { key = identifier; } else { //not unicode, likely the key name key = String.fromCharCode(parseInt(identifier.replace(/^U\+/,""), 16)).toLowerCase(); } } return key; } //page url contains the given string function windowUrlContains(url) { var windowUrl = window.location.href; if (windowUrl.search(url) != -1) { return true; } else { return false; } } function nextElement(n) { var next = n.nextSibling; if (next.nodeType == 1 || next === null) { return next; } else { return nextElement(next); } } function previousElement(n) { var previous = n.previousSibling; if (previous.nodeType == 1 || previous === null) { return previous; } else { return previousElement(previous); } } function isTyping() { // is a text field in focus? var active = document.activeElement; if (active.tagName == "TEXTAREA" || active.tagName == "INPUT") { return true; } else { return false; } } function getSubmissionSource(){ var controlBar = document.getElementsByClassName("button submission"); for (var i=0; i < controlBar.length; i++) { var link = controlBar[i].getElementsByTagName("a")[0]; if (link !== undefined && link.textContent == "Download") { return link.href; } } } //////////////////////////////////////// // hotkey actions /////////////////////////////////////// function fullView() { //sets image source to loading gif and then full image if (windowUrlContains(/www.furaffinity.net\/(view|full)/)) { var image = document.getElementById("submissionImg"); var largeImage = getSubmissionSource(); if (largeImage != image.src) { smallImage = image.src; image.src = loadingGif.src; image.src = largeImage; } else { image.src = smallImage; } /* old full view code var url = window.location.href; var newUrl = ""; if (url.search("view") != -1) { newUrl = url.replace("view", "full"); } else { newUrl = url.replace("full", "view"); } window.location.href = newUrl; */ } } function fitToScreen() { function ResizeImage(img) { var viewportX = window.innerWidth; var viewportY = window.innerHeight; var imageX = img.naturalWidth; var imageY = img.naturalHeight; if (isResized) { //if resized, return normal dimensions. img.width = imageX; img.height = imageY; isResized = false; } else { if (imageX > viewportX) { img.width = viewportX; img.height = imageY * (viewportX/imageX); } if (imageY > viewportY) { //image is larger then window img.width = imageX * (viewportY/imageY); img.height = viewportY; } isResized = true; } } var image = document.getElementById('submissionImg'); if (image) { window.location.hash = '#submissionImg'; ResizeImage(image); } } function favorite() { if (windowUrlContains(/www.furaffinity.net\/(view|full)/)) { var favButton = document.getElementsByClassName("button fav")[0]; if (favButton) { favButton.click(); } } /* old favorite code document.querySelector('a.p20r').click(); */ } function navigateLeft() { if (windowUrlContains(/www.furaffinity.net\/(view|full)/)) { var center = document.getElementsByClassName("minigallery-title")[0]; previousElement(center).firstChild.firstChild.firstChild.click(); //Prone to breaking if structure changes } else if (windowUrlContains(/www.furaffinity.net\/(gallery|scraps|favorites)/)) { var url = window.location.href; var reg = /(favorites|gallery|scraps)(\/.+\/)([^/]+)(\/$|$)/; var pageNum = url.match(reg); pageNum = pageNum === null ? 2 : pageNum[3]; pageNum = pageNum > 1 ? pageNum : 2; var nextPage = url.replace(reg, "$1$2" + (pageNum - 1) + "$4"); window.location.href = nextPage; } else if (windowUrlContains(/www.furaffinity.net\/browse/)) { var url = window.location.href; var reg = /(browse\/)(\d+)(?=\/$|$)/; var pageNum = url.match(reg); pageNum = pageNum === null ? 2 : pageNum[2]; pageNum = pageNum > 1 ? pageNum : 2; var nextPage = url.replace(reg, "$1" + (pageNum - 1)); window.location.href = nextPage; } } function navigateRight() { if (windowUrlContains(/www.furaffinity.net\/(view|full)/)) { var center = document.getElementsByClassName("minigallery-title")[0]; nextElement(center).firstChild.firstChild.firstChild.click(); } else if (windowUrlContains(/www.furaffinity.net\/(gallery|scraps|favorites)/)) { var url = window.location.href; var reg = /(favorites|gallery|scraps)(\/.+\/)([^/]+)(\/$|$)/; var pageNum = url.match(reg); var nextPage; if (pageNum === null) { //console.log(pageNum); nextPage = url + "2/"; } else { //console.log(pageNum); nextPage = url.replace(reg, "$1$2" + (+pageNum[3] + 1) + "$4"); } window.location.href = nextPage; } else if (windowUrlContains(/www.furaffinity.net\/browse/)) { console.log("TEST"); var url = window.location.href; var reg = /(browse\/)(\d+)(?=\/$|$)/; var pageNum = url.match(reg); console.log(pageNum); var nextPage; if (pageNum === null) { //console.log(pageNum); nextPage = url + "2/"; } else { //console.log(pageNum); nextPage = url.replace(reg, "$1" + (+pageNum[2] + 1)); } window.location.href = nextPage; } } function checkAll() { var button = document.getElementsByClassName("section-button invert-selection")[0]; if (button !== undefined) { button.click(); } } function removeChecked() { var button = document.getElementsByClassName("section-button remove-checked")[0]; if (button !== undefined) { button.click(); } } //////////////////////////////////////// // setup hotkeys /////////////////////////////////////// function saveHotkeys() { var save = ""; hotkeys.forEach(function(func, key) { save = save + key + ":" + func.name + "|"; }); storage.setItem("FA_HotkeySettings", save); log("hotkeys saved"); } function loadDefaultHotkeys() { //if no keys are saved or data to reset keys. hotkeys = new Map(); hotkeys.set("f", favorite); hotkeys.set("g", fitToScreen); hotkeys.set("ArrowLeft", navigateLeft); hotkeys.set("ArrowRight", navigateRight); hotkeys.set("q", checkAll); hotkeys.set("e", removeChecked); hotkeys.set("1", openSettings); log("hotkeys set, saving"); saveHotkeys(); } function loadHotkeys() {//load saved hotkey configuration. //format "key:functionName|key:functionName" var data = storage.getItem("FA_HotkeySettings"); var dataSuccess = false; log("loading hotkey data: ", data); if (data !== null) { var matches = data.match(/[^\|]+/g);//get key:value - note: would probably break if key was ':' or '|' if (matches !== null) { //bad save data for (var i in matches ) { if (typeof matches[i] === "string") { var key = matches[i].match(/^[^\:]+/g)[0]; var func = matches[i].match(/[^\:]+$/g)[0]; //a more direct connection between the function name and the function would be ideal.. if (func.search(/[\.\(\)]/g) === -1) { //hopefully filter out malacious modifications to localstorage hotkeys.set(key, eval(func)); //eval is my last resort to get the function pointer //Idea: if I assign the functions to a map I can properly get the functions with a string. } } } } else { dataSuccess = false; } } if (dataSuccess === false) { log("loading default hotkeys"); loadDefaultHotkeys(); } } function initiateHotkeys() { log("iniating hotkey script"); loadHotkeys(); //loadDefaultHotkeys(); //take key input and watch for hotkeys to call. window.addEventListener("keydown", function(event) { var key = keyFromKeyboardEvent(event); log("key:", key); log("unicode:", event.keyIdentifier); if (hotkeys.has(key) === true && isTyping() === false) { hotkeys.get(key)(); } }, true); } //////////////////////////////////////// // Hotkey settings /////////////////////////////////////// function openSettings() { if (settingsOpen === false) { settingsOpen = true; var inputs = []; //array of the text inputs //This isn't going to be pretty //create style sheet for settings menu var style = document.createElement("style"); style.innerHTML = "" + "#Hotkey-Settings { " + " position: fixed;" + " z-index: 99999;" + " width: 250px;" + " left: 50%;" + " right: 50%;" + " top: 50%;" + " transform: translate(-50%, -50%);" + " background-color: #DDD;" + " color: #2F2F2F;" + " padding: 5px;" + "} "; //generate settings menu var container = document.createElement("table"); var header = document.createElement("thead"); var tr1 = document.createElement("tr"); var th1 = document.createElement("th"); var titleLeft = document.createTextNode("Key"); var th2 = document.createElement("th"); var titleRight = document.createTextNode("Action"); container.appendChild(style); container.appendChild(header); header.appendChild(tr1); tr1.appendChild(th1); th1.appendChild(titleLeft); tr1.appendChild(th2); th2.appendChild(titleRight); //generate input fields var n = 0; hotkeys.forEach(function(func, key) { var row = document.createElement("tr"); var td1 = document.createElement("td"); var input = document.createElement("input"); var td2 = document.createElement("td"); var name = document.createTextNode(func.name);//hopefully func.name has enough support. container.appendChild(row); row.appendChild(td1); //append input later row.appendChild(td2); td2.appendChild(name); input.setAttribute("type","text"); input.setAttribute("name",func.name); input.setAttribute("value",key); td1.appendChild(input); inputs[n++] = input; //hotkey input text field input.addEventListener("keydown", function(event) { var key = keyFromKeyboardEvent(event); log("input: ", key); if (key !== "Enter") { event.preventDefault(); var oldKey = this.getAttribute("value"); var newKey = key; var action = hotkeys.get(oldKey); if (hotkeys.get(newKey) === undefined) { hotkeys.delete(oldKey); hotkeys.set(newKey, action); this.setAttribute("value", newKey); saveHotkeys(); } else { //hotkey already in use, alert user var alertStyle = document.createElement("style"); alertStyle.innerHTML = "" + "#Hotkey-Settings input[value='" + newKey + "'] {" + " -webkit-transition: background-color 0.2s;" + " transition: background-color 0.2s;" + " background-color: #FFA3A3;" + "} "; container.appendChild(alertStyle); window.setTimeout(function() { alertStyle.innerHTML = "" + "#Hotkey-Settings input[value='" + newKey + "'] {" + " -webkit-transition: background-color 0.5s;" + " transition: background-color 0.5s;" + " background-color: white;" + "} "; window.setTimeout(function() { container.removeChild(alertStyle); },600); }, 800); } } }, true); }); //reset settings button var bottomRow = document.createElement("tr"); var bottomData = document.createElement("td"); var reset = document.createElement("input"); container.appendChild(bottomRow); bottomRow.appendChild(bottomData); reset.setAttribute("type","button"); reset.setAttribute("value","Reset"); bottomData.appendChild(reset); reset.addEventListener("click", function(event) { loadDefaultHotkeys(); var i = 0; hotkeys.forEach(function(func, key) { inputs[i++].value = key; }); }); container.setAttribute("id", "Hotkey-Settings"); document.body.appendChild(container); settingsMenu = container; } else { settingsOpen = false; document.body.removeChild(settingsMenu); } } initiateHotkeys();