NHKニュースで、特定のキーワードを含む記事を非表示にするGreasemonkeyスクリプトです。
ほぼ、ChatGPT に作ってもらいました。
// ==UserScript== // @name NHKニュースフィルター // @namespace nhk-title-filter // @version 1.0 // @include https://www3.nhk.or.jp/news/* // @run-at document-end // @grant none // @noframes // ==/UserScript== (function () { 'use strict'; // キーワードを指定 const keywords = 'keyword1, keyword2'; const keywordsToHide = String(keywords) .split(/[,\u3001\uFF0C]/) .map(s => s.trim()) .filter(Boolean) .map(s => s.normalize('NFKC').toLowerCase()); if (keywordsToHide.length === 0) return; const CARD_SELECTORS = [ 'li', 'article', '.entrylist-contents', '.content--list-item', '.content__item', '.module--list-item', '.module__listItem', '.module-list__item', '.content-list-item', '.news-list-item' ].join(','); const MARK_ITEM = 'data-nhk-filtered'; const MARK_LINK = 'data-nhk-checked'; const norm = (s) => (s || '').normalize('NFKC').toLowerCase().trim(); const getTitleFromNode = (root) => { let text = root.textContent || ''; if (!text.trim() && root.getAttribute) { text = root.getAttribute('aria-label') || root.getAttribute('title') || ''; } if (!text.trim()) { const img = root.querySelector && root.querySelector('img[alt]'); if (img) text = img.getAttribute('alt') || ''; } return norm(text); }; const matches = (title) => !!title && keywordsToHide.some(kw => kw && title.includes(kw)); const findCard = (el) => { if (!el || el.nodeType !== 1) return null; const card = el.closest && el.closest(CARD_SELECTORS); if (card) return card; let p = el; for (let i = 0; i < 5 && p && p !== document.body; i++) { if (p.matches && p.matches(CARD_SELECTORS)) return p; p = p.parentElement; } return null; }; function process(scope = document) { const links = scope.querySelectorAll('a[href*="/news/html/"]'); for (const a of links) { if (a.getAttribute(MARK_LINK) === '1') continue; a.setAttribute(MARK_LINK, '1'); const title = getTitleFromNode(a); if (!matches(title)) continue; const card = findCard(a) || a.parentElement; if (card && card.getAttribute(MARK_ITEM) !== '1') { card.style.display = 'none'; card.setAttribute(MARK_ITEM, '1'); } } const headings = scope.querySelectorAll('h1, h2, h3, .entrylist-contents-title, .content__title, .title, .tit'); for (const h of headings) { if (h.getAttribute(MARK_ITEM) === '1') continue; const title = getTitleFromNode(h); if (!matches(title)) continue; const card = findCard(h); if (card) { card.style.display = 'none'; card.setAttribute(MARK_ITEM, '1'); } } } // initialize process(); const observer = new MutationObserver(muts => { for (const m of muts) { for (const node of m.addedNodes) { if (node && node.nodeType === 1) process(node); } } }); observer.observe(document.body, { childList: true, subtree: true }); })();