- // ==UserScript==
- // @name X - Optimized Tweet Buttons
- // @name:zh-TW X - 優化推文按鈕
- // @name:zh-CN X - 优化推文按钮
- // @namespace http://tampermonkey.net/
- // @version 5.1
- // @description You can freely show or hide the buttons on a tweet, including Reply, Retweet, Like, View Count, Bookmark, and Share. The interface supports switching between Chinese and English.
- // @description:zh-TW 可以自由顯示/隱藏,推文上的按鈕,包括,回覆、轉推、喜歡、觀看次數、書籤、分享等按鈕,並且有中英兩種功能語言可以切換
- // @description:zh-CN 可以自由显示/隐藏,推文上的按钮,包括,回覆、转推、喜欢、观看次数、书签、分享等按钮,并且有中英两种功能语言可以切换
- // @author chatgpt
- // @match https://twitter.com/*
- // @match https://x.com/*
- // @grant GM_registerMenuCommand
- // @grant GM_getValue
- // @grant GM_setValue
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // === 性能優化核心 ===
- const OPT = {
- debounceTime: 500, // 防抖間隔
- observerConfig: { // 監控配置:設定 subtree 為 true,以確保監控所有新增節點
- childList: true,
- subtree: true,
- attributes: false,
- characterData: false
- }
- };
-
- // === 配置系統 ===
- const CONFIG_KEY = 'XButtonSettings';
- const defaults = {
- hideReply: true,
- hideRetweet: true,
- hideBookmark: true,
- hideViews: true,
- hideShare: true,
- hideLike: false,
- language: 'EN' // 預設英文
- };
-
- const config = {
- get() {
- return { ...defaults, ...GM_getValue(CONFIG_KEY, {}) };
- },
- update(key, value) {
- const current = this.get();
- GM_setValue(CONFIG_KEY, { ...current, [key]: value });
- }
- };
-
- // === 多語言系統 ===
- const i18n = {
- EN: {
- reply: 'Reply',
- retweet: 'Retweet',
- bookmark: 'Bookmark',
- views: 'View count',
- share: 'Share',
- like: 'Like',
- language: 'Language'
- },
- ZH: {
- reply: '回覆',
- retweet: '轉推',
- bookmark: '書籤',
- views: '觀看次數',
- share: '分享',
- like: '喜歡',
- language: '語言'
- }
- };
-
- // 每次調用時根據最新配置返回對應語言字串
- const t = () => {
- const { language } = config.get();
- return i18n[language] || i18n.EN;
- };
-
- // === 樣式管理 ===
- const style = {
- element: null,
- rules: new Map([
- ['hideReply', '[data-testid="reply"] { display: none !important; }'],
- ['hideRetweet', '[data-testid="retweet"] { display: none !important; }'],
- ['hideBookmark', '[data-testid="bookmark"] { display: none !important; }'],
- ['hideViews', 'a[href*="/analytics"] { display: none !important; }'],
- ['hideShare', 'button[aria-label="分享貼文"]:not(:has(svg g.download)) { display: none !important; }'],
- ['hideLike', '[data-testid="like"], [data-testid="unlike"] { display: none !important; }']
- ]),
- init() {
- this.element = document.createElement('style');
- this.element.id = 'x-btn-hider-styles';
- document.head.appendChild(this.element);
- this.update();
- },
- update() {
- // 取得當前配置,並套用生效的 CSS 規則
- const currentConfig = config.get();
- const activeRules = Array.from(this.rules.entries())
- .filter(([key]) => currentConfig[key])
- .map(([, rule]) => rule);
- this.element.textContent = activeRules.join('\n');
- }
- };
-
- // === 選單系統 (帶防抖功能) ===
- const menu = {
- cmds: [],
- build() {
- // 清除舊選單(假如 GM_unregisterMenuCommand 可用)
- menu.cmds.forEach(id => {
- if (typeof GM_unregisterMenuCommand === 'function') {
- GM_unregisterMenuCommand(id);
- }
- });
- menu.cmds = [];
-
- // 使用配置的緩存,避免多次調用 config.get()
- const currentConfig = config.get();
- const items = [
- { key: 'hideReply', label: t().reply },
- { key: 'hideRetweet', label: t().retweet },
- { key: 'hideBookmark', label: t().bookmark },
- { key: 'hideViews', label: t().views },
- { key: 'hideShare', label: t().share },
- { key: 'hideLike', label: t().like }
- ];
-
- items.forEach(({ key, label }) => {
- const status = currentConfig[key] ? '✅' : '❌';
- menu.cmds.push(GM_registerMenuCommand(
- `${label} ${status}`,
- () => {
- // 更新對應設定,然後防抖後重載頁面
- config.update(key, !config.get()[key]);
- debouncedReload();
- }
- ));
- });
-
- // 語言切換
- const langStatus = currentConfig.language === 'EN' ? 'EN' : 'ZH';
- menu.cmds.push(GM_registerMenuCommand(
- `${t().language}: ${langStatus}`,
- () => {
- config.update('language', config.get().language === 'EN' ? 'ZH' : 'EN');
- debouncedReload();
- }
- ));
- }
- };
-
- // === 防抖工具函數 ===
- const debounce = (func, delay) => {
- let timer;
- return (...args) => {
- clearTimeout(timer);
- timer = setTimeout(() => func(...args), delay);
- };
- };
-
- const debouncedReload = debounce(() => location.reload(), 300);
- const debouncedStyleUpdate = debounce(() => style.update(), OPT.debounceTime);
-
- // === 初始化流程 ===
- (function init() {
- // 初始化樣式管理
- style.init();
- // 建立選單
- menu.build();
- // 初始化 MutationObserver,當 DOM 發生新增時更新樣式
- const observer = new MutationObserver(mutations => {
- if (mutations.some(m => m.addedNodes.length > 0)) {
- debouncedStyleUpdate();
- }
- });
- observer.observe(document.body, OPT.observerConfig);
- })();
- })();