Kone base64 autodecoder

auto decode Base64 encoded link in Kone.gg

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name           Kone base64 autodecoder
// @name:ko        코네 Base64 자동 디코더
// @version        1.1.4
// @author         OMNI7 (Original code by 'Laria')
// @match          https://kone.gg/*
// @description    auto decode Base64 encoded link in Kone.gg
// @description:ko 코네 내 Base64로 인코딩된 링크를 자동으로 복호화합니다.
// @icon           https://www.google.com/s2/favicons?sz=64&domain=kone.gg
// @require        https://cdn.jsdelivr.net/npm/sweetalert2@11
// @license        MIT
// @encoding       utf-8
// @run-at         document-end
// @grant          GM.getValue
// @grant          GM.setValue
// @grant          GM.deleteValue
// @grant          GM.registerMenuCommand
// @grant          GM.unregisterMenuCommand
// @grant          GM.setClipboard
// @namespace https://greasyfork.org/users/1575179
// ==/UserScript==
 
/* CREDITS:
  This script is a modified version of "Arca base64 autodecoder" originally created by 'Laria'.
  Original Script: https://greasyfork.org/ko/scripts/482577
 
  Special thanks to 'Laria' for the original code!
*/
 
const regexEncodedPrefixDef = [
    /(aHR0cDovL|aHR0cHM6Ly|bWVnYS5ue|a2lvLmFj)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(YUhSMGNEb3ZM|YUhSMGNITTZMe|YldWbllTVX|YTIsdmJtR)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(WVVoU01HTkViM1pN|WVVoU01HTklUVFpNZ|V1d4V1JteF|V1RJeHNXWn)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(V1ZWb1UwMUhUa1ZpTTFwT|V1ZWb1UwMUhUa2xVVkZwTl|VmpkNFYxSn|VjFSSmVIT)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(VjFaV2IxVXdNVWhVYTFacFRURndU|VjFaV2IxVXdNVWhVYTJ4VlZrWndUb)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(VmpGYVYySXhWWGROVldoVllURmFjRlJVUm5kV|VmpGYVYySXhWWGROVldoVllUSjRWbFpyV25kVW)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVUm1GalJsSlZVbTVrV|Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVU2pSV2JGcHlWMjVrVl)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(Vm0xd1IxbFdXWGxUV0doWFYwZFNUMVpzWkc5V2JHeFZVbTFHYWxKc1NsWlZiVFZyV|Vm0xd1IxbFdXWGxUV0doWFYwZFNUMVpzWkc5V2JHeFZVMnBTVjJKR2NIbFdNalZyVm)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(Vm0weGQxSXhiRmRYV0d4VVYwZG9XRll3WkZOVU1WcHpXa2M1VjJKSGVGWlZiVEZIWVd4S2MxTnNXbFppVkZaeV|Vm0weGQxSXhiRmRYV0d4VVYwZG9XRll3WkZOVU1WcHpXa2M1VjJKSGVGWlZNbkJUVmpKS1IyTkliRmROYWxaeVZt)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFppVkVaSVdWZDRTMk14VG5OWGJGcHBWa1phZ|Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZad)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
    /(Vm0wd2QyVkhVWGhUV0docFVtMVNXVll3WkRSV1ZsbDNXa2M1V0ZKc2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEtTMU5IVmtkWGJGcHBWa1ZhU1ZkV1pEUlRNazE0Vkc1T1dHSkdjSEJXYTFwaF|Vm0wd2QyVkhVWGhUV0docFVtMVNXVll3WkRSV1ZsbDNXa2M1V0ZKc2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEtTMU5IVmtkWGJGcE9ZbXRLVlZadGNFdFRNVWw1Vkd0c2FWSnRVazlaVjNoaFpWWmFk)([\w\-+\/=]*)(?=[^\+=\w\/]|$)/g,
];
 
let abadInternalDB = {
  encodedLink: {}, decodedLink: {}, decodedList: [], hostnameSetRaw: new Set(), hostnameSet: [],
  internalDB: { autoDecodingMaximum: 11, totlaDecodedCount: 0, dragDecodingEnable: false, swal2Enable: false },
};
 
const abadConstDB = {
  regInvalid: /[^\w\+\/=]/,
  logPrompt: { default: '['+GM.info.script.name+']', decodeManager: '['+GM.info.script.name+'-DEC]', paramManager: '['+GM.info.script.name+'-PAR]' },
  SWAL2Title: `<span style="font-size: 82.5%;">${('name:ko' in GM.info.script)?GM.info.script['name:ko']:GM.info.script.name}</span><i style="font-size: 40%;"> V ${GM.info.script.version}</i>`,
};
 
let hindex = 0;
let lastSelected = document;
let lastSelectedTime = Date.now();
let lastUrl = location.href;
 
let localParameter = {
  'autodecode': { 'param_name': 'autodecode', 'value': true, 'def_value': true },
  'basedepth': { 'param_name': 'basedepth', 'value': 3, 'def_value': 3 },
  'enclinkhide': { 'param_name': 'enclinkhide', 'value': false, 'def_value': false },
  'declinkhide': { 'param_name': 'declinkhide', 'value': false, 'def_value': false },
  'draggable': { 'param_name': 'draggable', 'value': false, 'def_value': false },
  'expandmenu': { 'param_name': 'expandmenu', 'value': true, 'def_value': true },
};
 
let menuStructure = {
  'autodecode': { 'param_name': localParameter.autodecode, 'name': '🗝 자동 디코딩 [상태]', 'desc': '페이지 접속 시 자동으로 디코딩하는 기능을 켜고 끕니다.', 'id': -1, 'func': menuFunctionAutodecode, 'visible': true },
  'basedepth': { 'param_name': localParameter.basedepth, 'name': '🎛 base64 깊이 조절하기', 'desc': '자동 base64 디코딩 깊이를 조절할 수 있습니다.', 'id': -1, 'func': menuFunctionBasedepth, 'visible': true },
  'enclinkhide': { 'param_name': localParameter.enclinkhide, 'name': '🔗 인코딩된 코드 [보이기/숨기기]', 'desc': '', 'id': -1, 'func': menuFunctionEnchide, 'visible': true },
  'declinkhide': { 'param_name': localParameter.declinkhide, 'name': '🔗 디코딩된 코드 [보이기/숨기기]', 'desc': '', 'id': -1, 'func': menuFunctionDechide, 'visible': true },
  'draggable': { 'param_name': localParameter.draggable, 'name': '🖱 드래그 시 자동 디코딩 [켜기/끄기]', 'desc': '', 'id': -1, 'func': menuFunctionDraggable, 'visible': true },
  'resetdefaults': { 'param_name': null, 'name': '🛠 스크립트 기본값 초기화', 'desc': '', 'id': -1, 'func': menuFunctionRstDefaults, 'visible': true },
  'expandmenu': { 'param_name': localParameter.expandmenu, 'name': '⚙️ 스크립트 메뉴 [축소/확장]', 'desc': '', 'id': -1, 'func': menuFunctionChangeExpandMode, 'visible': true },
};
 
