您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button that shows the completion time for the "Main Story" and links to HowLongToBeat.
// ==UserScript== // @name Steam - Intigrate HowLongToBeat // @namespace Threeskimo // @author Threeskimo // @version 1.26 // @description Adds a button that shows the completion time for the "Main Story" and links to HowLongToBeat. // @icon https://store.steampowered.com/favicon.ico // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js // @match https://store.steampowered.com/app/* // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== // Changelog ////////////////////////////////////////////////////////////////////////////////// // 1.25 : - More robust api calls // 1.17 : - Fixed button hyperlink not working. // 1.16 : - Updated and improved search query parameters. Added some additional error handling. // 1.15 : - Added some trickery to detect api changes. Let's see if this holds. // 1.12 : - api call updated (again), as predicted. // 1.11 : - api call updated. Call looks temporary so might break again. // 1.10 : - Updated `htbUrl` to include `/locate` as well as updated `hltbQuery` request as it requires additional data to perform query // 1.09 : - A few missed versions, but Fixed mobile loading (again). Should work now. // 1.06 : - `pageTotal` no longer is null when no game is found, it returns 0 instead. Updated script to handle this change. // 1.05 : - Updated to show "--" instead of "HLTB" on button when the GameName is found, but no data exists. (Will still show "HLTB" on the button if the game is not found at all.) // 1.04 : - Added mobile integration (with loader). // 1.03 : - Updated script to pull more accurate data by verifying game_name when possible. // 1.02 : - HLTB updated to JSON responses. Updated script to account for this change. // 1.01 : - Added "origin" and "referer" to GM_xmlhttpRequest headers as HLTB requires now. // 1.0 : - Release. /////////////////////////////////////////////////////////////////////////////////////////////// (async function() { //Setup loaders for mobile and desktop $("#appHeaderGridContainer").append('<style type="text/css">@keyframes ldio-www0qkokjy{0%,25%{transform:translate(6px,0) scale(0)}50%{transform:translate(6px,0) scale(1)}75%{transform:translate(40px,0) scale(1)}100%{transform:translate(74px,0) scale(1)}}@keyframes ldio-www0qkokjy-r{0%{transform:translate(74px,0) scale(1) :}100%{transform:translate(74px,0) scale(0)}}@keyframes ldio-www0qkokjy-c{0%,100%{background:#0051a2}25%{background:#89bff8}50%{background:#408ee0}75%{background:#1b75be}}.ldio-www0qkokjy div{position:absolute;width:20px;height:20px;border-radius:50%;transform:translate(40px,0) scale(1);background:#0051a2;animation:2s cubic-bezier(0,.5,.5,1) infinite ldio-www0qkokjy;box-sizing:content-box}.ldio-www0qkokjy div:first-child{background:#1b75be;transform:translate(74px,0) scale(1);animation:.5s cubic-bezier(0,.5,.5,1) infinite ldio-www0qkokjy-r,2s step-start infinite ldio-www0qkokjy-c}.ldio-www0qkokjy div:nth-child(2){animation-delay:-.5s;background:#0051a2}.ldio-www0qkokjy div:nth-child(3){animation-delay:-1s;background:#1b75be}.ldio-www0qkokjy div:nth-child(4){animation-delay:-1.5s;background:#408ee0}.ldio-www0qkokjy div:nth-child(5){animation-delay:-2s;background:#89bff8}.loadingio-spinner-ellipsis-xiqce8pxsmm{width:44px;height:10px;display:inline-block;overflow:hidden;background:0 0}.ldio-www0qkokjy{width:100%;height:100%;position:relative;transform:translateZ(0) scale(.44);backface-visibility:hidden;transform-origin:0 0}</style><div class="grid_label grid_date">HLTB</div><div class="grid_content"> <div class="loadingio-spinner-ellipsis-xiqce8pxsmm"><div class="ldio-www0qkokjy"><div></div><div></div><div></div><div></div><div></div></div></div> </div>'); $(".apphub_OtherSiteInfo").prepend('<style type="text/css">@keyframes ldio-www0qkokjy{0%,25%{transform:translate(6px,0) scale(0)}50%{transform:translate(6px,0) scale(1)}75%{transform:translate(40px,0) scale(1)}100%{transform:translate(74px,0) scale(1)}}@keyframes ldio-www0qkokjy-r{0%{transform:translate(74px,0) scale(1) :}100%{transform:translate(74px,0) scale(0)}}@keyframes ldio-www0qkokjy-c{0%,100%{background:#0051a2}25%{background:#89bff8}50%{background:#408ee0}75%{background:#1b75be}}.ldio-www0qkokjy div{position:absolute;width:20px;height:20px;border-radius:50%;transform:translate(40px,0) scale(1);background:#0051a2;animation:2s cubic-bezier(0,.5,.5,1) infinite ldio-www0qkokjy;box-sizing:content-box}.ldio-www0qkokjy div:first-child{background:#1b75be;transform:translate(74px,0) scale(1);animation:.5s cubic-bezier(0,.5,.5,1) infinite ldio-www0qkokjy-r,2s step-start infinite ldio-www0qkokjy-c}.ldio-www0qkokjy div:nth-child(2){animation-delay:-.5s;background:#0051a2}.ldio-www0qkokjy div:nth-child(3){animation-delay:-1s;background:#1b75be}.ldio-www0qkokjy div:nth-child(4){animation-delay:-1.5s;background:#408ee0}.ldio-www0qkokjy div:nth-child(5){animation-delay:-2s;background:#89bff8}.loadingio-spinner-ellipsis-xiqce8pxsmm{width:44px;height:10px;display:inline-block;overflow:hidden;background:0 0}.ldio-www0qkokjy{width:100%;height:100%;position:relative;transform:translateZ(0) scale(.44);backface-visibility:hidden;transform-origin:0 0}</style> <div id="loader" style="float:left;padding-right:5px;padding-top:10px;"> <div class="loadingio-spinner-ellipsis-xiqce8pxsmm"><div class="ldio-www0qkokjy"><div></div><div></div><div></div><div></div><div></div></div></div> </div>'); // Grab Steam game name (and normalize weird letters, get rid of symbols, lowercase everything, and split up words) let appNameString = document.getElementsByClassName("apphub_AppName")[0].textContent.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace("’","'").replace(/[^a-z _0-9`~!@#$%^&*()_=+|\\\]}[{;:',<.>/?]/gi,'') let appName = appNameString.toLowerCase().split(/\s+/).map(word => `"${word}"`).join(","); // Define URLs let fetchUrl = 'https://umadb.ro/hltb/fetch.php'; let hltbUrl = "https://howlongtobeat.com"; // Fetch Key try { const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: fetchUrl, onload: resolve, onerror: reject }); }); if (response.status === 200) { const key = response.responseText.trim(); console.log('%c[HLTB-Path/Key]:%c ' + key, 'color:#ff8c00; font-weight:bold;', ''); hltbUrl = "https://howlongtobeat.com" + key; } else { throw new Error('Failed to fetch key. Status: ' + response.status); } } catch (error) { console.log('%c[HLTB-Key]:%c Request failed. Proceeding without key. This might fail. Error:', 'color:#ff8c00; font-weight:bold;', '', error.message); } // Set POST data with correct query parameters and game name let hltbQuery = '{"searchType":"games","searchTerms":['+appName+'],"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}'; // Perform HLTB search request GM_xmlhttpRequest({ method: "POST", url: hltbUrl, data: hltbQuery, headers: { "Content-Type": "application/json", "origin": "https://howlongtobeat.com", "referer": "https://howlongtobeat.com/" }, onload: function (response) { // Grab response //console.log(response.responseText); // Check if the response contains a 404 error in the title and update output if (response.responseText.includes("<title>HowLongToBeat - 404</title>")) { console.log( '%c[HLTB-Response]:%c 404 Not Found', 'color:#ff8c00; font-weight:bold;', ''); $('#loader').remove(); $(".apphub_OtherSiteInfo").prepend(' <div class="apphub_OtherSiteInfo" style="float:left;padding-right:5px;"><a class="btnv6_blue_hoverfade btn_medium" href="https://howlongtobeat.com/?q='+appNameString+'" target="_blank" style="background-color:#222222"><span style="color:white;">404</span></a></div>'); return; } let hltb = JSON.parse(response.responseText); //Determine if data is present in response by checking the page count. If no data, set to default HLTB button. let hltbPages = hltb['count']; if(hltbPages == 0) { hltbTime = "HLTB"; bgcolor = "ff8c00"; console.log( '%c[HLTB-Response]:%c <empty>', 'color:#ff8c00; font-weight:bold;', ''); } else { //If data is present in response, let's rock! let hltbstring = JSON.stringify(hltb); //Show response in console for debugging purposes (or comment to hide) //console.log( '%c[HLTB-Response]:%c ' + hltbstring, 'color:#ff8c00; font-weight:bold;', ''); //Make sure you have the right game_name (if possible, otherwise just use first result from response) let n = 0; let loop = hltb['count']; for (let i = 0; i < loop; i++) { let hltbName = hltb['data'][i]['game_name']; if (hltbName.toLowerCase() == appNameString.toLowerCase()) { n = i; break; } } console.log( '%c[HLTB-GameName]:%c ' + hltb["data"][n]["game_name"], 'color:#ff8c00; font-weight:bold;', ''); // Extract time for "Main Story" and convert into hours hltbTime = hltb['data'][n]['comp_main']; hltbTime = hltbTime/60/60; // Convert to hours hltbTime = Math.round(hltbTime*2)/2; // Round to closes .5 hltbTime = hltbTime.toString() .replace(".5","½"); // Convert .5 to ½ to be consistent with HLTB console.log( '%c[HLTB-MainTime]:%c ' + hltbTime, 'color:#ff8c00; font-weight:bold;', ''); // Extract the Confidence level hltbConfidence = hltb['data'][n]['comp_main_count']; console.log( '%c[HLTB-Confidence]:%c ' + hltbConfidence, 'color:#ff8c00; font-weight:bold;', ''); // If game exists but no time was returned ("--"), set button accordingly if (!hltbTime) { hltbTime = "HLTB"; bgcolor = "ff8c00"; } else if (hltbTime == 0) { hltbTime = "--"; bgcolor = "222222"; } else { // Append "Hour(s)" to the end of the time if (hltbTime == 1 ) { hltbTime = hltbTime + " Hour"; } else { hltbTime = hltbTime + " Hours"; } // Determine what color to make button (based on HLTB confidence level). These might not reflect how HLTB calculates their confidence, this is just a guess. if (hltbConfidence < 5 ) { bgcolor = "FF3A3A"; } else if (hltbConfidence < 10) { bgcolor = "cc3b51"; } else if (hltbConfidence < 15) { bgcolor = "824985"; } else if (hltbConfidence < 20) { bgcolor = "5650a1"; } else if (hltbConfidence < 25) { bgcolor = "485cab"; } else if (hltbConfidence < 30) { bgcolor = "3a6db5"; } else if (hltbConfidence >= 30) { bgcolor = "287FC2"; } else { bgcolor = "" } } } //Display HLTB button next to "Community Hub" button and remove loader $('#loader').remove(); $(".apphub_OtherSiteInfo").prepend(' <div class="apphub_OtherSiteInfo" style="float:left;padding-right:5px;"><a class="btnv6_blue_hoverfade btn_medium" href="https://howlongtobeat.com/?q='+appNameString+'" target="_blank" style="background-color:#'+bgcolor+';"><span style="color:white;">'+hltbTime+'</span></a></div>'); //Or if on mobile (<500), display under the "Release Date" if ($(window).width() < 500 ) { $("div:contains('HLTB')").next("div.grid_content").html('<a class="btnv6_blue_hoverfade btn_small" href="https://howlongtobeat.com/?q=' + appNameString + '" target="_blank" style="background-color:#' + bgcolor + ';"><span style="color:white;">' + hltbTime + '</span></a>'); } }, onerror: function (error) { // General network or request errors console.log( '%c[HLTB-Error]:%c Network Error.', 'color:#ff8c00; font-weight:bold;', ''); $('#loader').remove(); $(".apphub_OtherSiteInfo").prepend(' <div class="apphub_OtherSiteInfo" style="float:left;padding-right:5px;"><a class="btnv6_blue_hoverfade btn_medium" href="https://howlongtobeat.com/?q='+appNameString+'" target="_blank" style="background-color:#222222"><span style="color:white;">Error</span></a></div>'); }, ontimeout: function () { // Handle request timeout console.log( '%c[HLTB-Error]:%c Request timed out.', 'color:#ff8c00; font-weight:bold;', ''); $('#loader').remove(); $(".apphub_OtherSiteInfo").prepend(' <div class="apphub_OtherSiteInfo" style="float:left;padding-right:5px;"><a class="btnv6_blue_hoverfade btn_medium" href="https://howlongtobeat.com/?q='+appNameString+'" target="_blank" style="background-color:#222222"><span style="color:white;">Error</span></a></div>'); }, timeout: 10000 // Timeout after 10 seconds }); })();