ProtonDB SteamPlay Integration

Adds game ratings from ProtonDB to the Steam Store

  1. // ==UserScript==
  2. // @name ProtonDB SteamPlay Integration
  3. // @description Adds game ratings from ProtonDB to the Steam Store
  4. // @version 0.3.0
  5. // @author Phlebiac
  6. // @match https://store.steampowered.com/app/*
  7. // @connect www.protondb.com
  8. // @run-at document-end
  9. // @noframes
  10. // @license MIT; https://opensource.org/licenses/MIT
  11. // @namespace Phlebiac/ProtonDB
  12. // @icon https://www.protondb.com/sites/protondb/images/apple-touch-icon.png
  13. // @grant GM_addStyle
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @grant GM_xmlhttpRequest
  17. // @grant unsafeWindow
  18. // ==/UserScript==
  19.  
  20. /* Inspired by / some ideas and code from:
  21. * https://openuserjs.org/install/DanMan/Steam_Play_Community_Rating_Notice.user.js
  22. * https://raw.githubusercontent.com/guihkx/user-scripts/master/scripts/protondb-integration-for-steam.user.js
  23. */
  24.  
  25. ;(async () => {
  26. 'use strict'
  27.  
  28. const PROTONDB_TIERS = [
  29. 'pending',
  30. 'borked',
  31. 'bronze',
  32. 'silver',
  33. 'gold',
  34. 'platinum'
  35. ]
  36. const PROTONDB_CONFIDENCE_LEVELS = ['low', 'moderate', 'good', 'strong']
  37. const PROTONDB_HOMEPAGE = 'https://www.protondb.com'
  38.  
  39. let userPrefs = {
  40. open_in_new_tab: GM_getValue('open_in_new_tab', false),
  41. skip_native_games: GM_getValue('skip_native_games', true),
  42. show_confidence_level: GM_getValue('show_confidence_level', true)
  43. }
  44.  
  45. const appId = getCurrentAppId();
  46.  
  47. if (!appId) {
  48. return;
  49. }
  50. if (userPrefs.skip_native_games) {
  51. if (document.querySelector('span.platform_img.linux') !== null) {
  52. log('Ignoring native Linux game:', appId);
  53. return;
  54. }
  55. }
  56. injectCSS();
  57.  
  58. GM_xmlhttpRequest({
  59. method: 'GET',
  60. url: `${PROTONDB_HOMEPAGE}/api/v1/reports/summaries/${appId}.json`,
  61. onload: addRatingToStorePage
  62. })
  63.  
  64. function getCurrentAppId() {
  65. const urlPath = window.location.pathname;
  66. const appId = urlPath.match(/\/app\/(\d+)/);
  67.  
  68. if (appId === null) {
  69. log('Unable to get AppId from URL path:', urlPath);
  70. return false;
  71. }
  72. return appId[1];
  73. }
  74.  
  75. function addRatingToStorePage(response) {
  76. let reports = {};
  77. let tier = 'N/A';
  78.  
  79. if (response.status === 200) {
  80. try {
  81. reports = JSON.parse(response.responseText);
  82. tier = reports.tier;
  83. } catch (err) {
  84. log('Unable to parse ProtonDB response as JSON:', response);
  85. log('Javascript error:', err);
  86. tier = 'error';
  87. }
  88. if (!PROTONDB_TIERS.includes(tier)) {
  89. log('Unknown tier:', tier);
  90. tier = 'unknown';
  91. }
  92. } else if (response.status === 404) {
  93. log(`App ${appId} doesn't have a page on ProtonDB yet`);
  94. tier = 'N/A';
  95. } else {
  96. log('Got unexpected HTTP code from ProtonDB:', response.status);
  97. tier = 'error';
  98. }
  99. let confidence, tooltip = '';
  100. if ('confidence' in reports && PROTONDB_CONFIDENCE_LEVELS.includes(reports.confidence)) {
  101. confidence = reports.confidence;
  102. tooltip = `Confidence: ${confidence}`;
  103. if ('total' in reports) {
  104. tooltip += ` with ${reports.total} reports`;
  105. }
  106. tooltip += ' | ';
  107. }
  108. let target = document.querySelector('.game_area_purchase_platform');
  109. if (target) {
  110. let node = Object.assign(document.createElement('span'), {
  111. className: 'protondb_rating_row'
  112. });
  113. node.appendChild(preferencesDialog());
  114. node.appendChild(createBadge(tier, confidence, tooltip, appId));
  115. target.insertBefore(node, target.firstChild);
  116. }
  117. }
  118.  
  119. function createBadge(tier, confidence, tooltip, appId) {
  120. let confidence_style = confidence && userPrefs.show_confidence_level ?` protondb_confidence_${confidence}` : '';
  121. return Object.assign(document.createElement('a'), {
  122. textContent: tier,
  123. className: `protondb_rating_link protondb_rating_${tier}${confidence_style}`,
  124. title: tooltip + 'View on www.protondb.com',
  125. href: `${PROTONDB_HOMEPAGE}/app/${appId}`,
  126. target: userPrefs.open_in_new_tab ? '_blank' : '_self'
  127. });
  128. }
  129.  
  130. function preferencesDialog() {
  131. const container = Object.assign(document.createElement('span'), {
  132. className: 'protondb_prefs_icon',
  133. title: 'Preferences for ProtonDB SteamPlay Integration',
  134. textContent: '⚙'
  135. })
  136.  
  137. container.addEventListener('click', () => {
  138. const html = `
  139. <div class="protondb_prefs">
  140. <div class="newmodal_prompt_description">
  141. New preferences will only take effect after you refresh the page.
  142. </div>
  143. <blockquote>
  144. <div>
  145. <input type="checkbox" id="protondb_open_in_new_tab" ${ userPrefs.open_in_new_tab ? 'checked' : '' } />
  146. <label for="protondb_open_in_new_tab">Open ProtonDB links in new tab</label>
  147. </div>
  148. <div>
  149. <input type="checkbox" id="protondb_skip_native_games" ${ userPrefs.skip_native_games ? 'checked' : '' } />
  150. <label for="protondb_skip_native_games">Don't check native Linux games</label>
  151. </div>
  152. <div>
  153. <input type="checkbox" id="protondb_show_confidence_level" ${ userPrefs.show_confidence_level ? 'checked' : '' } />
  154. <label for="protondb_show_confidence_level">Style based on confidence level of ratings</label>
  155. </div>
  156. </blockquote>
  157. </div>`
  158.  
  159. unsafeWindow.ShowDialog('ProtonDB SteamPlay Prefs', html);
  160.  
  161. // Handle preferences changes
  162. const inputs = document.querySelectorAll('.protondb_prefs input');
  163.  
  164. for (const input of inputs) {
  165. input.addEventListener('change', event => {
  166. const target = event.target;
  167. const prefName = target.id.replace('protondb_', '');
  168.  
  169. switch (target.type) {
  170. case 'text':
  171. userPrefs[prefName] = target.value;
  172. GM_setValue(prefName, target.value);
  173. break;
  174. case 'checkbox':
  175. userPrefs[prefName] = target.checked;
  176. GM_setValue(prefName, target.checked);
  177. break;
  178. default:
  179. break;
  180. }
  181. })
  182. }
  183. })
  184.  
  185. return container;
  186. }
  187.  
  188. function injectCSS() {
  189. GM_addStyle(`
  190. .protondb_rating_row {
  191. text-transform: capitalize;
  192. vertical-align: top;
  193. }
  194. .protondb_rating_link {
  195. background: #4d4b49 url('https://support.steampowered.com/images/custom/platform_steamplay.png') no-repeat -51px center;
  196. display: inline-block;
  197. line-height: 19px;
  198. font-size: 14px;
  199. padding: 1px 10px 2px 79px;
  200. margin-right: 1ex;
  201. color: #b0aeac;
  202. }
  203. .protondb_rating_borked {
  204. color: #FF1919 !important;
  205. }
  206. .protondb_rating_bronze {
  207. color: #CD7F32 !important;
  208. }
  209. .protondb_rating_silver {
  210. color: #C0C0C0 !important;
  211. }
  212. .protondb_rating_gold {
  213. color: #FFD799 !important;
  214. }
  215. .protondb_rating_platinum {
  216. color: #B4C7DC !important;
  217. }
  218. .protondb_confidence_low {
  219. font-style: italic;
  220. }
  221. .protondb_confidence_moderate {
  222. font-weight: normal;
  223. }
  224. .protondb_confidence_good {
  225. font-weight: bold;
  226. }
  227. .protondb_confidence_strong {
  228. font-variant: small-caps;
  229. font-weight: bold;
  230. }
  231. .protondb_prefs_icon {
  232. font-size: 16px;
  233. padding: 0 4px;
  234. cursor: pointer;
  235. }
  236. .protondb_prefs input[type="checkbox"], .protondb_prefs label {
  237. line-height: 20px;
  238. vertical-align: middle;
  239. display: inline-block;
  240. color: #66c0f4;
  241. cursor: pointer;
  242. }
  243. .protondb_prefs blockquote {
  244. margin: 15px 0 5px 10px;
  245. }`)
  246. }
  247.  
  248. function log() {
  249. console.log('[ProtonDB SteamPlay Integration]', ...arguments)
  250. }
  251. })()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址