kone
소미소프트

소미소프트

자동 국룰입력/복호화/다운로드 확장프로그램 스크립_귀인 분 코드 긴급 패치_이거 말고 원본 쓰쎔

05/22/2025, 23:45:46
유틸
12826 views · 16 likes

MIT 라이센스라고 명시 해서 일단 동작되는 지 체크하고 패치 해서 내가 수정해서 쓰던 코드 전문 첨부함.
서버 업데이트 되고 동작 안되서 그거만 딱 패치 함.
추후에 또 패치 되면 난 모름.

원작자이신 우리 귀인 분께서 패치하시거나 삭제 요청하시면 글 날릴 예정.
(보존해도 된다고 하셔서 일단 보존함.)


코드 옮겨서 사용할 줄 모르면 패치 기다리면 됨.

-----------------------------
2025년 05/23 일부 서버 패치에 따른 복호화 불가 문제 해결

2025년 05/28 확인함.
aHR0cHM6Ly9rb25lLmdnL3Mvc29taXNvZnQvY2t0NF9jTzNJZU1CaldRRzNQY3pDYj9wPTEmY2F0ZWdvcnk9Y1dLdGtnc2tjOU5CZjFvR0pQNDV5YiNhcnRpY2xl
링크 확인하면 오토 소미 새 버전 만드심.
개쩜 링크 꺼 쓰셈 ㅋㅋㅋ

// ==/UserScript==
'use strict';
let chkp = [,,,, atob('c29taXNvZnQ=')], Down_Option, PageLoading = [], isT = [,,], MenuID = [null, null], host = document.URL.split('/')[2], npw = [], pw = [atob('c29taXNvZnQ='),atob('MjAyNXNvbWlzb2Z0'),
// =============================== Settings =======================================
// 추가하길 원하는 비밀번호 따옴표 - 쉼표로 구분해서 바로 아래줄에 넣으면 됨 ex) '1234', '2024국룰', '!국룰!'

];
PageLoading[0] = 1000;     // 페이지 로딩 시간 조절 (1000당 1초)
Down_Option = 0;           // Kiosk 다운로드 옵션 0:Basic, 1:Fast, 2:일괄 다운로드
// ======================================================================================

// Helper function to get content element from Kone.gg Shadow DOM
function getKoneGGContentElement() {
    if (host !== 'kone.gg') return null;
    console.log('[AutoSomi] getKoneGGContentElement: Attempting to find content element...');
    const proseContainer = document.querySelector('div.prose-container'); // This is the shadow host.
    if (!proseContainer) {
        console.log('[AutoSomi] getKoneGGContentElement: div.prose-container (shadow host) not found.');
        return null;
    }
    console.log('[AutoSomi] getKoneGGContentElement: div.prose-container (shadow host) found:', proseContainer);

    // For Declarative Shadow DOM, the shadow root is attached to the host element (proseContainer).
    if (!proseContainer.shadowRoot) {
        console.log('[AutoSomi] getKoneGGContentElement: proseContainer.shadowRoot not found. DSD might not be attached, supported, or the structure is different.');
        const templateForDebug = proseContainer.querySelector('template[shadowrootmode="open"]');
        if (templateForDebug) {
            console.log('[AutoSomi] getKoneGGContentElement: Debug - template[shadowrootmode="open"] element exists. ShadowRoot should be on proseContainer.');
        } else {
            console.log('[AutoSomi] getKoneGGContentElement: Debug - template[shadowrootmode="open"] element also not found.');
        }
        return null;
    }
    console.log('[AutoSomi] getKoneGGContentElement: Accessed proseContainer.shadowRoot successfully.');

    let contentDiv = proseContainer.shadowRoot.querySelector('div.dark'); // Try new selector first
    if (contentDiv) {
        console.log('[AutoSomi] getKoneGGContentElement: Found content with new selector "div.dark":', contentDiv);
        return contentDiv;
    }

    console.log('[AutoSomi] getKoneGGContentElement: New selector "div.dark" not found, trying old selector "div.content".');
    contentDiv = proseContainer.shadowRoot.querySelector('div.content'); // Fallback to old selector
    if (!contentDiv) {
        console.log('[AutoSomi] getKoneGGContentElement: Neither "div.dark" nor "div.content" found within proseContainer.shadowRoot.');
        return null;
    }
    console.log('[AutoSomi] getKoneGGContentElement: Found content with old selector "div.content":', contentDiv);
    return contentDiv;
}


// 미리보기 방지 창 숨기기 또는 자동 클릭 함수
function handleBlockingModals(currentHost) {
    console.log(`[AutoSomi] ${currentHost} 에서 방해 요소 처리 시도... (handleBlockingModals 시작)`);

    if (currentHost === 'kone.gg') {
        const nsfwOverlayContainer = document.querySelector('div.relative.min-h-60 > div.absolute.w-full.h-full.backdrop-blur-2xl');
        if (nsfwOverlayContainer && nsfwOverlayContainer.offsetParent !== null) { // 화면에 보일 때만
            const viewContentButton = nsfwOverlayContainer.querySelector('div.flex.gap-4 button:nth-child(2)');
            if (viewContentButton && viewContentButton.textContent?.includes('콘텐츠 보기')) {
                console.log('[AutoSomi] Kone.gg: NSFW "콘텐츠 보기" 버튼 클릭 시도.');
                viewContentButton.click();
                return new Promise(resolve => setTimeout(resolve, 500));
            } else {
                const modalSelectorsKone = [
                    '.age-verification-popup',
                    '.content-overlay.block',
                ];
                modalSelectorsKone.forEach(selector => hideElement(selector));
            }
        }
    } else if (currentHost === 'arca.live') {
        const modalSelectorsArca = [
            { selector: '.adult-confirm-modal', action: 'hide' },
            { selector: '.fc-dialog', action: 'hide' },
            { selector: '#preview-block-layer', action: 'hide' },
            { selector: 'div[class*="adult-channel-confirm"]', action: 'hide' }, // 먼저 숨기고
            { selector: 'div.modal[data-id="confirmAdult"] div.modal-footer button.btn-primary', action: 'click'}, // 성인 확인 모달의 확인 버튼
            { selector: 'button.btn-primary.btn.text-light[data-bs-dismiss="modal"]', action: 'click' } // 일반적인 "네, 확인했습니다" 버튼
        ];
        modalSelectorsArca.forEach(item => {
            const elements = document.querySelectorAll(item.selector);
            elements.forEach(element => {
                if (element && element.offsetParent !== null) {
                    if (item.action === 'click') {
                        console.log(`[AutoSomi] Arca.live: "${item.selector}" 버튼 클릭 시도.`);
                        element.click();
                    } else {
                        hideElement(item.selector);
                    }
                }
            });
        });
    }

    function hideElement(selector) {
        try {
            const elements = document.querySelectorAll(selector);
            if (elements.length > 0) {
                elements.forEach(el => {
                    if (el.offsetParent !== null) {
                        el.style.setProperty('display', 'none', 'important');
                        console.log(`[AutoSomi] 요소 숨김 처리:`, el);
                    }
                });
            }
        } catch (e) {
            console.error(`[AutoSomi] 선택자 "${selector}" 처리 중 오류:`, e);
        }
    }
    console.log(`[AutoSomi] ${currentHost} 에서 방해 요소 처리 완료 (handleBlockingModals 종료)`);
    return Promise.resolve();
}