let _eventHandlers = {};
const addListener = (node, event, handler, capture = false) => {
  if (!(event in _eventHandlers)) _eventHandlers[event] = [];
  _eventHandlers[event].push({ node: node, handler: handler, capture: capture });
  node.addEventListener(event, handler, capture);
};
 
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
function getLocation(href) {
  var match = href.toString().match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
  return match && { href: href, hostname: match[3] };
}
function createElemID() { return 'abad_'+self.crypto.randomUUID(); }
function base64AddPadding(str) { return str + Array((4 - str.length % 4) % 4 + 1).join('='); }
 
const Base64 = {
  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  decode : function (input) {
    let output = ""; let chr1, chr2, chr3; let enc1, enc2, enc3, enc4; let i = 0;
    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    while (i < input.length) {
      enc1 = this._keyStr.indexOf(input.charAt(i++)); enc2 = this._keyStr.indexOf(input.charAt(i++));
      enc3 = this._keyStr.indexOf(input.charAt(i++)); enc4 = this._keyStr.indexOf(input.charAt(i++));
      chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4;
      output = output + String.fromCharCode(chr1);
      if (enc3 != 64) output = output + String.fromCharCode(chr2);
      if (enc4 != 64) output = output + String.fromCharCode(chr3);
    }
    return this._utf8_decode(output);
  },
  _utf8_decode : function (utftext) {
    let string = ""; let i = 0; let c = 0, c2 = 0, c3 = 0;
    while (i < utftext.length) {
      c = utftext.charCodeAt(i);
      if (c < 128) { string += String.fromCharCode(c); i++; }
      else if ((c > 191) && (c < 224)) { c2 = utftext.charCodeAt(i+1); string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); i += 2; }
      else { c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2); string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); i += 3; }
    }
    return string;
  }
};
 
const scrollToTarget = function(id_tmp, target) {
  let targetElem = document.getElementById(id_tmp);
  if (!targetElem) {
      const pc = document.querySelector("main #post_content");
      if (pc && pc.shadowRoot) targetElem = pc.shadowRoot.getElementById(id_tmp);
  }
  if (!targetElem) return;
 
  if (abadInternalDB.internalDB.swal2Enable) {
    Swal.close();
    Swal.fire({
      icon: 'success', title: abadConstDB.SWAL2Title,
      html: `<b style="font-size: 82.5%;">${(target==undefined)?'해당':target} 위치로 이동했습니다.</b>`, toast: true, position: 'top-end', timer: 3500, timerProgressBar: true, showConfirmButton: false
    });
  }
  targetElem.style.background = '#06ff004f';
  window.scrollTo({top:window.pageYOffset + targetElem.getBoundingClientRect().top - (window.innerHeight / 2), behavior:'smooth'});
  sleep(2750).then(() => { targetElem.style.background = null; targetElem.style.transition = "all 1s"; });
};
 
function copyToClipboard(target, cont) {
  let msgHeader = '';
  if (cont != undefined) msgHeader = `${cont}이(가) `;
  if (target == undefined) {
    console.warn('Copy target does not exist');
  } else {
    try {
      GM.setClipboard(target);
      if (abadInternalDB.internalDB.swal2Enable) {
        let timerInterval;
        Swal.fire({
          title: abadConstDB.SWAL2Title,
          html: `<b>${msgHeader}클립보드로 복사되었습니다.</b><br><div style="margin-top: 15px; text-align:left; font-size:72.5%">또는 아래 코드를 복사:<div style="overflow-y:auto; overflow-wrap: anywhere; margin: 5px 0; padding: 5px; width:100%; height:150px; background-color: #e6e6e6; border-radius: 4px; box-sizing: border-box; cursor: text; user-select: text;">${target}</div></div>`,
          confirmButtonText: '확인',
          icon: 'success',
          timer: 3000,
          timerProgressBar: true,
          footer: `<span id="footer" style="font-size: 82.5%;">&nbsp;</span>`,
          didOpen: (modal) => {
            let autoClose = true;
 
            const handleMouseEnter = () => {
              autoClose = false;
              Swal.stopTimer();
              const footer = modal.querySelector("#footer");
              if (footer) footer.innerHTML = `<i style="font-size: 82.5%;">창에서 마우스를 떼면 일정시간 후 자동으로 닫힙니다.</i>`;
            };
 
            const handleMouseLeave = () => {
              autoClose = true;
              Swal.resumeTimer();
            };
 
            modal.onmouseenter = handleMouseEnter;
            modal.onmouseleave = handleMouseLeave;
 
            if (modal.matches(':hover')) {
              handleMouseEnter();
            }
 
            timerInterval = setInterval(() => {
              if (autoClose) {
                let timeLeft = Swal.getTimerLeft();
                let seconds = isNaN(Math.floor(timeLeft / 1000)) ? '0' : Math.floor(timeLeft / 1000);
                const footer = modal.querySelector("#footer");
                if (footer) footer.innerHTML = `<i style="font-size: 82.5%;">약 ${seconds}초 후 창이 자동으로 닫힙니다.</i>`;
              }
            }, 100);
          },
          willClose: () => { clearInterval(timerInterval); },
        });
      } else {
        window.alert(msgHeader + '클립보드로 복사되었습니다.');
      }
    } catch (e) {
      console.warn('Copy failed:', e);
    }
  }
}
 
function showEncodedLink(event) {
  const self = event.currentTarget;
 
  if (abadInternalDB.encodedLink.hasOwnProperty(self.id)) {
    const data = abadInternalDB.encodedLink[self.id];
 
    if (data.isEnabled) {
        let sel = window.getSelection();
        let pc = document.querySelector("main #post_content");
        let shadowSel = (pc && pc.shadowRoot && typeof pc.shadowRoot.getSelection === 'function') ? pc.shadowRoot.getSelection() : null;
 
        if ((sel && sel.toString().length > 0) || (shadowSel && shadowSel.toString().length > 0)) {
            return;
        }
    }
 
    event.preventDefault();
    event.stopPropagation();
 
    const rawLink = data.raw;
    const label = data.label || '인코딩된';
 
    if (!data.isEnabled) {
      self.innerHTML = rawLink.replace(/\r?\n/g, '<br>');
      self.style.color = '#4758bc';
      self.style.fontWeight = 'normal';
      self.style.textDecoration = 'none';
      self.title = `${label} 코드입니다. 클릭 시 내용이 복사됩니다.`;
      data.isEnabled = true;
    } else {
      copyToClipboard(rawLink, `${label} 코드`);
    }
  }
}
 
