keut / arca/sm decoder

// ==UserScript==
// @name         arca/sm decoder
// @author       keut
// @version      230713190109
// @updateURL    https://openuserjs.org/meta/keut/arcasm_decoder.meta.js
// @downloadURL  https://openuserjs.org/install/keut/arcasm_decoder.user.js
// @match        https://arca.live/b/*
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @license      MIT
// ==/UserScript==

var toSolidBytes = (_, p1) => String.fromCharCode(parseInt(p1, 16));
var encode = (str) => {
  const iso8859 = encodeURIComponent(str).replace(
    /%([0-9A-F]{2})/g,
    toSolidBytes
  );
  return btoa(iso8859);
};
var escape = (ch) => `%${("0" + ch.charCodeAt(0).toString(16)).slice(-2)}`;
var decode = (str) => {
  try {
    return decodeURIComponent(atob(str).split("").map(escape).join(""));
  }
  catch {
    return;
  }
};
var textDecode = (str) => {
  const decoded = decode(str.replace(/=+$/, ""));
  if (!decoded) {
    return;
  }
  const hasControl = decoded.match(/[\x00-\x08\x0e-\x1f\x7f-\xff]/);
  if (hasControl) {
    return;
  }
  return decoded;
};
var padInsensitiveRegex = /(?<=^|[^A-Za-z0-9+/])(?:[A-Za-z0-9+/]{4}){2,}(?:[A-Za-z0-9+/]{2,3})?(?:=+|(?=[^A-Za-z0-9+/])|$)/g;

var isUrl = (url) => {
  try {
    new URL(url);
    return true;
  }
  catch {
    return false;
  }
};
var createAnchor = (url) => {
  const anchor = document.createElement("a");
  anchor.href = url;
  anchor.target = "_blank";
  anchor.textContent = url;
  return anchor;
};
var storageAttributeName = "data-encoded";
var createSpanForRecovery = (range) => {
  const div = document.createElement("div");
  div.append(range.extractContents());
  const html = div.innerHTML;
  const span = document.createElement("span");
  span.setAttribute(storageAttributeName, html);
  return span;
};
var wrapIfLink = (maybeUrl) => {
  return isUrl(maybeUrl) ? createAnchor(maybeUrl) : document.createTextNode(maybeUrl);
};
var replaceRange = (range, text) => {
  const span = createSpanForRecovery(range);
  range.deleteContents();
  const node = wrapIfLink(text);
  range.insertNode(node);
  range.surroundContents(span);
};
var findNodeByOffset = (walker, count, isInclusive = false) => {
  let start = 0;
  while (true) {
    const node = walker.currentNode;
    const end = start + node.textContent.length;
    if (count < end || isInclusive && count === end) {
      return {
        node,
        offset: count - start
      };
    }
    start = end;
    if (!walker.nextNode()) {
      return;
    }
  }
};
var getEndZeroRange = (root) => {
  const range = new Range();
  range.setStart(root, root.childNodes.length);
  range.setEnd(root, root.childNodes.length);
  return range;
};
var getRange = (root, targetText) => {
  const start = root.textContent.indexOf(targetText);
  if (start === -1) {
    return;
  }
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
  walker.nextNode();
  const anchor = findNodeByOffset(walker, start);
  if (!anchor) {
    return;
  }
  const end = targetText.length + anchor.offset;
  const focus = findNodeByOffset(walker, end, true);
  if (!focus) {
    return;
  }
  const range = new Range();
  range.setStart(anchor.node, anchor.offset);
  range.setEnd(focus.node, focus.offset);
  return range;
};