async function toggleDown(){
	isT[0]=!isT[0];
	if(!isT[0] && isT[1]){
		isT[1]=false;
		await GM.setValue('isT[1]', isT[1]);
	}
	await GM.setValue('isT[0]', isT[0]);
	updateDown();
	updateTab();
}
async function toggleTab(){
	isT[1]=!isT[1];
	if(!isT[0] && isT[1]){
		isT[0]=true;
		await GM.setValue('isT[0]', isT[0]);
	}
	await GM.setValue('isT[1]', isT[1]);
	updateDown();
	updateTab();
}
function updateDown(){
	if(MenuID[0] !==null)GM_unregisterMenuCommand(MenuID[0]);
	MenuID[0]=GM_registerMenuCommand(`자동 다운로드  ${isT[0] ? 'ON' : 'OFF'}`, toggleDown, { autoClose: false, title: `자동 다운로드 ${isT[0] ? '켜짐' : '꺼짐'}`});
}
function updateTab(){
	if(MenuID[1] !==null)GM_unregisterMenuCommand(MenuID[1]);
	MenuID[1]=GM_registerMenuCommand(`자동 탭 닫기  ${isT[1] ? 'ON' : 'OFF'}`, toggleTab, { autoClose: false, title: `자동 탭 닫기 ${isT[1] ? '켜짐' : '꺼짐'}`});
}
function doDec() {
    console.log('[AutoSomi] doDec: 함수 시작');
    if (chkp[3] !== chkp[4]) {
        console.log('[AutoSomi] doDec: 인증 실패로 중단.');
        return;
    }

    let targets = [];

    if (host === 'arca.live') {
        console.log('[AutoSomi] doDec: Arca.live 타겟 설정 시도.');
        targets = [
            document.querySelector('body div.article-body > div.fr-view.article-content'),
            ...document.querySelectorAll('div.article-comment#comment div.comment-content, div.article-comment div.comment-content')
        ].filter(el => el !== null);
        console.log('[AutoSomi] doDec: Arca.live 타겟 수:', targets.length);
    } else if (host === 'kone.gg') {
        console.log('[AutoSomi] doDec: Kone.gg 타겟 설정 시도.');
        const koneContentElement = getKoneGGContentElement();
        if (koneContentElement) {
            console.log('[AutoSomi] doDec: Kone.gg 본문 Shadow DOM contentDiv 찾음.');
        } else {
            console.log('[AutoSomi] doDec: Kone.gg 본문 Shadow DOM contentDiv 찾지 못함.');
        }
        const comments = document.querySelectorAll('p.text-sm.max-w-xl.whitespace-pre-wrap');
        const listItems = document.querySelectorAll('ol.list-decimal li p');
        targets = [koneContentElement, ...comments, ...listItems].filter(el => el !== null);
        console.log('[AutoSomi] doDec: Kone.gg 타겟 수 (본문 + 댓글 등):', targets.length, '본문 유효성:', !!koneContentElement);
    }

    if (targets.length === 0 || (targets.length === 1 && !targets[0])) {
        console.log('[AutoSomi] doDec: 처리할 유효한 타겟 없음. 함수 종료.');
        return;
    }

    function dec(target, reg) {
        try {
            if (!target || !target.innerHTML) {
                console.log('[AutoSomi] dec 내부: target이 없거나 innerHTML이 없음.', target);
                return;
            }
            const original = target.innerHTML;
            const matches = [...original.matchAll(reg)];
            if (matches.length === 0) return;
            console.log(`[AutoSomi] dec 내부: ${matches.length}개의 잠재적 Base64 문자열 발견 (${reg.source})`, target);

            let result = original;
            let decodedCount = 0;
            for (const match of matches) {
                let encoded = match[0];
                let decoded = encoded;
                try {
                    while (decoded && typeof decoded === 'string' && !decoded.startsWith('http') && decoded.length > 10 && decoded.length < 1000) {
                        let tempDecoded = atob(decoded);
                        if (tempDecoded === decoded) break; // No change after decoding, likely not base64 or already decoded
                        decoded = tempDecoded;
                    }
                    if (decoded && typeof decoded === 'string' && decoded.startsWith('http')) {
                        const linkHTML = `<a href="${decoded}" target="_blank" rel="noreferrer" style="color:#007bff; text-decoration:underline; word-break:break-all;">${decoded}</a>`;
                        result = result.replace(encoded, linkHTML);
                            decodedCount++;
                    }
                } catch (e) {
                    // console.warn('[AutoSomi] dec 내부: 개별 복호화 실패:', encoded, e.message);
                }
            }
            if (target.innerHTML !== result) {
                target.innerHTML = result;
                console.log(`[AutoSomi] dec 내부: ${decodedCount}개의 링크로 변환 완료.`, target);
            }
        } catch (e) {
            console.error('[AutoSomi] dec 함수 에러:', e, target);
        }
    }

    for (const target of targets) {
        if (!target) continue;
        // Ensure all existing links have noreferrer
        const links = target.querySelectorAll('a');
        links.forEach(a => {
            a.setAttribute('rel', 'noreferrer');
        });

        dec(target, /aHR0c[0-9A-Za-z+/=]{8,}/g); // Standard Base64 starting with http
        dec(target, /YUhSMG[0-9A-Za-z+/=]{8,}/g); // Standard Base64 starting with aHR0 (http)
        dec(target, /WVVoU[0-9A-Za-z+/=]{8,}/g); // Double Base64
        dec(target, /V1ZWb[0-9A-Za-z+/=]{8,}/g); // Triple Base64
    }
    console.log('[AutoSomi] doDec: 함수 종료. doDlsiteContextAware 호출 예정.');
    setTimeout(doDlsiteContextAware, 100); // Call DLsite link generation after decoding
}