function showDecodeSummary(event) {
  if (abadInternalDB.internalDB.swal2Enable) {
    const decodedLinkListWrapper = createElemID();
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      html: `<b>이 페이지에서 디코딩된 링크 목록</b><br><div style="margin-top: 15; text-align:left; font-size:80%"><div id="${decodedLinkListWrapper}" style="overflow: auto; margin: 10 0 10; width:100%; height:250px; background-color: #e6e6e6;">불러오는중...</div></div>`,
      confirmButtonText: '닫기',
      didOpen: (modal) => {
        Swal.showLoading();
        sleep(100).then(() => {
          modal.querySelector('#'+decodedLinkListWrapper).innerHTML = '';
          Object.keys(abadInternalDB.decodedLink).forEach(function(targetRaw) {
            const target = abadInternalDB.decodedLink[targetRaw];
            let cont = document.createElement("p");
            cont.style.marginBottom = '0.3rem'; cont.style.whiteSpace = 'nowrap';
 
            const elemGotoLocation = document.createElement("a");
            elemGotoLocation.id = createElemID(); elemGotoLocation.innerHTML = `[<u>클릭 시 해당 위치로 이동</u>]`; elemGotoLocation.href = "javascript:void(0);";
 
            const contLink = document.createElement("a");
            contLink.href = target.href; contLink.target = "_blank"; contLink.innerHTML = `&gt; ${target.no}번째 링크 (${target.hostname})`;
 
            cont.appendChild(contLink); cont.appendChild(document.createTextNode(" - ")); cont.appendChild(elemGotoLocation);
            modal.querySelector('#'+decodedLinkListWrapper).appendChild(cont);
            addListener(elemGotoLocation, 'click', function() { scrollToTarget(target.id, `${target.no}번째 링크`); });
          });
          Swal.hideLoading();
        });
      }
    });
  }
}
 
function createEncodedLink(src) { return `<span style="font-size: 87.5%;color: #47bc73 !important;">[ ${src.toString()} ]</span>`; }
function createMaskEncodedLink(src, decodedSrc, genMode, uuid) {
  const uuidDec = createElemID();
  let encPart = '';
  let decPart = '';
 
  if (localParameter.enclinkhide.value) {
    abadInternalDB.encodedLink[uuid] = { type: genMode, raw: src, isEnabled: true, label: '인코딩된' };
    encPart = `<a id="${uuid}" style="cursor:pointer; color:#4758bc; font-weight:normal; text-decoration:none;" title="인코딩된 코드입니다. 클릭 시 내용이 복사됩니다.">${src.replace(/\r?\n/g, '<br>')}</a>`;
  } else {
    abadInternalDB.encodedLink[uuid] = { type: genMode, raw: src, isEnabled: false, label: '인코딩된' };
    encPart = `<a id="${uuid}" style="cursor:pointer; text-decoration:underline;" title="클릭 시 인코딩된 코드를 표시합니다.">클릭 시 인코딩된 코드 보기</a>`;
  }
 
  if (localParameter.declinkhide.value) {
    abadInternalDB.encodedLink[uuidDec] = { type: genMode, raw: decodedSrc, isEnabled: true, label: '디코딩된' };
    decPart = `<a id="${uuidDec}" style="cursor:pointer; color:#4758bc; font-weight:normal; text-decoration:none;" title="디코딩된 코드입니다. 클릭 시 내용이 복사됩니다.">${decodedSrc.replace(/\r?\n/g, '<br>')}</a>`;
  } else {
    abadInternalDB.encodedLink[uuidDec] = { type: genMode, raw: decodedSrc, isEnabled: false, label: '디코딩된' };
    decPart = `<a id="${uuidDec}" style="cursor:pointer; text-decoration:underline;" title="클릭 시 디코딩된 코드를 표시합니다.">클릭 시 디코딩된 코드 보기</a>`;
  }
 
  return `${encPart} <span style="color:#aaa; cursor:default; text-decoration:none; margin: 0 4px;">|</span> ${decPart}`;
}
 
function createLink(src, decodedSrc, index, url, depth, genMode, uuid, parentuuid, isSplitChild = false) {
  abadInternalDB.hostnameSetRaw.add(url.hostname);
 
  let extraUI = "";
  if (isSplitChild) {
    extraUI = createEncodedLink(src);
  } else {
    extraUI = createEncodedLink(createMaskEncodedLink(src, decodedSrc, genMode, parentuuid));
  }
 
  return `<a id="${uuid}" href="${url.href}" title="${url.href} (새 창으로 열기)" target="_blank" rel="external nofollow noopener noreferrer">${index.toString()}번째 링크 (base64 깊이: ${depth.toString()}) <span style="font-size: 77.5%;">(${url.hostname})</span></a> ${extraUI}`;
}
 
function replacerGen(numIter, genMode) {
  return function(source) {
    const isBraille = /[\u2800-\u28FF]/.test(source);
    try {
      let rstring = "";
      let converted = "";
 
      if (isBraille) {
          const decodeBraille = (str) => {
              const brailleMap = {
                  '⠁': 'a', '⠃': 'b', '⠉': 'c', '⠙': 'd', '⠑': 'e', '⠋': 'f', '⠛': 'g', '⠓': 'h', '⠊': 'i', '⠚': 'j',
                  '⠅': 'k', '⠇': 'l', '⠍': 'm', '⠝': 'n', '⠕': 'o', '⠏': 'p', '⠟': 'q', '⠗': 'r', '⠎': 's', '⠞': 't',
                  '⠥': 'u', '⠧': 'v', '⠺': 'w', '⠭': 'x', '⠽': 'y', '⠵': 'z'
              };
              const numMap = {
                  '⠁': '1', '⠃': '2', '⠉': '3', '⠙': '4', '⠑': '5', '⠋': '6', '⠛': '7', '⠓': '8', '⠊': '9', '⠚': '0'
              };
 
              let result = '';
              let isNum = false;
              let isCaps = false;
              let isCapsLock = false;
 
              for (let i = 0; i < str.length; i++) {
                  const char = str[i];
 
                  if (char === '⠸') {
                      if (str[i+1] === '⠌') { result += '/'; i++; continue; }
                      if (str[i+1] === '⠹') { result += '#'; i++; continue; }
                  }
                  if (char === '⠠') {
                      isNum = false;
                      if (str[i+1] === '⠠') { isCapsLock = true; i++; continue; }
                      else if (str[i+1] === '⠄') { isCapsLock = false; i++; continue; }
                      else { isCaps = true; continue; }
                  }
                  if (char === '⠼') { isNum = true; isCaps = false; isCapsLock = false; continue; }
                  if (char === '⠰') { isNum = false; continue; }
 
                  if (char === '⠲') { result += '.'; isNum = false; continue; }
                  if (char === '⠒') { result += ':'; isNum = false; continue; }
                  if (char === '⠤') { result += '-'; isNum = false; continue; }
                  if (char === '⠐' && str[i+1] === '⠶') { result += '='; i++; isNum = false; continue; }
                  if (char === '⠖') { result += '+'; isNum = false; continue; }
                  if (char === '⠌') { result += '/'; isNum = false; continue; }
                  if (char === '⠹') { result += '#'; isNum = false; continue; }
                  if (char === '⠶') { result += '='; isNum = false; continue; }
                  if (char === '⠀') { result += ' '; isNum = false; continue; }
 
                  if (isNum && numMap[char]) { result += numMap[char]; continue; }
 
                  if (brailleMap[char]) {
                      let c = brailleMap[char];
                      if (isCaps || isCapsLock) {
                          c = c.toUpperCase();
                          isCaps = false;
                      }
                      result += c;
                      isNum = false;
                      continue;
                  }
 
                  if (char >= '⠀' && char <= '⣿') continue;
 
                  result += char;
                  isNum = false;
              }
              return result;
          };
          converted = decodeBraille(source).trim();
      } else {
          converted = Base64.decode(base64AddPadding(source));
          for (let i=0; i<numIter; i++) converted = Base64.decode(base64AddPadding(converted));
      }
 
      hindex++;
 
      converted = decodeURI(encodeURI(converted).replaceAll('%00', ''));
      converted = converted.trim();
 
      converted = converted.split(/\r?\n/).map(item => {
          let trimmed = item.trim();
          if (trimmed.startsWith('mega.nz') || trimmed.startsWith('kio.ac')) return 'https://' + trimmed;
          return trimmed;
      });
 
      const registerDecodedLink = function(_target, _uuid, _parentuuid) {
        abadInternalDB.decodedLink[_uuid] = { id: _uuid, no: hindex, type: genMode, hostname: _target.hostname, title: _target.href, href: _target.href, srcid: _parentuuid };
        abadInternalDB.decodedList.push(_uuid);
      };
 
      if (converted.length == 1) {
        const uuid = createElemID(); const parentuuid = createElemID();
        const url_t = getLocation(converted[0]);
        if(!url_t) throw new Error("Invalid URL");
        registerDecodedLink(url_t, uuid, parentuuid);
 
        rstring += createLink(source, converted[0], hindex, url_t, numIter+1, genMode, uuid, parentuuid, false);
      } else {
        const parentuuid = createElemID();
 
        rstring += createEncodedLink(createMaskEncodedLink(source.toString(), converted.join('\n'), genMode, parentuuid));
 
        let nindex = 1;
        converted.forEach(function(i) {
          if (i != '') {
            const uuid = createElemID(); const url_t = getLocation(i);
            if(url_t) {
                registerDecodedLink(url_t, uuid, parentuuid);
 
                rstring += `<br><span style="margin-left:2px;">└ </span>${createLink(`<span style="color: #47bc73; font-weight: bold;">링크 자동 분할 : ${nindex.toString()}번째</span>`, i, hindex, url_t, numIter+1, genMode, uuid, parentuuid, true)}`;
 
                hindex++; nindex++;
            }
          }
        });
        hindex--;
        rstring = `<span style="color: #e83e8c;"><b><i>분할된 링크 총 ${nindex-1}개</i></b></span> ${rstring}`;
      }
      return rstring;
    } catch(e) { return `<span style="color: #ff0000;">[ ${isBraille ? '점자' : 'base64'} 변환 실패: ${source.toString()}]</span>`; }
  };
}
 
