Files
twpkinfo-adblocker/twpkinfo-ad-blocker-sniper.user.js
Timmy d184c6d4f5 Initial commit
Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
2026-04-27 10:46:30 +08:00

656 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ==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();
})();