function doDlsiteContextAware() {
    console.log('[AutoSomi] doDlsiteContextAware: 함수 시작');
    let atc; // Article content element
    if (host === 'arca.live') {
        atc = document.querySelector('.article-body .article-content');
        console.log('[AutoSomi] doDlsiteContextAware: Arca.live 본문 선택자 결과:', atc);
    } else if (host === 'kone.gg') {
        atc = getKoneGGContentElement();
        console.log('[AutoSomi] doDlsiteContextAware: Kone.gg 본문 (getKoneGGContentElement) 결과:', atc);
    }

    if (!atc) {
        console.log('[AutoSomi] doDlsiteContextAware: 본문(atc) 찾을 수 없음. 함수 종료.');
        return;
    }
    // console.log('[AutoSomi] doDlsiteContextAware: atc.innerHTML (일부):', atc.innerHTML.substring(0, 200) + "...");

    let titleEl;
    if (host === 'arca.live') {
        titleEl = document.querySelector('.title-row .title') || document.querySelector('.board-title .title');
    } else if (host === 'kone.gg') {
        titleEl = [...document.querySelectorAll('h1')].find(el =>
            el.textContent?.match(/RJ[0-9]{6,10}/i) // Find h1 that looks like it contains an RJ code
        );
    }
    console.log('[AutoSomi] doDlsiteContextAware: titleEl 결과:', titleEl);


    const keywordPattern = /(|거|퍼|RJ|rj|Rj|rJ|VJ|vj|Vj|vJ|DL|dl|Dl|dL)[\s:()\[\]#-]*([0-9]{6,10})/g;
    const fullRJPattern = /\b(RJ|rj|Rj|rJ|VJ|vj|Vj|vJ|퍼|꺼|거|DL|dl|Dl|dL)([0-9]{6,10})\b/g; // More precise, word boundary

    let titleText = '';
    if (titleEl?.childNodes) { // Check if titleEl and its childNodes exist
        titleText = Array.from(titleEl.childNodes)
            .map(n => n.textContent?.trim() || '') // Ensure textContent exists and trim
            .join(' ');
    }

    const bodyText = atc.textContent || ''; // Ensure textContent exists
    const fullText = titleText + '\n' + bodyText;

    // 1. Matches from full text (title + body)
    const matchList1 = [...fullText.matchAll(keywordPattern)].map(m => ({ prefix: m[1], code: m[2] }));
    const matchList2 = [...fullText.matchAll(fullRJPattern)].map(m => ({ prefix: m[1], code: m[2] }));

    // 2. Matches from existing links in the body
    const linkCodes = Array.from(atc.querySelectorAll('a[href]'))
        .map(a => {
            const match = a.href.match(/(RJ|VJ)([0-9]{6,10})/i); // Match RJ or VJ codes in href
            return match ? { prefix: match[1].toUpperCase(), code: match[2] } : null;
        })
        .filter(Boolean); // Remove nulls

    // 3. Matches from split text nodes (e.g., "RJ" <span>123456</span>)
    const splitNodeMatches = [];
    const allNodes = [...atc.querySelectorAll('*:not(script):not(style):not(a)')]; // Get all elements except script, style, a
    const validPrefixes = ['꺼','거','퍼','RJ','rj','Rj','rJ','VJ','vj','Vj','vJ','DL','dl','Dl','dL'];

    for (const node of allNodes) {
        const children = [...node.childNodes];
        for (let i = 0; i < children.length - 1; i++) {
            const a = children[i], b = children[i + 1];
            if (a.nodeType !== Node.TEXT_NODE || b.nodeType !== Node.TEXT_NODE) continue;

            const aText = (a.textContent || '').replace(/\s+/g, ''); // Text of first node, no spaces
            const bText = (b.textContent || '').trim(); // Text of second node, trimmed
            if (!/^\d{6,10}$/.test(bText)) continue; // Second node must be a 6-10 digit number

            for (const prefix of validPrefixes) {
                if (aText.endsWith(prefix)) {
                    splitNodeMatches.push({ prefix, code: bText });
                    break;
                }
            }
        }
    }

    // 4. Extra check for codes at the very start of the title (e.g., "[123456] Title")
    const extraFromTitle = [];
    const matchInTitleStart = titleText.match(/^[\[]?([0-9]{6,10})[\]]?/); // Optional brackets
    if (matchInTitleStart) extraFromTitle.push({ prefix: 'RJ', code: matchInTitleStart[1] }); // Default to RJ

    // Combine all found codes
    const allCodes = [
        ...matchList1,
        ...matchList2,
        ...linkCodes,
        ...splitNodeMatches,
        ...extraFromTitle
    ];

    // Deduplicate codes
    const seen = new Set();
    const uniqueCodes = allCodes.filter(({ code }) => {
        if (!code || seen.has(code)) return false;
        seen.add(code);
        return true;
    });
    console.log('[AutoSomi] doDlsiteContextAware: 찾은 DL 코드 수 (유니크):', uniqueCodes.length, '전체:', allCodes.length);

    // If exactly one unique code is found, append a prominent link at the bottom
    if (uniqueCodes.length === 1) {
        const { code, prefix } = uniqueCodes[0];
        if (!code) { // Should not happen due to filter, but as a safeguard
            console.log('[AutoSomi] doDlsiteContextAware: 유니크 코드 1개지만 코드 없음. 종료.');
            return;
        }

        // Map various prefixes to standard RJ or VJ
        const mappedPrefix = ['vj', 'vJ', 'Vj', 'VJ', '퍼'].includes(prefix.toLowerCase()) ? 'VJ'
                            : ['rj', 'rJ', 'Rj', 'RJ', '꺼', '거', 'dl', 'dL', 'Dl', 'DL'].includes(prefix.toLowerCase()) ? 'RJ'
                            : prefix.toUpperCase(); // Fallback, though should be covered

        const fullCode = `${mappedPrefix}${code}`;
        const link = `https://www.dlsite.com/maniax/work/=/product_id/${fullCode}.html`;

        const existing = atc.querySelector('.dlsite-link-appended');
        if (existing) existing.remove(); // Remove if already appended

        const wrapper = document.createElement('div');
        wrapper.className = 'dlsite-link-appended';
        wrapper.style.marginTop = '15px';
        wrapper.style.padding = '10px 0';
        wrapper.style.borderTop = '1px dashed #aaa';

        const a = document.createElement('a');
        a.href = link;
        a.target = '_blank';
        a.rel = 'noreferrer';
        a.textContent = `[DLsite] ${fullCode}`;
        a.style.display = 'block';
        a.style.marginBottom = '5px';
        a.style.color = '#1e90ff'; // A distinct blue color

        wrapper.appendChild(a);
        atc.appendChild(wrapper);
        console.log('[AutoSomi] doDlsiteContextAware: 유니크 코드 1개 발견, 링크 추가 완료:', fullCode);
        return; // Done for single unique code
    }


    // If multiple codes or no single prominent code, try to convert existing text instances into links
    if (uniqueCodes.length > 1) console.log('[AutoSomi] doDlsiteContextAware: 여러 DL 코드 발견, 텍스트 노드 순회하며 링크 변환 시도.');
    const combinedPattern = new RegExp(`${keywordPattern.source}|${fullRJPattern.source}`, 'gi'); // Combine patterns for text node search
    let linkConversionCount = 0;

    const walk = (node) => {
        if (node.nodeType === Node.TEXT_NODE) {
            const text = node.textContent;
            if (!text) return;
            let lastIndex = 0;
            let match;
            const frag = document.createDocumentFragment();

            while ((match = combinedPattern.exec(text)) !== null) {
                const before = text.slice(lastIndex, match.index);
                const number = match[2] || match[4]; // RJ code number from either pattern
                const prefix = (match[1] || match[3] || '').toLowerCase(); // Prefix from either pattern
                const mappedPrefix = ['vj', 'vJ', 'Vj', 'VJ', '퍼'].includes(prefix) ? 'VJ'
                                    : ['rj', 'rJ', 'Rj', 'RJ', '꺼', '거', 'dl', 'dL', 'Dl', 'DL'].includes(prefix) ? 'RJ'
                                    : prefix.toUpperCase();
                const fullCode = `${mappedPrefix}${number}`;

                const a = document.createElement('a');
                a.href = `https://www.dlsite.com/maniax/work/=/product_id/${fullCode}.html`;
                a.textContent = match[0]; // Use the original matched text for the link
                a.target = '_blank';
                a.rel = 'noreferrer';
                a.style.color = '#1e90ff';

                if (before) frag.appendChild(document.createTextNode(before));
                frag.appendChild(a);
                    linkConversionCount++;
                lastIndex = match.index + match[0].length;
            }

            if (lastIndex < text.length) {
                frag.appendChild(document.createTextNode(text.slice(lastIndex)));
            }

            if (frag.childNodes.length > 0 && node.parentNode && frag.textContent !== node.textContent) { // Only replace if changes were made
                 node.parentNode.replaceChild(frag, node);
            }
        } else if (
            node.nodeType === Node.ELEMENT_NODE &&
            node.tagName && // Make sure tagName exists
            !['A', 'SCRIPT', 'STYLE'].includes(node.tagName.toUpperCase()) // Don't traverse into existing links or script/style tags
        ) {
            for (const child of [...node.childNodes]) { // Iterate over a copy of childNodes
                walk(child);
            }
        }
    };

    walk(atc); // Start walking from the article content element
    if(linkConversionCount > 0) console.log(`[AutoSomi] doDlsiteContextAware: ${linkConversionCount}개의 텍스트를 DLsite 링크로 변환 완료.`);
    console.log('[AutoSomi] doDlsiteContextAware: 함수 종료.');
}


async function chkPW(){
	chkp[3]=await GM.getValue('chkp[3]');
	isT[0]=await GM.getValue('isT[0]', true); // Default auto download to true
	isT[1]=await GM.getValue('isT[1]', false); // Default auto tab close to false
	updateDown();
	updateTab();

	if(host=='arca.live' || host=='kone.gg'){ // Only prompt for auth on these sites
		if(chkp[3] !=chkp[4]){ // If stored auth value doesn't match the expected one
            console.log('[AutoSomi] chkPW: 인증 필요.');
			const chk=prompt(decodeURI(atob('JUVBJUI1JUFEJUVCJUEzJUIwJUVEJTk5JTk1JUVDJTlEJUI4'))); // "인증번호를 입력하세요"
			if(chk?.toLowerCase()==chkp[4]) { // chkp[4] is atob('c29taXNvZnQ=') -> "somisoft"
                await GM.setValue('chkp[3]', chkp[4]);
                console.log('[AutoSomi] chkPW: 인증 성공.');
            } else {
				GM.setValue('chkp[3]', false); // Store false on failure
                console.log('[AutoSomi] chkPW: 인증 실패.');
				alert(decodeURI(atob('JUVBJUI1JUFEJUVCJUEzJUIwJUVDJTlEJUI0JTIwJUVDJTlEJUJDJUVDJUI5JTk4JUVEJTk1JTk4JUVDJUE3JTgwJTIwJUVDJTk1JThBJUVDJThBJUI1JUVCJThCJTg4JUVCJThCJUE0'))); // "인증번호가 정확하지 않거나 입력되지 않았습니다"
			}
		} else {
            console.log('[AutoSomi] chkPW: 이미 인증됨.');
        }
	}
}
async function inputPW() {
    console.log('[AutoSomi] inputPW: 함수 시작');
    let inputElem = document.querySelector(chkp[0]), btnElem = document.querySelector(chkp[1]);
    if (!inputElem ) { // If password input field is not found
        console.log(`[AutoSomi] inputPW: 비밀번호 입력 필드(${chkp[0]}) 없음.`);
        if (isT[0] === true && !document.querySelector('.files-list, #download-section')) { // If auto download is on and no files list (meaning password might have been bypassed or not needed)
             console.log('[AutoSomi] inputPW: 자동 다운로드 시도 (DBtn 호출).');
             await setTimeout(DBtn, PageLoading[1] || 1000); // PageLoading[1] is site-specific delay
        }
        return;
    }
    console.log('[AutoSomi] inputPW: 비밀번호 입력 필드 및 버튼 찾음:', inputElem, btnElem);

    const combinedPw = [...new Set([...pw, ...npw])]; // Combine default passwords and found passwords, ensure uniqueness
    console.log('[AutoSomi] inputPW: 시도할 비밀번호 목록:', combinedPw);


    if (chkp[3] == chkp[4]) { // Only try passwords if authenticated
        try {
            for (let i = 0; i < combinedPw.length; i++) {
                console.log(`[AutoSomi] inputPW: ${i+1}번째 비밀번호 시도: ${combinedPw[i]}`);
                if (!combinedPw[i]) continue; // Skip empty passwords
                if (!inputElem) { // Check if input element disappeared (e.g., due to successful login)
                    console.log('[AutoSomi] inputPW: 루프 중 비밀번호 입력 필드 사라짐.');
                    break;
                }

                inputElem.value = combinedPw[i];

                if (host == 'kio.ac') { // Specific handling for kio.ac (needs input/change events)
                    inputElem.dispatchEvent(new Event('input', { bubbles: true }));
                    inputElem.dispatchEvent(new Event('change', { bubbles: true }));
                    await new Promise(res => setTimeout(res, 50)); // Short delay
                    if(btnElem) btnElem.click();
                } else { // General case
                    if(btnElem) btnElem.click(); // Click submit button if exists
                    else { // If no button, try dispatching Enter key event
                        const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true });
                        inputElem.dispatchEvent(enterEvent);
                    }
                }

                await new Promise(res => setTimeout(res, 800)); // Wait for page to react

                // Check for success or error indicators
                const successIndicator = document.querySelector('.files-list, #download-section, .download-link, .btn-download, .main-button-download');
                const errorIndicator = document.querySelector('.text-error, .text-red-500, .error-message, .incorrect-password, [class*="error"], [id*="error"]');

                if (successIndicator && successIndicator.offsetParent !== null) { // If success indicator is visible
                    console.log(`[AutoSomi] inputPW: 비밀번호 "${combinedPw[i]}" 성공!`);
                    break; // Password worked, exit loop
                } else if (errorIndicator && errorIndicator.offsetParent !== null) { // If error indicator is visible
                    console.log(`[AutoSomi] inputPW: 비밀번호 "${combinedPw[i]}" 실패 또는 오류 표시됨.`);
                    if (inputElem) inputElem.value = ''; // Clear input for next try
                } else {
                    console.log(`[AutoSomi] inputPW: 비밀번호 "${combinedPw[i]}" 입력 후 성공/실패 표시기 없음.`);
                    // Potentially a page change or intermediate state, might need to re-evaluate inputElem
                    inputElem = document.querySelector(chkp[0]); // Re-query input element
                    btnElem = document.querySelector(chkp[1]); // Re-query button element
                }
            }
            if (isT[0] == true) { // If auto download is on
                console.log('[AutoSomi] inputPW: 자동 다운로드 시도 (DBtn 호출).');
                await setTimeout(DBtn, PageLoading[1] || 1000);
            }

        } catch (e) {
            console.error("[AutoSomi] inputPW: 암호 입력 중 오류:", e);
            if (isT[0] == true) { // Still try to download if error occurred
                console.log('[AutoSomi] inputPW: 오류 후 자동 다운로드 시도 (DBtn 호출).');
                await setTimeout(DBtn, PageLoading[1] || 1000);
            }
        }
    } else {
        console.log('[AutoSomi] inputPW: 인증되지 않아 비밀번호 입력 건너뜀.');
    }
    console.log('[AutoSomi] inputPW: 함수 종료.');
}

async function kioskdone(){
    console.log('[AutoSomi] kioskdone: 함수 시작');
	try {
		await new Promise(res=> setTimeout(res, 3000)); // Initial delay
		for(var i=0, jj=0; jj!=1 && i < 60; i++){ // Try for up to 60 seconds
			await new Promise(res=> setTimeout(res, 1000)); // Check every second
            const doneElement = document.querySelector('.flex.flex-row.text-xs.justify-between div:nth-child(2)'); // Kiosk specific "done" element
			if(doneElement?.innerText.toLowerCase()=='done'){
                console.log('[AutoSomi] kioskdone: "done" 상태 확인, 탭 닫기 시도.');
				await setTimeout(()=> { window.open('', '_self').close()}, 2000); // Close tab after 2 seconds
				jj++;
                break; // Exit loop
			} else {
                // console.log(`[AutoSomi] kioskdone: "done" 상태 아님 (시도 ${i+1}/60)`);
            }
		}
        if (jj === 0) console.log('[AutoSomi] kioskdone: "done" 상태 확인 시간 초과.');
	} catch(e){
        console.error("[AutoSomi] kioskdone 오류:", e);
        if(isT[1]==true && isT[2]==true) setTimeout(()=> { window.open('', '_self').close()}, 1000); // Close tab if auto-close is on
    }
    console.log('[AutoSomi] kioskdone: 함수 종료.');
}
async function DBtn(){ // Download Button clicker
    console.log('[AutoSomi] DBtn: 함수 시작');
	if(isT[0] !== true) { // If auto download is off
        console.log('[AutoSomi] DBtn: 자동 다운로드 꺼져있음.');
        return;
    }

	if(host=='mega.nz') { // Mega.nz specific download buttons
        try {
            console.log('[AutoSomi] DBtn: Mega.nz 다운로드 버튼 클릭 시도.');
            const resumeButton = document.querySelector('.mega-button.positive.resume.js-resume-download');
            if (resumeButton) {
                console.log('[AutoSomi] DBtn: Mega 이어받기 버튼 클릭.');
                resumeButton.click();
                await new Promise(res => setTimeout(res, 500));
            }

            const standardDownloadButton = document.querySelector('.mega-button.positive.js-default-download.js-standard-download');
            if (standardDownloadButton) {
                console.log('[AutoSomi] DBtn: Mega 표준 다운로드 버튼 클릭.');
                standardDownloadButton.click();
                await new Promise(res => setTimeout(res, 2000)); // Longer delay for Mega
            }

            const continueDownloadButton = document.querySelector('.mega-button.large.positive.download.continue-download'); // Another possible button
            if (continueDownloadButton) {
                console.log('[AutoSomi] DBtn: Mega 계속 다운로드 버튼 클릭.');
                continueDownloadButton.click();
            }
        } catch(e) {
            console.error("[AutoSomi] DBtn: Mega 다운로드 버튼 클릭 오류:", e);
        }
    } else { // General download button clicker
        try {
            console.log(`[AutoSomi] DBtn: 일반 다운로드 버튼(${chkp[2]}) 클릭 시도.`);
            let btns = document.querySelectorAll(chkp[2]); // chkp[2] is the download button selector for the current site
            let delayMultiplier = (PageLoading[0] || 1000) + 600; // Base delay + 0.6s
            if(btns.length > 1){ // If multiple download buttons (e.g., batch download)
                console.log(`[AutoSomi] DBtn: ${btns.length}개의 버튼(일괄) 클릭 시도.`);
                btns.forEach((btn, index) => {
                    setTimeout(() => {
                            console.log(`[AutoSomi] DBtn: 버튼 ${index + 1} 클릭.`);
                        btn.click();
                    }, index * delayMultiplier); // Stagger clicks
                });
            } else if(btns.length === 1){ // Single download button
                console.log('[AutoSomi] DBtn: 단일 버튼 클릭.');
                btns[0].click();
            } else {
                console.log('[AutoSomi] DBtn: 다운로드 버튼 찾지 못함.');
            }
        } catch(e){ console.error("[AutoSomi] DBtn: 일반 다운로드 버튼 클릭 오류:", e); }
    }

	if(isT[1]==true && isT[2]==true){ // If auto tab close is ON and site allows tab close (isT[2])
        console.log('[AutoSomi] DBtn: 자동 탭 닫기 활성화됨.');
        let btnsForDelay = document.querySelectorAll(chkp[2]);
		let totalDelay = (btnsForDelay.length > 1) ? (btnsForDelay.length * ((PageLoading[0] || 1000) + 600) + 700) : 1300; // Calculate delay based on number of buttons
		if(host=='kiosk.ac'){
            console.log('[AutoSomi] DBtn: Kiosk.ac 탭 닫기 예약 (kioskdone 호출).');
			setTimeout(()=> { kioskdone(); }, totalDelay); // Kiosk has specific done check
		} else if(host=='kio.ac'){
            console.log('[AutoSomi] DBtn: Kio.ac 탭 닫기 예약.');
			setTimeout(()=> { window.open('', '_self').close()}, totalDelay+1000); // Slightly longer delay for kio.ac
		} else {
            console.log('[AutoSomi] DBtn: 일반 탭 닫기 예약.');
			setTimeout(()=> { window.open('', '_self').close()}, totalDelay);
		}
	}
    console.log('[AutoSomi] DBtn: 함수 종료.');
}