function tryDragDecode(event) {
  let pc = document.querySelector("main #post_content");
  let selection = null;
 
  if (pc && pc.shadowRoot && typeof pc.shadowRoot.getSelection === 'function') {
      let shadowSel = pc.shadowRoot.getSelection();
      if (shadowSel && shadowSel.rangeCount > 0 && shadowSel.toString().trim().length > 0) {
          selection = shadowSel;
      }
  }
 
  if (!selection) {
      let winSel = window.getSelection();
      if (winSel && winSel.rangeCount > 0 && winSel.toString().trim().length > 0) {
          selection = winSel;
      }
  }
 
  if (!selection) return;
  const selText = selection.toString().trim();
 
  const isBraille = /^[\u2800-\u28FF\s]+$/.test(selText);
 
  if ((isBraille || !selText.match(abadConstDB.regInvalid)) && selText.length >= 4) {
    try {
      let converted = '';
 
      if (isBraille) {
          const decodeBraille = (str) => {
              const brailleMap = {
                  '⠁': 'a', '⠃': 'b', '⠉': 'c', '⠙': 'd', '⠑': 'e', '⠋': 'f', '⠛': 'g', '⠓': 'h', '⠊': 'i', '⠚': 'j',
                  '⠅': 'k', '⠇': 'l', '⠍': 'm', '⠝': 'n', '⠕': 'o', '⠏': 'p', '⠟': 'q', '⠗': 'r', '⠎': 's', '⠞': 't',
                  '⠥': 'u', '⠧': 'v', '⠺': 'w', '⠭': 'x', '⠽': 'y', '⠵': 'z'
              };
              const numMap = {
                  '⠁': '1', '⠃': '2', '⠉': '3', '⠙': '4', '⠑': '5', '⠋': '6', '⠛': '7', '⠓': '8', '⠊': '9', '⠚': '0'
              };
 
              let result = '';
              let isNum = false;
              let isCaps = false;
              let isCapsLock = false;
 
              for (let i = 0; i < str.length; i++) {
                  const char = str[i];
 
                  if (char === '⠸') {
                      if (str[i+1] === '⠌') { result += '/'; i++; continue; }
                      if (str[i+1] === '⠹') { result += '#'; i++; continue; }
                  }
                  if (char === '⠠') {
                      isNum = false;
                      if (str[i+1] === '⠠') { isCapsLock = true; i++; continue; }
                      else if (str[i+1] === '⠄') { isCapsLock = false; i++; continue; }
                      else { isCaps = true; continue; }
                  }
                  if (char === '⠼') { isNum = true; isCaps = false; isCapsLock = false; continue; }
                  if (char === '⠰') { isNum = false; continue; }
 
                  if (char === '⠲') { result += '.'; isNum = false; continue; }
                  if (char === '⠒') { result += ':'; isNum = false; continue; }
                  if (char === '⠤') { result += '-'; isNum = false; continue; }
                  if (char === '⠐' && str[i+1] === '⠶') { result += '='; i++; isNum = false; continue; }
                  if (char === '⠖') { result += '+'; isNum = false; continue; }
                  if (char === '⠌') { result += '/'; isNum = false; continue; }
                  if (char === '⠹') { result += '#'; isNum = false; continue; }
                  if (char === '⠶') { result += '='; isNum = false; continue; }
                  if (char === '⠀') { result += ' '; isNum = false; continue; }
 
                  if (isNum && numMap[char]) { result += numMap[char]; continue; }
 
                  if (brailleMap[char]) {
                      let c = brailleMap[char];
                      if (isCaps || isCapsLock) {
                          c = c.toUpperCase();
                          isCaps = false;
                      }
                      result += c;
                      isNum = false;
                      continue;
                  }
 
                  if (char >= '⠀' && char <= '⣿') continue;
 
                  result += char;
                  isNum = false;
              }
              return result;
          };
 
          converted = decodeBraille(selText).trim();
      } else {
          converted = decodeURI(encodeURI(Base64.decode(base64AddPadding(selText))).replaceAll('%00', ''));
          converted = converted.trim();
      }
 
      if (!converted || converted === selText) return;
 
      if (event) {
          event.preventDefault();
          event.stopPropagation();
          event.stopImmediatePropagation();
      }
 
      const anchorNode = selection.anchorNode;
 
      const populateWithLinks = (container, text) => {
          const urlPattern = /(https?:\/\/[^\s]+|(?:[a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?)/g;
          const parts = text.split(urlPattern);
          parts.forEach(part => {
              if (!part) return;
              if (/^(https?:\/\/|(?:[a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,})/.test(part)) {
                  const a = document.createElement('a');
                  a.href = /^https?:\/\//.test(part) ? part : 'https://' + part;
                  a.target = '_blank';
                  a.rel = 'noreferrer';
                  a.style.color = '#198754';
                  a.style.textDecoration = 'underline';
                  a.textContent = part;
                  a.title = '클릭 시 새 창으로 이동';
 
                  a.addEventListener('click', function(e) {
                      e.stopPropagation();
                  });
                  container.appendChild(a);
              } else {
                  container.appendChild(document.createTextNode(part));
              }
          });
      };
 
      if (anchorNode && anchorNode.nodeType === Node.TEXT_NODE) {
          const originalText = anchorNode.textContent;
          const startIdx = originalText.indexOf(selText);
 
          if (startIdx !== -1) {
              const beforeText = originalText.substring(0, startIdx);
              const afterText = originalText.substring(startIdx + selText.length);
 
              const container = document.createElement('span');
              container.className = 'abad-drag-container';
              if (beforeText) container.appendChild(document.createTextNode(beforeText));
 
              const wrapper = document.createElement('span');
              wrapper.style.color = '#198754';
              wrapper.style.fontWeight = 'bold';
              wrapper.style.cursor = 'pointer';
              wrapper.title = '드래그 하여 디코딩 된 결과입니다. (텍스트 클릭 시 원본 복구)';
              wrapper.className = 'abad-generated';
 
              populateWithLinks(wrapper, converted);
 
              wrapper.addEventListener('click', function(e) {
                  let sel = window.getSelection();
                  let pc = document.querySelector("main #post_content");
                  let shadowSel = (pc && pc.shadowRoot && typeof pc.shadowRoot.getSelection === 'function') ? pc.shadowRoot.getSelection() : null;
 
                  if ((sel && sel.toString().length > 0) || (shadowSel && shadowSel.toString().length > 0)) {
                      return;
                  }
 
                  e.preventDefault();
                  e.stopPropagation();
                  container.replaceWith(anchorNode);
              });
 
              container.appendChild(wrapper);
              if (afterText) container.appendChild(document.createTextNode(afterText));
 
              const parent = anchorNode.parentNode;
              anchorNode.replaceWith(container);
 
              if (parent) {
                  const mo = new MutationObserver((mutations) => {
                      let restored = false;
                      parent.childNodes.forEach(n => {
                          if (n !== container && n.nodeType === Node.TEXT_NODE && n.nodeValue.includes(selText)) {
                              restored = true;
                          }
                      });
                      if (restored) {
                          container.remove();
                          mo.disconnect();
                      }
                  });
                  mo.observe(parent, { childList: true, characterData: true, subtree: true });
              }
 
              selection.removeAllRanges();
              return;
          }
      }
 
      const range = selection.getRangeAt(0);
      const parent = range.commonAncestorContainer;
      const extractedNodes = range.extractContents();
 
      const resultNode = document.createElement('span');
      resultNode.style.color = '#198754';
      resultNode.style.fontWeight = 'bold';
      resultNode.style.cursor = 'pointer';
      resultNode.title = '드래그 하여 디코딩 된 결과입니다. (텍스트 클릭 시 원본 복구)';
      resultNode.className = 'abad-generated';
 
      populateWithLinks(resultNode, converted);
 
      resultNode.addEventListener('click', function(e) {
          let sel = window.getSelection();
          let pc = document.querySelector("main #post_content");
          let shadowSel = (pc && pc.shadowRoot && typeof pc.shadowRoot.getSelection === 'function') ? pc.shadowRoot.getSelection() : null;
 
          if ((sel && sel.toString().length > 0) || (shadowSel && shadowSel.toString().length > 0)) {
              return;
          }
 
          e.preventDefault();
          e.stopPropagation();
          this.replaceWith(extractedNodes);
      });
 
      range.insertNode(resultNode);
 
      const actualParent = parent.nodeType === Node.TEXT_NODE ? parent.parentNode : parent;
      if (actualParent) {
          const mo = new MutationObserver(() => {
              let restored = false;
              actualParent.childNodes.forEach(n => {
                  if (n !== resultNode && n.nodeType === Node.TEXT_NODE && n.nodeValue.includes(selText)) {
                      restored = true;
                  }
              });
              if (restored) {
                  resultNode.remove();
                  mo.disconnect();
              }
          });
          mo.observe(actualParent, { childList: true, characterData: true, subtree: true });
      }
 
      selection.removeAllRanges();
 
    } catch (e) {
    }
  }
}
 
function activateDragDecoding() {
  if (abadInternalDB.internalDB.dragDecodingEnable) return;
  abadInternalDB.internalDB.dragDecodingEnable = true;

  document.addEventListener('mouseup', function(e) {
      if (e.target && e.target.closest && e.target.closest('#abad-mobile-decode-btn')) return;

      setTimeout(() => {
          if (abadInternalDB.internalDB.dragDecodingEnable) {
              tryDragDecode(e);
          }
      }, 10);
  }, true);

  const isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
  if (isTouchDevice) {
      const mobileBtn = document.createElement('div');
      mobileBtn.id = 'abad-mobile-decode-btn';
      mobileBtn.innerHTML = '🔒';
      mobileBtn.title = '선택한 텍스트 디코딩';
      mobileBtn.style.cssText = `
          position: fixed;
          bottom: 25px;
          left: 25px;
          font-size: 24px;
          background: #198754;
          border: 2px solid #ddd;
          border-radius: 50%;
          width: 50px;
          height: 50px;
          display: none;
          align-items: center;
          justify-content: center;
          box-shadow: 0 4px 10px rgba(0,0,0,0.3);
          z-index: 10000;
          cursor: pointer;
          user-select: none;
      `;
      document.body.appendChild(mobileBtn);

      document.addEventListener('selectionchange', () => {
          if (!abadInternalDB.internalDB.dragDecodingEnable) {
              mobileBtn.style.display = 'none';
              return;
          }

          let pc = document.querySelector("main #post_content");
          let selection = null;

          if (pc && pc.shadowRoot && typeof pc.shadowRoot.getSelection === 'function') {
              let shadowSel = pc.shadowRoot.getSelection();
              if (shadowSel && shadowSel.toString().trim().length > 0) {
                  selection = shadowSel;
              }
          }

          if (!selection) {
              let winSel = window.getSelection();
              if (winSel && winSel.toString().trim().length > 0) {
                  selection = winSel;
              }
          }

          if (selection) {
              const selText = selection.toString().trim();
              const isBraille = /^[\u2800-\u28FF\s]+$/.test(selText);
              
              if ((isBraille || !selText.match(abadConstDB.regInvalid)) && selText.length >= 4) {
                  mobileBtn.style.display = 'flex';
                  return;
              }
          }
          mobileBtn.style.display = 'none';
      });

      const onMobileBtnClick = (e) => {
          e.preventDefault();
          e.stopPropagation();
          
          tryDragDecode(e);
          mobileBtn.style.display = 'none';
      };

      mobileBtn.addEventListener('mousedown', onMobileBtnClick);
      mobileBtn.addEventListener('touchstart', onMobileBtnClick, { passive: false });
  }
}
 
async function menuStructureUpdate(fistRun = false) {
  localParameter.basedepth.value = Math.min(localParameter.basedepth.value, 11);
  menuStructure.autodecode.name = '🗝 자동 디코딩 '+(localParameter.autodecode.value ? '끄기' : '켜기');
  menuStructure.basedepth.name = '🎛 base64 깊이 조절하기 - 현재 값 : '+localParameter.basedepth.value+'회';
  menuStructure.enclinkhide.name = '🔗 인코딩된 링크 '+(localParameter.enclinkhide.value?'숨기기':'보이기');
  menuStructure.declinkhide.name = '🔗 디코딩된 링크 '+(localParameter.declinkhide.value?'숨기기':'보이기');
  menuStructure.draggable.name = '🖱 드래그 시 자동 디코딩 '+(localParameter.draggable.value?'끄기':'켜기');
  menuStructure.expandmenu.name = '⚙️ 스크립트 메뉴 '+(localParameter.expandmenu.value?'축소':'확장');
 
  menuStructure.enclinkhide.visible = localParameter.autodecode.value;
  menuStructure.declinkhide.visible = localParameter.autodecode.value;
  menuStructure.basedepth.visible = localParameter.autodecode.value;
 
  if (!fistRun) {
    for (let i of Object.keys(menuStructure)) {
      try { await GM.unregisterMenuCommand(menuStructure[i].id); } catch(_) {}
    }
  }
 
  try {
    if(localParameter.expandmenu.value) {
      for (let i of Object.keys(menuStructure)) {
        if (menuStructure[i].visible) {
          menuStructure[i].id = await GM.registerMenuCommand(menuStructure[i].name, menuStructure[i].func, {title:menuStructure[i].desc});
        }
      }
    } else {
      menuStructure.expandmenu.id = await GM.registerMenuCommand(menuStructure.expandmenu.name, menuStructure.expandmenu.func, {title:menuStructure.expandmenu.desc});
    }
  } catch(e) {}
}
 
function menuFuncSubPageReload(showmsg) {
  if (abadInternalDB.internalDB.swal2Enable) {
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      html: `<b>${showmsg}</b><br><br><i>> 반영을 위해 사이트 새로고침이 필요합니다.<br>사이트를 새로고침할까요?</i>`,
      icon: 'info',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      confirmButtonText: '새로고침',
      cancelButtonText: '취소',
    }).then((result) => {
      if (result.isConfirmed) window.location.reload(true);
    });
  }
}
 
function menuFunctionAutodecode() {
  menuStructureUpdate();
  const currentState = localParameter.autodecode.value;
 
  if (abadInternalDB.internalDB.swal2Enable) {
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      html: `<b>자동 디코딩 기능을 ${(currentState ? '끄시' : '켜시')}겠습니까?</b>`,
      icon: 'question',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      confirmButtonText: (currentState ? '끄기' : '켜기'),
      cancelButtonText: '취소',
    }).then((result) => {
      if (result.isConfirmed) {
        const targetState = !currentState;
        localParameter.autodecode.value = targetState;
 
        GM.setValue(localParameter.autodecode.param_name, targetState).then(() => {
          menuStructureUpdate();
 
          if (targetState) {
            executeDecoder();
            Swal.fire({
              icon: 'success',
              title: abadConstDB.SWAL2Title,
              html: `<b style="font-size: 77.5%">자동 디코딩 기능이 활성화되었습니다.</b>`,
              toast: true,
              position: 'center',
              timer: 2000,
              timerProgressBar: true,
              showConfirmButton: false
            });
          } else {
            menuFuncSubPageReload('자동 디코딩 기능이 해제되었습니다. 원본 상태로 되돌리기 위해 페이지를 새로고침합니다.');
          }
        });
      }
    });
  }
}
 
