Backloggd - 集成Metacritic

在Backloggd上添加Metacritic评分

目前为 2025-02-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Backloggd - Metacritic Integration
  3. // @name:zh-CN Backloggd - 集成Metacritic
  4. // @namespace https://greasyfork.org/en/users/1410951-nzar-bayezid
  5. // @author Nzar Bayezid
  6. // @version 1.0
  7. // @description Adds Metacritic ratings on Backloggd
  8. // @description:zh-CN 在Backloggd上添加Metacritic评分
  9. // @icon https://www.backloggd.com/favicon.ico
  10. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
  11. // @match https://backloggd.com/*
  12. // @match https://www.backloggd.com/*
  13. // @grant GM_xmlhttpRequest
  14. // @connect metacritic.com
  15. // @license MIT
  16. // @noframes
  17. // @downloadURL
  18. // @updateURL
  19. // ==/UserScript==
  20.  
  21. /*========================= Version History ==================================
  22. v1.0 -
  23. - Optimized API requests and error handling
  24. */
  25.  
  26. (function() {
  27. 'use strict';
  28. const OBSERVER_CONFIG = {
  29. childList: true,
  30. subtree: true,
  31. attributes: false,
  32. characterData: false
  33. };
  34.  
  35. let processing = false;
  36. let currentPath = '';
  37.  
  38. function mainExecutor() {
  39. if (processing) return;
  40. if (location.pathname === currentPath) return;
  41. if (!document.querySelector('#game-body')) return;
  42.  
  43. currentPath = location.pathname;
  44. processing = true;
  45.  
  46. cleanExistingElements();
  47. addLoader();
  48. processGameData();
  49. }
  50.  
  51. function cleanExistingElements() {
  52. $('#loader, .integration-container').remove();
  53. }
  54.  
  55. function addLoader() {
  56. const target = $("#game-body > div.col > div:nth-child(2) > div.col-12.col-lg-cus-32.mt-1.mt-lg-2");
  57. if (target.length) {
  58. target.append('<div id="loader" style="display:inline-block;margin-left:10px;">'
  59. + '<div class="loadingio-spinner-ellipsis-xiqce8pxsmm">'
  60. + '<div class="ldio-www0qkokjy"><div></div><div></div><div></div><div></div><div></div></div>'
  61. + '</div></div>');
  62. }
  63. }
  64.  
  65. async function processGameData() {
  66. try {
  67. const gameName = getNormalizedGameName();
  68. const metacriticData = await fetchMetacriticData(gameName);
  69.  
  70. cleanExistingElements();
  71. renderIntegrationSection(metacriticData);
  72. } catch (error) {
  73. console.error('Integration Error:', error);
  74. } finally {
  75. processing = false;
  76. }
  77. }
  78.  
  79. function getNormalizedGameName() {
  80. const rawName = document.querySelector("#title h1").textContent;
  81. return rawName.normalize("NFD")
  82. .replace(/[\u0300-\u036f]/g, "")
  83. .replace(/[^a-z _0-9`~!@#$%^&*()-=+|\\\]}[{;:'",<.>/?]/gi, '')
  84. .toLowerCase();
  85. }
  86.  
  87. // Metacritic Functions
  88. async function fetchMetacriticData(gameName) {
  89. const normalizedTitle = gameName
  90. .replace(/[^a-z0-9 ]/gi, '')
  91. .replace(/\s+/g, '-')
  92. .toLowerCase();
  93.  
  94. return new Promise((resolve) => {
  95. GM_xmlhttpRequest({
  96. method: "GET",
  97. url: `https://www.metacritic.com/game/${normalizedTitle}/`,
  98. headers: {
  99. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
  100. },
  101. onload: function(response) {
  102. const parser = new DOMParser();
  103. const doc = parser.parseFromString(response.responseText, "text/html");
  104.  
  105. const criticRating = doc.querySelector('.c-siteReviewScore_background-critic_medium .c-siteReviewScore span')?.textContent || 'N/A';
  106. const userRating = doc.querySelector('.c-siteReviewScore_background-user .c-siteReviewScore span')?.textContent || 'N/A';
  107.  
  108. resolve({
  109. critic: criticRating,
  110. user: userRating,
  111. url: `https://www.metacritic.com/game/${normalizedTitle}/`
  112. });
  113. },
  114. onerror: () => resolve(null),
  115. timeout: 10000
  116. });
  117. });
  118. }
  119.  
  120. // Rendering
  121. function renderIntegrationSection(metacriticData) {
  122. const target = $("#game-body > div.col > div:nth-child(2) > div.col-12.col-lg-cus-32.mt-1.mt-lg-2");
  123. if (!target.length) return;
  124.  
  125. // Common styling
  126. const containerStyle = "margin-top:10px; margin-bottom:15px;";
  127. const linkStyle = "display:inline-block; text-decoration:none; color:white; "
  128. + "border-radius:4px; padding:8px 12px; border:1px solid #8f9ca7; "
  129. + "font-size:14.4px; line-height:1.5; white-space: normal; "
  130. + "min-height: 54px; display: flex; align-items: center; justify-content: center;";
  131.  
  132. const metacriticTextStyle = "display: flex; flex-direction: column; align-items: center;";
  133.  
  134. // Metacritic Box
  135. if (metacriticData) {
  136. target.append(`
  137. <div class="integration-container" style="${containerStyle}">
  138. <a href="${metacriticData.url}"
  139. target="_blank"
  140. style="${linkStyle} background-color:#16181c;">
  141. <div style="${metacriticTextStyle}">
  142. <div>
  143. <span style="color:#e0e0e0;">Metacritic: </span>
  144. <span style="color:#FFD700;">Critic: ${metacriticData.critic}</span> -
  145. <span style="color:#28c236;">User: ${metacriticData.user}</span>
  146. </div>
  147. </div>
  148. </a>
  149. </div>
  150. `);
  151. }
  152. }
  153.  
  154. // Observation system
  155. new MutationObserver(mutations => {
  156. if (!document.body.matches('#game-body') && !mutations.some(m => m.addedNodes.length)) return;
  157. mainExecutor();
  158. }).observe(document.documentElement, OBSERVER_CONFIG);
  159.  
  160. // Initial check
  161. addEventListener('DOMContentLoaded', mainExecutor);
  162. mainExecutor();
  163. })();