命运2工具网站 light.gg 的增强脚本,着重于本地化体验改善以及一些其他功能的优化。
// ==UserScript==
// @name light.gg Enhancer
// @name:zh light.gg 功能增强
// @name:zh-CN light.gg 功能增强
// @namespace https://github.com/HirotaZX
// @version 0.3.5
// @description Enhancement script for the Destiny 2 tool website light.gg, focused on localization improvements and optimization of other features.
// @description:zh 命运2工具网站 light.gg 的增强脚本,着重于本地化体验改善以及一些其他功能的优化。
// @description:zh-CN 命运2工具网站 light.gg 的增强脚本,着重于本地化体验改善以及一些其他功能的优化。
// @author HirotaZX
// @match https://www.light.gg/*
// @resource itemList https://light-gg-enhancer.hirotazx.com/item-list.json?v=202406170600
// @grant GM_getResourceText
// ==/UserScript==
(function () {
'use strict';
// append script to page
var script = document.createElement("script");
script.textContent =
"window.itemList = " + GM_getResourceText('itemList') + ";\n\n "
+ "window.lastSearchTime = null;\n\n "
+ makeReviewTabDefault.toString() + "\n\n"
+ transformReviewItems.toString() + "\n\n"
+ appendLocaleSearch.toString() + "\n\n"
+ persistLocale.toString() + "\n\n"
+ "(" + initEnhancer.toString() + ")()\n\n";
document.body.appendChild(script);
// make review tab default
function makeReviewTabDefault() {
var reviewTab = document.getElementById('review-tab');
reviewTab && reviewTab.click();
}
// transform item words in review to chs and tooltip trigger
function transformReviewItems(url, xhr) {
var itemElms = document.querySelectorAll('.item.show-hover');
var langStr = window.location.pathname.match(/\/db\/(.*)\/items/);
var lang = langStr ? langStr[1] : 'en';
var reviewUrlRegex = /api.light.gg\/items\/\d*\/reviews/;
if (reviewUrlRegex.test(url)) {
xhr.addEventListener('readystatechange', function (e) {
if (xhr.readyState === 4) {
var originalText = e.target.responseText;
var segmentTree = {
hash: null,
text: originalText,
children: null
};
itemElms.forEach(function (item) {
var key = item.dataset.id;
if (itemList[key] && itemList[key]['en'] && itemList[key]['en'].trim()) {
// match English name and common alias
var matchStr = itemList[key]['en'] + (itemList[key]['alias'] ? ('|' + itemList[key]['alias'].join('|')) : '');
// match hypen and masterwork
var replaceRegex = new RegExp(matchStr.replaceAll('-', '[\\s-_]').replace('Tier 9: ', ''), "ig");
function buildTree(node) {
if (!node.hash) {
var segArr = node.text.split(replaceRegex);
if (segArr.length > 1) {
node.hash = key;
node.children = segArr.map(function (seg) {
return {
hash: null,
text: seg,
children: null
}
});
}
} else if (node.hash != key && node.children) {
node.children.forEach(buildTree);
}
}
buildTree(segmentTree);
}
});
console.log('【light.gg Enhancer】Review segment tree built!', segmentTree);
function traverseTree(node) {
if (node.children) {
var newName = itemList[node.hash][lang] ? itemList[node.hash][lang] : itemList[node.hash]['en'];
return node.children.map(traverseTree).join(
'<span translate=\\"no\\" style=\\"color:dodgerblue;font-weight:bold;\\" '
+ 'class=\\"item show-hover notranslate\\" data-id=\\"' + node.hash + '\\">'
+ newName.replaceAll('"', '\\"')
+ '</span>');
} else {
return node.text;
}
}
var modifiedText = traverseTree(segmentTree);
Object.defineProperty(xhr, 'response', { writable: true });
Object.defineProperty(xhr, 'responseText', { writable: true });
xhr.response = xhr.responseText = modifiedText;
console.log('【light.gg Enhancer】Review transformed!');
}
});
}
}
// append locale item to search results
function appendLocaleSearch(url, xhr) {
var searchUrlRegex = /\/db\/search\/all\?q=([^&]*)/;
var searchUrlStr = url.match(searchUrlRegex);
if (searchUrlStr && searchUrlStr[1]) {
xhr.send = function () {
var searchTime = (new Date()).getTime();
if (lastSearchTime && (searchTime - lastSearchTime < 1000)) {
console.log('【light.gg Enhancer】Abort search!');
xhr.abort();
return;
}
lastSearchTime = searchTime;
var query = decodeURIComponent(searchUrlStr[1]).trim();
if (!query || query.length < 2) {
console.log('【light.gg Enhancer】Abort search!');
xhr.abort();
return;
}
setTimeout(function () {
var modifiedText = '<ul id="site-search-result-list" class="list-unstyled">';
var localeResults = {};
var nonLangProps = ['icon', 'type', 'alias'];
for (const key in itemList) {
for (const property in itemList[key]) {
if (!nonLangProps.includes(property) && itemList[key][property].toLowerCase().includes(query.toLowerCase())) {
if (!localeResults[key]) {
localeResults[key] = [];
}
localeResults[key].push(property);
}
}
}
var localeResultStr = '';
// sort results in weapon, armor, mods order and take first 50
Object.keys(localeResults).sort(function (entryA, entryB) {
return itemList[entryA].type - itemList[entryB].type;
}).slice(0, 50).forEach(function (key) {
var localeNames = '';
localeResults[key].forEach(function (lang) {
localeNames += itemList[key][lang] + ' / ';
});
if (localeNames) {
localeNames = localeNames.slice(0, -3);
}
localeResultStr += '<li class="basic "><a href="/db/items/' + key + '" class="clearfix">'
+ '<div class="icon"><img src="https://www.bungie.net/common/destiny2_content/icons/'
+ itemList[key].icon + '" alt="' + itemList[key]['en'] + '" class="pull-left" /></div>'
+ '<div><div><strong class="text-basic ">' + itemList[key]['en']
+ '</strong></div><div><span>' + localeNames + '</span></div></div></a></li>';
});
modifiedText += localeResultStr;
if (!localeResultStr) {
modifiedText += '<li><a href="javascript:"><div><span><em>No items found</em></span></div></a></li>';
}
modifiedText += '</ul>';
Object.defineProperty(xhr, 'response', { writable: true });
Object.defineProperty(xhr, 'responseText', { writable: true });
Object.defineProperty(xhr, 'status', { writable: true });
xhr.response = xhr.responseText = modifiedText;
xhr.status = 200;
xhr.onload();
console.log('【light.gg Enhancer】Locale search results appended!');
}, 0);
};
}
}
// remember locale
function persistLocale() {
var localeStr = window.location.pathname.match(/\/db\/(.*)\/items|\/db\/items/);
if (!localeStr || !localeStr[0]) {
console.log('【light.gg Enhancer】None item page.');
return;
}
initLocale();
listenLocaleChange();
// redirect to saved locale on enter item page
function initLocale() {
var savedLocale = window.localStorage.getItem("enhancer-locale");
console.log('【light.gg Enhancer】Saved locale: ' + savedLocale);
var curLocale = localeStr[1];
if (curLocale) {
if (savedLocale) {
if (savedLocale != curLocale) {
window.location.replace(window.location.pathname.replace(/\/db\/.*\/items/, '/db/' + savedLocale + '/items'));
console.log('【light.gg Enhancer】Redirect to saved locale: ' + savedLocale);
}
} else {
window.localStorage.setItem("enhancer-locale", curLocale);
console.log('【light.gg Enhancer】Locale ' + curLocale + ' saved!');
}
} else {
if (savedLocale && savedLocale != 'en') {
window.location.replace(window.location.pathname.replace(/\/db\/items/, '/db/' + savedLocale + '/items'));
console.log('【light.gg Enhancer】Redirect to saved locale: ' + savedLocale);
}
}
}
// save locale on click locale icon
function listenLocaleChange() {
var localeLinks = document.querySelectorAll('#localemodal a, #sidebar-locales a');
localeLinks.forEach(function (link) {
link.dataset.href = link.href;
link.href = 'javascript:void(0);';
link.addEventListener('click', handleLocaleClick);
});
function handleLocaleClick(e) {
var elm = e.currentTarget;
if (elm.dataset && elm.dataset.href) {
var elmLocale = 'en';
var elmLocaleStr = elm.dataset.href.match(/\/db\/(.*)\/items|\/db\/items/);
if (elmLocaleStr && elmLocaleStr[1]) {
elmLocale = elmLocaleStr[1];
}
window.localStorage.setItem("enhancer-locale", elmLocale);
console.log('【light.gg Enhancer】Locale ' + elmLocale + ' saved!');
window.location.replace(elm.dataset.href);
}
}
}
}
function initEnhancer() {
makeReviewTabDefault();
persistLocale();
// hijack xhr to modify requests
var realOpen = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function () {
var url = arguments['1'];
transformReviewItems(url, this);
appendLocaleSearch(url, this);
return realOpen.apply(this, arguments);
}
}
})();