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
});
})();