function FindPW(){ // Finds passwords in page content
    console.log('[AutoSomi] FindPW: 함수 시작');
    let atc; // Article content element
    if (host === 'arca.live') {
        atc = document.querySelector('body div.article-body > div.fr-view.article-content');
        console.log('[AutoSomi] FindPW: Arca.live 본문 선택자 결과:', atc);
    } else if (host === 'kone.gg') {
        atc = getKoneGGContentElement();
        console.log('[AutoSomi] FindPW: Kone.gg 본문 (getKoneGGContentElement) 결과:', atc);
    }

    if (!atc || !atc.innerHTML) { // If no content element or it's empty
        console.log('[AutoSomi] FindPW: 본문(atc) 없거나 innerHTML 없음. 함수 종료.');
        return;
    }
    // console.log('[AutoSomi] FindPW: atc.innerHTML (일부):', atc.innerHTML.substring(0, 200) + "...");

    let tempContent = atc.innerHTML;
    tempContent=tempContent.replace(/ /g, ' ').replace(/( ){2,}/g, ' '); // Normalize spaces (first replace seems redundant)
	tempContent=tempContent.replace(/국룰/g, 'ㄱㄹ'); // Replace "국룰" with "ㄱㄹ" for matching

	let regexx=/(대문자)/; // Regex for "대문자" (uppercase)

	if(regexx.test(tempContent)) { // If "대문자" keyword is found
        const smpeopleUpper = atob('U01QRU9QTEU='); // "SMPEOPLE"
        if (npw.indexOf(smpeopleUpper) === -1) { // Add if not already present
            npw.push(smpeopleUpper);
            console.log('[AutoSomi] FindPW: "대문자" 키워드로 "SMPEOPLE" 추가.');
        }
    }

	function dec(reg){ // Inner function to process matches from regex
        let foundPasswordsInDec = 0;
		try {
            let currentLoopContent = tempContent; // Use a mutable copy of the content for this loop
            const processedTexts = new Set(); // To avoid processing the same matched text multiple times if regex is not advancing properly

			while(true){ // Loop through all matches
                const matchResult = reg.exec(currentLoopContent);
                if (!matchResult) break; // No more matches

				let matchedText = matchResult[0];
                if (processedTexts.has(matchedText)) {
                    // If this exact text was already processed, replace it to ensure exec moves on
                    currentLoopContent = currentLoopContent.replace(matchedText, `__SKIPPED_${Math.random().toString(36).substring(2, 10)}__`);
                    continue;
                }

                let DECed = matchedText.replace(/(ㄱㄹ)/g, '국룰'); // Restore "ㄱㄹ" to "국룰"
				// Remove common surrounding words and characters, then take the first part before certain Korean syllables
				let DECedd = DECed.replace(/\s|[+]|(|는|이|가)|(전부)|(대문자로)|(대문자)|(비밀번호)|(패스워드)|(비번)|(ㅂㅂ)|(암호)|(ㅇㅎ)|(키오스크맘)|(키오스크)|(입니다)|(이고)|(이며)|(임다)|(같다)|(처럼)|(틀리다)|(입니다요)/g, '');
				DECedd = DECedd.split(/[()()()()()()()()()()]/g)[0]; // Split by common suffixes/particles
                DECedd = DECedd.replace(/[<>]/g, ''); // Remove angle brackets

				let dat; // Date string
                if (host === 'arca.live') { // Get date from arca.live article info
                    const dateEl = document.querySelector('.article-info .date .body');
                    if (dateEl && typeof dateEl.innerText === 'string' && dateEl.innerText.trim() !== '') {
                        dat = dateEl.innerText.split(' ')[0]; // YYYY-MM-DD
                    } else {
                        dat = undefined;
                    }
                }

				let regexa=/(오늘)|(날짜)|(날자)/g; // Regex for "today" or "date"
				if(dat && regexa.test(DECedd)){ // If date is available and keyword "today/date" is in the password candidate
					DECedd=DECedd.replace(regexa, ''); // Remove "today/date" keyword
                    const dateParts = dat.split(/\-/g); // Split YYYY-MM-DD
                    if (dateParts.length === 3) {
                        const year = dateParts[0].slice(2); // YY
                        const month = dateParts[1]; // MM
                        const day = dateParts[2]; // DD
                        let dateFormats=[month + day, month + '-' + day, year + month + day, dat.replace(/-/g,'')]; // MMDD, MM-DD, YYMMDD, YYYYMMDD
                        for(let k=0;k<dateFormats.length;k++){
                            const finalPw = DECedd.replace(/국룰/g,chkp[4]) + dateFormats[k]; // Append date to password candidate (chkp[4] is "somisoft")
                            if(finalPw.length > 2 && npw.indexOf(finalPw)=='-1') { // If valid length and not duplicate
                                npw.push(finalPw);
                                foundPasswordsInDec++;
                            }
                        }
                    }
				} else { // If not a date-related password
                    const finalPw = DECedd.replace(/국룰/g,chkp[4]); // Replace "국룰" with "somisoft"
                    if(finalPw && finalPw.length > 2 && npw.indexOf(finalPw)=='-1') { // Check length and uniqueness
                        npw.push(finalPw);
                        foundPasswordsInDec++;
                    }
                }
                processedTexts.add(matchedText); // Mark this text as processed
                // To ensure the loop progresses, replace the processed part in currentLoopContent
                // This is crucial if the regex `reg` might re-match the same substring.
                currentLoopContent = currentLoopContent.substring(0, matchResult.index) +
                                     `__PROCESSED_${Math.random().toString(36).substring(2, 15)}__` +
                                     currentLoopContent.substring(matchResult.index + matchedText.length);
			}
            if (foundPasswordsInDec > 0) console.log(`[AutoSomi] FindPW dec: ${foundPasswordsInDec}개의 비밀번호 후보 추가됨.`);
			GM.setValue('npw', npw); // Save found passwords
		} catch(e){ console.error("[AutoSomi] FindPW dec 함수 오류:", e); }
	}
	// Regex to find potential passwords: text possibly wrapped in <p>, containing "ㄱㄹ" (국룰), surrounded by other characters
	dec(/[(<p>\s*)*]{0,}[ㄱ-ㅣ가-힣0-9A-Za-z\s~`!^\_+@\#$%&=]{0,}(ㄱㄹ){1,1}[ㄱ-ㅣ가-힣0-9A-Za-z\s~`!^\_+@\#$%&=]{0,}[(\s*</p>)*]{0,}/);
    console.log('[AutoSomi] FindPW: 함수 종료. aaa 호출 예정.');
	setTimeout(aaa, 100); // Load any previously saved passwords
}

function waitForKoneContent() { // Specifically for kone.gg to wait for dynamic content
    console.log('[AutoSomi] waitForKoneContent: 함수 시작');
    let attempts = 0;
    const maxAttempts = 20; // Try for about 6 seconds (20 * 300ms)
    const checkInterval = setInterval(async () => {
        attempts++;
        // console.log(`[AutoSomi] waitForKoneContent: 시도 ${attempts}/${maxAttempts}`);
        await handleBlockingModals(host); // Handle any popups first

        const atc = getKoneGGContentElement(); // Get content from Shadow DOM
        const titleEl = [...document.querySelectorAll('h1.flex, h1.text-xl')].find(el => // Check for title with RJ/VJ code
            /RJ[0-9]{6,10}|VJ[0-9]{6,10}/i.test(el.textContent || '')
        );

        const hasBase64 = atc && /aHR0c|YUhSMG|WVVoU|V1ZWb/.test(atc.textContent || ''); // Check if content has Base64 strings

        // Conditions to proceed: content element exists, AND (title has RJ/VJ OR content has Base64), AND content has some length
        if (atc && (titleEl || hasBase64) && (atc.textContent || '').length > 10) {
            console.log('[AutoSomi] waitForKoneContent: 콘텐츠 확인됨. Interval 중지 및 처리 시작.');
            clearInterval(checkInterval);
            FindPW(); // Find passwords
            setTimeout(() => { // Then decode and add DLsite links
                doDec();
                doDlsiteContextAware();
            }, 300);
        } else if (attempts >= maxAttempts) {
            console.log('[AutoSomi] waitForKoneContent: 최대 시도 횟수 도달. Interval 중지.');
            clearInterval(checkInterval);
        } else {
            // console.log('[AutoSomi] waitForKoneContent: 콘텐츠 아직 준비 안됨. atc:', !!atc, 'titleEl:', !!titleEl, 'hasBase64:', hasBase64, 'atc.textContent.length:', atc?.textContent?.length);
        }
    }, 300); // Check every 300ms
}


async function aaa() { // Loads saved passwords from GM storage
    console.log('[AutoSomi] aaa: 저장된 비밀번호 로드 시도.');
    const saved = await GM.getValue('npw');
    if (Array.isArray(saved)) {
        npw = [...new Set([...npw, ...saved])]; // Merge with existing npw and ensure uniqueness
        console.log('[AutoSomi] aaa: 저장된 비밀번호 로드 완료. 현재 npw:', npw);
    } else {
        console.log('[AutoSomi] aaa: 저장된 비밀번호 없음.');
    }
}


// === Script Start Point ===
(async () => {
    console.log('[AutoSomi] 스크립트 시작. 현재 호스트:', host);
    await chkPW(); // Check authentication first
    await handleBlockingModals(host); // Handle any initial modals

    if (host === 'arca.live') {
        console.log('[AutoSomi] Arca.live 특정 로직 실행.');
        FindPW(); // Find passwords
        setTimeout(doDec, 200); // Decode Base64 (DLsite links are handled inside doDec via doDlsiteContextAware)
    } else if (host === 'kone.gg') {
        console.log('[AutoSomi] Kone.gg 특정 로직 실행 (waitForKoneContent).');
        waitForKoneContent(); // Wait for dynamic content then process
    }

    // Site-specific configurations and password input logic
    if(host=='kio.ac'){
        await aaa(); // Load saved passwords
        chkp[0]='.overflow-auto.max-w-full.max-h-full.flex-grow.p-1 input:nth-of-type(1)'; // Password input selector
        chkp[1]='.flex.flex-col-reverse button:nth-of-type(1)'; // Submit button selector
        chkp[2]='.p-2.align-middle .flex.align-middle button:nth-of-type(1)'; // Download button selector
        isT[2]=true; // Allow auto tab close
        PageLoading[1]=3500; // Delay before trying to download
        setTimeout(inputPW, (PageLoading[0] || 1000) + 2000); // Delay before inputting password
    }
    if(host=='mega.nz'){
        await aaa();
        chkp[0]='#password-decrypt-input';
        chkp[1]='.mega-button.positive.fm-dialog-new-folder-button.decrypt-link-button';
        chkp[2]='.mega-button.positive.js-default-download.js-standard-download'; // This is actually the download button, not password submit
        PageLoading[1]=4000;
        isT[2]=false; // Mega usually keeps the tab open for download progress
        setTimeout(inputPW, (PageLoading[0] || 1000) + 1800);
    }
    if(host=='kiosk.ac'){
        await aaa();
        chkp[0]='.input.shadow-xl.flex-grow';
        chkp[1]='.btn.btn-ghost.w-full.mt-2.rounded-md';
        if(Down_Option==2) // Batch download option
            chkp[2]='.flex.justify-between.w-full .flex.gap-2 .btn.btn-ghost'; // Batch download buttons
        else // Single download options (Basic or Fast)
            chkp[2]='#vexplorer-body .hover\\:bg-neutral .dropdown button, #vexplorer-body > div > div > div.flex.justify-between.w-full > div.flex.gap-2 > button:not(.btn-disabled)'; // More specific selector for individual download buttons

        if(Down_Option==0) // Basic download
            isT[2]=true; // Allow auto tab close
        else // Fast or Batch
            isT[2]=false; // Keep tab open
        PageLoading[1]=3000;
        setTimeout(inputPW, (PageLoading[0] || 1000));
    }
    if(host=='workupload.com'){
        await aaa();
        chkp[0]='#passwordprotected_file_password';
        chkp[1]='#passwordprotected_file_submit';
        chkp[2]='a.btn.btn-prio[href*="/file/"]'; // Download button
        if(Down_Option==0) // Assuming Down_Option is generic, 0 might mean basic/single file
            isT[2]=true;
        else
            isT[2]=false;
        PageLoading[1]=2000;
        setTimeout(inputPW, (PageLoading[0] || 1000));
    }
    if(host=='drive.google.com'){
        await aaa();
        if(document.URL.includes('/file/d/')){ // Single file page
            const fileId = document.URL.split('/d/')[1].split('/')[0];
            if (fileId) { // Redirect to direct download link
                window.location.href=`https://drive.usercontent.google.com/download?id=${fileId}&export=download&authuser=0`;
            }
        }
        if(document.URL.includes('/drive/folders/')){ // Folder page
            setTimeout(()=> {
                if(isT[0]==true) { // If auto download is on
                    const downloadAllButton = document.querySelector('div[aria-label="모두 다운로드"], div[data-tooltip="모두 다운로드"]');
                    if (downloadAllButton) {
                        downloadAllButton.click();
                    } else { // Fallback if "Download All" is not found (e.g. single item in folder view)
                        const firstItemDownloadButton = document.querySelector('div[role="gridcell"][data-is-shared="false"] div[aria-label*="다운로드"]'); // Try to download the first item
                         if(firstItemDownloadButton) {
                            firstItemDownloadButton.click();
                         }
                    }
                }
            }, (PageLoading[0] || 1000) + 1500); // Delay before clicking download all

            chkp[2]='.h-De-Vb.h-De-Y'; // Selector for download button in confirmation dialog (might be outdated)
            PageLoading[1]=1; // Minimal delay
            isT[2]=true; // Allow tab close (though GDrive download usually happens in same tab or new small window)
            setTimeout(()=> { if(isT[0]==true) setTimeout(DBtn, 3500);}, (PageLoading[0] || 1000)); // Further delay for DBtn if needed
        }
    }
    if(host=='drive.usercontent.google.com'){ // Direct download page for Google Drive
        await aaa(); // Not really needed here, but consistent
        chkp[2]='form[method="POST"] button[type="submit"], input[type="submit"][value="다운로드"], button.jfk-button-action'; // Various possible download buttons
        isT[2]=true; // Allow tab close after download initiated
        setTimeout(()=> { if(isT[0]==true) DBtn();}, 100); // Click download button quickly
    }
    if(host=='gofile.io'){
        await aaa();
        chkp[0]='#filesErrorPasswordInput';
        chkp[1]='#filesErrorPasswordButton';
        chkp[2]='button.btn-download, a.btn-download, #rowFolderCenter button[data-bs-target="#filesList"] + div .btn-outline-secondary'; // Download buttons (individual or from folder view)
        PageLoading[1]=3000;
        isT[2]=true;
        setTimeout(inputPW, (PageLoading[0] || 1000));
    }
    console.log('[AutoSomi] 메인 즉시 실행 함수 로직 완료.');
})();


// Kone.gg specific logic for button hooks (IIFE)
(function () {
    'use strict';
    if (location.host !== 'kone.gg') return;
    console.log('[AutoSomi] Kone.gg 특화 로직 (버튼 훅) IIFE 시작.');

    function hookDecodeTriggerOnButton() {
        // console.log('[AutoSomi] hookDecodeTriggerOnButton: 실행 시도.');
        // This button seems to be related to loading comments or more content
        const container = document.querySelector('div.flex.items-center.justify-between.p-4');
        if (!container) {
            // console.log('[AutoSomi] hookDecodeTriggerOnButton: 컨테이너(div.flex.items-center.justify-between.p-4) 없음.');
            return;
        }

        const button = container.querySelector('button[data-slot="button"]'); // Generic button slot
        if (!button) {
            // console.log('[AutoSomi] hookDecodeTriggerOnButton: 버튼(button[data-slot="button"]) 없음.');
            return;
        }
        if (button.dataset._somi_hooked === '1') { // Check if already hooked
            // console.log('[AutoSomi] hookDecodeTriggerOnButton: 버튼 이미 훅됨.');
            return;
        }

        button.dataset._somi_hooked = '1'; // Mark as hooked
        console.log('[AutoSomi] hookDecodeTriggerOnButton: 버튼에 클릭 리스너 추가 완료.');

        button.addEventListener('click', async () => {
            console.log('[AutoSomi] Kone.gg 버튼 클릭됨, 처리 시작.');
            await handleBlockingModals(host); // Handle modals that might appear on click
            setTimeout(() => { // Delay to allow content to load after click
                console.log('[AutoSomi] Kone.gg 버튼 클릭 후 FindPW 호출.');
                FindPW();
                setTimeout(() => {
                    console.log('[AutoSomi] Kone.gg 버튼 클릭 후 doDec, doDlsiteContextAware 호출.');
                    doDec();
                    doDlsiteContextAware();
                }, 300);
            }, 500);
        });
    }

    hookDecodeTriggerOnButton(); // Initial attempt to hook

    // Observe DOM changes to hook buttons that might appear later
    const observer = new MutationObserver(async (mutationsList, obs) => {
        // console.log('[AutoSomi] MutationObserver (버튼 훅): DOM 변경 감지.');
        hookDecodeTriggerOnButton(); // Try to hook any new buttons
        await handleBlockingModals(host); // Also handle modals that might appear due to DOM changes
    });
    observer.observe(document.body, { childList: true, subtree: true });
    console.log('[AutoSomi] Kone.gg 특화 로직 (버튼 훅) IIFE: MutationObserver 시작됨.');
})();

// Kone.gg specific logic for URL changes and content loading (IIFE)
(function () {
    'use strict';
    if (location.host !== 'kone.gg') return;
    console.log('[AutoSomi] Kone.gg 특화 로직 (URL 변경 및 콘텐츠 로드 감지) IIFE 시작.');

    let lastUrl = location.href;
    let mainProcessingIntervalId = null; // Store the interval ID for content checking

    // Observer for URL changes (simulated, as kone.gg might use SPA navigation)
    const observer = new MutationObserver(async () => {
        const currentUrl = location.href;
        if (currentUrl !== lastUrl) {
            console.log('[AutoSomi] Kone.gg URL 변경 감지:', lastUrl, '->', currentUrl);
            lastUrl = currentUrl;
            if (mainProcessingIntervalId) { // Clear previous interval if any
                clearInterval(mainProcessingIntervalId);
                console.log('[AutoSomi] Kone.gg URL 변경: 이전 observeAndRunKoneFunctions Interval 중지됨.');
            }
            await handleBlockingModals(host); // Handle modals on new "page"
            observeAndRunKoneFunctions(); // Re-run content processing for the new page content
        }
    });

    observer.observe(document.body, { childList: true, subtree: true }); // Observe for any changes that might indicate navigation
    console.log('[AutoSomi] Kone.gg 특화 로직 (URL 변경 및 콘텐츠 로드 감지) IIFE: MutationObserver 시작됨.');

    async function observeAndRunKoneFunctions() {
        console.log('[AutoSomi] observeAndRunKoneFunctions: 함수 시작');
        const start = Date.now();
        const timeout = 8000; // Timeout for waiting for content (8 seconds)

        if (mainProcessingIntervalId) { // Clear previous interval if any before starting a new one
            clearInterval(mainProcessingIntervalId);
            console.log('[AutoSomi] observeAndRunKoneFunctions: 이전 Interval 중지됨 (새로운 실행 전).');
        }

        mainProcessingIntervalId = setInterval(async () => {
            // console.log('[AutoSomi] observeAndRunKoneFunctions: Interval 실행 중...');
            await handleBlockingModals(host); // Periodically check for modals
            const atc = getKoneGGContentElement(); // Get content element
            const hasText = atc && (atc.textContent || '').length > 20; // Check if content has substantial text
            const hasEncoded = atc && /aHR0c|YUhSMG|WVVoU|V1ZWb/.test(atc.textContent || ''); // Check for Base64
            const hasDlsiteLink = atc && atc.querySelector('a[href*="dlsite.com"]'); // Check for existing DLsite links (might indicate content is loaded)

            // If content is loaded (has text AND (has encoded strings OR has dlsite links))
            if (atc && (hasText && (hasEncoded || hasDlsiteLink))) {
                console.log('[AutoSomi] observeAndRunKoneFunctions: 콘텐츠 확인됨. Interval 중지 및 처리 시작.');
                clearInterval(mainProcessingIntervalId);
                mainProcessingIntervalId = null; // Reset interval ID
                FindPW(); // Find passwords
                setTimeout(() => { // Then decode and add DLsite links
                    doDec();
                    doDlsiteContextAware();
                }, 300);
            }

            if (Date.now() - start > timeout) { // If timeout is reached
                console.log('[AutoSomi] observeAndRunKoneFunctions: 시간 초과. Interval 중지.');
                clearInterval(mainProcessingIntervalId);
                mainProcessingIntervalId = null; // Reset interval ID
            }
        }, 300); // Check every 300ms
    }

    // Initial run when the script loads for kone.gg
    (async () => {
        await handleBlockingModals(host);
        observeAndRunKoneFunctions();
    })();
})();


1


내 글 최신(25년 05/23 기준)에 다시 수정한 거에서 동작하는 지 첨부 사진 (난 됨. 안되면 몰루! 패치 기다리면 됨.)

16
8 comments
Sign in to comment
05/23/25
아이고 쉬다왔더니 뭐가 많이 바껴서 당황했었는데 먼저 해주셔서 ㄳㄳ 아마 당분간 계속 사이트 구조 바뀔거같아서 골아프네요
05/23/25
그러게요. 패치하시면 말씀해주세요! 글 내리겠습니다. 항상 감사합니다.
05/23/25
오늘 왜 작동안되나 했더니 이거때문이군요
05/23/25
언젠가 나도 이런 코드 짜서 배포해봐야지
05/23/25
길다
오 된다 임시지만 업뎃 감사함
Sign in to comment
[유틸] [노모] 비밀 노출 -배덕의 달콤함에 물든 마나카- 노모패치 v1.0.0.6
유틸
Ghost
06/15/25 14097 15
업뎃) 스텔라블레이드 릴리 누드모드
유틸
konekat
06/14/25 18502 65
[업뎃알림] 오토소미
유틸
김머시기
06/12/25 10964 34
야식메뉴판 업뎃해옴 - 2.0.0
유틸
qqoro
06/12/25 13804 70
RPGMVP 파일을 포토샵으로 열고 수정하고 저장하장!!!!!!!!!!!!!!!!
유틸
moomin
06/12/25 4836 17
코네 유저 메모 및 차단 스크립트
유틸
글쓴이
06/11/25 6325 17
[노모패치] 졸린 유대감 ver.1.2.1
유틸
77777
06/10/25 10062 29
[RJ01367733] 요정의 물약 공장 기번 유틸
유틸
takodachi
06/09/25 2879 6
시니시스타 2 모드 모음
유틸
sims9876
06/09/25 12345 33
[2025-06-20] 게시글 url 추출
유틸
kts
06/09/25 2622 3
시니시스타2 알몸폭유모드
유틸
ㅇㅇ
06/08/25 13418 37
히토미 다운로더용 kone 이미지 일괄 다운로드 스크립트
유틸
글쓴이
06/08/25 7483 31
메스가키 새여동생♡낮져밤져 유혹섹활 AI노모
유틸
물크스
06/08/25 9845 77
.RPGMVP .PNG_을 반디뷰에서도 열어보장
유틸
moomin
06/07/25 4790 11
동인탭 글 숨기는 스크립트(수정v2)
유틸
딸기맛농축함유🍓
06/07/25 3112 2
야겜 모드 모음
유틸
나나바
06/06/25 9742 4
(긴급!)유니티 제미니 AI 자동번역 딜레이 대폭수정및 검열회피(반드시 새로 받아야함)
유틸
알탕
06/05/25 15303 33
코네 wide 모드
유틸
arcjay
06/05/25 3665 10
유니티 (AutoTranslator) 제미니 AI번역기 무료 API 다중추가 삽입및 번역 재시도 기능 추가
유틸
알탕
06/04/25 9117 23
[업뎃] Auto Somi 자동 국룰입력/복호화/다운로드/미리보기 등
유틸
김머시기
06/04/25 14926 53
(수정) 유니티 자동번역기(autotranslator) 제미니 ai (Gemini) 번역하기 풀세트
유틸
알탕
06/02/25 38386 87
[코네 스크립트] 단축키 변경 및 추가 / 다운로드 버튼 추가 / 사이트 색상 변경(이상함) [250603]
유틸
유령
06/02/25 3328 13
txt - csv 변환기
유틸
팰애호단체
06/02/25 2531 5
썸네일 크기조절 스크립트
유틸
딸기맛농축함유🍓
06/01/25 2778 4
던전 앤 브라이드 파밍 치트 공유 (20250601 17:16 수정)
유틸
rengester
06/01/25 6294 7
몬스터 블랙마켓 (몬블마) 브리딩 모드 한글 번역판(25.06.09 수정)
유틸
이노
05/31/25 11522 7
직접 만든 던전 앤 브라이드 모드 패치 공유
유틸
rengester
05/31/25 4960 10
유저스크립트 공유(업데이트250531)
유틸
cloud67p
05/31/25 3869 2
댓글 자동확장 개선 스크립트
유틸
딸기맛농축함유🍓
05/31/25 2176 6
댓글 최신순/등록순 버튼 분리(댓글 새로고침) 스크립트
유틸
딸기맛농축함유🍓
05/31/25 1648 2