您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
添加来自 Backloggd 上的 HowLongToBeat 的完成时间
当前为
// ==UserScript== // @name Backloggd - Integrate HowLongToBeat // @name:zh-CN Backloggd - Integrate HowLongToBeat CN // @namespace https://greasyfork.org/en/users/1410951-nzar-bayezid // @author Nzar Bayezid // @version 1.2 // @description Adds completion times from HowLongToBeat on Backloggd // @description:zh-CN 添加来自 Backloggd 上的 HowLongToBeat 的完成时间 // @icon https://www.backloggd.com/favicon.ico // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js // @match https://backloggd.com/* // @match https://www.backloggd.com/* // @grant GM_xmlhttpRequest // @license MIT // @noframes // ==/UserScript== // /*========================= Version History ================================== changelog v1.1 - Fixed the HLTB button URL to use the processed game title for better search results. */ (function() { 'use strict'; const OBSERVER_CONFIG = { childList: true, subtree: true, attributes: false, characterData: false }; let processing = false; let currentPath = ''; function mainExecutor() { if (processing) return; if (location.pathname === currentPath) return; if (!document.querySelector('#game-body')) return; currentPath = location.pathname; processing = true; cleanExistingElements(); addLoader(); processGameData(); } function cleanExistingElements() { $('#loader, .hltb-container').remove(); } function addLoader() { const target = $("#game-body > div.col > div:nth-child(2) > div.col-12.col-lg-cus-32.mt-1.mt-lg-2"); if (target.length) { target.append('<div id="loader" style="display:inline-block;margin-left:10px;">' + '<div class="loadingio-spinner-ellipsis-xiqce8pxsmm">' + '<div class="ldio-www0qkokjy"><div></div><div></div><div></div><div></div><div></div></div>' + '</div></div>'); } } async function processGameData() { try { const gameName = getNormalizedGameName(); const hltbKey = await fetchHLTBKey(); const hltbData = await fetchHLTBData(gameName, hltbKey); cleanExistingElements(); renderHLTBButton(gameName, hltbData); } catch (error) { console.error('HLTB Error:', error); renderErrorState(getNormalizedGameName()); } finally { processing = false; } } function getNormalizedGameName() { const rawName = document.querySelector("#title h1").textContent; return rawName.normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .replace(/[^a-z _0-9`~!@#$%^&*()-=+|\\\]}[{;:'",<.>/?]/gi, '') .toLowerCase() .split(/\s+/) .map(word => `"${word}"`) .join(","); } async function fetchHLTBKey() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: "https://umadb.ro/hltb/fetch.php", onload: (res) => res.status === 200 ? resolve(res.responseText.trim()) : reject(), onerror: reject }); }); } async function fetchHLTBData(gameName, key) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "POST", url: `https://howlongtobeat.com${key}`, headers: { "Content-Type": "application/json", "Origin": "https://howlongtobeat.com", "Referer": "https://howlongtobeat.com/" }, data: JSON.stringify({ searchType: "games", searchTerms: JSON.parse(`[${gameName}]`), searchPage: 1, size: 20, searchOptions: { games: { userId: 0, platform: "", sortCategory: "popular", rangeCategory: "main", rangeTime: {min: null, max: null}, gameplay: {perspective: "", flow: "", genre: "", difficulty: ""}, rangeYear: {min: "", max: ""}, modifier: "" }, users: {sortCategory: "postcount"}, lists: {sortCategory: "follows"}, filter: "", sort: 0, randomizer: 0 }, useCache: true }), onload: (res) => res.status === 200 ? resolve(JSON.parse(res.responseText)) : reject(), onerror: reject, timeout: 10000 }); }); } function renderHLTBButton(gameName, hltbData) { const target = $("#game-body > div.col > div:nth-child(2) > div.col-12.col-lg-cus-32.mt-1.mt-lg-2"); if (!target.length) return; const originalTitle = document.querySelector("#title h1").textContent; // Use the original, unprocessed title const [time, color] = processHLTBResponse(hltbData); target.append(`<div class="hltb-container" style="margin-top:10px;"> <a class="btnv6_blue_hoverfade btn_medium" href="https://howlongtobeat.com/?q=${encodeURIComponent(originalTitle)}" // Use originalTitle here target="_blank" style="background-color:#${color};border:1px solid #8f9ca7;border-radius:4px;padding:1px 5px;font-size:14.4px;color:#cbd4dc;text-decoration:none;"> <span style="color:white;">${time}</span> </a> </div>`); } function processHLTBResponse(response) { if (!response.count) return ['HLTB', 'ff8c00']; const mainEntry = response.data.find(item => item.game_name.toLowerCase() === document.querySelector("#title h1").textContent.toLowerCase() ) || response.data[0]; if (!mainEntry.comp_main) return ['--', '222222']; const hours = Math.round((mainEntry.comp_main / 3600) * 2) / 2; return [ `${hours} Hour${hours !== 1 ? 's' : ''}`.replace('.5', '½'), getConfidenceColor(mainEntry.comp_main_count) ]; } function getConfidenceColor(confidence) { const colors = { 5: "FF3A3A", 10: "cc3b51", 15: "824985", 20: "5650a1", 25: "485cab", 30: "3a6db5", Infinity: "287FC2" }; return Object.entries(colors).find(([threshold]) => confidence < threshold)[1]; } function renderErrorState(gameName) { const target = $("#game-body > div.col > div:nth-child(2) > div.col-12.col-lg-cus-32.mt-1.mt-lg-2"); if (target.length) { target.append(`<div class="hltb-container" style="margin-top:10px;"> <a class="btnv6_blue_hoverfade btn_medium" href="https://howlongtobeat.com/?q=${encodeURIComponent(gameName)}" target="_blank" style="background-color:#222222;border:1px solid #8f9ca7;border-radius:4px;padding:1px 5px;font-size:14.4px;color:#cbd4dc;text-decoration:none;"> <span style="color:white;">Error</span> </a> </div>`); } } // Observation system new MutationObserver(mutations => { if (!document.body.matches('#game-body') && !mutations.some(m => m.addedNodes.length)) return; mainExecutor(); }).observe(document.documentElement, OBSERVER_CONFIG); // Initial check addEventListener('DOMContentLoaded', mainExecutor); mainExecutor(); })();