d !== CURRENT_DOMAIN); if (RELATED_DOMAINS.length === 0) { return; // 관련 도메인이 없으면 종료 } // 도메인에서 키워드 추출 (예: "flowertea.co.kr" -> ["flower", "tea"]) function extractKeywords(domain) { const domainWithoutTld = domain.replace(/\.(com|kr|co\.kr|net|org)$/i, ''); const keywords = domainWithoutTld .split(/[^a-z0-9가-힣]+/i) .filter(k => k.length >= 2) .map(k => k.toLowerCase()); return keywords; } // 도메인별 키워드 맵 생성 const domainKeywords = {}; RELATED_DOMAINS.forEach(domain => { const keywords = extractKeywords(domain); keywords.forEach(keyword => { if (!domainKeywords[keyword]) { domainKeywords[keyword] = []; } domainKeywords[keyword].push(domain); }); }); // 텍스트 노드에서 키워드를 찾아 백링크로 변환 function convertKeywordsToLinks(node) { if (node.nodeType === Node.TEXT_NODE) { const text = node.textContent; const parent = node.parentNode; // 이미 링크 안에 있거나, 스크립트/스타일 태그 안에 있으면 건너뛰기 if (parent.tagName === 'A' || parent.tagName === 'SCRIPT' || parent.tagName === 'STYLE' || parent.closest('a') || parent.closest('script') || parent.closest('style')) { return; } // 키워드 매칭 (긴 키워드부터 우선 매칭) const sortedKeywords = Object.keys(domainKeywords).sort((a, b) => b.length - a.length); let hasMatch = false; let newHTML = text; sortedKeywords.forEach(keyword => { // 이미 링크로 변환된 부분은 건너뛰기 // 정규식 특수 문자 이스케이프 const escaped = keyword.replace(/[.*+?^$\{}()|[]\]/g, function(match) { return '\\' + match; }); // 템플릿 문자열 안에서 \b는 정규식의 \b (단어 경계)를 의미 const pattern = '(?!]*>)(?)\\b(' + escaped + ')\\b(?![^<]*)'; const regex = new RegExp(pattern, 'gi'); if (regex.test(newHTML)) { hasMatch = true; const domains = domainKeywords[keyword]; const randomDomain = domains[Math.floor(Math.random() * domains.length)]; const linkUrl = '/domains/' + randomDomain; newHTML = newHTML.replace(regex, function(match) { return '' + match + ''; }); } }); if (hasMatch) { const tempDiv = document.createElement('div'); tempDiv.innerHTML = newHTML; // 원본 노드를 새 노드들로 교체 while (tempDiv.firstChild) { parent.insertBefore(tempDiv.firstChild, node); } parent.removeChild(node); } } else if (node.nodeType === Node.ELEMENT_NODE) { // 자식 노드들을 재귀적으로 처리 const children = Array.from(node.childNodes); children.forEach(child => { convertKeywordsToLinks(child); }); } } // 백링크 컨테이너 생성 function createBacklinkContainer() { // 이미 컨테이너가 있으면 생성하지 않음 if (document.getElementById('auto-backlinks-container')) { return; } // 관련 도메인 섹션 생성 const container = document.createElement('div'); container.id = 'auto-backlinks-container'; container.className = 'py-12 px-4 bg-gray-50'; container.style.cssText = 'margin-top: 2rem; border-top: 1px solid #e5e7eb;'; const innerDiv = document.createElement('div'); innerDiv.className = 'max-w-6xl mx-auto'; const title = document.createElement('h2'); title.className = 'text-2xl md:text-3xl font-bold text-gray-900 mb-6 text-center'; title.textContent = '관련 도메인'; const grid = document.createElement('div'); grid.className = 'grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4'; // flashgame.co.kr을 무조건 포함 (현재 도메인이 아닌 경우에만) const REQUIRED_DOMAIN = 'flashgame.co.kr'; const domainsToShow = []; // flashgame.co.kr이 현재 도메인이 아니고, 전체 도메인 목록에 있으면 추가 if (CURRENT_DOMAIN !== REQUIRED_DOMAIN && ALL_DOMAINS.includes(REQUIRED_DOMAIN)) { domainsToShow.push(REQUIRED_DOMAIN); } // 나머지 도메인을 랜덤으로 선택 (flashgame.co.kr 제외) const otherDomains = RELATED_DOMAINS.filter(d => d !== REQUIRED_DOMAIN); const shuffled = otherDomains.sort(() => 0.5 - Math.random()); const remainingSlots = 8 - domainsToShow.length; domainsToShow.push(...shuffled.slice(0, remainingSlots)); domainsToShow.forEach(domain => { const link = document.createElement('a'); link.href = '/domains/' + domain; link.target = '_blank'; link.rel = 'nofollow'; link.className = 'block p-4 bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow text-center'; link.style.cssText = 'text-decoration: none; color: #1f2937;'; const domainText = document.createElement('div'); domainText.className = 'font-semibold text-sm md:text-base'; domainText.textContent = domain; link.appendChild(domainText); grid.appendChild(link); }); innerDiv.appendChild(title); innerDiv.appendChild(grid); container.appendChild(innerDiv); // 페이지 하단에 추가 (footer 전에) const footer = document.querySelector('footer'); if (footer) { footer.parentNode.insertBefore(container, footer); } else { document.body.appendChild(container); } } // 페이지 로드 시 실행 function initBacklinks() { // 텍스트에서 키워드를 백링크로 변환 const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, { acceptNode: function(node) { // 스크립트, 스타일, 링크 안의 텍스트는 제외 if (node.nodeType === Node.ELEMENT_NODE) { const tagName = node.tagName; if (tagName === 'SCRIPT' || tagName === 'STYLE' || tagName === 'A') { return NodeFilter.FILTER_REJECT; } } return NodeFilter.FILTER_ACCEPT; } } ); const textNodes = []; let node; while (node = walker.nextNode()) { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0) { textNodes.push(node); } } // 텍스트 노드를 역순으로 처리 (DOM 변경 시 인덱스 문제 방지) textNodes.reverse().forEach(textNode => { convertKeywordsToLinks(textNode); }); // 백링크 컨테이너 생성 createBacklinkContainer(); } // DOMContentLoaded 이벤트에서 실행 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initBacklinks); } else { initBacklinks(); } // 동적으로 추가되는 콘텐츠도 처리 const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) { convertKeywordsToLinks(node); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })();