ProtonDB Integration for Steam

Adds game ratings from ProtonDB to the Steam Store

  1. // ==UserScript==
  2. // @name ProtonDB Integration for Steam
  3. // @description Adds game ratings from ProtonDB to the Steam Store
  4. // @version 1.0.0
  5. // @author guihkx
  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 https://github.com/guihkx
  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. // @homepageURL https://github.com/guihkx/user-scripts
  19. // @supportURL https://github.com/guihkx/user-scripts/issues
  20. // ==/UserScript==
  21.  
  22. /**
  23. * Changelog:
  24. *
  25. * v1.0.0 (2020-04-28):
  26. * - First release
  27. */
  28.  
  29. ;(async () => {
  30. 'use strict'
  31.  
  32. const PROTONDB_TIERS = [
  33. 'pending',
  34. 'borked',
  35. 'bronze',
  36. 'silver',
  37. 'gold',
  38. 'platinum'
  39. ]
  40. const PROTONDB_CONFIDENCE_LEVELS = ['low', 'moderate', 'good', 'strong']
  41. const PROTONDB_HOMEPAGE = 'https://www.protondb.com'
  42.  
  43. let tempPrefs = {}
  44. const userPrefs = {
  45. skip_native_games: GM_getValue('skip_native_games', true),
  46. open_in_new_tab: GM_getValue('open_in_new_tab', false),
  47. show_confidence_level: GM_getValue('show_confidence_level', true)
  48. }
  49.  
  50. const appId = getCurrentAppId()
  51.  
  52. if (!appId) {
  53. return
  54. }
  55. if (userPrefs.skip_native_games) {
  56. if (document.querySelector('span.platform_img.linux') !== null) {
  57. log('Ignoring native Linux game:', appId)
  58. return
  59. }
  60. }
  61. injectCSS()
  62.  
  63. GM_xmlhttpRequest({
  64. method: 'GET',
  65. url: `${PROTONDB_HOMEPAGE}/api/v1/reports/summaries/${appId}.json`,
  66. onload: addRatingToStorePage
  67. })
  68.  
  69. function getCurrentAppId () {
  70. const urlPath = window.location.pathname
  71. const appId = urlPath.match(/\/app\/(\d+)/)
  72.  
  73. if (appId === null) {
  74. log('Unable to get AppId from URL path:', urlPath)
  75. return false
  76. }
  77. return appId[1]
  78. }
  79.  
  80. function addRatingToStorePage (response) {
  81. let reports = {}
  82. let tier
  83.  
  84. if (response.status === 200) {
  85. try {
  86. reports = JSON.parse(response.responseText)
  87. tier = reports.tier
  88. } catch (err) {
  89. log('Unable to parse ProtonDB response as JSON:', response)
  90. log('Javascript error:', err)
  91. tier = 'error'
  92. }
  93. if (!PROTONDB_TIERS.includes(tier)) {
  94. log('Unknown tier:', tier)
  95. tier = 'unknown'
  96. }
  97. } else if (response.status === 404) {
  98. log(`App ${appId} doesn't have a page on ProtonDB yet`)
  99. tier = 'unavailable'
  100. } else {
  101. log('Got unexpected HTTP code from ProtonDB:', response.status)
  102. tier = 'error'
  103. }
  104. const container = Object.assign(document.createElement('div'), {
  105. className: 'protondb_rating_row',
  106. title: 'View on www.protondb.com'
  107. })
  108.  
  109. const subtitle = Object.assign(document.createElement('div'), {
  110. className: 'subtitle column',
  111. textContent: 'ProtonDB Score:'
  112. })
  113.  
  114. const link = Object.assign(document.createElement('a'), {
  115. className: `protondb_rating_link protondb_rating_${tier}`,
  116. href: `${PROTONDB_HOMEPAGE}/app/${appId}`,
  117. target: userPrefs.open_in_new_tab ? '_blank' : '_self'
  118. })
  119.  
  120. if (
  121. 'confidence' in reports &&
  122. userPrefs.show_confidence_level &&
  123. PROTONDB_CONFIDENCE_LEVELS.includes(reports.confidence)
  124. ) {
  125. tier += ` (${reports.confidence} confidence)`
  126. }
  127. link.textContent = tier
  128.  
  129. container.appendChild(subtitle)
  130. container.appendChild(link)
  131.  
  132. const element = document.querySelector('.user_reviews')
  133. element.prepend(container)
  134.  
  135. buildPreferencesDialog()
  136. }
  137.  
  138. function buildPreferencesDialog () {
  139. const container = Object.assign(document.createElement('div'), {
  140. className: 'protondb_prefs_icon',
  141. title: 'Preferences for ProtonDB for Steam',
  142. textContent: '⚙'
  143. })
  144.  
  145. container.addEventListener('click', () => {
  146. // Clear any temporary preferences
  147. tempPrefs = {}
  148.  
  149. const html = `
  150. <div class="protondb_prefs">
  151. <div class="newmodal_prompt_description">
  152. New preferences will only take effect after you refresh the page.
  153. </div>
  154. <blockquote>
  155. <div>
  156. <input type="checkbox" id="protondb_open_in_new_tab" ${
  157. userPrefs.open_in_new_tab ? 'checked' : ''
  158. } />
  159. <label for="protondb_open_in_new_tab">Open ProtonDB links in new tab</label>
  160. </div>
  161. <div>
  162. <input type="checkbox" id="protondb_skip_native_games" ${
  163. userPrefs.skip_native_games ? 'checked' : ''
  164. } />
  165. <label for="protondb_skip_native_games">Don't check native Linux games</label>
  166. </div>
  167. <div>
  168. <input type="checkbox" id="protondb_show_confidence_level" ${
  169. userPrefs.show_confidence_level ? 'checked' : ''
  170. } />
  171. <label for="protondb_show_confidence_level">Show the confidence level of ratings</label>
  172. </div>
  173. </blockquote>
  174. </div>`
  175.  
  176. unsafeWindow
  177. .ShowConfirmDialog('ProtonDB for Steam', html, 'Save')
  178. .done(() => {
  179. log('Saving preferences')
  180. saveUserPreferences()
  181. })
  182. .fail(() => {
  183. log('Ignoring changed preferences')
  184. })
  185.  
  186. // Handle preferences changes
  187. const inputs = document.querySelectorAll('.protondb_prefs input')
  188.  
  189. for (const input of inputs) {
  190. input.addEventListener('change', event => {
  191. const target = event.target
  192. const prefName = target.id.replace('protondb_', '')
  193.  
  194. switch (target.type) {
  195. case 'text':
  196. log(
  197. `Temporarily setting preference '${prefName}' to ${
  198. target.value
  199. }`
  200. )
  201. tempPrefs[prefName] = target.value
  202. break
  203. case 'checkbox':
  204. log(
  205. `Temporarily setting preference '${prefName}' to ${
  206. target.checked
  207. }`
  208. )
  209. tempPrefs[prefName] = target.checked
  210. break
  211. default:
  212. break
  213. }
  214. })
  215. }
  216. })
  217.  
  218. document.querySelector('.protondb_rating_row').appendChild(container)
  219. }
  220.  
  221. function saveUserPreferences () {
  222. for (const prefName in tempPrefs) {
  223. userPrefs[prefName] = tempPrefs[prefName]
  224. GM_setValue(prefName, userPrefs[prefName])
  225. }
  226. }
  227.  
  228. function injectCSS () {
  229. GM_addStyle(`
  230. .protondb_rating_row {
  231. display: flex;
  232. line-height: 16px;
  233. text-transform: capitalize;
  234. margin: 13px 0 13px 0;
  235. }
  236. .protondb_rating_link {
  237. margin-left: -3px;
  238. }
  239. .protondb_rating_error, .protondb_rating_unavailable, .protondb_rating_unknown {
  240. color: #386b86 !important;
  241. }
  242. .protondb_rating_borked {
  243. color: #FF1919 !important;
  244. }
  245. .protondb_rating_bronze {
  246. color: #CD7F32 !important;
  247. }
  248. .protondb_rating_silver {
  249. color: #C0C0C0 !important;
  250. }
  251. .protondb_rating_gold {
  252. color: #FFD799 !important;
  253. }
  254. .protondb_rating_platinum {
  255. color: #B4C7DC !important;
  256. }
  257. .protondb_prefs_icon {
  258. margin-left: 5px;
  259. cursor: pointer;
  260. }
  261. .protondb_prefs input[type="checkbox"], .protondb_prefs label {
  262. line-height: 20px;
  263. vertical-align: middle;
  264. display: inline-block;
  265. color: #66c0f4;
  266. cursor: pointer;
  267. }
  268. .protondb_prefs blockquote {
  269. margin: 15px 0 5px 10px;
  270. }`)
  271. }
  272.  
  273. function log () {
  274. console.log('[ProtonDB Integration for Steam]', ...arguments)
  275. }
  276. })()

QingJ © 2025

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