// ==UserScript==
// @name kone base64 자동복호화
// @namespace http://tampermonkey.net/
// @version 1.0.2
// @description base64코드 자동복호화
// @author SYJ
// @match https://arca.live/*
// @match https://kone.gg/s/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=arca.live
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addValueChangeListener
// @require https://openuserjs.org/src/libs/sizzle/GM_config.js
// @license MIT
// ==/UserScript==
// 자주 바뀜. 취약한 셀렉터
const SHADOW_ROOT_SELECTOR = "body main div.prose-container";
async function main(){
const isAutoMode = await GM_getValue('toggleVal', true);
if (isAutoMode) {
autoApply();
setInterval(autoApply, 1500);
}
else {
setTimeout(manuallyApply, 1000);
}
}
function doDecode(text) {
///'use strict';
let result = text;
result = dec(/aHR0c[0-9A-Za-z+]{20,}[=]{0,2}/, result); //aHR0c:1회인코딩된것.
result = dec(/YUhSMGN[0-9A-Za-z]{20,}[=]{0,2}/, result); //YUhSMGN:2회인코딩된것.
result = dec(/WVVoU01HTklUVFp[0-9A-Za-z+]{20,}[=]{0,2}/, result); //aHR0c:1회인코딩된것.
result = dec(/V1ZWb1UwMUhUa2[0-9A-Za-z]{20,}[=]{0,2}/, result); //YUhSMGN:2회인코딩된것.
result = dec(/[0-9A-Za-z]{30,}[=]{1,2}/, result); //문자열 30회 + '=' 1~2회
result = dec(/[0-9A-Za-z]{200,}[=]{0,2}/, result); //문자열 200회 + '=' 0~2회
return result;
}
function dec(reg, text) {
let result = text;
try {
while (reg.test(result)) {
const original = reg.exec(result)[0];
let decoded = original;
while (decoded.match(/aHR0c[0-9A-Za-z]{8,}[=]{0,2}/) == null){
decoded = atob(decoded);
}
decoded = atob(decoded); console.log(decoded);
result = result.replace(original, decoded);
}
return result;
}
catch(e) { console.log('er', e);
return text;}
}
function applyATag(el){
if (original === decodedLink) return;
while(el.firstChild) el.removeChild(el.firstChild);
el.appendChild(makeATag(decodedLink));
}
function manuallyApply() {
document.body.addEventListener('dblclick', function(e) {
console.log('더블클릭 감지! 🎉',e.target,event.composedPath()[0]);
const el = e.composedPath()[0];
const nodes = Array.from(el.childNodes).filter(node=>node.nodeType ===Node.TEXT_NODE)
console.log(nodes)
for (const node of nodes){
const original = node.textContent;
const decodedLink = doDecode(original);
// console.log(node, original, decodedLink);
if (original === decodedLink) continue;
linkifyTextNode(node, decodedLink);
}
})
}
// 텍스트노드 중 url을 찾아 a태그로 변환. (액션 포함)
function linkifyTextNode(Node, text) {
// URL 매칭 (https:// 로 시작해서 공백 전까지)
const urlRegex = /(https?:\/\/[^\s]+)/;
const match = urlRegex.exec(text);
if (!match) return; // URL 없으면 종료
const url = match[0];
const start = match.index;
const urlLen = url.length;
// "텍스트1 URL 텍스트2" 꼴의 텍스트노드를 세 개로 분리
// 1) URL 앞부분과 뒤를 분리
const textNode = document.createTextNode(text);
const afterUrlStart = textNode.splitText(start);
const afterUrlEnd = afterUrlStart.splitText(urlLen);
const beforeUrlStart = textNode;
// 3) <a> 요소 생성 후 URL 텍스트 노드 대신 교체
const a = makeATag(url)
Node.parentNode.replaceChild(a, Node);
a.before(beforeUrlStart);
a.after(afterUrlEnd);
}
const textTagNames = 'p, span, div, a, li,' + // 일반 컨테이너
'h1, h2, h3, h4, h5, h6,' + // 제목 요소
'em, strong, b, i, small, mark, ' + // 인라인 포맷팅 요소
'label, button, option, textarea' // 폼/인터페이스 요소
function autoApply() {
// console.log('auto')
const contents = Array.from(document.body.querySelectorAll(`main ${textTagNames}`));
const mainContents = Array.from(document.querySelector(SHADOW_ROOT_SELECTOR)?.shadowRoot?.querySelectorAll(textTagNames) ?? []);
contents.push(...mainContents);
for (const tag of contents) {
const nodes = Array.from(tag.childNodes).filter(node=>node.nodeType ===Node.TEXT_NODE)
for (const node of nodes){
const original = node.textContent;
const decodedLink = doDecode(original);
if (original === decodedLink) {continue;}
linkifyTextNode(node, decodedLink);
}
}
}
function makeATag(link){
const aTag = document.createElement('a');
aTag.href = link;
aTag.textContent = link;
aTag.target = '_blank';
aTag.rel = 'noreferrer';
return aTag;
}
window.addEventListener('load', ()=>{
main();
});
// UI
/* 프롬프트형 입력
(async function(){
let v = await GM_getValue('myValue', 123);
let menuId;
function registerMenu(){
if(menuId) GM_unregisterMenuCommand(menuId);
// 레이블에 현재 v 를 포함
menuId = GM_registerMenuCommand(
`설정 입력 (현재 값: ${v})`,
async () => {
const input = prompt('새 값을 입력하세요:', v);
if(input !== null){
const n = parseInt(input);
if(!isNaN(n)){
v = n;
await GM_setValue('myValue', v);
registerMenu(); // 메뉴 레이블 갱신
} else {
alert('숫자만 입력하세요');
}
}
}
);
}
registerMenu();
// — 여기서 v 를 실제 화면에 표시하거나 쓰고 싶다면,
// document.body.append(`현재 값: ${v}`) 같은 DOM 추가로 처리하세요.
})();
*/
(async function() {
// 1) 값 로드
let val = await GM_getValue('toggleVal', false);
let menuId;
// 2) 배지 생성
/* const badge = document.createElement('div');
Object.assign(badge.style, {
position: 'fixed',
top: '10px',
right: '10px',
padding: '4px 8px',
background: 'rgba(0,0,0,0.7)',
color: '#fff',
fontSize: '14px',
borderRadius: '4px',
zIndex: '9999',
});
document.body.append(badge);
*/
// 3) 렌더 함수
function render() {
// 메뉴 해제 후 다시 등록
if (menuId) GM_unregisterMenuCommand(menuId);
menuId = GM_registerMenuCommand(
`자동모드 토글 (현재: ${val?'ON':'OFF'})`,
toggleValue
);
// 배지 업데이트
//badge.textContent = `현재 값: ${val}`;
}
// 4) 토글 함수 (즉시 UI 업데이트 포함)
async function toggleValue() {
const newVal = !val;
await GM_setValue('toggleVal', newVal);
val = newVal; // 변수 갱신
render(); // 메뉴·배지 즉시 갱신
}
// 초기 렌더
render();
})();