function menuFunctionBasedepth() {
  menuStructureUpdate();
  const previousValue = localParameter.basedepth.value;
  const str_common_1 = ' ( 지정 가능한 범위: 1~'+abadInternalDB.internalDB.autoDecodingMaximum.toString()+' )';
 
  if (abadInternalDB.internalDB.swal2Enable) {
    const slideHandler = function(event) {
      const target = Swal.getPopup().querySelector("#footer");
      if (event.target.value > 7) {
        target.style.display = 'block';
        target.innerHTML = `<i>(값을 너무 크게 지정하면 브라우저 성능에 영향을 줄 수 있습니다.)</i>`;
      } else { target.style.display = 'none'; }
    };
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      icon: "question",
      input: "range",
      html: `<b>Base64 자동 디코딩 중첩 횟수를 얼마로 지정할까요?</b><div style = "font-size: 75%; margin: 1em auto 1em"><i>(인코딩을 인코딩한 것을 여러번 반복한 것을 자동으로 풀어냅니다.)</i></div><span style = "font-size: 87.5%;">현재 값: ${previousValue.toString()}회,${(previousValue == 3 ? '' : ' 기본값: 3회,')}${str_common_1}`,
      inputAttributes: { min: "1", max: abadInternalDB.internalDB.autoDecodingMaximum.toString(), step: "1" },
      footer: `<i id="footer">${(previousValue > 7)?'(값을 너무 크게 지정하면 브라우저 성능에 영향을 줄 수 있습니다.)':''}</i>`,
      inputValue: previousValue,
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      confirmButtonText: '변경',
      cancelButtonText: '취소',
      inputValidator: (value) => {
        return new Promise((resolve) => {
          if (value == previousValue) { resolve(`기존값과 동일합니다, 현재 값: ${previousValue}회`); } else { resolve(); }
        });
      },
      didOpen: (modal) => { modal.querySelector(".swal2-range").firstChild.addEventListener('input', slideHandler, false); },
      willClose: (modal) => { modal.querySelector(".swal2-range").firstChild.removeEventListener('input', slideHandler); },
    }).then((result) => {
      if (result.isConfirmed) {
        const targetValue = parseInt(result.value);
        localParameter.basedepth.value = targetValue;
        try {
          GM.setValue(localParameter.basedepth.param_name, targetValue);
          menuFuncSubPageReload('자동 디코딩 중첩 횟수가 '+previousValue.toString()+'에서 '+targetValue.toString()+'(으)로<br>변경이 완료되었습니다.');
        } catch(e) { localParameter.basedepth.value = previousValue; }
        finally { menuStructureUpdate(); }
      }
    });
  }
}
 
