Initial commit

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 10:36:53 +08:00
commit f5f2eb2706
6 changed files with 1334 additions and 0 deletions

View File

@@ -0,0 +1,655 @@
// ==UserScript==
// @name TWPKInfo 廣告清除器
// @namespace http://tampermonkey.net/
// @version 1.1.7
// @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;
// 清理用 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;
}
#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') ||
div.closest('#map') ||
div.closest('.leaflet-container')) {
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 => {
const classList = 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);
}
});
// 策略3DOM 結構識別(最後一個子元素)
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') ||
element.closest('#map') ||
element.closest('.leaflet-container')) {
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) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) { // 元素節點
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') ||
div.closest('#map') ||
div.closest('.leaflet-container')) {
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') ||
div.closest('#map') ||
div.closest('.leaflet-container')) {
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();
})();