var getBlockAncestor = (element) => {
  const majorBlockRegex = /^(?:blockquote|body|div|form|h\\d|li|main|ol|p|pre|section|table|ul)/i;
  let cursor = element;
  while (!cursor.tagName.match(majorBlockRegex)) {
    cursor = cursor.parentElement;
  }
  return cursor;
};
var replaceByString = (element, match) => {
  const decoded = textDecode(match);
  if (!decoded) {
    return false;
  }
  const matchRange = getRange(element, match) || getEndZeroRange(element);
  replaceRange(matchRange, decoded);
  return true;
};
var getThisOrParentElement = (target) => {
  if (target.nodeType === target.ELEMENT_NODE) {
    return target;
  }
  return target.parentElement ?? void 0;
};
var decodeSelection = (selection) => {
  const draggedText = `${selection}`.replace(/\s+/g, "");
  if (!draggedText) {
    return;
  }
  const decoded = textDecode(draggedText);
  if (!decoded) {
    return;
  }
  const canCoincidence = draggedText.length < 12 && /^([\d ]+|[a-z ]+)$/i.test(draggedText);
  if (canCoincidence) {
    return;
  }
  const range = selection.getRangeAt(0);
  const element = getThisOrParentElement(range.commonAncestorContainer);
  replaceByString(element, draggedText);
  selection.collapseToEnd();
};
var getModifiableBlock = (target) => {
  const element = getThisOrParentElement(target);
  if (!element) {
    return;
  }
  const isNonsense = element.tagName.match(/^(A|INPUT|TEXTAREA)$/) || element.isContentEditable;
  const block = getBlockAncestor(element);
  const hasInput = !!block.querySelector?.("textarea,[contenteditable]");
  if (isNonsense || hasInput) {
    return;
  }
  return block;
};
var decodeElementContent = (element) => {
  const matches = element.innerText?.match(padInsensitiveRegex);
  if (!matches) {
    return false;
  }
  return matches.map((match) => replaceByString(element, match)).some(Boolean);
};
var tryDecodeBase64 = (event) => {
  const selection = event.view?.getSelection?.();
  if (selection?.toString?.()) {
    const block = selection.getRangeAt(0)?.commonAncestorContainer;
    if (block && getModifiableBlock(block)) {
      decodeSelection(selection);
    }
    return false;
  }
  const target = event.target;
  const element = getModifiableBlock(target);
  return element ? decodeElementContent(element) : false;
};
var isDecoded = false;
var mouseHandler = (event) => {
  if (event.type === "click") {
    isDecoded = tryDecodeBase64(event);
  }
  else {
    const canWordSelect = event.detail >= 2;
    if (canWordSelect && isDecoded) {
      event.preventDefault();
    }
  }
};
var hookPointer = (document2) => {
  document2.body.addEventListener("click", mouseHandler);
  document2.body.addEventListener("mousedown", mouseHandler);
};

var getParentElement = (node) => {
  return node instanceof HTMLElement ? node : node?.parentElement;
};
var encodeAndPaste = (document2, text) => {
  const encoded = encode(text);
  const input = document2.activeElement;
  if (input.selectionStart != null) {
    const text2 = input.value;
    const head = text2.slice(0, input.selectionStart);
    const tail = text2.slice(input.selectionEnd);
    input.value = `${head}${encoded}${tail}`;
    return true;
  }
  if (getParentElement(getSelection().anchorNode)?.isContentEditable) {
    replaceRange(getSelection().getRangeAt(0), encoded);
    getSelection().collapseToEnd();
    return true;
  }
  return false;
};
var hookPaste = (document2) => {
  const capture = {
    capture: true
  };
  let isShiftPressed = false;
  const recordShiftPress = (event) => {
    isShiftPressed = event.shiftKey;
  };
  document2.body.addEventListener("keydown", recordShiftPress, capture);
  document2.body.addEventListener("keyup", recordShiftPress, capture);
  const handlePaste = (event) => {
    if (!isShiftPressed) {
      return;
    }
    const text = event.clipboardData?.getData?.("text/plain");
    if (!text) {
      return;
    }
    if (encodeAndPaste(document2, text)) {
      event.stopImmediatePropagation();
      event.preventDefault();
    }
  };
  document2.body.addEventListener("paste", handlePaste, capture);
};

var supressAgree = (document2) => {
  document2.cookie = "allow_sensitive_media=true";
};
var waitAnimationFrame = async (window2) => {
  let id = 0;
  await new Promise((resolve) => {
    id = window2.requestAnimationFrame(resolve);
  });
  window2.cancelAnimationFrame(id);
};
var hookPage = async (window2) => {
  const {
    document: document2
  } = window2;
  supressAgree(document2);
  while (!document2.body) {
    await waitAnimationFrame(window2);
  }
  hookPointer(document2);
  hookPaste(document2);
};

var decodeAll = () => {
  decodeElementContent(document.body);
};
var restoreAll = () => {
  const spans = document.querySelectorAll(`[${storageAttributeName}]`);
  for (const span of spans) {
    const html = span.getAttribute(storageAttributeName);
    if (html) {
      span.outerHTML = html;
    }
  }
};
var registerMenuCommand = () => {
  GM_registerMenuCommand("Decode all", decodeAll, "D");
  GM_registerMenuCommand("Restore all", restoreAll, "R");
};

hookPage(window);
registerMenuCommand();