function menuFunctionEnchide() {
  menuStructureUpdate();
  const currentState = localParameter.enclinkhide.value;
  if (abadInternalDB.internalDB.swal2Enable) {
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      html: `<b>인코딩된 코드를 ${(currentState?'숨기시':'표시하')}겠습니까?</i>`,
      icon: 'question',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      confirmButtonText: (currentState?'숨기기':'표시하기'),
      cancelButtonText: '취소',
    }).then((result) => {
      if (result.isConfirmed) {
        const targetState = !currentState;
        localParameter.enclinkhide.value = targetState;
        try {
          GM.setValue(localParameter.enclinkhide.param_name, targetState);
          menuFuncSubPageReload('앞으로 인코딩된 코드를 '+(targetState?'표시합':'숨깁')+'니다.');
        } catch(e) { localParameter.enclinkhide.value = currentState; }
        finally { menuStructureUpdate(); }
      }
    });
  } else {
    const targetState = !currentState;
    localParameter.enclinkhide.value = targetState;
    try { GM.setValue(localParameter.enclinkhide.param_name, targetState); } catch(e) {}
    location.reload();
  }
}
 
function menuFunctionDechide() {
  menuStructureUpdate();
  const currentState = localParameter.declinkhide.value;
  if (abadInternalDB.internalDB.swal2Enable) {
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      html: `<b>디코딩된 코드를 ${(currentState?'숨기시':'표시하')}겠습니까?</i>`,
      icon: 'question',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      confirmButtonText: (currentState?'숨기기':'표시하기'),
      cancelButtonText: '취소',
    }).then((result) => {
      if (result.isConfirmed) {
        const targetState = !currentState;
        localParameter.declinkhide.value = targetState;
        try {
          GM.setValue(localParameter.declinkhide.param_name, targetState);
          menuFuncSubPageReload('앞으로 디코딩된 코드를 '+(targetState?'표시합':'숨깁')+'니다.');
        } catch(e) { localParameter.declinkhide.value = currentState; }
        finally { menuStructureUpdate(); }
      }
    });
  } else {
    const targetState = !currentState;
    localParameter.declinkhide.value = targetState;
    try { GM.setValue(localParameter.declinkhide.param_name, targetState); } catch(e) {}
    location.reload();
  }
}
 
