提供对 Kimochi Gaming 的界面汉化、搜索标签汉化辅助以及一些小功能
// ==UserScript==
// @name Kimochi Gaming Tools
// @namespace http://tampermonkey.net/
// @version 2.0.1
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=kimochi.info
// @description 提供对 Kimochi Gaming 的界面汉化、搜索标签汉化辅助以及一些小功能
// @author RONUKUIGO
// @match https://kimochi.info/*
// @grant none
// @run-at document-start
// @require https://update.greasyfork.org/scripts/582521/1850372/Kimochi%20Gaming%20Dictionary.js
// ==/UserScript==
(function () {
'use strict';
let DictLib = null;
let TranslationRules = [];
// ================== 1. 运行时匹配规则引擎 ==================
const RuleEngine = {
lastUrl: '',
activeRules: [],
updateActiveRules() {
const currentUrl = window.location.href;
if (currentUrl !== this.lastUrl) {
this.lastUrl = currentUrl;
this.activeRules = TranslationRules.filter(rule =>
rule.urls.some(regex => regex.test(currentUrl))
);
}
},
translate(originalText, element) {
this.updateActiveRules();
let text = originalText;
for (let rule of this.activeRules) {
if (!rule.dict) continue;
// 检查元素白名单限制
if (rule.selectors && rule.selectors.length > 0) {
const selectorStr = rule.selectors.join(',');
if (!element || !element.closest(selectorStr)) {
continue;
}
}
// 精准匹配
if (rule.dict[text]) return rule.dict[text];
// 长词部分包含替换 (长度限制>3防止短词误伤)
for (let key in rule.dict) {
if (text.includes(key) && key.length > 3) {
text = text.replace(key, rule.dict[key]);
}
}
}
return text;
}
};
// ================== 2. 界面汉化功能 ==================
// 动态匹配替换规则
function dynamicTranslate(text) {
const monthsMap = {
jan: '1月', janua: '1月', feb: '2月', febru: '2月',
mar: '3月', march: '3月', apr: '4月', april: '4月',
may: '5月', jun: '6月', june: '6月', jul: '7月', july: '7月',
aug: '8月', augus: '8月', sep: '9月', sept: '9月', septe: '9月',
oct: '10月', octob: '10月', nov: '11月', novem: '11月', dec: '12月', decem: '12月'
};
const monthsRegexStr = "\\b(Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sept?(?:ember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\\b";
function getMonthChinese(monthStr) {
let key = monthStr.toLowerCase().replace('.', '').substring(0, 4);
if (monthsMap[key]) return monthsMap[key];
key = key.substring(0, 3);
return monthsMap[key] || (monthStr + '月');
}
// 处理带有年份的日期
let dateWithYearRegex = new RegExp(monthsRegexStr + '(?:\\.)?\\s+(\\d{1,2}),?\\s+(\\d{4})', 'i');
if (dateWithYearRegex.test(text)) {
text = text.replace(dateWithYearRegex, (match, m, d, y) => {
return `${y}年${getMonthChinese(m)}${parseInt(d)}日`;
});
}
// 处理不带年份的日期
let dateRegex = new RegExp('(?:[A-Za-z]{3,10},\\s+)?' + monthsRegexStr + '(?:\\.)?\\s+(\\d{1,2})\\b', 'i');
if (dateRegex.test(text)) {
text = text.replace(dateRegex, (match, m, d) => {
if (match.toLowerCase().includes('view') || match.toLowerCase().includes('game')) return match;
return `${getMonthChinese(m)}${parseInt(d)}日`;
});
}
// 浏览量、游戏数量转换
text = text.replace(/(•\s*)([\d.]+K?)\s+VIEWS?\s+TODAY/i, '$1今日 $2 浏览量');
text = text.replace(/(\d+)\s+games?/i, '$1 个游戏');
// 累计浏览量转换
let viewsRegex = /(\d+(?:\.\d+)?K?)\s+views?/i;
if (viewsRegex.test(text) && !text.includes('今日')) {
text = text.replace(viewsRegex, '$1 次浏览');
}
return text;
}
// 获取并缓存页面中需要排除的黑名单文本(H1和H3)
function getHeaderTexts() {
const selector = [
'h1.text-4xl.md\\:text-5xl.lg\\:text-6xl.font-headline.font-black.tracking-tighter.uppercase.leading-\\[0\\.95\\].text-on-surface',
'h3.font-headline.font-bold.text-sm.md\\:text-base.leading-snug.line-clamp-2.text-on-surface.group-hover\\:text-primary.transition-colors'
].join(',');
return new Set(
Array.from(document.querySelectorAll(selector))
.map(el => el.textContent.trim())
.filter(text => text.length > 0)
);
}
// 替换节点文本的核心函数
function translateNode(node, blacklistedTexts) {
if (!node) return;
// 核心过滤逻辑 (包含新增 of searctags)
const skipSelector = [
'h1.text-4xl.md\\:text-5xl.lg\\:text-6xl.font-headline.font-black.tracking-tighter.uppercase.leading-\\[0\\.95\\].text-on-surface',
'h3.font-headline.font-bold.text-sm.md\\:text-base.leading-snug.line-clamp-2.text-on-surface.group-hover\\:text-primary.transition-colors',
'.searctags', // 跳过标签候选词的英文部分
'.search-suggest-panel' // 跳过整个标签面板
].join(',');
const currentElement = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
if (currentElement) {
// 规则 1:匹配跳过列表自身或父级
if (currentElement.matches(skipSelector) || (currentElement.parentElement && currentElement.parentElement.matches(skipSelector))) {
return;
}
// 规则 2:P 标签内容去重跳过
const pElement = currentElement.closest('p');
if (pElement && blacklistedTexts.has(pElement.textContent.trim())) {
return;
}
}
// 1. 处理纯文本节点
if (node.nodeType === Node.TEXT_NODE) {
let text = node.nodeValue.trim();
if (!text) return;
let translated = RuleEngine.translate(text, node.parentElement);
if (translated !== node.nodeValue) {
node.nodeValue = translated;
} else {
let dynamicText = dynamicTranslate(node.nodeValue);
if (dynamicText !== node.nodeValue) {
node.nodeValue = dynamicText;
}
}
}
// 2. 处理带有文本属性的元素节点
else if (node.nodeType === Node.ELEMENT_NODE) {
if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA'].includes(node.nodeName)) return;
if (node.placeholder) {
let trimmedPlaceholder = node.placeholder.trim();
let translatedPlaceholder = RuleEngine.translate(trimmedPlaceholder, node);
if (translatedPlaceholder !== trimmedPlaceholder) {
node.placeholder = translatedPlaceholder;
}
}
for (let child of node.childNodes) {
translateNode(child, blacklistedTexts);
}
}
}
// 全局扫描翻译入口
function translatePage() {
const blacklistedTexts = getHeaderTexts(); // 每次大范围翻译前获取一次,节省算力
translateNode(document.body, blacklistedTexts);
}
// ================== 3. 标签搜索建议功能 ==================
// 核心样式更新
const style = document.createElement('style');
style.innerHTML = `
.custom-target-p {
display: inline-flex !important;
align-items: center !important;
flex-wrap: nowrap !important;
width: 100% !important;
}
/* 标签标题防折叠的样式保护 */
div[data-tax="post_tag"] > div:first-child {
white-space: nowrap !important;
flex-shrink: 0 !important;
}
.search-suggest-panel {
display: inline-flex;
flex-direction: row;
align-items: center;
background-color: #252020;
border-left: 1px solid #d195ff;
border-right: 1px solid #d195ff;
border-top: none;
border-bottom: none;
align-self: stretch !important;
box-sizing: border-box !important;
margin-left: 12px;
width: max-content;
flex-shrink: 1;
min-width: 0;
overflow-x: auto;
overflow-y: hidden;
z-index: 99999;
padding: 0 4px;
box-shadow: 0px 2px 6px rgba(0,0,0,0.4);
color: #e0d0cc;
font-family: sans-serif;
-ms-overflow-style: none;
scrollbar-width: none;
}
.search-suggest-panel::-webkit-scrollbar {
display: none;
}
.search-suggest-item {
padding: 0 8px;
height: 100%;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
border-radius: 4px;
font-size: 13px;
transition: background 0.2s;
}
.search-suggest-item:hover {
background-color: #3d302f;
color: #ffb4ab;
}
/* 独立的 searctags 英文标签容器样式 */
.search-suggest-item .searctags {
font-size: 0.8em;
color: #a08c8a;
background: #1e1b1b;
padding: 1px 4px;
border-radius: 3px;
}
`;
document.head.appendChild(style);
function initDropdownLogic(inputEl, targetContainer) {
if (inputEl.dataset.suggestBound) return;
inputEl.dataset.suggestBound = "true";
const targetP = targetContainer.querySelector('div > div:nth-of-type(2) > p');
if (!targetP) return;
targetP.classList.add('custom-target-p');
const suggestPanel = document.createElement('span');
suggestPanel.className = 'search-suggest-panel';
suggestPanel.style.display = 'none';
targetP.appendChild(suggestPanel);
inputEl.addEventListener('input', function () {
const query = this.value.trim().toLowerCase();
if (!query) {
suggestPanel.style.display = 'none';
return;
}
// 使用专用的 DictLib.dict.tags 进行搜索
const dictTags = (DictLib && DictLib.dict && DictLib.dict.tags) || {};
const matches = Object.entries(dictTags).filter(([eng, chn]) => {
return chn.toLowerCase().includes(query) || eng.toLowerCase().includes(query);
});
if (matches.length > 0) {
suggestPanel.style.display = 'inline-flex';
suggestPanel.innerHTML = '';
matches.forEach(([eng, chn]) => {
const item = document.createElement('div');
item.className = 'search-suggest-item';
item.innerHTML = `<span>${chn}</span><span class="searctags">${eng}</span>`;
item.addEventListener('mousedown', function (e) {
e.preventDefault();
e.stopPropagation();
inputEl.value = eng;
suggestPanel.style.display = 'none';
inputEl.focus();
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
inputEl.dispatchEvent(new Event('change', { bubbles: true }));
});
suggestPanel.appendChild(item);
});
} else {
suggestPanel.style.display = 'none';
}
});
document.addEventListener('click', function (e) {
if (e.target !== inputEl && !suggestPanel.contains(e.target)) {
suggestPanel.style.display = 'none';
}
});
}
function findAndBindInput() {
const targetContainers = document.querySelectorAll('div[data-tax="post_tag"]');
targetContainers.forEach(container => {
const input = container.querySelector('input[type="text"], input[type="search"], input:not([type])');
if (input) {
initDropdownLogic(input, container);
}
});
}
// ================== 4. 自动下载与全局初始化 ==================
// 跳过下载页等待并直接跳转
function checkDownload() {
const downloadBtn = document.getElementById('downloadBtn');
if (downloadBtn && downloadBtn.href) {
const targetUrl = downloadBtn.href.trim();
if (targetUrl.startsWith('http') && targetUrl !== window.location.href) {
// 常见国外云盘特征正则
const isTargetCloud = /drive\.google|mediafire|mega\.nz|mega\.co\.nz|dropbox|pixeldrain|gofile|onedrive|1drv\.ms|yadi\.sk|yandex|workupload|qiwi|krakenfiles|terabox|rapidgator|ddownload/i.test(targetUrl);
const isDownloadPage = window.location.pathname.includes('/download');
if (isTargetCloud || isDownloadPage) {
console.log('已捕获目标链接,立即执行无等待跳转:', targetUrl);
window.location.replace(targetUrl); // 使用 replace 避免历史记录混乱,且跳过跳转页面等待
}
}
}
}
// 统一使用一个 MutationObserver 处理动态元素及按钮 href 变更(优化性能与跳过跳转)
const observer = new MutationObserver((mutations) => {
let hasNewNodes = false;
let hrefUpdated = false;
for (let mutation of mutations) {
if (mutation.addedNodes.length > 0) {
hasNewNodes = true;
}
if (mutation.type === 'attributes' && mutation.attributeName === 'href' && mutation.target.id === 'downloadBtn') {
hrefUpdated = true;
}
}
// 只有在真的添加了新节点时才执行遍历和翻译
if (hasNewNodes) {
const blacklistedTexts = getHeaderTexts();
for (let mutation of mutations) {
if (mutation.addedNodes.length > 0) {
for (let node of mutation.addedNodes) {
translateNode(node, blacklistedTexts);
}
}
}
findAndBindInput(); // 重新检查并绑定输入框
}
if (hasNewNodes || hrefUpdated) {
checkDownload(); // 立即触发下载跳转检查
}
});
// 延时与兜底的初始化函数
function init() {
DictLib = window.GameTranslationLib;
if (!DictLib || !DictLib.dict) {
// 延时重试,防止由于脚本加载顺序造成的竞态问题导致失效
setTimeout(init, 10);
return;
}
const dict = DictLib.dict;
// 如果外置字典为老版本(仅 ui 与 tags),则自动兜底将 ui 作为全局公共字典 common
const commonDict = dict.common || dict.ui || {};
const tagsDict = dict.tags || {};
TranslationRules = [
// 1. 标签专属汉化 (白名单限定,优先级最高)
{
name: "标签专属汉化",
urls: [/.*/],
selectors: ['.searctags', '.search-suggest-panel', 'div[data-tax="post_tag"]', 'a[href*="/tag/"]'],
dict: tagsDict
},
// 2. 各页面特定作用域汉化 (按专有度降序排序,未匹配到的专属分区将自动使用 common 兜底)
{
name: "主页专属汉化",
urls: [/^https?:\/\/kimochi\.info\/?$/, /^https?:\/\/kimochi\.info\/\?/],
selectors: [],
dict: dict.home || commonDict
},
{
name: "趋势页专属汉化",
urls: [/\/trending/],
selectors: [],
dict: dict.trending || commonDict
},
{
name: "探索页专属汉化",
urls: [/\/explore$/],
selectors: [],
dict: dict.explore || commonDict
},
{
name: "子页专属汉化",
urls: [/\/browse/, /\/explore\//],
selectors: [],
dict: dict.browse || commonDict
},
{
name: "下载页专属汉化",
urls: [/\/download/],
selectors: [],
dict: dict.download || commonDict
},
{
name: "搜索页专属汉化",
urls: [/\/search/, /\/query/],
selectors: [],
dict: dict.search || commonDict
},
{
name: "详情页专属汉化",
urls: [/\/game\//],
selectors: [],
dict: dict.detail || commonDict
},
// 3. 全局共享兜底词库 (UI/类别/盘/语言)
{
name: "全局基础UI",
urls: [/.*/],
selectors: [],
dict: commonDict
},
{
name: "类别汉化",
urls: [/.*/],
selectors: [],
dict: dict.categories || commonDict
},
{
name: "盘类型汉化",
urls: [/.*/],
selectors: [],
dict: dict.drives || commonDict
},
{
name: "语言汉化",
urls: [/.*/],
selectors: [],
dict: dict.languages || commonDict
}
];
RuleEngine.updateActiveRules();
const runAll = () => {
translatePage();
findAndBindInput();
checkDownload();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
runAll();
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['href']
});
});
} else {
runAll();
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['href']
});
}
}
window.addEventListener('load', runAll);
}
init();
})();