// ==UserScript== // @name Card Data // @namespace Violentmonkey Scripts // @match *://rentry.co/* // @match *://rentry.org/* // @match *://*catbox.moe/* // @grant none // @version 1.2 // @author me :) // @description 8/17/2021, 5:50:10 PM // @require https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js // @grant GM_xmlhttpRequest // ==/UserScript== // Card Data Shower: This script shows a box of the json data for an NAI card // Hover over the card and press control to make the box appear // Hover over the box and press shift to make the box go away // Double click on the box to copy just the json data // things you might want to change const triggerKey = 17; // 17 is control const closeKey = 16; // 16 is shift const boxWidth = "800px"; const boxHeight = "600px"; const boxMargin = "-300px 0 0 -400px"; const fontSize = "0.7em"; // other constants const pngSignature = [137, 80, 78, 71, 13, 10, 26, 10]; const chunkSignature = [116, 69, 88, 116]; const chunkStart = 33; // beginning of the NAI data chunk const typeStart = chunkStart + 4; const dataStart = chunkStart + 8; jQuery.noConflict(); jQuery( document ).ready(function( $ ) { var source; // identifying images by src // styles $("head").append(` `); // wait until new images appear waitForKeyElements ("img[cardEvent!='set']", function() { $("img[cardEvent!='set']").on("mouseover",function() { source = this.src; if (source[0] == '/') { source = "https:" + source; } $(document).on("keydown",function(e) { if (e.keyCode == triggerKey) { getImageData(source); } }); }).on("mouseleave",function() { $(document).off("keydown"); }); $("img").attr("cardEvent","set"); // stops repeated event assignment(?) }); // Gets the data from the card and writes function getImageData(image) { return GM_xmlhttpRequest({ method: "GET", url: image, responseType: "arraybuffer", onloadend: function(responseObject) { const byteArray = new Uint8Array(responseObject.response); if (arraysEqual(byteArray.slice(0,8), pngSignature) && arraysEqual(byteArray.slice(typeStart,typeStart+4), chunkSignature)) { $("#follow").remove(); let dataLength = getLength(byteArray.slice(chunkStart,chunkStart + 4)); jsonData = atob(String.fromCharCode(...byteArray.slice(dataStart + 8, dataStart+dataLength))); objData = JSON.parse(jsonData); // create the infobox and fill it up $("body").prepend("
"); $("#follow").prepend(`${objData.entries[i].text}
`); } $("#follow").append(`${jsonData}
`); $("#follow").append(`
json copied
`); followEvents(); } } }); } // Reads the 4 bytes as given in decimal and returns proper value for data length function getLength(lengthBytes) { var total = 0; for (let i = 0; i < lengthBytes.length; i++) { total += lengthBytes[i] * (256**(lengthBytes.length - i - 1)); } return total; } // sets the event handlers for the infobox function followEvents() { $("#follow").on("mouseover", function() { $(document).on("keydown",function(e) { if (e.keyCode == closeKey) { $("#follow").remove(); } }); }).on("mouseleave", function() { $(document).off("keydown"); }).on("dblclick", function() { navigator.clipboard.writeText($("#jsonData").text()); $("#copyIndicator").css("opacity", 1); $("#copyIndicator").fadeTo("slow", 0); }); } // checks if two (ordered) arrays are equal function arraysEqual(a, b) { if (a === b) return true; if (a == null || b == null) return false; if (a.length !== b.length) return false; for (var i = 0; i < a.length; ++i) { if (a[i] !== b[i]) return false; } return true; } // Gotten from https://gist.github.com/BrockA/2625891 ; for handling ajax stuff function waitForKeyElements ( selectorTxt, /* Required: The jQuery selector string that specifies the desired element(s). */ actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */ bWaitOnce, /* Optional: If false, will continue to scan for new elements even after the first match is found. */ iframeSelector /* Optional: If set, identifies the iframe to search. */ ) { var targetNodes, btargetsFound; if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt); else targetNodes = $(iframeSelector).contents () .find (selectorTxt); if (targetNodes && targetNodes.length > 0) { btargetsFound = true; /*--- Found target node(s). Go through each and act if they are new. */ targetNodes.each ( function () { var jThis = $(this); var alreadyFound = jThis.data ('alreadyFound') || false; if (!alreadyFound) { //--- Call the payload function. var cancelFound = actionFunction (jThis); if (cancelFound) btargetsFound = false; else jThis.data ('alreadyFound', true); } } ); } else { btargetsFound = false; } //--- Get the timer-control variable for this selector. var controlObj = waitForKeyElements.controlObj || {}; var controlKey = selectorTxt.replace (/[^\w]/g, "_"); var timeControl = controlObj [controlKey]; //--- Now set or clear the timer as appropriate. if (btargetsFound && bWaitOnce && timeControl) { //--- The only condition where we need to clear the timer. clearInterval (timeControl); delete controlObj [controlKey] } else { //--- Set a timer, if needed. if ( ! timeControl) { timeControl = setInterval ( function () { waitForKeyElements ( selectorTxt, actionFunction, bWaitOnce, iframeSelector ); }, 300 ); controlObj [controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } });