您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
base64코드 자동복호화
当前为
// ==UserScript== // @name kone base64 자동복호화 // @namespace http://tampermonkey.net/ // @version 1.3.1 // @description base64코드 자동복호화 // @author SYJ // @match https://arca.live/* // @match https://kone.gg/* // @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"; const MAX_DECODE_COUNT = 10+1; window.addEventListener('load', ()=>setTimeout(main, 1000)); async function main(){ observeUrlChange(renderUI); const isAutoMode = await GM_getValue('toggleVal', true); if (isAutoMode) { observeUrlChange(applyAuto); } else { setTimeout(applyManually, 1000); } } function applyManually() { 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); } }) } function applyAuto() { 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); } } console.log('더이상 디코드할 수 없는 목록 :', nonBase64Collection); } const textTagNames = 'p, span, div, a, li,' + // 일반 컨테이너 'h1, h2, h3, h4, h5, h6,' + // 제목 요소 'em, strong, u, b, i, small, mark, ' + // 인라인 포맷팅 요소 'label, button, option, textarea' // 폼/인터페이스 요소 // 텍스트노드에 존재하는 url을 a태그로 바꿈. (텍스트노드 -> 텍스트노드1 + a태그 + 텍스트노드2) function linkifyTextNode(Node, text) { // 텍스트노드 중 url을 찾아 a태그로 변환. (액션 포함) // URL 매칭 (https:// 로 시작해서 공백 전까지) const urlRegex = /(https?:\/\/[^\s]+)/; Node.textContent = text; if (!urlRegex.test(text)) { // URL 없으면 텍스트 덮어씌우고 종료 return; } let node = Node; while(urlRegex.test(node?.textContent ?? '')){ const match = urlRegex.exec(node.textContent); const url = match[0]; const start = match.index; const urlLen = url.length; // "텍스트1 URL 텍스트2" 꼴의 텍스트노드를 세 개로 분리 // 1) URL 앞부분과 뒤를 분리 const textNode = document.createTextNode(node.textContent); const afterUrlStart = textNode.splitText(start); const afterUrlEnd = afterUrlStart.splitText(urlLen); const beforeUrlStart = textNode; // 3) <a> 요소 생성 후 URL 텍스트 노드 대신 교체. parent const a = makeATag(url) node.parentNode.replaceChild(a, node); node = afterUrlEnd; a.before(beforeUrlStart); a.after(afterUrlEnd); } function makeATag(link){ const aTag = document.createElement('a'); aTag.href = link; aTag.textContent = link; aTag.target = '_blank'; aTag.rel = 'noreferrer'; return aTag; } } function doDecode(text) { ///'use strict'; let result = text; result = dec(/[0-9A-Za-z+/]{6,}[=]{0,2}/g, result); //문자열 6회 + '=' 0~2회 return result; function dec(reg, text) { let result = text; const originals = Array.from(result.match(reg) ?? []); for (const original of originals){ const decoded = decodeNtime(original); result = result.replace(original, decoded); } return result; } } // 노드 하나에 존재하는 모든 base64구문을 복원함. function doDecode(text) { ///'use strict'; let result = text; result = dec(/[0-9A-Za-z+/]{6,}[=]{0,2}/g, result); //문자열 6회 + '=' 0~2회 return result; function dec(reg, text) { let result = text; const maps = Array.from(result.match(reg) ?? []) // base64 청크 .map(o=>({before:o, after:decodeNtime(o)})) // base64 to 원본 매핑 maps.forEach(({before, after})=>{result = result.replace(before, after)}); // 적용 return result; } } // 원문으로 가능한 패턴 (한영숫자 + 자주쓰는 특문 + 한자) // 허용 범위 // 한글 : \uAC00-\uD7A3 // 히라가나 : \u3040-\u309F // 카타카나 : \u30A0-\u30FF // CJK 한자 : \u3400-\u4DBF, \u4E00-\u9FFF, \uF900-\uFAFF // CJK 구두점·전각 특수문자: \u3000-\u303F // 전각 괄호 : \uFF08-\uFF09 // 영숫자 : A-Za-z0-9 // 반각 특수문자 : !@#\$%\^&\*\(\)_\-\+=\[\]\{\}\\|;:'",.<>\/\? const WORD_TEST = /^[ㄱ-ㅎ가-힣A-Za-z0-9!@#\$%\^&\*\(\)_\-\+=\[\]\{\}\\|;:/'",.<>\/\?!@#$%^&*()_+-=`~|\s\uAC00-\uD7A3\u3040-\u309F\u30A0-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\u3000-\u303F\uFF08-\uFF09]+$/; const nonBase64Collection = []; function decodeNtime(str) { let decoded = str; for (let i=0; i<MAX_DECODE_COUNT; i++){ const old = decoded; decoded = decodeOneTime(decoded); if (decoded === old) return decoded; } function decodeOneTime(str) { try { const decoded = base64DecodeUnicode(str) if (!WORD_TEST.test(decoded)) { nonBase64Collection.push(str); throw new Error('[정상 유니코드 범위가 아님]'+JSON.stringify(str)+JSON.stringify(decoded));} return decoded; } catch(e) { //console.log('[FAIL]',str, e); return str; } } function base64DecodeUnicode0(str) { // 1) atob으로 디코딩 → 바이너리(한 글자당 1바이트) 문자열 // 2) 각 문자 코드를 16진수 %xx 형태로 변환 // 3) decodeURIComponent로 UTF-8 해석 const percentEncodedStr = Array .from(atob(str)) .map(char => '%' + char.charCodeAt(0).toString(16).padStart(2, '0')) .join(''); return decodeURIComponent(percentEncodedStr); } function base64DecodeUnicode(str) { // 1) atob으로 디코딩 → 1바이트 문자열 const binary = atob(str); // 2) 각 문자(=바이트)를 숫자로 뽑아 Uint8Array 생성 const bytes = new Uint8Array( Array.from(binary, ch => ch.charCodeAt(0)) ); // 3) TextDecoder로 'utf-8' 디코딩 return new TextDecoder('utf-8').decode(bytes); } } // UI async function renderUI() { // 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(); } const observeUrlChange = (func) => { func(); let oldHref = document.location.href; const body = document.querySelector('body'); const observer = new MutationObserver(mutations => { if (oldHref !== document.location.href) { oldHref = document.location.href; setTimeout(func, 1000); } }); observer.observe(body, { childList: true, subtree: true }); };
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址