您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
只要推文主体、标签、用户名等任一区块命中关键字,整则推文一起隐藏。支援关键字新增、清单、单独删除。支援快速封锁、清单、单独删除。
当前为
- // ==UserScript==
- // @name Keyword-based Tweet Filtering for Threads
- // @name:zh-TW Threads 關鍵字過濾推文
- // @name:zh-CN Threads 关键字过滤推文
- // @namespace http://tampermonkey.net/
- // @version 3.5
- // @description If any part of a post—such as the main content, hashtags, or username—matches a keyword, the entire post will be hidden. Supports adding keywords, viewing the list, and deleting them individually. Quick block is also supported, along with blocklist viewing and individual removal.
- // @description:zh-TW 只要推文主體、標籤、用戶名等任一區塊命中關鍵字,整則推文一起隱藏。支援關鍵字新增、清單、單獨刪除。支援快速封鎖、清單、單獨刪除。
- // @description:zh-CN 只要推文主体、标签、用户名等任一区块命中关键字,整则推文一起隐藏。支援关键字新增、清单、单独删除。支援快速封锁、清单、单独删除。
- // @author chatgpt
- // @match https://www.threads.net/*
- // @grant GM_registerMenuCommand
- // @grant GM_setValue
- // @grant GM_getValue
- // @license MIT
- // ==/UserScript==
- (function() {
- 'use strict';
- // 關鍵字相關
- function getKeywords() {
- return GM_getValue('keywords', []);
- }
- function setKeywords(keywords) {
- GM_setValue('keywords', keywords);
- }
- // 封鎖用戶相關
- function getBlockedUsers() {
- return GM_getValue('blockedUsers', []);
- }
- function setBlockedUsers(users) {
- GM_setValue('blockedUsers', users);
- }
- // 取得所有推文主容器
- function getAllPostContainers() {
- return document.querySelectorAll('div[data-pressable-container][class*=" "]');
- }
- // 在推文主容器下,找所有可能含有文字的區塊
- function getAllTextBlocks(container) {
- return container.querySelectorAll('span[dir="auto"]:not([translate="no"]), a[role="link"], span, div');
- }
- // 取得用戶名稱(Threads 通常在 a[href^="/@"] 內)
- function getUsername(container) {
- let a = container.querySelector('a[href^="/@"]');
- if (a) {
- let username = a.getAttribute('href').replace('/', '').replace('@', '');
- return username;
- }
- return null;
- }
- // 過濾推文
- function filterPosts() {
- let keywords = getKeywords();
- let blockedUsers = getBlockedUsers();
- let containers = getAllPostContainers();
- containers.forEach(container => {
- let blocks = getAllTextBlocks(container);
- let matched = false;
- // 關鍵字過濾
- blocks.forEach(block => {
- let text = (block.innerText || block.textContent || "").trim();
- if (text && keywords.some(keyword => keyword && text.includes(keyword))) {
- matched = true;
- }
- });
- // 封鎖用戶過濾
- let username = getUsername(container);
- if (username && blockedUsers.includes(username)) {
- matched = true;
- }
- if (matched) {
- container.style.display = 'none';
- } else {
- container.style.display = '';
- }
- });
- }
- // 插入封鎖用戶按鈕(插在「分享」按鈕右邊)
- function insertBlockButtons() {
- let containers = getAllPostContainers();
- let blockedUsers = getBlockedUsers();
- containers.forEach(container => {
- // 避免重複插入
- if (container.querySelector('.tm-block-user-btn')) return;
- let username = getUsername(container);
- if (!username) return;
- // 找到「分享」按鈕
- let shareBtn = container.querySelector('div[role="button"] svg[aria-label="分享"]');
- if (!shareBtn) return;
- let shareBtnDiv = shareBtn.closest('div[role="button"]');
- if (!shareBtnDiv) return;
- // 建立封鎖按鈕
- let blockBtn = document.createElement('button');
- blockBtn.className = 'tm-block-user-btn';
- blockBtn.title = '封鎖用戶';
- blockBtn.style.marginLeft = '8px';
- blockBtn.style.background = 'none';
- blockBtn.style.border = 'none';
- blockBtn.style.cursor = 'pointer';
- blockBtn.style.fontSize = '18px';
- blockBtn.style.color = '#d00';
- blockBtn.innerHTML = '🚫';
- blockBtn.onclick = function(e) {
- e.stopPropagation();
- if (confirm(`確定要封鎖 @${username} 嗎?\n(此用戶所有推文將被隱藏)`)) {
- let users = getBlockedUsers();
- if (!users.includes(username)) {
- users.push(username);
- setBlockedUsers(users);
- alert(`已封鎖 @${username}!`);
- filterPosts();
- }
- }
- };
- // 插入在「分享」按鈕之後
- shareBtnDiv.parentNode.insertBefore(blockBtn, shareBtnDiv.nextSibling);
- });
- }
- // observer 只監控新節點
- const observer = new MutationObserver(mutations => {
- let needFilter = false;
- for (const m of mutations) {
- if (m.addedNodes && m.addedNodes.length > 0) {
- needFilter = true;
- break;
- }
- }
- if (needFilter) {
- filterPosts();
- insertBlockButtons();
- }
- });
- observer.observe(document.body, { childList: true, subtree: true });
- // 初始執行一次
- filterPosts();
- insertBlockButtons();
- // 新增關鍵字
- GM_registerMenuCommand('新增關鍵字', () => {
- let input = prompt('請輸入要新增的關鍵字(可用半形或全形逗號分隔,一次可多個):');
- if (input !== null) {
- let arr = input.split(/,|,/).map(s => s.trim()).filter(Boolean);
- let keywords = getKeywords();
- let newKeywords = [...keywords];
- arr.forEach(k => {
- if (!newKeywords.includes(k)) newKeywords.push(k);
- });
- setKeywords(newKeywords);
- alert('已新增關鍵字!');
- location.reload();
- }
- });
- // 關鍵字清單與單獨刪除
- GM_registerMenuCommand('關鍵字清單/刪除', () => {
- let keywords = getKeywords();
- if (keywords.length === 0) {
- alert('目前沒有設定任何關鍵字。');
- return;
- }
- let msg = '目前關鍵字如下:\n';
- keywords.forEach((k, i) => {
- msg += `${i+1}. ${k}\n`;
- });
- msg += '\n請輸入要刪除的關鍵字編號(可多個,用逗號分隔),或留空取消:';
- let input = prompt(msg, '');
- if (input !== null && input.trim() !== '') {
- let idxArr = input.split(/,|,/).map(s => parseInt(s.trim(), 10) - 1).filter(i => !isNaN(i) && i >= 0 && i < keywords.length);
- if (idxArr.length > 0) {
- let newKeywords = keywords.filter((k, i) => !idxArr.includes(i));
- setKeywords(newKeywords);
- alert('已刪除指定關鍵字!');
- location.reload();
- }
- }
- });
- // 清除所有關鍵字
- GM_registerMenuCommand('清除所有關鍵字', () => {
- setKeywords([]);
- alert('已清除所有關鍵字!');
- location.reload();
- });
- // 封鎖名單管理
- GM_registerMenuCommand('封鎖名單管理', () => {
- let users = getBlockedUsers();
- if (users.length === 0) {
- alert('目前沒有封鎖任何用戶。');
- return;
- }
- let msg = '目前封鎖用戶如下:\n';
- users.forEach((u, i) => {
- msg += `${i+1}. @${u}\n`;
- });
- msg += '\n請輸入要解除封鎖的用戶編號(可多個,用逗號分隔),或留空取消:';
- let input = prompt(msg, '');
- if (input !== null && input.trim() !== '') {
- let idxArr = input.split(/,|,/).map(s => parseInt(s.trim(), 10) - 1).filter(i => !isNaN(i) && i >= 0 && i < users.length);
- if (idxArr.length > 0) {
- let newUsers = users.filter((u, i) => !idxArr.includes(i));
- setBlockedUsers(newUsers);
- alert('已解除指定用戶封鎖!');
- location.reload();
- }
- }
- });
- // 清除所有封鎖用戶
- GM_registerMenuCommand('清除所有封鎖用戶', () => {
- setBlockedUsers([]);
- alert('已清除所有封鎖用戶!');
- location.reload();
- });
- })();