Auto somi

자동 복호화/국룰입력/다운

当前为 2025-05-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Auto somi
  3. // @name:ko 자동 소미
  4. // @namespace http://tampermonkey.net/
  5. // @description 자동 복호화/국룰입력/다운
  6. // @version new 6.3
  7. // @author 김머시기
  8. // @match https://kiosk.ac/c/*
  9. // @match https://kio.ac/c/*
  10. // @match https://kone.gg/*
  11. // @match https://arca.live/b/*
  12. // @match https://mega.nz/*
  13. // @match https://gofile.io/d/*
  14. // @match https://workupload.com/*
  15. // @match https://drive.google.com/file/d/*
  16. // @match https://drive.google.com/drive/folders/*
  17. // @match https://drive.usercontent.google.com/download?id*
  18. // @icon https://lh3.google.com/u/0/d/18OVO7VmnwIuHK6Ke-z7035wKFmMKZ28W=w1854-h959-iv1
  19. // @grant GM.setValue
  20. // @grant GM.getValue
  21. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  22. // @grant GM.registerMenuCommand
  23. // @grant GM_registerMenuCommand
  24. // @grant GM_unregisterMenuCommand
  25. // @grant GM_getValue
  26. // @grant GM_setValue
  27. // @grant GM.xmlHttpRequest
  28. // @license MIT
  29. // @run-at document-end
  30. // ==/UserScript==
  31. 'use strict';
  32. let chkp = [,,,, atob('c29taXNvZnQ='), null], Down_Option, PageLoading = [], isT = [,,], MenuID = [null, null, null], host = document.URL.split('/')[2], npw = [], pw = [atob('c29taXNvZnQ='),atob('MjAyNXNvbWlzb2Z0'),
  33. // ================================== Settings ==========================================
  34. // 추가하길 원하는 비밀번호 따옴표 - 쉼표로 구분해서 바로 아래줄에 넣으면 됨 ex) '1234', '2024국룰', '!국룰!'
  35.  
  36. ];
  37. PageLoading[0] = 1000;
  38. Down_Option = 0;
  39. // ======================================================================================
  40.  
  41.  
  42.  
  43. const dlsitePreview = {
  44.     element: null,
  45.     images: [],
  46.     currentIndex: 0,
  47.     isShowing: false,
  48.     activeKeyListener: null,
  49.     activeWheelListener: null,
  50.     globalKeydownListener: null,
  51.     isEnabled: true
  52. };
  53.  
  54. function getKoneGGContentElement() {
  55.     if (host !== 'kone.gg') return null;
  56.     const proseContainer = document.querySelector('div.prose-container');
  57.     if (!proseContainer || !proseContainer.shadowRoot) return null;
  58.     const contentDiv = proseContainer.shadowRoot.querySelector('div.dark');
  59.     return contentDiv;
  60. }
  61.  
  62. async function handleBlockingModals(currentHost) {
  63.     function hideElement(selector) {
  64.         try {
  65.             const elements = document.querySelectorAll(selector);
  66.             if (elements.length > 0) {
  67.                 elements.forEach(el => {
  68.                     if (el.offsetParent !== null) {
  69.                         el.style.setProperty('display', 'none', 'important');
  70.                     }
  71.                 });
  72.             }
  73.         } catch (e) {
  74.         }
  75.     }
  76.  
  77.     if (currentHost === 'kone.gg') {
  78.         const nsfwOverlayContainer = document.querySelector('div.relative.min-h-60 > div.absolute.w-full.h-full.backdrop-blur-2xl');
  79.         if (nsfwOverlayContainer && nsfwOverlayContainer.offsetParent !== null) {
  80.             const viewContentButton = nsfwOverlayContainer.querySelector('div.flex.gap-4 button:nth-child(2)');
  81.             if (viewContentButton && viewContentButton.textContent?.includes('콘텐츠 보기')) {
  82.                 viewContentButton.click();
  83.                 await new Promise(resolve => setTimeout(resolve, 500));
  84.             } else {
  85.                 const modalSelectorsKone = [
  86.                     '.age-verification-popup',
  87.                     '.content-overlay.block',
  88.                 ];
  89.                 modalSelectorsKone.forEach(selector => hideElement(selector));
  90.             }
  91.         }
  92.     } else if (currentHost === 'arca.live') {
  93.         const modalSelectorsArca = [
  94.             { selector: '.adult-confirm-modal', action: 'hide' },
  95.             { selector: '.fc-dialog', action: 'hide' },
  96.             { selector: '#preview-block-layer', action: 'hide' },
  97.             { selector: 'div[class*="adult-channel-confirm"]', action: 'hide' },
  98.             { selector: 'div.modal[data-id="confirmAdult"] div.modal-footer button.btn-primary', action: 'click'},
  99.             { selector: 'button.btn-primary.btn.text-light[data-bs-dismiss="modal"]', action: 'click' }
  100.         ];
  101.         modalSelectorsArca.forEach(item => {
  102.             const elements = document.querySelectorAll(item.selector);
  103.             elements.forEach(element => {
  104.                 if (element && element.offsetParent !== null) {
  105.                     if (item.action === 'click') {
  106.                         element.click();
  107.                     } else {
  108.                         hideElement(item.selector);
  109.                     }
  110.                 }
  111.             });
  112.         });
  113.     }
  114. }
  115.  
  116. async function toggleDown(){
  117.     isT[0]=!isT[0];
  118.     if(!isT[0] && isT[1]){
  119.         isT[1]=false;
  120.         await GM.setValue('isT[1]', isT[1]);
  121.     }
  122.     await GM.setValue('isT[0]', isT[0]);
  123.     updateDown();
  124.     updateTab();
  125. updateDlsitePreviewMenu();
  126. }
  127.  
  128. async function toggleTab(){
  129.     isT[1]=!isT[1];
  130.     if(!isT[0] && isT[1]){
  131.         isT[0]=true;
  132.         await GM.setValue('isT[0]', isT[0]);
  133.     }
  134.     await GM.setValue('isT[1]', isT[1]);
  135.     updateDown();
  136.     updateTab();
  137. updateDlsitePreviewMenu();
  138. }
  139.  
  140. async function toggleDlsitePreview() {
  141.     dlsitePreview.isEnabled = !dlsitePreview.isEnabled;
  142.     await GM.setValue('dlsitePreviewEnabled', dlsitePreview.isEnabled);
  143. updateDown();
  144.     updateTab();
  145.     updateDlsitePreviewMenu();
  146.     if (!dlsitePreview.isEnabled && dlsitePreview.isShowing) {
  147.         hideDlsitePreview();
  148.     }
  149.     const allDlsiteLinks = document.querySelectorAll('a[href*="dlsite.com"]');
  150.     allDlsiteLinks.forEach(link => {
  151.         link.removeEventListener('mouseenter', showDlsitePreview);
  152.         link.removeEventListener('mouseleave', hideDlsitePreview);
  153.         delete link.dataset._dlsite_preview_hooked;
  154.         if (dlsitePreview.isEnabled) {
  155.             link.addEventListener('mouseenter', showDlsitePreview);
  156.             link.dataset._dlsite_preview_hooked = '1';
  157.         }
  158.     });
  159. }
  160.  
  161. function updateDown(){
  162.     if(MenuID[0] !==null)GM_unregisterMenuCommand(MenuID[0]);
  163.     MenuID[0]=GM_registerMenuCommand(`자동 다운로드  ${isT[0] ? 'ON' : 'OFF'}`, toggleDown, { autoClose: false, title: `자동 다운로드 ${isT[0] ? '켜짐' : '꺼짐'}`});
  164. }
  165.  
  166. function updateTab(){
  167.     if(MenuID[1] !==null)GM_unregisterMenuCommand(MenuID[1]);
  168.     MenuID[1]=GM_registerMenuCommand(`자동 닫기    ${isT[1] ? 'ON' : 'OFF'}`, toggleTab, { autoClose: false, title: `자동 닫기 ${isT[1] ? '켜짐' : '꺼짐'}`});
  169. }
  170.  
  171. function updateDlsitePreviewMenu(){
  172.     if(MenuID[2] !==null)GM_unregisterMenuCommand(MenuID[2]);
  173.     MenuID[2]=GM_registerMenuCommand(`DLsite 미리보기 ${dlsitePreview.isEnabled ? 'ON' : 'OFF'}`, toggleDlsitePreview, { autoClose: false, title: `DLsite 미리보기 ${dlsitePreview.isEnabled ? '켜짐' : '꺼짐'}`});
  174. }
  175.  
  176. function decodeContent(target, reg) {
  177.     try {
  178.         if (!target || !target.innerHTML) return;
  179.         const originalHTML = target.innerHTML;
  180.         let newHTML = originalHTML;
  181.  
  182.         const matches = [...originalHTML.matchAll(reg)];
  183.         if (matches.length === 0) return;
  184.  
  185.         for (const match of matches) {
  186.             let encodedString = match[0];
  187.             let decodedPotentialUrl = encodedString;
  188.  
  189.             try {
  190.                 let previousDecoded = "";
  191.                 for (let i = 0; i < 5; i++) {
  192.                     if (!decodedPotentialUrl || typeof decodedPotentialUrl !== 'string') break;
  193.                     let currentDecoded;
  194.                     try {
  195.                         currentDecoded = atob(decodedPotentialUrl);
  196.                     } catch(e) {
  197.                         break;
  198.                     }
  199.  
  200.                     if (currentDecoded.toLowerCase().startsWith('http://') || currentDecoded.toLowerCase().startsWith('https://')) {
  201.                         decodedPotentialUrl = currentDecoded;
  202.                         break;
  203.                     }
  204.                     if (previousDecoded === currentDecoded) {
  205.                         break;
  206.                     }
  207.                     previousDecoded = currentDecoded;
  208.                     decodedPotentialUrl = currentDecoded;
  209.                 }
  210.  
  211.  
  212.                 if (decodedPotentialUrl && typeof decodedPotentialUrl === 'string' &&
  213.                     (decodedPotentialUrl.toLowerCase().startsWith('http://') || decodedPotentialUrl.toLowerCase().startsWith('https://'))) {
  214.  
  215.                     try {
  216.                         const parsedUrl = new URL(decodedPotentialUrl);
  217.                         if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
  218.                             continue;
  219.                         }
  220.  
  221.                         const cleanHref = parsedUrl.href;
  222.  
  223.                         const textSpan = document.createElement('span');
  224.                         textSpan.textContent = cleanHref;
  225.                         const safeLinkDisplayText = textSpan.innerHTML;
  226.  
  227.                         const linkHTML = `<a href="${cleanHref}" target="_blank" rel="noreferrer" style="color:#007bff; text-decoration:underline; word-break:break-all;">${safeLinkDisplayText}</a>`;
  228.                         newHTML = newHTML.replace(encodedString, linkHTML);
  229.  
  230.                     } catch (urlError) {
  231.                     }
  232.                 }
  233.             } catch (decodeError) {
  234.             }
  235.         }
  236.  
  237.         if (target.innerHTML !== newHTML) {
  238.             target.innerHTML = newHTML;
  239.         }
  240.     } catch (e) {
  241.     }
  242. }
  243.  
  244. function doDec() {
  245.     if (chkp[3] !== chkp[4]) return;
  246.  
  247.     let targets = [];
  248.  
  249.     if (host === 'arca.live') {
  250.         targets = [
  251.             document.querySelector('body div.article-body > div.fr-view.article-content'),
  252.             ...document.querySelectorAll('div.article-comment#comment div.comment-content, div.article-comment div.comment-content')
  253.         ].filter(el => el !== null);
  254.     } else if (host === 'kone.gg') {
  255.         const koneContentElement = getKoneGGContentElement();
  256.         const comments = document.querySelectorAll('p.text-sm.whitespace-pre-wrap');
  257.         const listItems = document.querySelectorAll('ol.list-decimal li p');
  258.         targets = [koneContentElement, ...comments, ...listItems].filter(el => el !== null);
  259.     }
  260.  
  261.     if (targets.length === 0 || (targets.length === 1 && !targets[0])) return;
  262.  
  263.     for (const target of targets) {
  264.         if (!target) continue;
  265.         const links = target.querySelectorAll('a');
  266.         links.forEach(a => {
  267.             a.setAttribute('rel', 'noreferrer');
  268.         });
  269.  
  270.         decodeContent(target, /aHR0c[0-9A-Za-z+/=]{8,}/g);
  271.         decodeContent(target, /YUhSMG[0-9A-Za-z+/=]{8,}/g);
  272.         decodeContent(target, /WVVoU[0-9A-Za-z+/=]{8,}/g);
  273.         decodeContent(target, /V1ZWb[0-9A-Za-z+/=]{8,}/g);
  274.  
  275.         doDlsiteContextAwareForElement(target);
  276.     }
  277.  
  278.     if (host === 'kone.gg') {
  279.         const koneContentElement = getKoneGGContentElement();
  280.         if (!koneContentElement || koneContentElement.querySelector('.dlsite-link-appended')) return;
  281.  
  282.         const titleText = document.querySelector('h1.flex, h1.text-xl')?.textContent || '';
  283.         const commentTexts = [...document.querySelectorAll('p.text-sm.whitespace-pre-wrap')]
  284.             .map(el => el.textContent || '')
  285.             .join(' ');
  286.         const bodyText = koneContentElement?.textContent || '';
  287.  
  288.         const allText = `${titleText} ${bodyText} ${commentTexts}`;
  289.         const rjMatches = [...allText.matchAll(/\b(RJ|rj|Rj|rJ)([0-9]{5,10})\b/g)];
  290.         const rjSet = new Set(rjMatches.map(m => m[1].toUpperCase() + m[2]));
  291.  
  292.         if (rjSet.size === 1) {
  293.             const onlyCode = [...rjSet][0];
  294.             const linkUrl = `https://www.dlsite.com/maniax/work/=/product_id/${onlyCode}.html`;
  295.  
  296.             const finalLine = document.createElement('div');
  297.             finalLine.className = 'dlsite-link-appended';
  298.             finalLine.style.marginTop = '1em';
  299.             finalLine.innerHTML = `<a href="${linkUrl}" target="_blank" rel="noreferrer" style="color:#1e90ff; font-weight:bold;">▶ ${onlyCode} DLsite 링크</a>`;
  300.             koneContentElement.appendChild(finalLine);
  301.  
  302.             const appendedLink = finalLine.querySelector('a[href*="dlsite.com"]');
  303.             if (appendedLink && !appendedLink.dataset._dlsite_preview_hooked) {
  304.                 if (dlsitePreview.isEnabled) {
  305.                     appendedLink.addEventListener('mouseenter', showDlsitePreview);
  306.                     appendedLink.addEventListener('mouseleave', hideDlsitePreview, { once: true });
  307.                 }
  308.                 appendedLink.dataset._dlsite_preview_hooked = '1';
  309.             }
  310.         }
  311.     }
  312. }
  313.  
  314. function updateDlsitePreviewImage() {
  315.     if (!dlsitePreview.element || dlsitePreview.images.length === 0) {
  316.         return;
  317.     }
  318.  
  319.     dlsitePreview.element.innerHTML = '';
  320.     const img = document.createElement('img');
  321.     img.src = dlsitePreview.images[dlsitePreview.currentIndex];
  322.     img.style.maxWidth = '100%';
  323.     img.style.maxHeight = '100%';
  324.     dlsitePreview.element.appendChild(img);
  325. }
  326.  
  327. function handleDlsiteKeydown(event) {
  328.     if (!dlsitePreview.element || !dlsitePreview.isShowing || dlsitePreview.images.length <= 1) {
  329.         return;
  330.     }
  331.  
  332.     if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
  333.         event.stopPropagation();
  334.         event.preventDefault();
  335.  
  336.         if (event.key === 'ArrowLeft') {
  337.             dlsitePreview.currentIndex = (dlsitePreview.currentIndex - 1 + dlsitePreview.images.length) % dlsitePreview.images.length;
  338.         } else if (event.key === 'ArrowRight') {
  339.             dlsitePreview.currentIndex = (dlsitePreview.currentIndex + 1) % dlsitePreview.images.length;
  340.         }
  341.         updateDlsitePreviewImage();
  342.     }
  343. }
  344.  
  345. function handleDlsiteMouseWheel(event) {
  346.     if (!dlsitePreview.element || !dlsitePreview.isShowing || dlsitePreview.images.length <= 1) {
  347.         return;
  348.     }
  349.  
  350.     event.stopPropagation();
  351.     event.preventDefault();
  352.  
  353.     if (event.deltaY < 0) {
  354.         dlsitePreview.currentIndex = (dlsitePreview.currentIndex - 1 + dlsitePreview.images.length) % dlsitePreview.images.length;
  355.     } else if (event.deltaY > 0) {
  356.         dlsitePreview.currentIndex = (dlsitePreview.currentIndex + 1) % dlsitePreview.images.length;
  357.     }
  358.     updateDlsitePreviewImage();
  359. }
  360.  
  361.  
  362. function showDlsitePreview(event) {
  363.     if (!dlsitePreview.isEnabled) return;
  364.  
  365.     const link = event.target;
  366.     const productUrl = link.href;
  367.  
  368.     if (dlsitePreview.isShowing) return;
  369.     dlsitePreview.isShowing = true;
  370.  
  371.     dlsitePreview.element = document.createElement('div');
  372.     dlsitePreview.element.style.cssText = `
  373.         position: fixed;
  374.         z-index: 9999;
  375.         background-color: rgba(0, 0, 0, 0.8);
  376.         border: 1px solid #333;
  377.         box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  378.         max-width: 480px;
  379.         max-height: 480px;
  380.         overflow: hidden;
  381.         pointer-events: auto;
  382.         display: flex;
  383.         justify-content: center;
  384.         align-items: center;
  385.     `;
  386.     document.body.appendChild(dlsitePreview.element);
  387.  
  388.     moveDlsitePreview(event);
  389.     document.addEventListener('mousemove', moveDlsitePreview);
  390.  
  391.     GM.xmlHttpRequest({
  392.         method: "GET",
  393.         url: productUrl,
  394.         onload: function(response) {
  395.             const parser = new DOMParser();
  396.             const doc = parser.parseFromString(response.responseText, "text/html");
  397.             const imgContainer = doc.querySelector('.product-slider');
  398.  
  399.             if (!imgContainer) {
  400.                 if (dlsitePreview.element) {
  401.                     dlsitePreview.element.textContent = '미리보기를 불러올 수 없습니다.';
  402.                     dlsitePreview.element.style.color = '#fff';
  403.                 }
  404.                 return;
  405.             }
  406.  
  407.             dlsitePreview.images = [];
  408.             dlsitePreview.currentIndex = 0;
  409.  
  410.             const imageDataElements = imgContainer.querySelectorAll('.product-slider-data > div[data-src]');
  411.             imageDataElements.forEach(dataEl => {
  412.                 const imageUrl = dataEl.dataset.src;
  413.                 if (
  414.                     imageUrl &&
  415.                     !imageUrl.includes('data:image') &&
  416.                     !imageUrl.toLowerCase().startsWith('javascript:') &&
  417.                     !imageUrl.includes('/resize/')
  418.                 ) {
  419.                     dlsitePreview.images.push(imageUrl);
  420.                 }
  421.             });
  422.  
  423.             if (dlsitePreview.element && dlsitePreview.images.length > 0) {
  424.                 updateDlsitePreviewImage();
  425.  
  426.                 if (dlsitePreview.globalKeydownListener) {
  427.                     document.removeEventListener('keydown', dlsitePreview.globalKeydownListener);
  428.                 }
  429.                 dlsitePreview.globalKeydownListener = handleDlsiteKeydown;
  430.                 document.addEventListener('keydown', dlsitePreview.globalKeydownListener);
  431.  
  432.                 if (dlsitePreview.activeWheelListener) {
  433.                     dlsitePreview.element.removeEventListener('wheel', dlsitePreview.activeWheelListener);
  434.                 }
  435.                 dlsitePreview.activeWheelListener = handleDlsiteMouseWheel;
  436.                 dlsitePreview.element.addEventListener('wheel', dlsitePreview.activeWheelListener, { passive: false });
  437.  
  438.  
  439.             } else if (dlsitePreview.element) {
  440.                 dlsitePreview.element.textContent = '이미지를 찾을 수 없습니다.';
  441.                 dlsitePreview.element.style.color = '#fff';
  442.             }
  443.         },
  444.         onerror: function() {
  445.             if (dlsitePreview.element) {
  446.                 dlsitePreview.element.textContent = '미리보기 로드 실패';
  447.                 dlsitePreview.element.style.color = '#fff';
  448.             }
  449.         }
  450.     });
  451.  
  452.     link.addEventListener('mouseleave', hideDlsitePreview);
  453. }
  454.  
  455.  
  456. function moveDlsitePreview(event) {
  457.     if (dlsitePreview.element) {
  458.         dlsitePreview.element.style.top = `${event.clientY + 15}px`;
  459.         dlsitePreview.element.style.left = `${event.clientX + 15}px`;
  460.     }
  461. }
  462.  
  463. function hideDlsitePreview() {
  464.     if (dlsitePreview.element) {
  465.         if (dlsitePreview.activeWheelListener) {
  466.             dlsitePreview.element.removeEventListener('wheel', dlsitePreview.activeWheelListener);
  467.             dlsitePreview.activeWheelListener = null;
  468.         }
  469.         document.body.removeChild(dlsitePreview.element);
  470.         dlsitePreview.element = null;
  471.     }
  472.     dlsitePreview.isShowing = false;
  473.     dlsitePreview.images = [];
  474.     dlsitePreview.currentIndex = 0;
  475.  
  476.     document.removeEventListener('mousemove', moveDlsitePreview);
  477.  
  478.     if (dlsitePreview.globalKeydownListener) {
  479.         document.removeEventListener('keydown', dlsitePreview.globalKeydownListener);
  480.         dlsitePreview.globalKeydownListener = null;
  481.     }
  482. }
  483.  
  484. function doDlsiteContextAwareForElement(element) {
  485.     if (!element) return;
  486.  
  487.     const existingAppendedLink = element.querySelector('.dlsite-link-appended');
  488.     if (existingAppendedLink) {
  489.         existingAppendedLink.remove();
  490.     }
  491.  
  492.     const keywordPattern = /(꺼|거|퍼|RJ|rj|Rj|rJ|VJ|vj|Vj|vJ|DL|dl|Dl|dL)[\s:()\[\]#-]*([0-9]{5,10})/g;
  493.     const fullRJPattern = /\b(RJ|rj|Rj|rJ|VJ|vj|Vj|vJ|퍼|꺼|거|DL|dl|Dl|dL)([0-9]{5,10})\b/g;
  494.  
  495.     const textContent = element.textContent || '';
  496.  
  497.     const allFoundCodes = new Map();
  498.     let match;
  499.  
  500.     const combinedRegex = new RegExp(`${keywordPattern.source}|${fullRJPattern.source}`, 'gi');
  501.     while ((match = combinedRegex.exec(textContent)) !== null) {
  502.         const prefix = (match[1] || match[3] || '').toLowerCase();
  503.         const code = match[2] || match[4];
  504.         if (code && !allFoundCodes.has(code)) {
  505.             allFoundCodes.set(code, { prefix: prefix, originalText: match[0] });
  506.         }
  507.     }
  508.  
  509.     let tempHTML = element.innerHTML;
  510.  
  511.     allFoundCodes.forEach(({ prefix, originalText }, code) => {
  512.         const mappedPrefix = ['vj', 'vJ', 'Vj', 'VJ', '퍼'].includes(prefix) ? 'VJ'
  513.                             : ['rj', 'rJ', 'Rj', 'RJ', '꺼', '거', 'dl', 'dL', 'Dl', 'DL'].includes(prefix) ? 'RJ'
  514.                             : prefix.toUpperCase();
  515.         const fullCode = `${mappedPrefix}${code}`;
  516.         const linkUrl = `https://www.dlsite.com/maniax/work/=/product_id/${fullCode}.html`;
  517.  
  518.         const textSpan = document.createElement('span');
  519.         textSpan.textContent = originalText;
  520.         const safeLinkText = textSpan.innerHTML;
  521.  
  522.         const escapedOriginalText = originalText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  523.         const replaceRegex = new RegExp(`(${escapedOriginalText})(?![^<]*>|[^<>]*<\/a>)`, 'g');
  524.  
  525.         const safeHref = linkUrl.replace(/"/g, '"');
  526.  
  527.         tempHTML = tempHTML.replace(
  528.             replaceRegex,
  529.             `<a href="${safeHref}" target="_blank" rel="noreferrer" style="color:#1e90ff;">${safeLinkText}</a>`
  530.         );
  531.     });
  532.  
  533.     if (element.innerHTML !== tempHTML) {
  534.         element.innerHTML = tempHTML;
  535.     }
  536.  
  537.     const dlsiteLinks = element.querySelectorAll('a[href*="dlsite.com"]');
  538.     dlsiteLinks.forEach(link => {
  539.         if (!link.dataset._dlsite_preview_hooked) {
  540.             if (dlsitePreview.isEnabled) {
  541.                 link.addEventListener('mouseenter', showDlsitePreview);
  542.                 link.addEventListener('mouseleave', hideDlsitePreview, { once: true });
  543.             }
  544.             link.dataset._dlsite_preview_hooked = '1';
  545.         }
  546.     });
  547. }
  548.  
  549. async function chkPW(){
  550.     chkp[3]=await GM.getValue('chkp[3]');
  551.     isT[0]=await GM.getValue('isT[0]', true);
  552.     isT[1]=await GM.getValue('isT[1]', false);
  553.     dlsitePreview.isEnabled = await GM.getValue('dlsitePreviewEnabled', true);
  554.     updateDown();
  555.     updateTab();
  556.     updateDlsitePreviewMenu();
  557.  
  558.     if(host=='arca.live' || host=='kone.gg'){
  559.         if(chkp[3] !=chkp[4]){
  560.             const chk=prompt('국룰을 입력해주세요');
  561.             if(chk?.toLowerCase()==chkp[4]) {
  562.                 await GM.setValue('chkp[3]', chkp[4]);
  563.             } else {
  564.                 GM.setValue('chkp[3]', false);
  565.                 alert('국룰이 틀렸습니다');
  566.             }
  567.         }
  568.     }
  569. }
  570.  
  571. async function inputPW() {
  572.     let inputElem = document.querySelector(chkp[0]), btnElem = document.querySelector(chkp[1]);
  573.     if (!inputElem ) {
  574.         if (isT[0] === true && !document.querySelector('.files-list, #download-section')) {
  575.             await new Promise(res => setTimeout(res, PageLoading[1] || 1000)).then(DBtn);
  576.         }
  577.         return;
  578.     }
  579.  
  580.     const combinedPw = [...new Set([...pw, ...npw])];
  581.  
  582.     if (chkp[3] == chkp[4]) {
  583.         try {
  584.             for (let i = 0; i < combinedPw.length; i++) {
  585.                 if (!combinedPw[i]) continue;
  586.                 if (!inputElem) break;
  587.  
  588.                 inputElem.value = combinedPw[i];
  589.  
  590.                 if (host == 'kio.ac') {
  591.                     inputElem.dispatchEvent(new Event('input', { bubbles: true }));
  592.                     inputElem.dispatchEvent(new Event('change', { bubbles: true }));
  593.                     await new Promise(res => setTimeout(res, 50));
  594.                     if(btnElem) btnElem.click();
  595.                 } else {
  596.                     if(btnElem) btnElem.click();
  597.                     else {
  598.                         const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true });
  599.                         inputElem.dispatchEvent(enterEvent);
  600.                     }
  601.                 }
  602.  
  603.                 await new Promise(res => setTimeout(res, 800));
  604.  
  605.                 const successIndicator = document.querySelector('.files-list, #download-section, .download-link, .btn-download, .main-button-download');
  606.                 const errorIndicator = document.querySelector('.text-error, .text-red-500, .error-message, .incorrect-password, [class*="error"], [id*="error"]');
  607.  
  608.                 if (successIndicator && successIndicator.offsetParent !== null) {
  609.                     break;
  610.                 } else if (errorIndicator && errorIndicator.offsetParent !== null) {
  611.                     if (inputElem) inputElem.value = '';
  612.                 }
  613.             }
  614.             if (isT[0] === true) {
  615.                 await new Promise(res => setTimeout(res, PageLoading[1] || 1000)).then(DBtn);
  616.             }
  617.  
  618.         } catch (e) {
  619.             if (isT[0] === true) {
  620.                 await new Promise(res => setTimeout(res, PageLoading[1] || 1000)).then(DBtn);
  621.             }
  622.         }
  623.     }
  624. }
  625.  
  626. async function kioskdone(){
  627.     try {
  628.         await new Promise(res=> setTimeout(res, 3000));
  629.         for(let i=0, jj=0; jj!=1; i++){
  630.             await new Promise(res=> setTimeout(res, 1000));
  631.             if(document.querySelector('.flex.flex-row.text-xs.justify-between div:nth-child(2)').innerText=='done'){
  632.                 await new Promise(res=> setTimeout(res, 2000)).then(() => window.close());
  633.                 jj++;
  634.             }
  635.         }
  636.     } catch(e){
  637.         if(isT[1]==true && isT[2]==true) window.close();
  638.     }
  639. }
  640.  
  641. let dbtnCalled = false;
  642. async function DBtn(){
  643.     if (isT[0] !== true || dbtnCalled) return;
  644.     dbtnCalled = true;
  645.  
  646.     if (host === 'kiosk.ac') {
  647.         try {
  648.             const btns = document.querySelectorAll(chkp[2]);
  649.             const clickedSet = new Set();
  650.  
  651.             for (const btn of btns) {
  652.                 if (!btn || btn.offsetParent === null) continue;
  653.                 const key = btn.closest('tr')?.innerText?.trim();
  654.                 if (clickedSet.has(key)) continue;
  655.  
  656.                 let waitCount = 0;
  657.                 while (btn.disabled && waitCount < 10) {
  658.                     await new Promise(res => setTimeout(res, 200));
  659.                     waitCount++;
  660.                 }
  661.  
  662.                 if (btn.disabled) continue;
  663.  
  664.                 clickedSet.add(key);
  665.                 btn.click();
  666.                 await new Promise(res => setTimeout(res, 1800));
  667.             }
  668.         } catch (e) {}
  669.     }
  670.  
  671.     else if (host === 'mega.nz') {
  672.         try {
  673.             const resumeButton = document.querySelector('.mega-button.positive.resume.js-resume-download');
  674.             if (resumeButton) resumeButton.click();
  675.  
  676.             const standardDownloadButton = document.querySelector('.mega-button.positive.js-default-download.js-standard-download');
  677.             if (standardDownloadButton) standardDownloadButton.click();
  678.  
  679.             const continueDownloadButton = document.querySelector('.mega-button.large.positive.download.continue-download');
  680.             if (continueDownloadButton) continueDownloadButton.click();
  681.         } catch (e) {}
  682.     }
  683.  
  684.     else {
  685.         try {
  686.             const btns = document.querySelectorAll(chkp[2]);
  687.             const clickedSet = new Set();
  688.  
  689.             for (const btn of btns) {
  690.                 if (!btn || btn.offsetParent === null || btn.classList.contains('btn-disabled')) continue;
  691.                 const key = btn.closest('tr')?.innerText?.trim();
  692.                 if (!clickedSet.has(key)) {
  693.                     clickedSet.add(key);
  694.                     btn.click();
  695.                     await new Promise(res => setTimeout(res, 300));
  696.                 }
  697.             }
  698.         } catch (e) {}
  699.     }
  700.  
  701.     if (isT[1] === true && isT[2] === true) {
  702.         setTimeout(() => {
  703.             if (host === 'kiosk.ac') {
  704.                 kioskdone();
  705.             } else {
  706.                 window.close();
  707.             }
  708.         }, 1500);
  709.     }
  710. }
  711.  
  712. async function FindPW(){
  713.     let atc;
  714.     if (host === 'arca.live') {
  715.         atc = document.querySelector('body div.article-body > div.fr-view.article-content');
  716.     } else if (host === 'kone.gg') {
  717.         atc = getKoneGGContentElement();
  718.     }
  719.  
  720.     if (!atc || !atc.innerHTML) return;
  721.  
  722.     let tempContent = atc.innerHTML;
  723.     tempContent=tempContent.replace(/ /g, ' ').replace(/( ){2,}/g, ' ');
  724.     tempContent=tempContent.replace(/국룰/g, 'ㄱㄹ');
  725.  
  726.     let regexx=/(대문자)/;
  727.  
  728.     if(regexx.test(tempContent)) {
  729.         const smpeopleUpper = atob('U01QRU9QTEU=');
  730.         if (npw.indexOf(smpeopleUpper) === -1) {
  731.             npw.push(smpeopleUpper);
  732.         }
  733.     }
  734.  
  735.     function processPasswordRegex(reg){
  736.         let currentLoopContent = tempContent;
  737.         const processedTexts = new Set();
  738.  
  739.         while(true){
  740.             const matchResult = reg.exec(currentLoopContent);
  741.             if (!matchResult) break;
  742.  
  743.             let matchedText = matchResult[0];
  744.             if (processedTexts.has(matchedText)) {
  745.                 currentLoopContent = currentLoopContent.replace(matchedText, `__SKIPPED_${Math.random().toString(36).substring(2, 10)}__`);
  746.                 continue;
  747.             }
  748.  
  749.             let DECed = matchedText.replace(/(ㄱㄹ)/g, '국룰');
  750.             let DECedd = DECed.replace(/\s|[+]|(은|는|이|가)|(전부)|(대문자로)|(대문자)|(비밀번호)|(패스워드)|(비번)|(ㅂㅂ)|(암호)|(ㅇㅎ)|(키오스크맘)|(키오스크)|(입니다)|(이고)|(이며)|(임다)|(같다)|(처럼)|(틀리다)|(입니다요)/g, '');
  751.             DECedd = DECedd.split(/[(입)(임)(이)(이)(이)(입)(임)(같)(처)(틀)]/g)[0];
  752.             DECedd = DECedd.replace(/[<>]/g, '');
  753.  
  754.             let dat;
  755.             if (host === 'arca.live') {
  756.                 const dateEl = document.querySelector('.article-info .date .body');
  757.                 if (dateEl && typeof dateEl.innerText === 'string' && dateEl.innerText.trim() !== '') {
  758.                     dat = dateEl.innerText.split(' ')[0];
  759.                 } else {
  760.                     dat = undefined;
  761.                 }
  762.             }
  763.  
  764.             let regexa=/(오늘)|(날짜)|(날자)/g;
  765.             if(dat && regexa.test(DECedd)){
  766.                 DECedd=DECedd.replace(regexa, '');
  767.                 const dateParts = dat.split(/\-/g);
  768.                 if (dateParts.length === 3) {
  769.                     const year = dateParts[0].slice(2);
  770.                     const month = dateParts[1];
  771.                     const day = dateParts[2];
  772.                     let dateFormats=[month + day, month + '-' + day, year + month + day, dat.replace(/-/g,'')];
  773.                     for(let k=0;k<dateFormats.length;k++){
  774.                         const finalPw = DECedd.replace(/국룰/g,chkp[4]) + dateFormats[k];
  775.                         if(finalPw.length > 2 && npw.indexOf(finalPw)=='-1') {
  776.                             npw.push(finalPw);
  777.                         }
  778.                     }
  779.                 }
  780.             } else {
  781.                 const finalPw = DECedd.replace(/국룰/g,chkp[4]);
  782.                 if(finalPw && finalPw.length > 2 && npw.indexOf(finalPw)=='-1') {
  783.                     npw.push(finalPw);
  784.                 }
  785.             }
  786.             processedTexts.add(matchedText);
  787.             currentLoopContent = currentLoopContent.replace(matchedText, `__PROCESSED_${Math.random().toString(36).substring(2, 15)}__`);
  788.         }
  789.     }
  790.     processPasswordRegex(/[(<p>\s*)*]{0,}[ㄱ-ㅣ가-힣0-9A-Za-z\s~`!^\_+@\#$%&=]{0,}(ㄱㄹ){1,1}[ㄱ-ㅣ가-힣0-9A-Za-z\s~`!^\_+@\#$%&=]{0,}[(\s*</p>)*]{0,}/);
  791.     await GM.setValue('npw', npw);
  792. }
  793.  
  794. function waitForKoneContentAndProcess() {
  795.     let attempts = 0;
  796.     const maxAttempts = 20;
  797.     const checkInterval = setInterval(async () => {
  798.         attempts++;
  799.         await handleBlockingModals(host);
  800.  
  801.         const atc = getKoneGGContentElement();
  802.         const titleEl = [...document.querySelectorAll('h1.flex, h1.text-xl')].find(el =>
  803.             el.textContent?.match(/RJ[0-9]{6,10}|VJ[0-9]{6,10}/i)
  804.         );
  805.  
  806.         const hasBase64 = atc && /aHR0c|YUhSMG|WVVoU|V1ZWb/.test(atc.textContent || '');
  807.  
  808.         if (atc && (titleEl || hasBase64) && (atc.textContent || '').length > 10) {
  809.             clearInterval(checkInterval);
  810.             await FindPW();
  811.             setTimeout(doDec, 200);
  812.         } else if (attempts >= maxAttempts) {
  813.             clearInterval(checkInterval);
  814.         }
  815.     }, 200);
  816. }
  817.  
  818. async function loadNpw() {
  819.     const saved = await GM.getValue('npw');
  820.     if (Array.isArray(saved)) {
  821.         npw = [...new Set([...npw, ...saved])];
  822.     }
  823. }
  824.  
  825. async function initHostSpecificSettings() {
  826.     await loadNpw();
  827.  
  828.     const hostConfigs = {
  829.         'kio.ac': {
  830.             chkpSelectors: [
  831.                 '.overflow-auto.max-w-full.flex-grow.p-1 input:nth-of-type(1)',
  832.                 '.flex.flex-col-reverse button:nth-of-type(1)',
  833.                 'td.align-middle > div.flex > button[type="button"].inline-flex'
  834.             ],
  835.             isT2: true,
  836.             pageLoadDelay: 1500,
  837.             inputPwDelay: 2000
  838.         },
  839.         'kiosk.ac': {
  840.             chkpSelectors: [
  841.                 '.input.shadow-xl.flex-grow',
  842.                 '.btn.btn-ghost.w-full.mt-2.rounded-md',
  843.                 '.dropdown.group > button'
  844.             ],
  845.             isT2: Down_Option === 0,
  846.             pageLoadDelay: 500,
  847.             inputPwDelay: 300
  848.         },
  849.         'mega.nz': {
  850.             chkpSelectors: [
  851.                 '#password-decrypt-input',
  852.                 '.mega-button.positive.fm-dialog-new-folder-button.decrypt-link-button',
  853.                 '.mega-button.positive.js-default-download.js-standard-download'
  854.             ],
  855.             isT2: false,
  856.             pageLoadDelay: 2500,
  857.             inputPwDelay: 1800
  858.         },
  859.         'workupload.com': {
  860.             chkpSelectors: [
  861.                 '#passwordprotected_file_password',
  862.                 '#passwordprotected_file_submit',
  863.                 'a.btn.btn-prio[href*="/file/"]'
  864.             ],
  865.             isT2: Down_Option === 0,
  866.             pageLoadDelay: 1500,
  867.             inputPwDelay: 0
  868.         },
  869.         'drive.google.com': {
  870.             chkpSelectors: [],
  871.             isT2: true,
  872.             pageLoadDelay: 500,
  873.             inputPwDelay: 0
  874.         },
  875.         'drive.usercontent.google.com': {
  876.             chkpSelectors: [
  877.                 null,
  878.                 null,
  879.                 'form[method="POST"] button[type="submit"], input[type="submit"][value="다운로드"], button.jfk-button-action'
  880.             ],
  881.             isT2: true,
  882.             pageLoadDelay: 500,
  883.             inputPwDelay: 0
  884.         },
  885.         'gofile.io': {
  886.             chkpSelectors: [
  887.                 '#filesErrorPasswordInput',
  888.                 '#filesErrorPasswordButton',
  889.                 'button.btn-download, a.btn-download, #rowFolderCenter button[data-bs-target="#filesList"] + div .btn-outline-secondary'
  890.             ],
  891.             isT2: true,
  892.             pageLoadDelay: 1500,
  893.             inputPwDelay: 0
  894.         }
  895.     };
  896.  
  897.     const config = hostConfigs[host];
  898.     if (config) {
  899.         chkp[0] = config.chkpSelectors[0];
  900.         chkp[1] = config.chkpSelectors[1];
  901.         chkp[2] = config.chkpSelectors[2];
  902.         isT[2] = config.isT2;
  903.         PageLoading[1] = config.pageLoadDelay;
  904.  
  905.         if (host === 'drive.google.com') {
  906.             if (document.URL.includes('/file/d/')) {
  907.                 const fileId = document.URL.split('/d/')[1].split('/')[0];
  908.                 if (fileId) {
  909.                     window.location.href = `https://drive.usercontent.google.com/download?id=${fileId}&export=download&authuser=0`;
  910.                 }
  911.             } else if (document.URL.includes('/drive/folders/')) {
  912.                 await new Promise(res => setTimeout(res, (PageLoading[0] || 1000) + 1500));
  913.                 if (isT[0] === true) {
  914.                     const downloadAllButton = document.querySelector('div[aria-label="모두 다운로드"], div[data-tooltip="모두 다운로드"]');
  915.                     if (downloadAllButton) {
  916.                         downloadAllButton.click();
  917.                     } else {
  918.                         const firstItemDownloadButton = document.querySelector('div[role="gridcell"][data-is-shared="false"] div[aria-label*="다운로드"]');
  919.                         if (firstItemDownloadButton) {
  920.                             firstItemDownloadButton.click();
  921.                         }
  922.                     }
  923.                 }
  924.                 await new Promise(res => setTimeout(res, (PageLoading[0] || 1000) + 3500)).then(() => {
  925.                     if (isT[0] === true) DBtn();
  926.                 });
  927.             }
  928.         } else if (host === 'drive.usercontent.google.com') {
  929.             await new Promise(res => setTimeout(res, 100)).then(() => {
  930.                 if (isT[0] === true) DBtn();
  931.             });
  932.         } else {
  933.             await new Promise(res => setTimeout(res, (PageLoading[0] || 1000) + config.inputPwDelay)).then(inputPW);
  934.             if (isT[0] === true) {
  935.                 DBtn();
  936.             }
  937.         }
  938.     }
  939. }
  940.  
  941.  
  942. (async () => {
  943.     await chkPW();
  944.     await handleBlockingModals(host);
  945.  
  946.     if (host === 'arca.live') {
  947.         await FindPW();
  948.         setTimeout(doDec, 200);
  949.     } else if (host === 'kone.gg') {
  950.         waitForKoneContentAndProcess();
  951.     }
  952.  
  953.     await initHostSpecificSettings();
  954. })();
  955.  
  956. (function () {
  957.     if (location.host !== 'kone.gg') return;
  958.  
  959.     function hookDecodeTriggerOnButton() {
  960.         const buttonsToHook = [
  961.             document.querySelector('div.flex.items-center.justify-between.p-4 button[data-slot="button"]'),
  962.             [...document.querySelectorAll('button.flex.cursor-pointer.items-center')]
  963.                 .find(button => button.textContent?.trim() === '댓글 더 불러오기'),
  964.         ].filter(b => b instanceof Element && b.dataset._somi_hooked !== '1');
  965.  
  966.         buttonsToHook.forEach(button => {
  967.             button.dataset._somi_hooked = '1';
  968.             button.addEventListener('click', async () => {
  969.                 await handleBlockingModals(host);
  970.                 setTimeout(async () => {
  971.                     await FindPW();
  972.                     setTimeout(doDec, 200);
  973.                 }, 500);
  974.             });
  975.         });
  976.     }
  977.  
  978.     hookDecodeTriggerOnButton();
  979.  
  980.     const observer = new MutationObserver(async (mutationsList) => {
  981.         for (const mutation of mutationsList) {
  982.             if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  983.                 hookDecodeTriggerOnButton();
  984.                 await handleBlockingModals(host);
  985.                 setTimeout(async () => {
  986.                     await FindPW();
  987.                     setTimeout(doDec, 200);
  988.                 }, 100);
  989.             } else if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
  990.                 await handleBlockingModals(host);
  991.                 setTimeout(async () => {
  992.                     await FindPW();
  993.                     setTimeout(doDec, 200);
  994.                 }, 100);
  995.             }
  996.         }
  997.         await handleBlockingModals(host);
  998.     });
  999.     observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
  1000. })();
  1001.  
  1002.  
  1003. (function () {
  1004.     if (location.host !== 'kone.gg') return;
  1005.  
  1006.     let lastUrl = location.href;
  1007.     let mainProcessingIntervalId = null;
  1008.  
  1009.     const observer = new MutationObserver(async () => {
  1010.         const currentUrl = location.href;
  1011.         if (currentUrl !== lastUrl) {
  1012.             lastUrl = currentUrl;
  1013.             if (mainProcessingIntervalId) {
  1014.                 clearInterval(mainProcessingIntervalId);
  1015.             }
  1016.             await handleBlockingModals(host);
  1017.             observeAndRunKoneFunctions();
  1018.         }
  1019.     });
  1020.  
  1021.     observer.observe(document.body, { childList: true, subtree: true });
  1022.  
  1023.     async function observeAndRunKoneFunctions() {
  1024.         const start = Date.now();
  1025.         const timeout = 8000;
  1026.  
  1027.         if (mainProcessingIntervalId) {
  1028.             clearInterval(mainProcessingIntervalId);
  1029.         }
  1030.  
  1031.         mainProcessingIntervalId = setInterval(async () => {
  1032.             await handleBlockingModals(host);
  1033.             const atc = getKoneGGContentElement();
  1034.             const hasText = atc && (atc.textContent || '').length > 20;
  1035.             const hasEncoded = atc && /aHR0c|YUhSMG|WVVoU|V1ZWb/.test(atc.textContent || '');
  1036.             const hasDlsiteLink = atc && atc.querySelector('a[href*="dlsite.com"]');
  1037.             const comments = document.querySelectorAll('p.text-sm.whitespace-pre-wrap');
  1038.             let hasEncodedComments = false;
  1039.             for(const comment of comments) {
  1040.                 if (/aHR0c|YUhSMG|WVVoU|V1ZWb/.test(comment.textContent || '')) {
  1041.                     hasEncodedComments = true;
  1042.                     break;
  1043.                 }
  1044.             }
  1045.  
  1046.  
  1047.             if (atc && (hasText && (hasEncoded || hasDlsiteLink)) || hasEncodedComments) {
  1048.                 clearInterval(mainProcessingIntervalId);
  1049.                 mainProcessingIntervalId = null;
  1050.                 await FindPW();
  1051.                 setTimeout(doDec, 200);
  1052.             }
  1053.  
  1054.             if (Date.now() - start > timeout) {
  1055.                 clearInterval(mainProcessingIntervalId);
  1056.                 mainProcessingIntervalId = null;
  1057.             }
  1058.         }, 200);
  1059.     }
  1060.  
  1061.     (async () => {
  1062.         await handleBlockingModals(host);
  1063.         observeAndRunKoneFunctions();
  1064.     })();
  1065. })();