Skip to content

Instantly share code, notes, and snippets.

@kyohsuke
Last active July 5, 2026 06:42
Show Gist options
  • Select an option

  • Save kyohsuke/5a1915b10c2ccd12b781a85b4a2f0f0a to your computer and use it in GitHub Desktop.

Select an option

Save kyohsuke/5a1915b10c2ccd12b781a85b4a2f0f0a to your computer and use it in GitHub Desktop.
X Reply Filter
// ==UserScript==
// @name X Reply Filter
// @version 1.1.0
// @description Filter replies on X
// @match https://x.com/*
// @match https://twitter.com/*
// @homepageURL https://gist.github.com/kyohsuke/5a1915b10c2ccd12b781a85b4a2f0f0a
// @updateURL https://raw.github.com/gist/kyohsuke/5a1915b10c2ccd12b781a85b4a2f0f0a/raw/twrefresh.user.js
// @downloadURL https://raw.github.com/gist/kyohsuke/5a1915b10c2ccd12b781a85b4a2f0f0a/raw/twrefresh.user.js
// @icon https://help.twitter.com/content/dam/help-twitter/brand/logo.png
// @run-at document-start
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
// 二重インストール防止
if (window.__X_REPLY_FILTER_INSTALLED__) {
return;
}
window.__X_REPLY_FILTER_INSTALLED__ = true;
const CONFIG = {
KEEP_FOLLOWING: GM_getValue("KEEP_FOLLOWING", true),
FILTER_WORD: GM_getValue("FILTER_WORD", true),
FILTER_WORD_LIST: GM_getValue("FILTER_WORD_LIST", "").split(",").filter(Boolean),
FILTER_BLUE: GM_getValue("FILTER_BLUE", true),
MIN_FOLLOWER_GAP: GM_getValue("MIN_FOLLOWER_GAP", 1000),
DEBUG_MODE: GM_getValue("DEBUG_MODE", false),
};
GM_registerMenuCommand(`フォロー中を常に表示: ${CONFIG.KEEP_FOLLOWING ? "ON" : "OFF"}`, () => {
GM_setValue("KEEP_FOLLOWING", !CONFIG.KEEP_FOLLOWING);
const toggleTo = !CONFIG.KEEP_FOLLOWING ? "ON" : "OFF";
alert(`${toggleTo} に変更しました。\nページを再読み込みします。`);
location.reload();
});
GM_registerMenuCommand(`特定ワードでフィルタ: ${CONFIG.FILTER_WORD ? "ON" : "OFF"}`, () => {
GM_setValue("FILTER_WORD", !CONFIG.FILTER_WORD);
const toggleTo = !CONFIG.FILTER_WORD ? "ON" : "OFF";
alert(`${toggleTo} に変更しました。\nページを再読み込みします。`);
location.reload();
});
GM_registerMenuCommand(`フィルタワード (現在${CONFIG.FILTER_WORD_LIST.length}件登録)`, () => {
const value = prompt("フィルタワード (カンマ区切り)", CONFIG.FILTER_WORD_LIST);
if (value === null) return;
GM_setValue("FILTER_WORD_LIST", value);
alert("保存しました。\nページを再読み込みします。");
location.reload();
});
GM_registerMenuCommand(`Blueをフォロワー差でフィルタ: ${CONFIG.FILTER_BLUE ? "ON" : "OFF"}`, () => {
GM_setValue("FILTER_BLUE", !CONFIG.FILTER_BLUE);
const toggleTo = !CONFIG.FILTER_BLUE ? "ON" : "OFF";
alert(`${toggleTo} に変更しました。\nページを再読み込みします。`);
location.reload();
});
GM_registerMenuCommand(`フォロワー差 (${CONFIG.MIN_FOLLOWER_GAP})`, () => {
const prevGap = CONFIG.MIN_FOLLOWER_GAP;
const value = prompt("フォロワー数 - フォロー数", prevGap);
if (value === null) return;
const num = Number(value);
if (!Number.isFinite(num) || num < 0) {
alert("数値を入力してください");
return;
}
if (prevGap == num) return;
GM_setValue("MIN_FOLLOWER_GAP", num);
alert(`${prevGap} -> ${num}に変更しました。\nページを再読み込みします。`);
location.reload();
});
GM_registerMenuCommand(`デバッグモード: ${CONFIG.DEBUG_MODE ? "ON" : "OFF"}`, () => {
GM_setValue("DEBUG_MODE", !CONFIG.DEBUG_MODE);
const toggleTo = !CONFIG.DEBUG_MODE ? "ON" : "OFF";
alert(`${toggleTo} に変更しました。\nページを再読み込みします。`);
location.reload();
});
GM_registerMenuCommand("設定を表示", () => {
alert(JSON.stringify(CONFIG, null, 2));
});
GM_registerMenuCommand("設定を初期化", () => {
GM_setValue("KEEP_FOLLOWING", true);
GM_setValue("FILTER_WORD", true);
GM_setValue("FILTER_WORD_LIST", "web3,crypto");
GM_setValue("FILTER_BLUE", true);
GM_setValue("MIN_FOLLOWER_GAP", 1000);
GM_setValue("DEBUG_MODE", false);
location.reload();
});
const pageCode = `
(() => {
if (window.__X_REPLY_FILTER_PAGE__) return;
window.__X_REPLY_FILTER_PAGE__ = true;
const CONFIG = ${JSON.stringify(CONFIG)};
const logger = CONFIG.DEBUG_MODE ? console : new Proxy({}, {
get() {
return () => {};
}
});
function cleanupTweetInstructions(instructions) {
for (const instruction of instructions) {
if (instruction.type !== "TimelineAddEntries") {
continue;
}
instruction.entries = instruction.entries.filter(entry => {
try {
if(entry.content?.entryType !== "TimelineTimelineModule") {
return true;
}
const userResult = entry.content?.items?.[0]?.item?.itemContent?.tweet_results?.result?.core?.user_results?.result;
if(!userResult) {
return true;
}
logger.groupCollapsed("ReplyFilterEntryCheck");
logger.debug("UserResult:", userResult);
const isFollowing = userResult.relationship_perspectives?.following;
logger.debug("UserName:", userResult.core.name, "(", userResult.core.screen_name, ")");
logger.debug("Following:", isFollowing);
// フォロー中なら表示
if(CONFIG.KEEP_FOLLOWING && isFollowing) {
return true;
}
const blueVerified = userResult.is_blue_verified;
logger.debug("BlueVerified:", blueVerified);
// Blue人数差フィルタ
if(CONFIG.FILTER_BLUE && blueVerified) {
const followers = userResult.legacy?.followers_count ?? 0;
const following = userResult.legacy?.friends_count ?? 0;
logger.debug("Followers:", followers);
logger.debug("Following:", following);
logger.debug("Remove by followers filter:", ((followers - following) <= CONFIG.MIN_FOLLOWER_GAP));
// 人数差に満たなければ非表示
if((followers - following) <= CONFIG.MIN_FOLLOWER_GAP) {
return false;
}
}
// ワードフィルタ
if(CONFIG.FILTER_WORD && 0 < CONFIG.FILTER_WORD_LIST.length) {
const currentBio = userResult.profile_bio.description.toLowerCase();
const result = CONFIG.FILTER_WORD_LIST.some(word => {
// Bio に特定文字列が含まれていたら非表示
const checkResult = currentBio.includes(word);
if(checkResult) {
logger.debug("Hit filter word:", word);
}
return checkResult;
});
console.log("Word Check Result:", result);
if(result) {
return false;
}
}
return true;
} finally {
logger.groupEnd();
}
});
}
return instructions;
}
const originalParse = JSON.parse;
JSON.parse = function(text, reviver) {
const obj = originalParse.call(this, text, reviver);
const instructions = obj?.data?.threaded_conversation_with_injections_v2?.instructions;
if (instructions) {
cleanupTweetInstructions(instructions);
logger.debug("[ReplyFilter] TweetDetail");
}
return obj;
};
logger.info("[ReplyFilter] Hook Installed");
})();
`;
const script = document.createElement("script");
script.src = "data:text/javascript;charset=utf-8," + encodeURIComponent(pageCode);
(document.head || document.documentElement).appendChild(script);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment