修复凯恩之角论坛发帖功能中的物品/技能调用工具代码不能正常显示的BUG
// ==UserScript==
// @name 凯恩之角论坛数据库调用修复
// @namespace https://bbs.d.163.com/
// @version 1.0.2
// @description 修复凯恩之角论坛发帖功能中的物品/技能调用工具代码不能正常显示的BUG
// @author diablo_sin
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @match *://bbs.d.163.com/*
// @connect 163.com
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 主配置
const config = {
baseUrl: "https://d.163.com",
defaultLang: "cn",
supportedLangs: ["cn", "en", "tw"],
tooltipPaths: {
skill: "/js/tooltip/skill.js",
item: "/js/tooltip/item.js",
skillrune: "/js/tooltip/skillrune.js",
trainingbook: "/js/tooltip/trainingbook.js"
},
cssUrl: "https://bbs.d.163.com/template/d3/css/tooltips.css"
};
// 工具函数
const utils = {
getScript: function(url, callback, charset) {
const script = document.createElement('script');
script.src = url;
if (charset) script.charset = charset;
script.onload = callback;
document.head.appendChild(script);
},
loadCss: function(url) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
document.head.appendChild(link);
},
getTooltipUrl: function(type, lang) {
return `${config.baseUrl}/${lang}${config.tooltipPaths[type]}`;
},
getTooltipDataUrl: function(type, lang, data) {
const baseurl = `${config.baseUrl}/${lang}/js/tooltip`;
if (type === 'skillrune') {
const [id, variant] = data.split('#');
return `${baseurl}${id}-${variant}.js`;
} else {
const safeData = data.replace(/[#?]/g, '');
return `${baseurl}${safeData}.js`;
}
},
createTooltipElement: function() {
const wrapper = document.createElement('div');
wrapper.className = 'd3-tooltip-wrapper';
wrapper.style.display = 'none';
wrapper.style.position = 'absolute';
wrapper.style.zIndex = '9999';
const inner = document.createElement('div');
inner.className = 'd3-tooltip-wrapper-inner';
wrapper.appendChild(inner);
document.body.appendChild(wrapper);
return {wrapper, inner};
}
};
// 主功能
class D3Tooltip {
constructor() {
this.tooltipData = {};
this.infoLinkMap = {};
this.currentLink = null;
this.tooltipDelay = 500;
this.tooltipTimeout = null;
// 创建tooltip元素
const {wrapper, inner} = utils.createTooltipElement();
this.tooltipWrapper = wrapper;
this.tooltipInner = inner;
// 加载CSS
utils.loadCss(config.cssUrl);
// 初始化事件
this.initEvents();
}
initEvents() {
document.addEventListener('mouseover', (e) => {
const target = e.target;
if (target.classList.contains('diablo3tipfix')) {
this.handleMouseOver(target);
}
});
document.addEventListener('mouseout', (e) => {
if (this.currentLink) {
this.hideTooltip();
}
});
}
handleMouseOver(element) {
this.currentLink = element;
// 如果是span元素,先转换为a标签
if (element.classList.contains('diablo3db')) {
this.convertToLink(element);
return;
}
// 显示加载中状态
this.showLoading();
// 获取数据并显示tooltip
const type = element.getAttribute('dtype');
const lang = element.getAttribute('dlang') || config.defaultLang;
const name = element.getAttribute('name') || element.textContent;
this.requestTooltipData(type, lang, name);
}
convertToLink(spanElement) {
const type = spanElement.getAttribute('dtype');
const lang = spanElement.getAttribute('dlang') || config.defaultLang;
const name = spanElement.textContent;
// 创建新的a标签
const aElement = document.createElement('a');
aElement.className = 'diablo3tipfix';
aElement.setAttribute('dtype', type);
aElement.setAttribute('dlang', lang);
aElement.setAttribute('name', name);
aElement.textContent = name;
// 根据不同类型设置不同的href
if(type==='skillrune'){
aElement.setAttribute('href', 'javascript:;');
}else{
var dtype = type==='trainingbook'?'item':type;
const baseUrl = `https://d.163.com/db/${lang}/49522/searchList/${dtype}?id=${encodeURIComponent(name)}`;
aElement.setAttribute('href', baseUrl);
aElement.setAttribute('target', '_blank');
}
// 替换span为a标签
spanElement.parentNode.replaceChild(aElement, spanElement);
}
requestTooltipData(type, lang, name) {
const cacheKey = `${type}-${lang}-${name}`;
// 检查缓存
if (this.tooltipData[cacheKey]) {
this.showTooltip(this.tooltipData[cacheKey]);
return;
}
// 请求数据
const url = utils.getTooltipUrl(type, lang);
utils.getScript(url, () => {
if (window.d3typedata) {
if(window.d3typedata[name]){
const dataUrl = utils.getTooltipDataUrl(type, lang, window.d3typedata[name]);
utils.getScript(dataUrl, () => {
this.tooltipData[cacheKey] = window.d3_data;
this.showTooltip(window.d3_data);
window.d3_data = null;
}, 'gbk');
} else {
this.showError();
}
} else {
this.showError();
}
}, 'gbk');
}
showTooltip(content) {
clearTimeout(this.tooltipTimeout);
this.tooltipInner.innerHTML = content;
this.positionTooltip();
this.tooltipWrapper.style.display = 'block';
}
showLoading() {
clearTimeout(this.tooltipTimeout);
this.tooltipTimeout = setTimeout(() => {
this.tooltipInner.innerHTML = '<div class="d3-tooltip"><div class="loading"></div></div>';
this.positionTooltip();
this.tooltipWrapper.style.display = 'block';
}, this.tooltipDelay);
}
showError() {
clearTimeout(this.tooltipTimeout);
this.tooltipInner.innerHTML = '<div class="d3-tooltip" style="color:#fff;">无数据...</div>';
this.positionTooltip();
this.tooltipWrapper.style.display = 'block';
}
hideTooltip() {
clearTimeout(this.tooltipTimeout);
this.tooltipWrapper.style.display = 'none';
this.currentLink = null;
}
positionTooltip() {
if (!this.currentLink) return;
const rect = this.currentLink.getBoundingClientRect();
const scrollX = window.scrollX || document.documentElement.scrollLeft;
const scrollY = window.scrollY || document.documentElement.scrollTop;
let left = rect.right + 5;
let top = rect.top + scrollY - this.tooltipWrapper.offsetHeight - 5;
// 检查边界
if (top < scrollY) {
top = scrollY;
}
if (left + this.tooltipWrapper.offsetWidth > window.innerWidth + scrollX) {
left = rect.left - this.tooltipWrapper.offsetWidth - 5;
}
this.tooltipWrapper.style.left = `${left}px`;
this.tooltipWrapper.style.top = `${top}px`;
}
}
// 页面加载完成后初始化
window.addEventListener('DOMContentLoaded', () => {
new D3Tooltip();
// 初始转换所有diablo3db元素
document.querySelectorAll('span.diablo3db').forEach(span => {
const tooltip = new D3Tooltip();
tooltip.convertToLink(span);
});
});
// 如果页面是动态加载内容,添加MutationObserver
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.classList && node.classList.contains('diablo3db')) {
const tooltip = new D3Tooltip();
tooltip.convertToLink(node);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();