Skip to content

Commit

Permalink
feat: rating badges in search results (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
mxgic1337 authored Jan 8, 2025
1 parent b1f9750 commit a1575a5
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 38 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ Protonfox is an extension for [Firefox](https://firefox.com) that displays [Prot

![preview.png](.github/assets/preview.png)

# Building:
## 🚀 Building:
Requirements:
- Node.js (version 22.11.0 recommended)
- PNPM package manager

> [!IMPORTANT]
> The build script currently works only on **Linux**.
- [Node.js](https://nodejs.org) (recommended version: 22.x)
- [pnpm](https://pnpm.io/installation) package manager (recommended version: 9.x)

To build the extension:
- Run `pnpm install` to install dependencies.
- Run `pnpm run build` to build the extension.

## 📰 Credits:
- [MostwantedRBX/proton-chrome-extension](https://github.com/MostwantedRBX/proton-chrome-extension) - Original extension

This extension is not affiliated with ProtonDB and/or Valve Corporation.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"name": "protonfox",
"version": "0.2.0",
"version": "0.3.0",
"description": "Displays information about game compatibility with Proton on Steam.",
"main": "index.js",
"type": "module",
"scripts": {
"build": "webpack --mode production && node scripts/manifest.cjs"
Expand Down
18 changes: 18 additions & 0 deletions src/game_page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {createSpan, getGameID, getProtonDBRating, ProtonDBRating, ProtonDBSummary, upperCase} from "./utils/utils";
import {addStoreRatingBadge, addSystemRequirementsRating} from "./utils/store";
import './styles/steam.less'

export function checkGamePage() {
const gameId = getGameID();

getProtonDBRating(gameId).then(async (rating) => {
addStoreRatingBadge(gameId, rating);
addSystemRequirementsRating(gameId, rating);

let native = false;
for (const tab of document.getElementsByClassName('sysreq_tab')) {
if (tab.getAttribute('data-os') === 'linux') native = true
}
if (native) addStoreRatingBadge(gameId, ProtonDBRating.NATIVE)
})
}
5 changes: 2 additions & 3 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
"content_scripts": [
{
"matches": [
"*://store.steampowered.com/app/*"
"*://store.steampowered.com/*"
],
"js": [
"steam.js"
]
}
],
"permissions": [
"*://*.protondb.com/api/v1/*",
"tabs"
"*://*.protondb.com/api/v1/*"
]
}
25 changes: 25 additions & 0 deletions src/search_page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {createSpan, getGameID, getProtonDBRating, ProtonDBSummary, upperCase} from "./utils/utils";

export function checkSearchPage() {
const searchResults = document.getElementById('search_resultsRows');
const mutationObserver = new window.MutationObserver(async ()=>{
checkSearchResults()
});
if (searchResults) {
checkSearchResults()
mutationObserver.observe(searchResults, {childList: true});
}
}

function checkSearchResults() {
const games = document.getElementsByClassName('search_result_row')
for (const game of games) {
if (game.getElementsByClassName('tag').length > 0) { continue; }
const name = game.getElementsByClassName('title')[0]
const gameId = getGameID(game.getAttribute('href') || "10");
getProtonDBRating(gameId).then(async (rating) => {
if (game.getElementsByClassName('tag').length > 0) { return; }
name.appendChild(createSpan(upperCase(rating), ['tag', rating, 'search']))
})
}
}
34 changes: 12 additions & 22 deletions src/steam.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import {ProtonDBRating, ProtonDBSummary} from "./utils/utils";
import {addStoreRatingBadge, addSystemRequirementsRating} from "./utils/store";
import './styles/steam.less'
import {checkGamePage} from "./game_page";
import {checkSearch} from "./store_search";
import {checkSearchPage} from "./search_page";
import {ProtonDBRating} from "./utils/utils";

function getGameID() {
return window.location.href.substring("https://store.steampowered.com/app/".length).split('/')[0]
}
export const ratings: {[key: string]: ProtonDBRating} = {}

const gameId = getGameID();
fetch(`https://www.protondb.com/api/v1/reports/summaries/${gameId}.json`).then(async (res) => {
if (res.ok) {
const json = await res.json() as ProtonDBSummary;
addStoreRatingBadge(gameId, json.tier)
addSystemRequirementsRating(gameId, json.tier)
}else{
addStoreRatingBadge(gameId, ProtonDBRating.PENDING)
addSystemRequirementsRating(gameId, ProtonDBRating.PENDING)
}
if (window.location.href.startsWith('https://store.steampowered.com/app')) {
checkGamePage()
}
if (window.location.href.startsWith('https://store.steampowered.com/search')) {
checkSearchPage()
}

let native = false;
for (const tab of document.getElementsByClassName('sysreq_tab')) {
if (tab.getAttribute('data-os') === 'linux') native = true
}
if (native) addStoreRatingBadge(gameId, ProtonDBRating.NATIVE)
})
checkSearch()
18 changes: 18 additions & 0 deletions src/store_search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {createSpan, getGameID, getProtonDBRating, ProtonDBSummary, upperCase} from "./utils/utils";

export function checkSearch() {
const searchResults = document.getElementById('search_suggestion_contents');
const mutationObserver = new window.MutationObserver(()=>{
const games = document.getElementsByClassName('match_app')
for (const game of games) {
const name = game.getElementsByClassName('match_name')[0]
const gameId = getGameID(game.getAttribute('href') || "10");
getProtonDBRating(gameId).then(async (rating) => {
name.appendChild(createSpan(upperCase(rating), ['tag', rating]))
})
}
});
if (searchResults) {
mutationObserver.observe(searchResults, {childList: true});
}
}
18 changes: 15 additions & 3 deletions src/styles/steam.less
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@native: rgb(142, 214, 41);
@pending: rgb(187, 187, 187);
@pending: rgb(103, 131, 245);
@unknown: rgb(136, 124, 124);
@platinum: rgb(180, 199, 220);
@gold: rgb(207, 181, 59);
@silver: rgb(166, 166, 166);
Expand All @@ -24,16 +25,27 @@
color: @color;

&.badge {
background-color: fade(@color, 20%);
background-color: fade(@color, 20%) !important;

&:hover {
color: #fff;
background-color: fade(@color, 40%);
background-color: fade(@color, 40%) !important;
}
}

&.tag {
background-color: fade(@color, 20%) !important;

&.search {
padding: 0 6px;
border-radius: 4px;
margin-left: 6px;
}
}
}

.native { .rating(@native) }
.unknown { .rating(@unknown) }
.pending { .rating(@pending) }
.platinum { .rating(@platinum) }
.gold { .rating(@gold) }
Expand Down
37 changes: 35 additions & 2 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,56 @@
import {ratings} from "../steam";

export enum ProtonDBRating {
NATIVE = 'native',
PLATINUM = 'platinum',
GOLD = 'gold',
SILVER = 'silver',
BRONZE = 'bronze',
BORKED = 'borked',
PENDING = 'pending',
UNKNOWN = 'unknown',
}

export interface ProtonDBSummary {
tier: ProtonDBRating
}

export function createSpan(text: string) {
export function createSpan(text: string, classes?: string[]) {
const element = document.createElement("span");

if (classes) {
for (const prop of classes) {
element.classList.add(prop);
}
}

element.appendChild(document.createTextNode(text))
return element
}

export function upperCase(text: string) {
return text.charAt(0).toUpperCase() + text.substring(1)
}

export function getGameID(url?: string) {
if (!url) url = window.location.href;
return url.substring("https://store.steampowered.com/app/".length).split('/')[0]
}

export function getProtonDBRating(gameId: string) {
return new Promise<ProtonDBRating>((resolve, reject) => {
if (ratings[gameId]) {
resolve(ratings[gameId]);
}else{
fetch(`https://www.protondb.com/api/v1/reports/summaries/${gameId}.json`).then(async (res) => {
if (res.ok) {
const json = await res.json() as ProtonDBSummary;
ratings[gameId] = json.tier;
resolve(json.tier);
}else{
ratings[gameId] = ProtonDBRating.UNKNOWN;
resolve(ProtonDBRating.UNKNOWN)
}
}).catch(console.error);
}
})
}

0 comments on commit a1575a5

Please sign in to comment.