// ==UserScript== // @name TWPKInfo 廣告清除器 // @namespace http://tampermonkey.net/ // @version 1.1.15 // @description 移除頂部導覽列、底部功能列與空白列,保留地圖工具 // @author AdBlocker // @match https://twpkinfo.com/* // @match https://*.twpkinfo.com/* // @grant none // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; console.log('TWPKInfo 廣告清除器啟動'); let cleanupStarted = false; let tooltipTimer = null; // 判斷元素是否屬於 Leaflet 地圖/彈窗系統(清理時必須跳過) function isLeafletDescendant(element) { if (!element || !element.closest) { return false; } return Boolean( element.closest('#map') || element.closest('.leaflet-container') || element.closest('.leaflet-pane') || element.closest('.leaflet-popup') || element.closest('.leaflet-tooltip') || element.closest('.leaflet-control') ); } // 清理用 CSS function addCleanupCSS() { if (document.getElementById('twpk-ad-blocker-cleanup')) { return; } const styleRoot = document.head || document.documentElement; if (!styleRoot) { setTimeout(addCleanupCSS, 0); return; } const style = document.createElement('style'); style.id = 'twpk-ad-blocker-cleanup'; style.textContent = ` /* 移除 Google AdSense */ .adsbygoogle, ins.adsbygoogle, [data-ad-client], [data-ad-slot] { display: none !important; } html, body { margin: 0 !important; padding: 0 !important; width: 100% !important; height: 100% !important; min-height: 100vh !important; overflow: hidden !important; background: transparent !important; } /* 移除頂部標題列 */ body > div:first-child:not(#map):not([class*="leaflet"]) { display: none !important; visibility: hidden !important; height: 0 !important; min-height: 0 !important; overflow: hidden !important; } body > div:has(> h1), body > div:has(> h2), body > div:has(> h3), body > div:has(> ins.adsbygoogle) { display: none !important; visibility: hidden !important; height: 0 !important; min-height: 0 !important; overflow: hidden !important; } /* 移除 igym.aspx 的頂部網站導覽列 */ #myheader, #myheader > .navbar, nav.navbar.main-menu, nav.navbar-fixed-top, body > nav.navbar, body > .navbar, body > [class*="navbar"] { display: none !important; visibility: hidden !important; height: 0 !important; min-height: 0 !important; overflow: hidden !important; } /* 移除底部功能列 */ #div_close_cf, body > div[style*="position"][style*="bottom"], body > div[style*="z-index"][style*="bottom"]:not(.leaflet-control-attribution), div[style*="position: fixed; bottom"], div[style*="position: absolute; bottom"], div[style*="bottom: 0"]:not(.leaflet-control-attribution):not([class*="leaflet"]) { display: none !important; } /* 移除特定結構的底部列 */ body > div:last-child:not(#map):not([id*="map"]):not([class*="leaflet"]):not(.leaflet-control-attribution) { display: none !important; } /* 強制隱藏常見的底部導航類名 */ .bottom-nav, .bottom-bar, .footer-nav, .nav-bottom, .bottom-menu, .bottom-toolbar, [class*="bottom"][class*="nav"], [class*="bottom"][class*="bar"], [class*="bottom"][class*="menu"]:not([class*="pokemon"]) { display: none !important; visibility: hidden !important; height: 0 !important; overflow: hidden !important; } /* 確保重要元素可見 */ #map, .leaflet-container, .leaflet-control-attribution, [class*="pokemon"], [class*="sidebar"]:not([class*="bottom"]), [class*="panel"]:not([class*="bottom"]), [class*="menu"]:not([class*="bottom"]) { display: block !important; visibility: visible !important; } /* 保護 Leaflet 彈出視窗(popup)所有內容,避免被廣告清除規則誤殺 */ html body .leaflet-pane, html body .leaflet-pane *, html body .leaflet-popup, html body .leaflet-popup * { visibility: visible !important; opacity: 1 !important; } /* 只強制 popup 內文(rows)的顯示,避免被 display:none 規則蓋掉; 不再覆寫 .leaflet-popup / .leaflet-popup-content-wrapper / tip 容器尺寸, 讓 TWPKInfo 與 Leaflet 自身的排版(含底部 tip 三角)正常運作 */ html body .leaflet-popup-content > *, html body .leaflet-popup-content > * > *, html body .leaflet-popup-content > * > * > *, html body .leaflet-popup-content > * > * > * > * { display: revert !important; height: auto !important; min-height: 0 !important; max-height: none !important; overflow: visible !important; } /* 把 Leaflet popup 預設的白色三角 tip 拿掉,並把 wrapper 改成自己控制留白 */ html body .leaflet-popup-content-wrapper { overflow: hidden !important; border-radius: 12px !important; padding: 14px 4px !important; } /* 內層 content margin 歸零,全部交給 wrapper 的 padding 負責 */ html body .leaflet-popup-content { margin: 0 14px !important; } html body .leaflet-popup-tip-container, html body .leaflet-popup-tip, html body [class*="leaflet-popup-tip"] { display: none !important; } #map, .leaflet-container { top: 0 !important; width: 100vw !important; height: 100vh !important; min-height: 100vh !important; } [data-twpk-tooltip] { position: relative !important; } [data-twpk-tooltip]::after { content: attr(data-twpk-tooltip); position: absolute; top: 50%; left: calc(100% + 10px); transform: translateY(-50%); z-index: 2147483647; display: block; max-width: 220px; padding: 6px 9px; border-radius: 4px; background: rgba(20, 20, 20, 0.9); color: #fff; font-size: 13px; font-weight: 500; line-height: 1.35; letter-spacing: 0; text-align: left; white-space: nowrap; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.24); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 120ms ease, visibility 120ms ease; } [data-twpk-tooltip]::before { content: ""; position: absolute; top: 50%; left: calc(100% + 4px); transform: translateY(-50%); z-index: 2147483647; border: 6px solid transparent; border-right-color: rgba(20, 20, 20, 0.9); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 120ms ease, visibility 120ms ease; } [data-twpk-tooltip]:hover::after, [data-twpk-tooltip]:hover::before, [data-twpk-tooltip]:focus::after, [data-twpk-tooltip]:focus::before, [data-twpk-tooltip]:focus-within::after, [data-twpk-tooltip]:focus-within::before { opacity: 1; visibility: visible; } [data-twpk-tooltip-side="left"]::after { right: calc(100% + 10px); left: auto; } [data-twpk-tooltip-side="left"]::before { right: calc(100% + 4px); left: auto; border-right-color: transparent; border-left-color: rgba(20, 20, 20, 0.9); } `; styleRoot.appendChild(style); console.log('清理 CSS 規則已應用'); } // 讓移除底部列後的 Leaflet 地圖重新吃滿視窗 function resizeMapToViewport() { const mapElement = document.getElementById('map'); if (mapElement) { mapElement.style.width = '100vw'; mapElement.style.height = '100vh'; mapElement.style.minHeight = '100vh'; } window.requestAnimationFrame(() => { window.dispatchEvent(new Event('resize')); if (window.map && typeof window.map.invalidateSize === 'function') { window.map.invalidateSize(false); } }); } function setToolbarTooltip(element, label) { if (!element || element.closest('#map .leaflet-pane')) { return; } const labeledParent = element.parentElement && element.parentElement.closest('[data-twpk-tooltip]'); if (!labeledParent || labeledParent === element) { element.setAttribute('data-twpk-tooltip', label); } element.setAttribute('title', label); element.setAttribute('aria-label', label); updateTooltipSide(element); } function updateTooltipSide(element) { const rect = element.getBoundingClientRect(); const label = element.getAttribute('data-twpk-tooltip') || element.getAttribute('title') || ''; const estimatedWidth = Math.min(Math.max(label.length * 14 + 18, 90), 220); if (rect.right + estimatedWidth + 18 > window.innerWidth) { element.setAttribute('data-twpk-tooltip-side', 'left'); } else { element.removeAttribute('data-twpk-tooltip-side'); } } function applyToolbarTooltips() { const directTooltips = [ ['#update', '重新整理地圖資料'], ['#filter_btn', '開啟/關閉篩選面板'], ['#filter_bGYM', '切換道館/補給站顯示'], ['#gym', '切換到道館地圖'], ['#ipoke', '切換到寶可夢地圖'], ['#iquest', '切換到任務地圖'], ['#generation', '切換寶可夢世代顯示'], ['#a_iquest', '切換到任務地圖'], ['#quest_img', '切換到任務地圖'], ['.leaflet-control a[href*="iquest"]', '切換到任務地圖'], ['.leaflet-control a[href*="igym"]', '切換到道館地圖'], ['.leaflet-control a[href*="ipoke"]', '切換到寶可夢地圖'], ['.leaflet-control-locate a', '定位到我的位置'], ['.leaflet-control a[title*="Location"]', '定位到我的位置'], ['.leaflet-control a[title*="位置"]', '定位到我的位置'], ['.leaflet-control-zoom-in', '放大地圖'], ['.leaflet-control-zoom-out', '縮小地圖'] ]; directTooltips.forEach(([selector, label]) => { document.querySelectorAll(selector).forEach(element => { setToolbarTooltip(element, label); }); }); const imageTooltips = [ ['img[src*="GYMgo"]', '切換到道館地圖'], ['img[src*="ipoke"]', '切換到寶可夢地圖'], ['img[src*="quest"]', '切換到任務地圖'], ['img[src*="Quest"]', '切換到任務地圖'], ['img[src*="iquest"]', '切換到任務地圖'], ['img[src*="target"]', '定位/移到目前位置'], ['img[src*="radar"]', '定位/移到目前位置'], ['img[src*="locat"]', '定位/移到目前位置'], ['img[src*="gps"]', '定位/移到目前位置'], ['img[src*="blue"]', '定位/移到目前位置'], ['img[src*="eye_"]', '切換道館/補給站顯示'] ]; imageTooltips.forEach(([selector, label]) => { document.querySelectorAll(selector).forEach(image => { const target = image.closest('a, button, div') || image; setToolbarTooltip(target, label); image.setAttribute('alt', label); }); }); } function scheduleToolbarTooltips(delay = 100) { window.clearTimeout(tooltipTimer); tooltipTimer = window.setTimeout(applyToolbarTooltips, delay); } // 移除底部功能列 function cleanupBottomBar() { let count = 0; // 移除廣告 document.querySelectorAll('.adsbygoogle, ins.adsbygoogle, [data-ad-client], [data-ad-slot]').forEach(ad => { ad.remove(); count++; }); // 移除頂部標題 document.querySelectorAll('h1, h2, h3').forEach(title => { if (title.textContent.includes('台灣寶可夢資訊站') || title.textContent.includes('TWPKInfo')) { const container = title.closest('div'); if (container && container !== document.getElementById('map') && !container.classList.contains('leaflet-container')) { container.remove(); count++; } } }); // 清理底部列 const potentialBottomBars = []; // 策略1:通過位置識別 document.querySelectorAll('div').forEach(div => { if (div.id === 'map' || div.classList.contains('leaflet-container') || div.classList.contains('leaflet-control-attribution') || isLeafletDescendant(div)) { return; } const rect = div.getBoundingClientRect(); const style = window.getComputedStyle(div); // 識別底部固定元素 if ((style.position === 'fixed' || style.position === 'absolute') && (style.bottom === '0px' || parseInt(style.bottom) <= 30) && rect.width > 200 && // 有一定寬度 rect.height > 10 && rect.height < 150) { // 適中高度 potentialBottomBars.push(div); } // 識別位於底部的大寬度元素 if (rect.bottom >= window.innerHeight - 80 && rect.width > window.innerWidth * 0.4 && rect.height < 100 && rect.left <= 100) { // 不是右側小元素 potentialBottomBars.push(div); } }); // 策略2:通過內容識別 document.querySelectorAll('div').forEach(div => { if (isLeafletDescendant(div)) { return; } const classList = (typeof div.className === 'string' ? div.className : '').toLowerCase(); if (classList.includes('bottom') && (classList.includes('nav') || classList.includes('bar') || classList.includes('menu') || classList.includes('toolbar')) && !classList.includes('pokemon') && !classList.includes('leaflet')) { potentialBottomBars.push(div); } }); // 策略3:DOM 結構識別(最後一個子元素) const bodyChildren = Array.from(document.body.children); bodyChildren.reverse().slice(0, 3).forEach(child => { // 檢查最後3個子元素 if (child.id !== 'map' && !child.classList.contains('leaflet-container') && !child.classList.contains('leaflet-control-attribution')) { const rect = child.getBoundingClientRect(); if (rect.width > 200 && rect.height < 120 && rect.height > 20) { potentialBottomBars.push(child); } } }); // 執行移除(去重) const uniqueElements = [...new Set(potentialBottomBars)]; uniqueElements.forEach(element => { // 最後確認:確保不是重要元素 if (!element.querySelector('[class*="pokemon"]') && !element.querySelector('img[src*="pokemon"]') && !element.textContent.includes('Leaflet') && !element.textContent.includes('MapTiler') && !element.textContent.includes('OpenStreetMap')) { element.remove(); count++; } }); if (count > 0) { console.log(`已清理 ${count} 個元素`); } resizeMapToViewport(); return count; } // 移除 igym.aspx 這類頁面的頂部網站導覽列 function cleanupTopNavigation() { let count = 0; const navTexts = ['首頁', '官方消息', '關於寶可夢', '寵物圖鑑', '寵物身高體重最大最小參考值', '對戰排行榜']; const candidates = document.querySelectorAll('body > nav, body > header, body > div, body > ul'); candidates.forEach(element => { if (element.id === 'map' || element.classList.contains('leaflet-container') || isLeafletDescendant(element)) { return; } const rect = element.getBoundingClientRect(); const text = element.textContent.replace(/\s+/g, ''); const navTextMatches = navTexts.filter(item => text.includes(item)).length; const className = element.className.toString().toLowerCase(); const looksLikeTopBar = rect.top <= 80 && rect.width > window.innerWidth * 0.5 && rect.height > 20 && rect.height < 120; const looksLikeSiteNav = navTextMatches >= 2 || className.includes('navbar') || className.includes('nav-bar') || className.includes('navigation'); if (looksLikeTopBar && looksLikeSiteNav) { element.remove(); count++; } }); if (count > 0) { console.log(`已清理 ${count} 個頂部導覽列`); } resizeMapToViewport(); return count; } // 持續清理監控 function startCleanupMonitor() { const observer = new MutationObserver((mutations) => { let needsCleanup = false; let hasElementChanges = false; mutations.forEach((mutation) => { // 略過 Leaflet popup / pane 內部的變動,避免誤判清理 if (mutation.target && mutation.target.nodeType === 1 && isLeafletDescendant(mutation.target)) { return; } mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // 元素節點 if (isLeafletDescendant(node)) { return; } hasElementChanges = true; const rect = node.getBoundingClientRect(); const style = window.getComputedStyle(node); // 檢測新增的底部元素 if ((style.position === 'fixed' || style.position === 'absolute') && (style.bottom === '0px' || parseInt(style.bottom) <= 50) && rect.width > 100) { needsCleanup = true; } if (rect.top <= 80 && rect.width > window.innerWidth * 0.5 && rect.height > 20 && rect.height < 120) { const text = node.textContent.replace(/\s+/g, ''); if (text.includes('首頁') && text.includes('官方消息')) { needsCleanup = true; } } if (node.matches('div') && (node.querySelector('h1, h2, h3, .adsbygoogle, ins.adsbygoogle') || node.textContent.includes('台灣寶可夢資訊站') || node.textContent.includes('TWPKInfo'))) { needsCleanup = true; } } }); }); if (hasElementChanges) { scheduleToolbarTooltips(); } if (needsCleanup) { console.log('偵測到新列元素,準備清理...'); setTimeout(cleanupTopNavigation, 200); setTimeout(cleanupBottomBar, 200); } }); observer.observe(document.body, { childList: true, subtree: true }); console.log('清理監控已啟動'); } // 阻止廣告腳本 function blockAdScripts() { if (!window.adsbygoogle) { window.adsbygoogle = []; } window.adsbygoogle.push = () => console.log('AdSense 已阻止'); } // 檢查清理效果 function checkCleanupEffect() { setTimeout(() => { const map = document.querySelector('#map, .leaflet-container'); const pokemonElements = document.querySelectorAll('[src*="pokemon"], [class*="pokemon"]'); const leafletAttribution = document.querySelector('.leaflet-control-attribution'); // 檢查是否還有底部列存在 const remainingBottomElements = []; document.querySelectorAll('div').forEach(div => { if (div.id === 'map' || div.classList.contains('leaflet-container') || isLeafletDescendant(div)) { return; } const rect = div.getBoundingClientRect(); const style = window.getComputedStyle(div); if (((style.position === 'fixed' || style.position === 'absolute') && (style.bottom === '0px' || parseInt(style.bottom) <= 30)) || (rect.bottom >= window.innerHeight - 50 && rect.width > 200)) { if (!div.classList.contains('leaflet-control-attribution') && !div.closest('.leaflet-control-attribution')) { remainingBottomElements.push(div); } } }); console.log('清理效果檢查:'); console.log('- 地圖:', map ? '正常' : '缺失'); console.log('- 寶可夢元素:', pokemonElements.length > 0 ? `${pokemonElements.length} 個` : '缺失'); console.log('- Leaflet 版權:', leafletAttribution ? '保留' : '缺失'); console.log('- 剩餘底部元素:', remainingBottomElements.length, remainingBottomElements.length > 0 ? '仍有底部列' : '底部乾淨'); if (remainingBottomElements.length > 0) { console.log('發現剩餘底部元素,再次清理...'); setTimeout(cleanupBottomBar, 1000); } else { console.log('清理任務完成'); } }, 3000); } // 主要初始化 function init() { blockAdScripts(); addCleanupCSS(); startCleanupWhenReady(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startCleanupWhenReady, { once: true }); } else { startCleanupWhenReady(); } } function startCleanupWhenReady() { if (cleanupStarted) { return; } if (!document.body) { setTimeout(startCleanupWhenReady, 50); return; } cleanupStarted = true; performCleanup(); } // 執行清理 function performCleanup() { console.log('開始清理任務'); resizeMapToViewport(); applyToolbarTooltips(); cleanupTopNavigation(); cleanupBottomBar(); // 多次清理提高穩定性 setTimeout(() => applyToolbarTooltips(), 300); setTimeout(() => cleanupTopNavigation(), 500); setTimeout(() => cleanupBottomBar(), 1000); setTimeout(() => applyToolbarTooltips(), 1500); setTimeout(() => cleanupTopNavigation(), 2000); setTimeout(() => cleanupBottomBar(), 2500); setTimeout(() => cleanupBottomBar(), 4000); setTimeout(() => applyToolbarTooltips(), 4500); // 啟動持續監控 startCleanupMonitor(); // 檢查清理效果 checkCleanupEffect(); // 定期檢查 setInterval(() => { let hasBottomBars = false; document.querySelectorAll('div').forEach(div => { if (div.id === 'map' || div.classList.contains('leaflet-container') || isLeafletDescendant(div)) { return; } const rect = div.getBoundingClientRect(); const style = window.getComputedStyle(div); if (((style.position === 'fixed' || style.position === 'absolute') && (style.bottom === '0px' || parseInt(style.bottom) <= 30)) || (rect.bottom >= window.innerHeight - 50 && rect.width > 200)) { if (!div.classList.contains('leaflet-control-attribution')) { hasBottomBars = true; } } }); if (hasBottomBars) { console.log('定期檢查:發現底部元素'); cleanupBottomBar(); } }, 8000); } console.log('初始化 TWPKInfo 廣告清除器'); init(); })();