function menuFunctionDraggable() {
  menuStructureUpdate();
  const currentState = localParameter.draggable.value;
  if (abadInternalDB.internalDB.swal2Enable) {
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      html: `<b>드래그 시 자동 디코딩을 ${(currentState?'비':'')}활성화 하시겠습니까?</b><br><br><i>(앞으로 인코딩된 부분을 드래그${(currentState?'해도<br>자동으로 디코딩되지 않습':' 시 Base64로 인코딩된<br>것으로 판단 되면 자동으로 디코딩을 시도합')}니다.)</i>`,
      icon: 'question',
      showDenyButton: true,
      showCancelButton: !currentState,
      confirmButtonColor: '#3085d6',
      confirmButtonText: (currentState?'비활성화':'활성화'),
      denyButtonText: '취소',
      cancelButtonText: '이번에만 활성화',
    }).then((result) => {
      if (result.isConfirmed) {
        const targetState = !currentState;
        localParameter.draggable.value = targetState;
        try {
          GM.setValue(localParameter.draggable.param_name, targetState);
          if (targetState) {
            activateDragDecoding();
            Swal.fire({ icon: 'success', title: abadConstDB.SWAL2Title, html: `<b style="font-size: 77.5%">앞으로 드래그 시 자동 디코딩을 진행합니다.</b>`, toast: true, position: 'center', timer: 2000, timerProgressBar: true, showConfirmButton: false });
          } else { menuFuncSubPageReload('앞으로 드래그 해도 반응하지 않습니다.'); }
        } catch(e) { localParameter.draggable.value = currentState; }
        finally { menuStructureUpdate(); }
      } else if (result.isDismissed && result.dismiss === Swal.DismissReason.cancel) {
        try {
          activateDragDecoding();
          Swal.fire({ icon: 'success', title: abadConstDB.SWAL2Title, html: `<b style="font-size: 77.5%">드래그 시 자동 디코딩이 활성화되었습니다.</b><br><i style="font-size: 67.5%">새로고침 시 자동으로 비활성화됩니다.</i>`, toast: true, position: 'center', timer: 2000, timerProgressBar: true, showConfirmButton: false });
        } catch(e) {}
      }
    });
  }
}
 
function menuFunctionChangeExpandMode() {
  menuStructureUpdate();
  const currentState = localParameter.expandmenu.value;
  if (abadInternalDB.internalDB.swal2Enable) {
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      html: `<b>메뉴에 나타나는 항목을 ${(currentState?'줄일':'늘릴')}까요?</b><br><br><i>(앞으로 세부설정 메뉴가 ${(currentState?'숨겨':'보여')}집니다.)</i>`,
      icon: 'question',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      confirmButtonText: (currentState?'줄이기':'표시하기'),
      cancelButtonText: '취소',
    }).then((result) => {
      if (result.isConfirmed) {
        const targetState = !currentState;
        localParameter.expandmenu.value = targetState;
        try {
          GM.setValue(localParameter.expandmenu.param_name, targetState);
          Swal.fire({ icon: 'success', title: abadConstDB.SWAL2Title, html: `<b style="font-size: 87.5%">앞으로 세부설정 메뉴가 ${(targetState?'보여':'숨겨')}집니다.</b>`, toast: true, position: 'center', timer: 2000, timerProgressBar: true, showConfirmButton: false });
        } catch(e) { localParameter.expandmenu.value = currentState; }
        finally { menuStructureUpdate(); }
      }
    });
  }
}
 
function menuFunctionRstDefaults() {
  menuStructureUpdate();
  if (abadInternalDB.internalDB.swal2Enable) {
    Swal.fire({
      title: abadConstDB.SWAL2Title,
      html: `<b>정말 스크립트 설정을 기본값으로 초기화하시겠습니까?</b><br><br><i>(초기화 완료 후 자동으로 새로고침됩니다.)</i>`,
      icon: 'warning',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      focusCancel: true,
      confirmButtonText: '초기화 진행',
      cancelButtonText: '취소',
      showLoaderOnConfirm: true,
      timer: 10000,
      timerProgressBar: true,
      didOpen: (modal) => {
        modal.onmouseenter = Swal.stopTimer;
        modal.onmouseleave = Swal.resumeTimer;
      },
    }).then((result) => {
      if (result.isConfirmed) {
        Swal.fire({
          title: abadConstDB.SWAL2Title,
          html: `<b>설정값을 제거중입니다, 잠시만 기다려주세요..</b>`,
          footer: `<i>1분 이내로 이 창이 사라지지 않으면 수동으로 새로고침해주세요.</i>`,
          didOpen: () => { Swal.showLoading(); },
          showConfirmButton: false, allowOutsideClick: false, allowEscapeKey: false, allowEnterKey: false,
        });
        try {
          Object.keys(menuStructure).forEach(function(i) { try { GM.unregisterMenuCommand(menuStructure[i].id); } catch(_) {} });
          for (const i of Object.keys(localParameter)) { GM.deleteValue(localParameter[i].param_name); }
          sleep(250).then(() => {
            Swal.fire({
              title: abadConstDB.SWAL2Title,
              html: `<b>설정값이 모두 제거되었습니다.</b><br><br><i>(확인 후 현재 창이 자동으로 새로고침됩니다.)</i>`,
              footer: `<i style="font-size: 82.5%;">비정상적으로 동작 시 스크립트를 재설치해주세요.</i>`,
              icon: 'success',
              confirmButtonColor: '#3085d6',
              confirmButtonText: '확인',
              didOpen: () => { Swal.hideLoading(); },
            }).then(() => { window.location.reload(true); });
          });
        } catch(e) { Swal.close(); }
      }
    });
  }
}
 
function processTextNode(node) {
    let text = node.textContent;
    if (!text || !text.trim()) return false;
 
    let brailleRegex = /[\u2800-\u28FF]{4,}/g;
    let bMatch = brailleRegex.exec(text);
    if (bMatch) {
        let sourceStr = bMatch[0];
        let start = bMatch.index;
 
        let afterStart = node.splitText(start);
        let afterEnd = afterStart.splitText(sourceStr.length);
 
        let htmlString = replacerGen(0, 'article')(sourceStr);
 
        let wrapper = document.createElement('span');
        wrapper.className = 'abad-generated';
        wrapper.innerHTML = htmlString;
 
        afterStart.replaceWith(wrapper);
 
        wrapper.querySelectorAll('[id^="abad_"]').forEach(el => {
            if (abadInternalDB.encodedLink.hasOwnProperty(el.id)) {
                el.addEventListener('click', showEncodedLink);
            }
        });
 
        processTextNode(afterEnd);
        return true;
    }
 
    for (let i = 0; i < localParameter.basedepth.value; i++) {
        let regex = new RegExp(regexEncodedPrefixDef[i].source, regexEncodedPrefixDef[i].flags);
        let match = regex.exec(text);
        if (match) {
            let sourceStr = match[0];
            let start = match.index;
 
            let afterStart = node.splitText(start);
            let afterEnd = afterStart.splitText(sourceStr.length);
 
            let htmlString = replacerGen(i, 'article')(sourceStr);
 
            let wrapper = document.createElement('span');
            wrapper.className = 'abad-generated';
            wrapper.innerHTML = htmlString;
 
            afterStart.replaceWith(wrapper);
 
            wrapper.querySelectorAll('[id^="abad_"]').forEach(el => {
                if (abadInternalDB.encodedLink.hasOwnProperty(el.id)) {
                    el.addEventListener('click', showEncodedLink);
                }
            });
 
            processTextNode(afterEnd);
            return true;
        }
    }
    return false;
}
 
function hasSummaryBox() {
    if (document.getElementById('abad_summary_box')) return true;
    const pc = document.querySelector("main #post_content");
    if (pc && pc.shadowRoot && pc.shadowRoot.getElementById('abad_summary_box')) return true;
    return false;
}
 
function executeDecoder() {
    if (!localParameter.autodecode.value) return;
 
    let containers = [];
    let pc = document.querySelector("main #post_content");
    if (pc && pc.shadowRoot) containers.push(pc.shadowRoot);
    else if (pc) containers.push(pc);
    document.querySelectorAll('main .grow.flex .grow.grid, main .md\\:items-center').forEach(e => containers.push(e));
 
    let processedAny = false;
 
    containers.forEach(container => {
        const textTagNames = 'p, span, div, a, li, h1, h2, h3, h4, h5, h6, em, strong, td, code, pre, blockquote';
        const elements = Array.from(container.querySelectorAll(textTagNames));
        elements.push(container);
 
        elements.forEach(el => {
            if (typeof el.closest === 'function' && el.closest('.abad-generated')) return;
 
            const textNodes = Array.from(el.childNodes).filter(n => n.nodeType === Node.TEXT_NODE);
            textNodes.forEach(node => {
                if (processTextNode(node)) processedAny = true;
            });
        });
    });
 
    if (processedAny && hindex > 0 && !hasSummaryBox()) {
        abadInternalDB.hostnameSet = Array.from(abadInternalDB.hostnameSetRaw).sort();
 
        let result = document.createElement("div");
        result.id = 'abad_summary_box';
        result.style.margin = '10px 0';
        result.style.paddingTop = '3px';
 
        let result_box = document.createElement("span");
        result_box.style.cursor = 'pointer';
        let result_in = '<div style="color: #e83e8c; border: 1.5px solid #68b3ff; padding: 7px 15px 5px 15px; border-radius: 5px; background: #fff;">';
        result_box.title = '클릭 시 디코딩된 링크를 한번에 볼 수 있습니다.';
        result_in += `총 <b>${hindex}개</b>의 링크가 자동 디코딩되었습니다.<br><span style="font-size: 90%; color: #666;">( 감지된 사이트 종류: ${abadInternalDB.hostnameSet.length}개 )</span>`;
        result_in += `<p style="margin-top: 5px; margin-bottom: 0px; font-size: 13px; color: #888;"><i>(클릭하여 전체 목록 보기)</i></p>`;
        result_in += '</div>';
        result_box.innerHTML = result_in;
        result_box.addEventListener('click', showDecodeSummary);
 
        result.appendChild(result_box);
        result.appendChild(document.createElement("hr"));
 
        let insertTarget = document.querySelector("main #post_content");
        if (insertTarget && insertTarget.shadowRoot) {
            insertTarget.shadowRoot.insertBefore(result, insertTarget.shadowRoot.firstChild);
        } else if (insertTarget && insertTarget.parentNode) {
            insertTarget.parentNode.insertBefore(result, insertTarget);
        } else {
            let fallbackTarget = document.querySelector('main .grow.flex .grow.grid') || document.querySelector('main');
            if (fallbackTarget && fallbackTarget.parentNode) {
                fallbackTarget.parentNode.insertBefore(result, fallbackTarget);
            }
        }
    }
}
 
function debounce(func, delay) {
    let timer;
    return function() { clearTimeout(timer); timer = setTimeout(func, delay); };
}
 
(async () => {
  if (window.Swal) {
    const styleSA2 = document.createElement('style');
    styleSA2.textContent = '.swal2-container { z-index: 2400; }';
    document.head.appendChild(styleSA2);
    abadInternalDB.internalDB.swal2Enable = true;
  }
 
  for (const i of Object.keys(localParameter)) {
    localParameter[i].value = await GM.getValue(localParameter[i].param_name, localParameter[i].def_value);
  }
  menuStructureUpdate(true);
  if (localParameter.draggable.value) activateDragDecoding();
 
  executeDecoder();
 
  const debouncedExecute = debounce(() => {
      if (lastUrl !== location.href) {
          lastUrl = location.href;
          hindex = 0;
          abadInternalDB.decodedLink = {};
          abadInternalDB.encodedLink = {};
          abadInternalDB.hostnameSetRaw.clear();
          const pc = document.querySelector("main #post_content");
          let oldBox = document.getElementById('abad_summary_box');
          if (!oldBox && pc && pc.shadowRoot) oldBox = pc.shadowRoot.getElementById('abad_summary_box');
          if (oldBox) oldBox.remove();
      }
      executeDecoder();
  }, 400);
 
  const observer = new MutationObserver(debouncedExecute);
  observer.observe(document.body, { childList: true, subtree: true });
})();