// ==UserScript==
// @name VNDB优先原文和中文化
// @namespace http://tampermonkey.net/
// @version 2.0.0
// @description 优先显示原文(title->value),以及中文化(map[value]->value)
// @author aotmd
// @match https://vndb.org/*
// @noframes
// @license MIT
// @run-at document-body
// @grant none
// ==/UserScript==
/*
// @require file://D:/IntelliJ IDEA 2019.3.3/IdeaProjects/自动化小程序/src/其他JS/VNDB优先原文和中文化.js
*/
/*通用主map,作用在全局*/
let map = {
/*https://vndb.org/用户ID/ulist?vnlist=1*/
/*显示选项*/
"display options": "显示选项",
"Order by": "排序方式",
"Results": "显示数量",
"Update": "更新",
"Visible": "显示的",
"columns": "列",
/*排序标记*/
"Title": "标题",
"Vote date": "评分时间",
"Vote": "我的评分",
"Rating": "评分",
"Labels": "标识",
"Added": "添加时间",
"Modified": "修改时间",
"Start date": "开始日期",
"Finish date": "完成日期",
"Release date": "发布日期",
/*带排序标记*/
"Title ▴": "标题 ▴", "Vote date ▴": "评分时间 ▴", "Vote ▴": "我的评分 ▴", "Rating ▴": "评分 ▴", "Labels ▴": "标识 ▴", "Added ▴": "添加时间 ▴", "Modified ▴": "修改时间 ▴", "Start date ▴": "开始日期 ▴", "Finish date ▴": "完成日期 ▴", "Release date ▴": "发布日期 ▴",
/*Opt*/
'Notes': '笔记',
'Remove VN': '删除 VN',
'-- add release --':'--添加版本--',
/*状态*/
"Playing": "在玩",
"Finished": "玩过",
"Stalled": "搁置",
"Dropped": "抛弃",
"Wishlist": "愿望单",
"Blacklist": "黑名单",
/*VNDB封面插件翻译*/
"Always Show the VN Info": "始终显示 VN 信息",
"Show NSFW Covers": "显示 NSFW 封面",
"Disable tooltip": "禁用工具提示",
"Skip Additional Info": "跳过附加信息",
"Async Cover": "异步封面",
"Query Mode": "查询方式",
"Legacy View": "旧版视图",
/*左侧栏*/
/*菜单*/
"Support VNDB": "赞助 VNDB",
"Patreon": "Patreon",
"SubscribeStar": "SubscribeStar",
"Menu": "菜单",
"Home": "首页",
"Visual novels": "视觉小说",
"Tags": "标签",
"Releases": "版本",
"Producers": "制作人",
"Staff": "工作人员",
"Characters": "人物",
"Traits": "特征",
"Users": "用户",
"Recent changes": "最近更改",
"Discussion board": "讨论区",
"FAQ":"常见问题",
"Random visual novel": "随机视觉小说",
"Dumps": "转储",
"API":"API",
"Query": "查询",
"Search": "搜索",
"search": "搜索",
/*我的*/
"My Profile": "我的个人资料",
"My Visual Novel List": "我的视觉小说列表",
"My Votes": "我的评分",
"My Wishlist": "我的愿望单",
"My Notifications": "我的通知",
"My Recent Changes": "我的最近更改",
"My Tags": "我的标签",
"Image Flagging": "图片标记",
"Add Visual Novel": "添加视觉小说",
"Add Producer": "添加制作人",
"Add Staff": "添加工作人员",
"Logout": "退出登录",
/*数据库统计*/
"Database Statistics": "数据库统计",
"Visual Novels": "视觉小说"
};
/*额外map,作用在指定页面*/
let otherMap = [
{
/*作用页说明*/
name: '作用页说明',
/*正则表达式*/
regular: /.+/i,
/*map k->v*/
content: {
}
},
{}
];
/** ---------------------------map处理---------------------------*/
let pathname=window.location.pathname;
otherMap.forEach((item)=>{
//当regular是正则才执行
if (item.regular!==undefined&&item.regular instanceof RegExp){
if (item.regular.test(pathname)){
//添加到主map,若存在重复项则覆盖主mpa
Object.assign(map,item.content);
console.log(item.name+',规则匹配:'+pathname+'->'+item.regular);
}
}
});
/** ----------------------------END----------------------------*/
/**
* 递归节点
* @param el 要处理的节点
* @param func 调用的函数
*/
function 递归(el, func) {
const nodeList = el.childNodes;
for (let i = 0; i < nodeList.length; i++) {
const node = nodeList[i];
if (node.nodeType === 1) {
//为元素则递归
递归(node, func);
let attribute, value, flag = false;
if (node.nodeName === 'INPUT') {
value = node.getAttribute('value');
attribute = 'value';
if(value==null||value.trim().length===0){
value = node.getAttribute('placeholder');
attribute = 'placeholder';
}
flag = true;
} else if (node.nodeName === 'TEXTAREA') {
value = node.getAttribute('placeholder');
attribute = 'placeholder';
flag = true;
}
if (!flag) continue;
func(node, value, attribute);
} else if (node.nodeType === 3) {
//为文本节点则处理数据
func(node, node.nodeValue);
}
}
}
(function () {
/*title->value*/
(function(){
let doc = '';
let 线程锁 = false;
/*立即执行*/
runMap();
window.setInterval(runMap, 100);
function runMap() {
if (doc === document.body.innerHTML) {
// console.log('元素无变化');
return;
}
if (线程锁) {
console.log('线程锁');
return;
}
线程锁 = true;
try {
递归(document.body, 数据处理);
doc = document.body.innerHTML;
} catch (e) {console.error('执行错误')}
线程锁 = false;
}
function 数据处理(node, value, attribute = '3') {
if (value == null || value.trim().length === 0) return;
value = value.trim();
if (attribute==='3'){
let title = node.parentNode.getAttribute("title");
//显示的部分不为中文或日文,并且交换的部分为中文或日文
if (title != null
&&!/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(value)
&& /[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(title)) {
node.parentNode.setAttribute("title", value);
node.nodeValue = title;
console.log(value+'->'+title)
}
}else {
let title = node.getAttribute("title");
//显示的部分不为中文或日文,并且交换的部分为中文或日文
if (title != null
&&!/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(value)
&& /[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(title)) {
//若为通常节点则正常设置属性
node.setAttribute('title', value);
node.setAttribute(attribute, title);
console.log(value+'->'+title)
}
}
}
})();
/*map->value*/
(function(){
let doc = '';
let 线程锁 = false;
/*立即执行*/
runMap();
window.setInterval(runMap, 100);
function runMap() {
if (doc === document.body.innerHTML) {
// console.log('元素无变化');
return;
}
if (线程锁) {console.log('线程锁');return;}
线程锁 = true;
try {
递归(document.body, 数据处理);
doc = document.body.innerHTML;
} catch (e) {console.error('执行错误')}
线程锁 = false;
}
function 数据处理(node, value, attribute = '3') {
if (value == null || value.trim().length === 0) return;
value = value.trim();
if (map[value] !== undefined) {
if (attribute === '3') {
//若为文本节点则追加父节点title属性
let title = node.parentNode.getAttribute('title');
if (title != null&&title.trim()!==value) {
node.parentNode.setAttribute('title', title + ' ' + value);
} else {
node.parentNode.setAttribute('title', value);
}
node.nodeValue = map[value]
} else {
//若为通常节点则正常设置属性
node.setAttribute('title', value);
node.setAttribute(attribute, map[value])
}
}
}
})();
})();
/** 开启后通过控制台调用函数即可*/
let 翻译模式 = false;
if (翻译模式) {
/**
* 导出新的已被翻译的内容到控制台显示
* <br>即手动在网页上改文本,注意:
* <br>先在要翻译的文本中间写入翻译后的内容
* <br>然后用del和backspace删除前后内容
* <br>开启编辑模式:
* <br>document.body.contentEditable='true';
* <br>document.designMode='on';
*/
exportMap = function () {
let addMap = {};
递归(document.body, 数据处理);
/*导出到控制台处理*/
console.log(JSON.stringify(addMap));
function 数据处理(node, value, attribute = '3') {
if (value == null || value.trim().length === 0) return;
value = value.trim();
//没有在map中找到翻译
if (map[value] === undefined) {
//长度介于3~40,是中文、不是日文
if (/[\u4E00-\u9FA5]+/.test(value) &&
!/[ぁ-んァ-ヶ]+/.test(value)) {
if (attribute === '3') {
node = node.parentNode;
}
let title = node.getAttribute('title');
//如果title没有翻译,则记录
if (title !== null && map[title] === undefined) {
addMap[title] = value;
}
}
}
}
};
/*** 记录所有满足条件的未翻译内容<br>因为找不到上下文,所以一般不用*/
noMap = {};
/*** 用以复制value到title<br>若出现新元素,请手动通过控制台重新调用 */
copyToTitle = () => {
//清空
noMap = {};
递归(document.body, 数据处理);
function 数据处理(node, value, attribute = '3') {
if (value == null || value.trim().length === 0) return;
value = value.trim();
//没有在map中找到翻译
if (map[value] === undefined) {
//长度介于3~40,不为中文、日文
if (3 < value.length && value.length < 40
&& !/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(value)) {
//归一化处理
if (attribute === '3') {node = node.parentNode;}
let title = node.getAttribute('title');
//title属性为中文或日文时不执行后续操作
if (title != null && /[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(title)) {
return;
}
//复制value->title
node.setAttribute('title', value);
//设置没有翻译的map标记
noMap[value] = value;
}
}
}
};
/*立即执行*/
copyToTitle();
}