Greasy Fork is available in English.
自动折叠包含"互fo"、"互粉"、"互关"、"互赞"等关键词的用户推文
// ==UserScript==
// @name Twitter/X 屏蔽互fo/互粉/互关推文
// @namespace https://github.com/Asakushen/Twitter_Mutual_Follow_Blocker
// @version 1.0
// @description 自动折叠包含"互fo"、"互粉"、"互关"、"互赞"等关键词的用户推文
// @author Asakushen
// @match https://twitter.com/*
// @match https://x.com/*
// @icon https://s2.loli.net/2025/11/27/NsGnT9k3HFJeUhv.png
// @grant GM_addStyle
// @run-at document-end
// @license MIT
// @homepageURL https://github.com/Asakushen/Twitter_Mutual_Follow_Blocker
// @supportURL https://github.com/Asakushen/Twitter_Mutual_Follow_Blocker/issues
// ==/UserScript==
(function() {
'use strict';
//Configs: 你可以在这里添加更多你想屏蔽的关键词
const BLOCK_KEYWORDS = [/互(fo|粉|关|赞|推|回)/i, /fo回/i, /诚信互/i];
// CSS样式:定义折叠后的外观
GM_addStyle(`
.my-blocked-tweet {
display: none !important;
}
.my-blocked-placeholder {
padding: 12px 16px;
background-color: rgba(29, 155, 240, 0.1); /* 推特蓝底色 */
border-bottom: 1px solid rgb(56, 68, 77);
color: #71767b;
font-size: 13px;
cursor: pointer;
text-align: center;
border-radius: 4px;
margin: 5px 0;
transition: all 0.2s;
}
.my-blocked-placeholder:hover {
background-color: rgba(29, 155, 240, 0.2);
color: #1d9bf0;
}
/* 适配亮色模式 */
@media (prefers-color-scheme: light) {
.my-blocked-placeholder {
background-color: #f7f9f9;
border-bottom: 1px solid #eff3f4;
}
}
`);
// 核心检测函数
function checkAndBlockTweets() {
// 选取所有的推文容器 (根据你提供的HTML,article元素带有 data-testid="tweet")
const tweets = document.querySelectorAll('article[data-testid="tweet"]:not([data-mutual-checked])');
tweets.forEach(tweet => {
// 标记已检查,避免重复处理
tweet.setAttribute('data-mutual-checked', 'true');
// 1. 查找用户名区域
const userNameNode = tweet.querySelector('[data-testid="User-Name"]');
if (!userNameNode) return;
// 2. 获取所有文本内容(包括昵称、@ID等)
const textContent = userNameNode.innerText;
// 3. 正则匹配
const isMatch = BLOCK_KEYWORDS.some(regex => regex.test(textContent));
if (isMatch) {
// 找到匹配的关键词,执行折叠操作
console.log(`[屏蔽互粉] 折叠了用户: ${textContent.replace(/\n/g, ' ')}`);
foldTweet(tweet, textContent);
}
});
}
// 折叠推文的具体逻辑
function foldTweet(tweetElement, userInfo) {
// 获取第一行文本作为提示(通常是昵称)
const displayName = userInfo.split('\n')[0] || "互关用户";
// 1. 隐藏推文内容的直接子元素(不使用display:none隐藏article本身,因为可能会影响推特的虚拟滚动计算)
const children = Array.from(tweetElement.children);
children.forEach(child => child.classList.add('my-blocked-tweet'));
// 2. 插入占位提示条
const placeholder = document.createElement('div');
placeholder.className = 'my-blocked-placeholder';
placeholder.textContent = `已折叠一条来自 "${displayName}" 的推文 (包含互粉关键词) - 点击查看`;
// 3. 点击事件:恢复显示
placeholder.addEventListener('click', (e) => {
e.stopPropagation(); // 防止触发推特原本的点击事件
placeholder.remove();
children.forEach(child => child.classList.remove('my-blocked-tweet'));
});
tweetElement.appendChild(placeholder);
}
// 使用 MutationObserver 监听页面变化(因为推特是无限滚动页面)
const observer = new MutationObserver((mutations) => {
// 简单的防抖优化,不需要每次微小变动都执行,但推特流很快,直接执行通常也无妨
checkAndBlockTweets();
});
// 开始监听 body
observer.observe(document.body, {
childList: true,
subtree: true
});
// 初始运行一次
checkAndBlockTweets();
})();