// ==UserScript==
// @name 推特仿生阅读
// @namespace http://tampermonkey.net/
// @version 1.6
// @description 根据单词长度智能加粗推特推文和评论中的词根部分,小于3个字母的单词只加粗第一个字母
// @author yitong233
// @match https://x.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// 常见后缀字典
const suffixes = [
'ly', 'ion', 'ness', 'ed', 'al', 'ive', 'ing', 'ar', 'er', 'ir', 'or', 'ur',
'itude', 'able', 'ible', 'ary', 'ate', 'ess', 'less', 'ship', 'fy', 'ic', 'um',
'us', 'ty', 'ity', 'ant', 'ent', 'end', 'and', 'ance', 'ence', 'ancy', 'ency',
'id', 'te', 'ize', 'ise', 'ous', 'hood', 'icle', 'cle', 'et', 'kin', 'let', 'y',
'ward', 'wise', 'dom', 'craft', 'cracy', 'ice', 'ology', 'graphy', 'ry', 'ment',
'ship', 'fy', 'en', 'ate', 'ish'
];
// 检查单词是否有常见后缀,并返回词根与后缀的分割
function splitRootAndSuffix(word) {
for (let suffix of suffixes) {
if (word.toLowerCase().endsWith(suffix) && word.length > suffix.length + 2) { // 确保词根长度至少为2
let root = word.slice(0, word.length - suffix.length); // 获取词根
return { root, suffix }; // 返回词根和后缀
}
}
return { root: word, suffix: '' }; // 没有常见后缀时,整个单词视作词根
}
// 格式化单词:根据单词长度加粗不同数量的字母
function formatWord(word) {
let { root, suffix } = splitRootAndSuffix(word);
let boldLength;
// 根据单词长度决定加粗字母的数量
if (root.length <= 3) {
boldLength = 1; // 1-3长度的单词加粗第一个字母
} else {
boldLength = Math.min(4, root.length); // 其他单词最多加粗4个字母
}
let boldPart = root.slice(0, boldLength);
let restPart = root.slice(boldLength) + suffix; // 剩余部分加上后缀
return `<b>${boldPart}</b>${restPart}`;
}
// 处理单个文本节点
function processTextNode(textNode) {
const words = textNode.textContent.split(/\s+/);
const formattedWords = words.map(word => formatWord(word)).join(' ');
// 用于将HTML插入到文本节点
const span = document.createElement('span');
span.innerHTML = formattedWords;
// 替换当前的文本节点
textNode.replaceWith(span);
}
// 遍历元素的子节点,处理其中的文本节点
function traverseAndFormatText(node) {
node.childNodes.forEach(child => {
if (child.nodeType === Node.TEXT_NODE && child.textContent.trim().length > 0) {
// 处理文本节点
processTextNode(child);
} else if (child.nodeType === Node.ELEMENT_NODE && child.tagName !== 'SCRIPT' && child.tagName !== 'STYLE') {
// 递归处理子节点
traverseAndFormatText(child);
}
});
}
// 应用到推文和评论的节点
function applyBionicReading(batchSize = 10) {
let tweetContents = document.querySelectorAll('article div[lang]:not([data-bionic-processed])'); // 仅选择未处理的推文
let processedCount = 0;
tweetContents.forEach(content => {
if (processedCount >= batchSize) return; // 一次只处理一定数量的推文
traverseAndFormatText(content); // 处理推文中的文本节点
content.setAttribute('data-bionic-processed', 'true'); // 标记为已处理
processedCount++;
});
}
// 延迟执行以防止瞬间处理过多数据
function debounce(fn, delay) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, delay);
};
}
// 初次运行
applyBionicReading();
// 监听推特页面动态加载的变化,使用节流避免卡顿
let observer = new MutationObserver(debounce(() => {
applyBionicReading(5); // 每次只处理5条推文
}, 500)); // 每500ms检查一次新内容
observer.observe(document.body, { childList: true, subtree: true });
})();