auto decode Base64 encoded link in Kone.gg
// ==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%;"> </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 = `> ${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 });
})();