あにまん広告完全削除

全広告スクリプト削除、画面表示後の<script>挿入ブロック、ページ内広告要素削除

// ==UserScript==
// @name         あにまん広告完全削除
// @version      1.0.1
// @description  全広告スクリプト削除、画面表示後の<script>挿入ブロック、ページ内広告要素削除
// @match        *://bbs.animanch.com/*
// @run-at       document-start
// @grant        none
// @license      MIT
// @namespace https://greasyfork.org/users/1494168
// ==/UserScript==
(function() {

	'use strict';

	// ────────────────────────────────────────────────
	// ホワイトリスト判定:Twitter埋め込み用iframe/script
	// ────────────────────────────────────────────────
	function isTwitterIframe(node) {
		return node.tagName === 'IFRAME' &&
			node.id &&
			node.id.toLowerCase().includes('twitter');
	}

	function isTwitterScript(node) {
		return node.tagName === 'SCRIPT' &&
			node.src &&
			node.src.includes('platform.twitter.com/widgets.js');
	}

	// ────────────────────────────────────────────────
	// RemoveAd 関数:ページ内の広告要素を削除
	// ────────────────────────────────────────────────
	function RemoveAd() {
		if (!window.jQuery) return;
		const $ = window.jQuery;
		// ID/Classが"AD"で始まる要素
		$('[id^="AD"], [class^="AD"]').remove();
		// div.inner
		$('div.inner').remove();
		// div[data-cptid]
		$('div[data-cptid]').remove();
		// #reslist下の不正 iframe
		$('#reslist').find('iframe[id^="gnpbad_"]').closest('li').remove();
	}

	// ────────────────────────────────────────────────
	// 1. document-start:既存広告<script>/<iframe>を即削除
	// ────────────────────────────────────────────────
	const AD_PATTERNS = [
		/adrecover\.com/, /delivery\.adrecover\.com/, /geniee\.jp/,
		/cpt\.geniee\.jp/, /iago\.min\.js/, /pubmatic\.com/,
		/rtbhouse/, /onetag-sys\.com/, /undertone\.com/,
		/rubiconproject\.com/, /criteo\.com/, /doubleclick\.net/,
		/g\.doubleclick\.net/, /googletagmanager\.com/, /prebid-v\d+/,
		/dmp\.im-apps\.net/, /ads\.pubmatic\.com/, /authorizedvault\.com/
	];

	function isAdNode(node) {
		if (!node.tagName) return false;
		// Twitter関連は除外
		if (isTwitterIframe(node) || isTwitterScript(node)) return false;
		const tag = node.tagName.toUpperCase();
		if (tag !== 'SCRIPT' && tag !== 'IFRAME') return false;
		const src = node.src || node.getAttribute('src') || '';
		return AD_PATTERNS.some(rx => rx.test(src));
	}

	// 既存の<script>/<iframe>を即座に削除
	document.querySelectorAll('script, iframe').forEach(el => {
		if (isAdNode(el)) el.remove();
	});

	// 動的挿入の広告ノードも監視して削除
	const adObserver = new MutationObserver(records => {
		for (const rec of records) {
			rec.addedNodes.forEach(node => {
				if (node.nodeType === 1 && isAdNode(node)) {
					node.remove();
				}
			});
		}
		// ページ内広告要素も合わせて削除
		RemoveAd();
	});
	adObserver.observe(document.documentElement, {
		childList: true,
		subtree: true
	});

	// <script>/<iframe> の src 設定自体をブロック
	const origCreate = Document.prototype.createElement;
	Document.prototype.createElement = function(tagName, options) {
		const el = origCreate.call(this, tagName, options);
		const tn = tagName.toLowerCase();
		if (tn === 'script' || tn === 'iframe') {
			const desc = Object.getOwnPropertyDescriptor(el.__proto__, 'src');
			Object.defineProperty(el, 'src', {
				get: desc.get,
				set(value) {
					// Twitter関連スクリプトは許可、それ以外の広告URLを阻止
					if (!isTwitterIframe(el) && !isTwitterScript(el) && AD_PATTERNS.some(
							rx => rx.test(value))) {
						console.debug('Blocked ad src:', value);
						return;
					}
					return desc.set.call(this, value);
				},
				configurable: true,
				enumerable: true
			});
		}
		return el;
	};

	// appendChild/insertBefore も広告ノードは阻止
	[Element.prototype, Node.prototype].forEach(proto => {
		['appendChild', 'insertBefore'].forEach(fnName => {
			const orig = proto[fnName];
			proto[fnName] = function(node, ref) {
				if (node.nodeType === 1 && isAdNode(node)) {
					console.debug(`Blocked ad node on ${fnName}`, node);
					return node;
				}
				return orig.call(this, node, ref);
			};
		});
	});

	// ────────────────────────────────────────────────
	// 2. document-end:動的<script>挿入をブロック&RemoveAd実行
	// ────────────────────────────────────────────────
	function onDocumentEnd() {
		// ページ内広告要素を一度削除
		RemoveAd();

		// 動的に追加される<script>を監視して削除
		const obs = new MutationObserver(records => {
			for (const rec of records) {
				rec.addedNodes.forEach(node => {
					if (node.nodeType !== 1) return;
					// Twitter関連スクリプトはそのまま、その他<script>は削除
					if (node.tagName === 'SCRIPT' && !isTwitterScript(node)) {
						node.remove();
					}
					// 子孫に<script>を含む場合はまとめて削除
					else if (node.getElementsByTagName('script').length) {
						node.remove();
					}
				});
			}
			// 同時に広告要素も削除
			RemoveAd();
		});
		obs.observe(document.documentElement, {
			childList: true,
			subtree: true
		});

		// innerHTML/insertAdjacentHTML/document.write系での<script>混入も除去
		const sanitize = html => String(html).replace(/<script[\s\S]*?<\/script>/gi,
			'');
		const htmlDesc = Object.getOwnPropertyDescriptor(Element.prototype,
			'innerHTML');
		Object.defineProperty(Element.prototype, 'innerHTML', {
			set(value) {
					return htmlDesc.set.call(this, sanitize(value));
				},
				get() {
					return htmlDesc.get.call(this);
				},
				configurable: true,
				enumerable: true
		});
		const origIAH = Element.prototype.insertAdjacentHTML;
		Element.prototype.insertAdjacentHTML = function(pos, html) {
			return origIAH.call(this, pos, sanitize(html));
		};
		['write', 'writeln'].forEach(fn => {
			const orig = Document.prototype[fn];
			Document.prototype[fn] = function(...args) {
				return orig.call(this, args.map(a => sanitize(a)).join(''));
			};
		});

        // ボタンを正常な位置へ
        const btn = document.getElementById('fixbtn');
        if (btn)
        {
            btn.style.setProperty('bottom', '20px', 'important');
        }
	}

	if (document.readyState === 'loading') {
		window.addEventListener('DOMContentLoaded', onDocumentEnd);
	} else {
		onDocumentEnd();
	}
})();