// ==UserScript== // @name Namu Hot Now // @name:ko 나무위키 실검 알려주는 스크립트 // @namespace https://arca.live/b/namuhotnow // @version 0.9.3.4 // @description 이게 왜 실검? // @author KEMOMIMI // @match https://namu.wiki/* // @match https://arca.live/* // @connect arca.live // @icon https://www.google.com/s2/favicons?sz=64&domain=namu.wiki // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // @grant GM_xmlhttpRequest // @grant GM.xmlhttpRequest // @downloadURL none // ==/UserScript== function findLinkByPartialMatch(pairs, keyword) { for (let i = 0; i < pairs.length; i++) { let pair = pairs[i]; let text = pair.text; const regex = /<\/b>|]*>/g; var modifiedText = text.replace(regex, ''); if (modifiedText.toLowerCase().includes(keyword.toLowerCase())) { return [pair.link, pair.badges]; } } return [null, null]; } function getSpansContent() { var spansContent = []; var spans = Array.from(document.querySelectorAll('#app ul>li>a>span')).slice(0, 10); spans.forEach(function(span) { spansContent.push(span.textContent); }); return spansContent.join('').trim(); } var linkElements = []; var pairs = []; var previousSpansContent = ""; var storedElements = []; function removeLinkElements() { for (var i = 0; i < linkElements.length; i++) { var linkElement = linkElements[i]; linkElement.parentNode.removeChild(linkElement); } linkElements = []; } function checkMobileHotkewordOpened(){ const aTags = Array.from(document.querySelector('a[title="아무 문서로 이동"]').parentElement.querySelectorAll('a')); if (aTags.length > 10) { return true }else{ return false } } function checkMobileHotkeword(){ var chk = setInterval(function() { var svgTags = Array.from(document.querySelector('a[title="아무 문서로 이동"]').parentElement.querySelectorAll('svg')); if (svgTags.length<5) { var whyHotElements = document.querySelectorAll('.whyHot'); whyHotElements.forEach(function(element) { element.remove(); }); const elementsWithParentClass = document.querySelectorAll('.namuHotParentClass'); elementsWithParentClass.forEach(parentElement => { const childAElement = parentElement.querySelector('a'); if (childAElement) { parentElement.parentNode.insertBefore(childAElement, parentElement.nextSibling); parentElement.remove(); } }); }else if (svgTags.length>=5){ const elementsWithParentClass = document.querySelectorAll('.namuHotParentClass'); let count = 0; elementsWithParentClass.forEach(parentElement => { const childAnchorElements = parentElement.querySelectorAll('a'); childAnchorElements.forEach(anchorElement => { if (anchorElement.getAttribute('href') === '#') { count++; } }); }); if (count == 0) { const elementsWithParentClass = document.querySelectorAll('.namuHotParentClass'); elementsWithParentClass.forEach(function(element) { element.remove(); }); } if (elementsWithParentClass.length == 0) { if (checkMobileHotkewordOpened()) { clearInterval(chk); refreshLink(2); } } } }, 100); } //실검챈에서 게시물 링크를 수집하여 pairs에 저장하는 함수 //page : 긁어올 실검챈 페이지 길이 async function updatePairs(page) { try { let requests = []; for (var i = 1; i <= page; i++) { requests.push(new Promise((resolveRequest, rejectRequest) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://arca.live/b/namuhotnow?p=' + i, onload: function(response) { const htmlData = response.responseText; const parser = new DOMParser(); const doc = parser.parseFromString(htmlData, 'text/html'); var elements = doc.querySelectorAll('.article-list .list-table a:not(.notice)'); storedElements = Array.from(elements); let pagePairs = []; elements.forEach(function(element) { const badgesElement = element.querySelector('.badges'); var badgesText = badgesElement ? badgesElement.textContent.trim() : '이왜실?'; var link = element.getAttribute('href'); var titleElement = element.querySelector('.table .title'); var text = titleElement ? titleElement.innerText.trim() : ''; pagePairs.push({text: text, link: link, badges: badgesText}); }); resolveRequest({page: i, pairs: pagePairs}); }, onerror: function(error) { rejectRequest(error); } }); })); } const results = await Promise.all(requests); // 페이지 정렬 results.sort((a, b) => a.page - b.page); // pairs 배열에 추가 results.forEach(result => { pairs.push(...result.pairs); }); // 수동 연결 항목 추가 pairs.push({ text: "나무위키 실검 알려주는 채널, 실검챈", link: "/b/namuhotnow/112775488", badges : "❗️공지"}); const emojiDisplay = await GM.getValue('emojiDisplay', true); if(!emojiDisplay){ pairs.forEach(pair => { let index = 0; for (let i = 0; i < pair.badges.length; i++) { if (/[가-힣a-zA-Z]/.test(pair.badges[i])) { index = i; break; } } pair.badges = pair.badges.substring(index); }); } } catch (error) { console.error('Error in updatePairs:', error); throw error; } } async function refreshLink(type) { try { await updatePairs(2); } catch (error) { console.error("업데이트 중 오류:", error); } if(type == 0){ var realtimeList = Array.from(document.querySelectorAll('#app ul>li>a>span')).slice(0, 10); realtimeList.forEach(function(titleElement) { var [resultLink, resultBadges] = findLinkByPartialMatch(pairs, titleElement.innerText.trim()); if (resultLink != null){ var linkElement = document.createElement('a'); linkElement.href = 'https://arca.live' + resultLink; linkElement.textContent = resultBadges; linkElement.display = 'flex'; linkElement.width = '40%'; linkElement.target="_blank"; linkElement.style.margin = "auto 5px"; linkElement.setAttribute('data-v-userxcript', ''); linkElement.className = 'namuHotBtnStyle'; const parentLiTag = titleElement ? titleElement.parentElement.parentElement : null; parentLiTag.querySelector('a').style.width = "60%"; parentLiTag.appendChild(linkElement); linkElements.push(linkElement); } }); }else if(type == 1){ var firstLinkList = document.querySelector('aside .link-list'); var arcalinkElements = firstLinkList.querySelectorAll('a'); var titleArray = []; arcalinkElements.forEach(function(aLinkElement) { var [resultLink, resultBadges] = findLinkByPartialMatch(pairs, aLinkElement.getAttribute('title').trim()); if(resultLink != null){ aLinkElement.style.paddingRight = "1em"; var newSpanHTML = `
${aLinkElement.getAttribute('title')}
`; aLinkElement.insertAdjacentHTML('beforebegin', newSpanHTML); aLinkElement.remove() } }); }else if(type == 2){ var namuHotParentClass = document.querySelectorAll('.namuHotParentClass'); if (!namuHotParentClass[0]) { const aTags = Array.from(document.querySelector('a[title="아무 문서로 이동"]').parentElement.querySelectorAll('a')); const mobileList = aTags.length > 10 ? aTags.slice(-10) : aTags; mobileList.forEach(function(element) { var [resultLink, resultBadges] = findLinkByPartialMatch(pairs, element.innerText.trim()); var newParent = document.createElement('span'); newParent.classList.add('namuHotParentClass'); if (resultLink != null){ var linkElement = document.createElement('a'); linkElement.href = 'https://arca.live' + resultLink; linkElement.textContent = resultBadges; linkElement.width = '20px'; linkElement.target="_blank"; linkElement.title = resultBadges; linkElement.className = 'namuHotBtnStyle whyHot'; linkElement.setAttribute('data-v-userxcript', ''); linkElement.style.margin = "auto 5px"; element.style.width = "70%"; var beforePseudoElement = window.getComputedStyle(element, ':before'); element.parentNode.insertBefore(newParent, element); newParent.appendChild(element); newParent.appendChild(linkElement); newParent.style.display = 'flex'; linkElements.push(linkElement); }else{ element.parentNode.insertBefore(newParent, element); newParent.appendChild(element); element.style.width = "100%"; newParent.style.display = 'flex'; } }); checkMobileHotkeword(); } } } function isPC() { if ((window.innerWidth || document.documentElement.clientWidth) >= 1024) { return true; } else { return false; } } function appendStyle() { var style = document.createElement('style'); var css = ` ${[...Array(10)].map((_, i) => ` .namuHotParentClass:nth-of-type(${i + 1}) > a:nth-child(1):before { content: "${i + 1}." !important; }`).join('')} .whyHot { align-items: center; border: 1px solid transparent; border-radius: var(--nav-bar-child-radius-var); display: flex; padding: var(--search-box-suggest-item-gutter-y-var) var(--search-box-suggest-item-gutter-x-var); text-decoration: none; word-break: break-all; } .namuHotBtnStyle[data-v-userxcript] { display: inline-flex; font-size: 0.8rem; justify-content: center; overflow: hidden; padding: 0.2rem 0.4rem; text-decoration: none; transition: background-color 0.1s ease-in, box-shadow 0.1s linear; white-space: nowrap; border-color: #e0e0e0; border-radius: 3px; height: 1.6rem; min-width: 2.4rem; color: black; } .namuHotBtnStyle[data-v-userxcript]:hover, .namuHotBtnStyle[data-v-userxcript]:active { background-color: #f2f2f2; } .namuHotBtnStyle[data-v-userxcript]:focus-visible { --focus-outline-color: var(--brand-bright-color-2, #e3e3e3); box-shadow: 0 0 0 0.2rem var(--focus-outline-color); } .namuHotBtnStyle[data-v-userxcript] svg { height: 0.8rem; fill: currentColor; } .theseed-dark-mode .namuHotBtnStyle[data-v-userxcript] { background-color: #282829; border-color: #484848; color: var(--dark-text-color, var(--text-color, #e0e0e0)); } .theseed-dark-mode .namuHotBtnStyle[data-v-userxcript]:hover { background-color: #555; } .theseed-dark-mode .namuHotBtnStyle[data-v-userxcript]:active { background-color: #515151; } .theseed-dark-mode .namuHotBtnStyle[data-v-userxcript]:focus-visible { --focus-outline-color: #4e4e4e; } `; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } function checkPopularSearchText() { const itemTitles = document.querySelectorAll('.item-title'); for (let title of itemTitles) { if (title.textContent.trim() === "인기검색어") { return true; } } return false; } (async () => { 'use strict'; if (window.location.href.includes('namu.wiki')) { appendStyle(); if(isPC()){ setInterval(function() { var content = getSpansContent(); if (content.length > 0 && previousSpansContent !== getSpansContent()) { previousSpansContent = getSpansContent(); removeLinkElements(); refreshLink(0); } }, 100); }else{ var interNamuMobile = setInterval(function() { if (checkMobileHotkewordOpened()) { clearInterval(interNamuMobile); refreshLink(2); } }, 50); } } if (/arca.live\/b\/namuhotnow\/[0-9]+/.test(window.location.host + window.location.pathname)) { const spanElement = document.querySelector('span.badge.badge-success.category-badge'); var isNotice = false var isStreaming = false if (spanElement) { const textContent = spanElement.textContent.trim(); if (textContent.includes("공지")) { isNotice = true; } if (textContent.includes("인방")) { isStreaming = true; } } if(isStreaming && await GM.getValue('streamingCommentDisplay', true)){ const commentForm = document.getElementById('commentForm'); if (commentForm) { commentForm.style.display = 'none'; const toggleButton = document.createElement('button'); toggleButton.textContent = '❗️인방탭 댓글쓰기❗️'; toggleButton.style.marginBottom = '1.75em'; toggleButton.style.marginRight = '.75em'; toggleButton.style.float = 'right'; toggleButton.className = 'btn btn-arca btn-arca-article-write'; toggleButton.addEventListener('click', function() { toggleButton.style.display = 'none'; commentForm.style.display = 'block'; }); commentForm.parentNode.insertBefore(toggleButton, commentForm); const replyLinks = document.querySelectorAll('.reply-link'); replyLinks.forEach(link => { link.innerHTML = ` 답글(인방)`; const icon = link.querySelector('.ion-reply'); icon.style.color = '#F9312E'; icon.style.fill = '#F9312E'; }); } } if(!isNotice){ const titleElement = document.querySelector('.title-row > .title'); const titleOriginalText = titleElement.lastChild.data.trim(); var pattern = /.+\)\s.+/; var prefix = ""; var suffix = titleOriginalText; if (pattern.test(titleOriginalText)) { pattern = /^(.+)\)\s(.+)$/; const match = titleOriginalText.match(pattern); prefix = match[1]+") "; // "괄호부분) " suffix = match[2]; // "실검 키워드" } titleElement.removeChild(titleElement.lastChild); titleElement.appendChild(document.createTextNode("\n")); titleElement.appendChild(document.createTextNode(prefix)); suffix.split(', ').forEach((title, idx, array) => { var linkElement = document.createElement('a'); linkElement.href = 'https://namu.wiki/w/' + encodeURIComponent(title); linkElement.textContent = title; linkElement.target="_blank" const element = document.querySelector('.containe-fluid.board-article'); if (element) { const bgColor = window.getComputedStyle(element).backgroundColor; const rgbValues = bgColor.match(/\d+/g); if (rgbValues && rgbValues.length >= 3) { const allAbove200 = rgbValues.slice(0, 3).every(value => Number(value) > 200); if (allAbove200){ linkElement.style.color = '#144c75'; // 진한 남색 }else{ linkElement.style.color = '#a8cfed'; // 연한 하늘색 } } else { console.log('RGB 값을 확인할 수 없습니다.'); } } else { console.log('해당 클래스를 가진 요소를 찾을 수 없습니다.'); } linkElement.style.cursor = 'pointer'; titleElement.appendChild(linkElement); if (idx + 1 < array.length) { titleElement.appendChild(document.createTextNode(", ")); } }); } } else if (window.location.href.includes('arca.live') && checkPopularSearchText()) { var intervalId = setInterval(function() { var firstLinkLista = document.querySelector('aside .link-list a'); if (firstLinkLista && firstLinkLista.innerHTML !== " ") { clearInterval(intervalId); refreshLink(1); } }, 50); } if(window.location.href.includes('arca.live/b/namuhotnow') && await GM.getValue('rankDisplay', true)){ fetch('https://search.namu.wiki/api/ranking') .then(response => response.json()) .then(data => { const rankings = {}; const emojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '️5️⃣', '6️⃣', '️7️⃣', '️8️⃣', '9️⃣', '🔟']; data.slice(0, 10).forEach((item, index) => { const emoji = emojis[index]; const trimmedItem = item.trim(); rankings[trimmedItem] = emoji; }); // 테스트 항목 추가 // rankings["La"] = "⭐"; // rankings["실검"] = "⭐"; const keywords = Object.keys(rankings).sort((a, b) => b.length - a.length); // ❗️공지가 포함된 .table 요소들 필터링 const filteredTables = Array.from(document.querySelectorAll('.col-title')).filter(table => { const badgesEl = table.querySelector('.badges'); return !badgesEl || !badgesEl.textContent.includes('❗️공지'); }); const titleElements = filteredTables.flatMap(table => Array.from(table.querySelectorAll('.title')) ); titleElements.forEach(element => { let text = element.innerHTML; // DOMParser를 사용하여 HTML 텍스트에서 순수 텍스트만 추출하는 함수 keywords.forEach(keyword => { // title 요소를 임시 div에 복사하여 작업 const tempDiv = document.createElement('div'); tempDiv.innerHTML = text; // innerText로 순수 텍스트 추출 const pureText = tempDiv.innerText; // 정확한 매칭 시도 (대소문자 구분) let keywordIndex = pureText.indexOf(keyword); // 정확한 매칭 실패시 대소문자 구분없이 매칭 시도 if (keywordIndex === -1) { const lowerPureText = pureText.toLowerCase(); const lowerKeyword = keyword.toLowerCase(); keywordIndex = lowerPureText.indexOf(lowerKeyword); } if (keywordIndex !== -1) { // 원본 HTML 구조는 유지한 채로 텍스트 노드만 수정 const textNodes = []; const walker = document.createTreeWalker( tempDiv, // root node NodeFilter.SHOW_TEXT // 텍스트 노드만 선택 ); let node; while (node = walker.nextNode()) { textNodes.push(node); } let currentPosition = 0; for (let textNode of textNodes) { const nodeLength = textNode.textContent.length; if (currentPosition <= keywordIndex && keywordIndex < currentPosition + nodeLength) { const offset = keywordIndex - currentPosition; // span 컨테이너를 사용하여 HTML 요소 생성 const container = document.createElement('span'); container.innerHTML = textNode.textContent.slice(0, offset) + `${rankings[keyword]}` + textNode.textContent.slice(offset + keyword.length); // 기존 텍스트 노드를 새로운 HTML 구조로 교체 while (container.firstChild) { textNode.parentNode.insertBefore(container.firstChild, textNode); } textNode.parentNode.removeChild(textNode); break; } currentPosition += nodeLength; } text = tempDiv.innerHTML; } }); element.innerHTML = text; }); titleElements.forEach(element => { let html = element.innerHTML; // rankings 객체를 순회하면서 이모지에 해당하는 키워드 매칭 Object.entries(rankings).forEach(([keyword, emoji]) => { const regex = new RegExp(emoji, 'g'); html = html.replace(regex, `${emoji}${keyword}`); }); element.innerHTML = html; }); }) .catch(error => console.error('Error:', error)); } if(window.location.href.includes('arca.live/b/namuhotnow') && await GM.getValue('viewWholeTitle', true)){ document.querySelectorAll('.table .title').forEach(function(element) { element.style.overflow = 'visible'; element.style.whiteSpace = 'normal'; element.style.textOverflow = 'clip'; var vrow = element.closest('.vrow.column'); if (vrow) { // vrow 스타일 설정 Object.assign(vrow.style, { height: 'auto', minHeight: 'fit-content', boxSizing: 'border-box' }); // vrow-inner 스타일 설정 const vrowInner = vrow.querySelector('.vrow-inner'); if (vrowInner) { Object.assign(vrowInner.style, { height: 'auto', width: '100%' }); } } }); } if(window.location.href.includes('arca.live/b/namuhotnow/98121715')){ const settingTitle = document.createElement('h4'); settingTitle.textContent = '<스크립트 설정>'; let EMOJI_STORAGE_KEY = 'emojiDisplay'; let RANK_STORAGE_KEY = 'rankDisplay'; let STREAMING_COMMENT_STORAGE_KEY = 'streamingCommentDisplay'; let VIEW_WHOLE_TITLE_STORAGE_KEY = 'viewWholeTitle'; const articleContent = document.querySelector('.article-content'); const isEmojiDisplayed = await GM.getValue(EMOJI_STORAGE_KEY, true); const isRankDisplayed = await GM.getValue(RANK_STORAGE_KEY, true); const isStreamingCommentDisplayed = await GM.getValue(STREAMING_COMMENT_STORAGE_KEY, true); const isWholeTitleDisplayed = await GM.getValue(VIEW_WHOLE_TITLE_STORAGE_KEY, true); // 이모지 표시 체크박스 const emojiCheckbox = document.createElement('input'); emojiCheckbox.type = 'checkbox'; emojiCheckbox.checked = isEmojiDisplayed; const emojiLabel = document.createElement('label'); emojiLabel.textContent = '이모지 표시: '; emojiLabel.appendChild(emojiCheckbox); emojiLabel.style.marginRight = '10px'; // 오른쪽 여백 추가 // 랭킹 표시 체크박스 const rankCheckbox = document.createElement('input'); rankCheckbox.type = 'checkbox'; rankCheckbox.checked = isRankDisplayed; const rankLabel = document.createElement('label'); rankLabel.textContent = '실검챈에서 순위표시: '; rankLabel.appendChild(rankCheckbox); rankLabel.style.marginRight = '10px'; // 오른쪽 여백 추가 // 인방탭 댓글 보호 체크박스 const streamingCheckbox = document.createElement('input'); streamingCheckbox.type = 'checkbox'; streamingCheckbox.checked = isStreamingCommentDisplayed; const streamingLabel = document.createElement('label'); streamingLabel.textContent = '인방탭 댓글 보호: '; streamingLabel.appendChild(streamingCheckbox); streamingLabel.style.marginRight = '10px'; // 오른쪽 여백 추가 // 글제목 줄이지 않기 체크박스 const wholeTitleCheckbox = document.createElement('input'); wholeTitleCheckbox.type = 'checkbox'; wholeTitleCheckbox.checked = isWholeTitleDisplayed; const wholeTitleLabel = document.createElement('label'); wholeTitleLabel.textContent = '글제목 줄이지 않기: '; wholeTitleLabel.appendChild(wholeTitleCheckbox); // 설정 요소들을 DOM에 추가 articleContent.parentNode.insertBefore(settingTitle, articleContent); articleContent.parentNode.insertBefore(emojiLabel, articleContent); articleContent.parentNode.insertBefore(rankLabel, articleContent); articleContent.parentNode.insertBefore(streamingLabel, articleContent); articleContent.parentNode.insertBefore(wholeTitleLabel, articleContent); // 이벤트 리스너 추가 emojiCheckbox.addEventListener('change', async () => { await GM.setValue(EMOJI_STORAGE_KEY, emojiCheckbox.checked); }); rankCheckbox.addEventListener('change', async () => { await GM.setValue(RANK_STORAGE_KEY, rankCheckbox.checked); }); streamingCheckbox.addEventListener('change', async () => { await GM.setValue(STREAMING_COMMENT_STORAGE_KEY, streamingCheckbox.checked); }); wholeTitleCheckbox.addEventListener('change', async () => { await GM.setValue(VIEW_WHOLE_TITLE_STORAGE_KEY, wholeTitleCheckbox.checked); }); } })();