adds .local/share/millennium and easyeffects

This commit is contained in:
2026-08-06 18:23:36 +02:00
parent f92fb90275
commit 8dc466cbac
2581 changed files with 339035 additions and 0 deletions
@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1 @@
[{"description":"treehash per file","signed_content":{"payload":"eyJjb250ZW50X2hhc2hlcyI6W3siYmxvY2tfc2l6ZSI6NDA5NiwiZGlnZXN0Ijoic2hhMjU2IiwiZmlsZXMiOlt7InBhdGgiOiJMSUNFTlNFIiwicm9vdF9oYXNoIjoiR0VmZzRHbUJRdTFEUjhGRUdwLW9ISS05M1VTeDJMdk5YalpILVpGMW5YOCJ9LHsicGF0aCI6ImFzc2V0cy9kZWNrLWljb24uc3ZnIiwicm9vdF9oYXNoIjoicFRXRUxSYnRjUTNSbEJzVm5qdGM5UjcyalgxODBESmUtSUVWalFSVXhQbyJ9LHsicGF0aCI6ImFzc2V0cy9kZWNrX3BsYXlhYmxlLnBuZyIsInJvb3RfaGFzaCI6IjRZZ21vNl9kUU1DcEJQUC1Ud21MOTdPRW9xQjlCckdnZjV3UUZWVHQtV2cifSx7InBhdGgiOiJhc3NldHMvZGVja191bmtub3duLnBuZyIsInJvb3RfaGFzaCI6InRKREI5cUdpZ0QwdHE5QksxRXQ4b0hmd1RGNGxWVHB0dU5aMkx2eUd1aDAifSx7InBhdGgiOiJhc3NldHMvZGVja191bnN1cHBvcnRlZC5wbmciLCJyb290X2hhc2giOiJReDB3Z2h1UG9Ub3lHbGFZZ0p0R2dMM3pGTTVCLUxxYmRkcEQyTjlRTWdvIn0seyJwYXRoIjoiYXNzZXRzL2RlY2tfdmVyaWZpZWQucG5nIiwicm9vdF9oYXNoIjoiVmVZOGY3VjZsNTBVY3RrN2VfVVZoWkxlOEg3SEFwTmtra01zUU43a0c0YyJ9LHsicGF0aCI6ImFzc2V0cy9wcm90b25kYl9pY29uLnBuZyIsInJvb3RfaGFzaCI6Ik9QZmVoMFdXLWZUZ1F6cXBnQ3VmZWFRZGlNZTU4ZnRlNVFQNkNwR2hlVm8ifSx7InBhdGgiOiJiYWNrZ3JvdW5kLmpzIiwicm9vdF9oYXNoIjoiSTYyeERVcXFiVUsyQnBsV09pRy05dlUxLXpWMUlEbHhjcU53R0R6TGUtdyJ9LHsicGF0aCI6Imljb25zL2ljb24xMjgucG5nIiwicm9vdF9oYXNoIjoiX1lqOWllSUU5djlxN0JHM191S202SVpFNFBUV2RUdDlIYjZGc3daZjdlYyJ9LHsicGF0aCI6Imljb25zL2ljb24xNi5wbmciLCJyb290X2hhc2giOiJEVnhmYV9Fdnh4U0l3eElldjVyMUlIQ3p1R05taGxQMkFVWFBKWEpOWkFNIn0seyJwYXRoIjoiaWNvbnMvaWNvbjMyLnBuZyIsInJvb3RfaGFzaCI6Il9Oc19LWGZKVHc4amhrdGtTeWtsZ2t3aWhPRVYtSl90OW9YTURZYnFUSmMifSx7InBhdGgiOiJpY29ucy9pY29uNDgucG5nIiwicm9vdF9oYXNoIjoiVWdoM1puX01qTjNHdjRKTmxPZnhoTnFTeEFqMFdwZ1pNU05mYTRXb3ZZWSJ9LHsicGF0aCI6ImluZGV4Lmh0bWwiLCJyb290X2hhc2giOiJ3OGtHcjEyRExWZjNhejVJX25sLXZPekl5clhNSnJXSHVoeGVSOEREUnRzIn0seyJwYXRoIjoibWFuaWZlc3QuanNvbiIsInJvb3RfaGFzaCI6Illqd0YzZlU2ZExKd1VVdXpnRHBTWEp3NnZGcFNtY3FZMmc0YnFldU9NT3MifSx7InBhdGgiOiJwcm90b25kYl9pY29uMjU2LnBuZyIsInJvb3RfaGFzaCI6Ijl2VFlOT3JhSVB2Z29pdkZBTS1FaGRibXpMUUpyOUlNUVVjdlM3TWpoMzgifSx7InBhdGgiOiJzY3JpcHRzL2FwcF9wYWdlLnNjcmlwdC5qcyIsInJvb3RfaGFzaCI6IkVSZ2dQZWJLTlgwanh2QU1BT2ZsN0N5ZzVPcTRQaHh6MDQ4cjFLSXJOZG8ifSx7InBhdGgiOiJzY3JpcHRzL2NvbG9yLnNjcmlwdC5qcyIsInJvb3RfaGFzaCI6IjF0NDVvYjJzbnlCLWlveUpvT1pZdjVrSWZQYWEyX3QydmJzUkp0WGJGUTQifSx7InBhdGgiOiJzY3JpcHRzL3N0b3JlLnNjcmlwdC5qcyIsInJvb3RfaGFzaCI6IlZSNVA3ZjZzeS1idC0wSTVFV1dfZTVKRnF0V3c1YzNzN1g3ODNKb0h5VmMifSx7InBhdGgiOiJzdHlsZXMvYXBwX3BhZ2Uuc3R5bGUuY3NzIiwicm9vdF9oYXNoIjoiS1kwaXhLbjEwZXdZTWthSkZKcHVUbmdjMTBtQjYySEJjU3ppQzVXZDBBWSJ9LHsicGF0aCI6InN0eWxlcy9zdG9yZS5zdHlsZS5jc3MiLCJyb290X2hhc2giOiJEcW9yS0x4ZUZ0REVBMEMtcGt1TVktZHRzUEtYaTBoOEtUbWtLNDFRa2FNIn0seyJwYXRoIjoic3R5bGVzL3N0eWxlLmNzcyIsInJvb3RfaGFzaCI6IkZFMm43bDZod2c1cGpQRTFfN2I3UUNKTHY2NUU2MlNJOTY1azM5TDc2cDAifSx7InBhdGgiOiJ1dGlscy9kb21faGFuZGxlcnMuanMiLCJyb290X2hhc2giOiJubURvR093aFlHb3dRazVKLTZBX08xWDF4MjI4bi1mU3pUczVVSzZvLVRJIn0seyJwYXRoIjoidXRpbHMvZ2xvYmFscy5qcyIsInJvb3RfaGFzaCI6ImtxbndmcVpFd3VubThuT3lGNU5INExUWUxxZkM5N3Vmdi0ySVhubndXOEkifSx7InBhdGgiOiJ1dGlscy9oZWxwZXJzLmpzIiwicm9vdF9oYXNoIjoiNWtEc08zRzNZWDNDWk1kQ240a3VSeHUtY1dyVGxWelJ6ME85Zm1sVFlDTSJ9LHsicGF0aCI6InV0aWxzL2l0ZW1zLmpzIiwicm9vdF9oYXNoIjoiUEFiZTE0ZEU0VTgtSTNjNjdMZE8tRTNDcWU3c0RfY0NZUWlvZmZQbGxVNCJ9LHsicGF0aCI6InV0aWxzL29ic2VydmVycy5qcyIsInJvb3RfaGFzaCI6Ijk4ZElzd3lFVTB0M3hqN0hHNmwyX0xLVkV3OEFpUlFHMFF0dE1qeERGLVkifV0sImZvcm1hdCI6InRyZWVoYXNoIiwiaGFzaF9ibG9ja19zaXplIjo0MDk2fV0sIml0ZW1faWQiOiJmbG9nYm9ma2lqbGVtcG1saWNsaG5tY2ZjZmtucGRpYSIsIml0ZW1fdmVyc2lvbiI6IjEuNC4yMiIsInByb3RvY29sX3ZlcnNpb24iOjF9","signatures":[{"header":{"kid":"publisher"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"U8UgHNvhviGmeDxTdNv2BwI57IyX0OyzocOtupvtwqtrOsjAlZ502oRp7yFnsKnD-Uc7nEXgvOUkvI5fivyd6QV-Wx5oKSLxCKaSPNMhJq5q6AsIL_D7op_pjNeIvkfDrn8Y6JNalzZcgKHic61we1cU4Dnq4tk9w98VmDqaJRLInXScP_rJBjViyPx2XSpZq9WhE3lu7SWqnms9UxAS1icBBOf0AYEYnytv_Ei8VPbcl2hnFiFi_7wYm7Z4FRUaFWeLUYDuF5ak3vwIgfIXJpaCFH_qwgRN8jTGU7Hu7fK_iybTNNqGOGv2umD8wor5YmYaA3xGKvSXD33FcpmtBw"},{"header":{"kid":"webstore"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"bPgP7Nm4GwjtF4_9Y1ibQ2SGGR1XWQS8g6mzWUBD6x2AaWbXUuFohaLY1QGR4MTvCg40AmsD5CD4YvVzg2J6IPh-z9-pugvmNm_8ZO6ByncAUBVnuJJwDJmzTtN9fKtcuCJSkurqEA7iSWbbtMGKE5LOYMYp98-ab2alV3CMh7sdl5ZMm5Xq9eKIfZk4gmF35iMdL63IRsKjuHk83KM8iUZAZALK8yaeucUrOzN-lGiLVB_4svbAsOR32hMSwzBNVG3hgRs9eZd-AGHtuzhhD7qw0yHWGTp4mPLFV47Vc8_dV_UCeDUuapiWAgymrElPtvWtvCN5RvJjQWVZj4D9ew"}]}}]
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="89" height="115" viewBox="0 0 89 115" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M65.5648 57.5C65.5648 37.1433 49.1526 20.641 28.9072 20.641V0C60.4901 0 86.093 25.7436 86.093 57.5C86.093 89.2564 60.4901 115 28.9072 115V94.359C49.1526 94.359 65.5648 77.8567 65.5648 57.5Z" fill="black"/>
<circle cx="28.5929" cy="57.5001" r="28.5929" fill="url(#paint0_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="-22.0009" y1="39.4667" x2="45.1598" y2="71.8891" gradientUnits="userSpaceOnUse">
<stop offset="0.106991" stop-color="#C957E6"/>
<stop offset="1" stop-color="#1A9FFF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

@@ -0,0 +1,113 @@
// Browser API compatibility
const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
browserAPI.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.contentScriptQuery == "queryGame") {
var url = `https://www.protondb.com/api/v1/reports/summaries/${request.appId}.json`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((response) => {
sendResponse(response);
})
.catch((error) => {
sendResponse({ error: error.message }); // Send an error response if needed
});
return true; // Keep the message channel open
} else if (request.contentScriptQuery == "queryReview") {
var url = `http://protondb.solidet.com/api/reports/${request.appId}`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
sendResponse(data);
})
.catch((error) => {
sendResponse({ error: error.message }); // Send an error response if needed
});
return true; // Keep the message channel open
} else if (request.contentScriptQuery == "queryDeckStats") {
var url = `https://store.steampowered.com/saleaction/ajaxgetdeckappcompatibilityreport?nAppID=${request.appId}`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
let status = "";
if (!data.success) {
throw new Error(`Error! No game found, success: ${data.success}`);
}
let icon;
const category = data.results.resolved_category;
switch (category) {
case 0:
status = "unknown";
icon = "deck_unknown";
break;
case 1:
status = "unsupported";
icon = "deck_unsupported";
break;
case 2:
status = "playable";
icon = "deck_playable";
break;
case 3:
status = "verified";
icon = "deck_verified";
break;
default:
return;
}
const deckInfo = {
status: status,
icon: icon,
};
sendResponse(deckInfo);
})
.catch((error) => {
sendResponse({ error: error.message }); // Send an error response if needed
});
return true; // Keep the message channel open
} else if (request.contentScriptQuery == "queryPlatforms") {
var url = `https://www.protondb.com/proxy/steam/api/appdetails/?appids=${request.appId}`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
let responseObject=data[request.appId]
let status = "";
if (!responseObject.success) {
throw new Error(`Error! No game found, success: ${data.success}`);
}
sendResponse(responseObject.data.platforms);
})
.catch((error) => {
sendResponse({ error: error.message }); // Send an error response if needed
});
return true; // Keep the message channel open
}
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html>
<head>
<title>Protondb Community Extension</title>
<link rel="stylesheet" href="styles/style.css" />
</head>
<body>
<p class=version-text>v1.4.22</p>
<img
src="assets/protondb_icon.png"
alt="ProtonDB Extension Icon"
width="60px"
style="display: block; margin: 0.5rem auto"
/>
<h2 style="text-align: center">ProtonDB Extension</h2>
<p style="margin-top: 1rem; text-align: center">Choose Colors for the Ratings:</p>
<div class="color-picker-list">
<div></div>
<div class="color-picker-wrapper">
<button class="custom-color-picker" picker-for="platinum">
<input type="color" id="platinum" name="platinum" value="#B4C7DC" />
</button>
<label for="platinum">Platinum</label>
</div>
<div class="color-picker-wrapper">
<button class="custom-color-picker" picker-for="gold">
<input type="color" id="gold" name="gold" value="#CFB53B" />
</button>
<label for="gold">Gold</label>
</div>
<div></div>
<div></div>
<div class="color-picker-wrapper">
<button class="custom-color-picker" picker-for="silver">
<input type="color" id="silver" name="silver" value="#A6A6A6" />
</button>
<label for="silver">Silver</label>
</div>
<div class="color-picker-wrapper">
<button class="custom-color-picker" picker-for="bronze">
<input type="color" id="bronze" name="bronze" value="#CD7F32" />
</button>
<label for="bronze">Bronze</label>
</div>
<div></div>
<div></div>
<div class="color-picker-wrapper">
<button class="custom-color-picker" picker-for="borked">
<input type="color" id="borked" name="borked" value="#FF0000" />
</button>
<label for="borked">Borked</label>
</div>
<div class="color-picker-wrapper">
<button class="custom-color-picker" picker-for="unknown">
<input type="color" id="unknown" name="unknown" value="#3D3D3D" />
</button>
<label for="unknown">Unknown</label>
</div>
<div></div>
</div>
<div style="display: flex; justify-content: center; margin-top: 1rem">
<button id="resetColorsButton">Reset</button>
</div>
<footer class="color-picker-footer">
<span style="font-weight: 300">from</span>
<a
href="https://linktr.ee/solidet"
class="solidet-linktree"
target="_blank"
rel="noopener noreferrer"
title="solidet on linktree"
>
<p class="solidet-text">solidet</p>
</a>
</footer>
<script src="scripts/color.script.js"></script>
</body>
</html>
@@ -0,0 +1,81 @@
{
"update_url": "https://clients2.google.com/service/update2/crx",
"manifest_version": 3,
"name": "ProtonDB Community Extension",
"description": "Elevate Your Linux Gaming Experience with ProtonDB Community Extension!",
"version": "1.4.22",
"permissions": [
"storage"
],
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"action": {
"default_popup": "index.html",
"default_icon": "icons/icon128.png"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": [
"https://store.steampowered.com/*",
"https://steamcommunity.com/id/*/games/*",
"https://steamcommunity.com/profiles/*/games/*"
],
"exclude_matches": [
"*://store.steampowered.com/dynamicstore/*",
"*://store.steampowered.com/login/*",
"*://store.steampowered.com/join/*",
"*://steamcommunity.com/login/*",
"*://checkout.steampowered.com/*",
"*://store.steampowered.com/charts/*"
],
"js": [
"./utils/globals.js",
"./utils/observers.js",
"./utils/helpers.js",
"./utils/items.js",
"./utils/dom_handlers.js",
"./scripts/store.script.js"
],
"css": [
"./styles/store.style.css"
],
"run_at": "document_idle"
},
{
"matches": [
"https://store.steampowered.com/app/*"
],
"js": [
"./scripts/app_page.script.js"
],
"css": [
"./styles/app_page.style.css"
],
"run_at": "document_idle"
}
],
"web_accessible_resources": [
{
"resources": [
"assets/*"
],
"matches": [
"https://store.steampowered.com/*",
"https://steamcommunity.com/*"
]
}
],
"host_permissions": [
"https://store.steampowered.com/*",
"https://www.protondb.com/*"
]
}
@@ -0,0 +1 @@
{"extensionId":"flogbofkijlempmliclhnmcfcfknpdia","url":"https://chromewebstore.google.com/detail/protondb-community-extens/flogbofkijlempmliclhnmcfcfknpdia?hl=tr&authuser=0"}
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,133 @@
// Wait for both DOM and stylesheets to be loaded
function waitForStyles() {
return new Promise((resolve) => {
if (document.styleSheets.length > 0) {
resolve();
return;
}
const observer = new MutationObserver((mutations, obs) => {
if (document.styleSheets.length > 0) {
obs.disconnect();
resolve();
}
});
observer.observe(document.head, {
childList: true,
subtree: true
});
});
}
async function initializeExtension() {
try {
console.log('[ProtonDB] Waiting for styles to load...');
await waitForStyles();
console.log('[ProtonDB] Styles loaded, proceeding with initialization');
const appId = getAppId();
if (!appId) {
console.error('[ProtonDB] Could not find app ID');
return;
}
// Process the main game section
const parentDiv = document.querySelector(".user_reviews");
if (!parentDiv) {
console.error('[ProtonDB] Could not find .user_reviews element');
return;
}
const appHeaderContent = document.querySelector(".highlight_overflow");
if (!appHeaderContent) {
console.error('[ProtonDB] Could not find .highlight_overflow element');
return;
}
console.log('[ProtonDB] Found required DOM elements');
appHeaderContent.setAttribute("data-ds-appid", appId);
// Only add icon if not in excluded classes
if (!hasAnyClass(appHeaderContent, ClassNamesToCheck) &&
!hasAnyParentClass(appHeaderContent, ClassNamesToCheckInParent)) {
addIconToItem(appHeaderContent);
}
try {
// Get game data
const gameData = await new Promise((resolve, reject) => {
browserAPI.runtime.sendMessage(
{ contentScriptQuery: "queryGame", appId: appId },
(response) => {
if (browserAPI.runtime.lastError) {
reject(browserAPI.runtime.lastError);
return;
}
resolve(response);
}
);
});
if (!gameData) {
console.error('[ProtonDB] No game data received');
return;
}
// Create and add rank rows
const rankRowsContainer = document.createElement("div");
rankRowsContainer.classList.add("protondb_rank_rows");
rankRowsContainer.appendChild(
createRankRow("ProtonDB Rank:", gameData.tier, gameData.tier)
);
rankRowsContainer.appendChild(
createRankRow("Trending Rank:", gameData.trendingTier, gameData.trendingTier)
);
parentDiv.appendChild(rankRowsContainer);
// Get platform data
const platformData = await new Promise((resolve, reject) => {
browserAPI.runtime.sendMessage(
{ contentScriptQuery: "queryPlatforms", appId },
(response) => {
if (browserAPI.runtime.lastError) {
reject(browserAPI.runtime.lastError);
return;
}
resolve(response);
}
);
});
if (platformData) {
parentDiv.appendChild(createPlatformRow(platformData.linux));
}
} catch (error) {
console.error('[ProtonDB] Error fetching data:', error.message);
}
// Process all game links on the page
const gameLinks = document.querySelectorAll('a[href^="https://store.steampowered.com/app/"]');
gameLinks.forEach(item => {
if (!items.has(item) &&
!hasAnyClass(item, ClassNamesToCheck) &&
!hasAnyParentClass(item, ClassNamesToCheckInParent)) {
addElementsToItems(item);
}
});
// Start the observers from utils/observers.js
startObservers();
} catch (error) {
console.error('[ProtonDB] Initialization error:', error.message);
}
}
// Initialize when the page is ready
if (document.readyState === 'complete') {
initializeExtension();
} else {
window.addEventListener('load', initializeExtension);
}
@@ -0,0 +1,74 @@
const DEFAULT_COLOR_PALETTE = {
platinumColor: "#B4C7DC",
goldColor: "#CFB53B",
silverColor: "#A6A6A6",
bronzeColor: "#CD7F32",
borkedColor: "#FF0000",
unknownColor: "#3D3D3D",
};
var colorPickers = document.querySelectorAll(".custom-color-picker");
function getStoredColorPalette(callback) {
chrome.storage.local.get(DEFAULT_COLOR_PALETTE, callback);
}
function saveColors(event) {
const pickerFor = event.target.parentElement.getAttribute("picker-for");
let currentColorPalette;
getStoredColorPalette((colorPalette) => {
currentColorPalette = colorPalette;
});
const newColorPalette = {
...currentColorPalette,
[`${pickerFor}Color`]: event.target.value,
};
chrome.storage.local.set(newColorPalette);
updateColorPickers(newColorPalette);
}
function loadColors() {
getStoredColorPalette(updateColorPickers);
}
function updateColorPickers(newColorPalette) {
colorPickers.forEach((colorPicker) => {
const pickerFor = colorPicker.getAttribute("picker-for");
const colorPickerInput = colorPicker.querySelector("input");
colorPickerInput.value = newColorPalette[`${pickerFor}Color`];
colorPicker.style.backgroundColor = newColorPalette[`${pickerFor}Color`];
});
}
function resetColors() {
if (!confirm("Are you sure you want to reset the colors to default values?")) {
return;
}
chrome.storage.local.set(DEFAULT_COLOR_PALETTE, loadColors);
}
/* Event Listeners */
window.addEventListener("DOMContentLoaded", function () {
loadColors();
});
colorPickers.forEach((colorPicker) => {
const colorPickerInput = colorPicker.querySelector("input");
colorPicker.addEventListener("click", (event) => {
colorPickerInput.click();
});
colorPickerInput.addEventListener("input", saveColors);
});
document.getElementById("resetColorsButton").addEventListener("click", resetColors);
@@ -0,0 +1,16 @@
// Initialize colors
setColors();
const newItems = getNewItems();
if (newItems.length > 0) {
newItems.forEach((item) => {
if (!items.has(item) &&
!hasAnyClass(item, ClassNamesToCheck) &&
!hasAnyParentClass(item, ClassNamesToCheckInParent)) {
addElementsToItems(item);
}
});
}
// Start the observers from utils/observers.js
startObservers();
@@ -0,0 +1,38 @@
.protondb_rank_rows {
margin: 10px 0;
font-size: 14px;
}
.protondb_rank_row {
display: flex;
align-items: center;
margin: 5px 0;
}
.protondb_rank_label {
margin-right: 10px;
color: #8f98a0;
}
.protondb_icon {
display: inline-block;
transform-origin: 0 0;
transform: scale(0.8);
margin-right: 5px;
vertical-align: middle;
}
.protondb_rank_value {
font-weight: bold;
color: #fff;
padding: 2px 8px;
border-radius: 3px;
}
/* Rank colors */
.platinum { background-color: #b4c7dc; }
.gold { background-color: #cfb53b; }
.silver { background-color: #c0c0c0; }
.bronze { background-color: #cd7f32; }
.borked { background-color: #ff0000; }
.pending { background-color: #666666; }
@@ -0,0 +1,64 @@
.deck-status-icon {
position: absolute;
top: -3px;
right: -3px; /* Position the icon at the top-right corner */
z-index: 14;
width: 37px !important;
}
.deck-status-icon[data-ds-deck-tier="unsupported"],
.deck-status-icon[data-ds-deck-tier="unknown"] {
filter: contrast(10);
}
.deck-status-icon[data-ds-deck-tier="verified"] {
filter: contrast(1.25);
}
.deck-status-icon[data-ds-deck-tier="playable"] {
filter: contrast(1.8);
}
.vertical-line {
background-color: transparent;
width: 4px;
height: 100%;
position: absolute;
z-index: 11;
left: 0;
top: 0;
bottom: 0;
}
/* Specific styles for list view */
.search_result_row .vertical-line {
height: 100%;
top: 0;
bottom: 0;
}
/* Specific styles for grid view */
.store_capsule .vertical-line {
height: 100%;
top: 0;
bottom: 0;
}
.deck-status-icon.on-list {
position: absolute;
width: 24px !important;
height: 24px;
top: 10.5px;
left: -16px;
}
.deck-status-icon.on-tab-list {
position: initial;
width: 22px !important;
height: 22px;
display: inline;
vertical-align: bottom;
}
.gameslistitems_PerfectRibbon_1uKlf {
left: 0px !important;
}
@@ -0,0 +1,92 @@
@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@700&family=Poppins:wght@200;400&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Poppins, sans-serif;
}
body {
padding: 1rem;
background-color: #20232b;
color: white;
font-family: Poppins, sans-serif;
min-width: fit-content;
max-width: 300px;
max-height: 400px;
aspect-ratio: 2.5 / 3;
}
.solidet-text {
font-family: Orbitron, sans-serif;
font-size: 1rem;
}
.color-picker-wrapper input {
visibility: hidden;
}
.custom-color-picker {
height: 32px;
width: 32px;
outline: none;
border: none;
border-radius: 50%;
background-color: currentcolor;
box-shadow: inset 0 0 0 2px #43434a, inset 0 0 0 4px black;
cursor: pointer;
}
#resetColorsButton {
padding: 0.5rem 1.2rem;
border-radius: 2rem;
border: none;
background-color: #17191f;
color: white;
transition: all 0.2s ease-in-out;
cursor: pointer;
}
#resetColorsButton:hover {
background-color: #0b0c0f;
}
.color-picker-wrapper {
display: flex;
column-gap: 12px;
align-items: center;
}
.color-picker-list {
margin-top: 16px;
display: grid;
gap: 0.5rem;
grid-template-columns: 1fr 1fr 1fr 1fr;
/* justify-content: center; */
align-items: center;
flex-wrap: wrap;
column-gap: 24px;
}
.color-picker-footer {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 24px;
}
.solidet-linktree {
text-decoration: none;
color: inherit;
cursor: pointer;
}
.version-text {
top: 0.7rem;
right: 0.7rem;
position: absolute;
font-size: 0.8rem;
margin-top: 8px;
color: #858592;
float: right;
}
@@ -0,0 +1,130 @@
function createRankRow(rankTitle, rankValue, textColor) {
const rankRow = document.createElement("div");
rankRow.classList.add("user_reviews_summary_row");
const createDivWithClasses = (...classes) => {
const div = document.createElement("div");
div.classList.add(...classes);
return div;
};
const subtitleDiv = createDivWithClasses("subtitle", "column");
subtitleDiv.textContent = rankTitle;
const summaryDiv = createDivWithClasses("summary", "column", "protondb_rank");
const rankValueSpan = document.createElement("span");
rankValueSpan.classList.add("protondb_rank_value");
rankValueSpan.textContent = rankValue || "N/A";
rankValueSpan.style.color = colors[textColor] || colors.unknown;
rankValueSpan.setAttribute("data-tier", rankValue);
const link = document.createElement("a");
link.href = `https://protondb.com/app/${getAppId()}`;
link.textContent = "(?)";
const rankTitleSpan = document.createElement("span");
rankTitleSpan.classList.add("responsive_reviewdesc_short");
rankTitleSpan.textContent = rankTitle.toUpperCase();
summaryDiv.append(rankValueSpan, link, rankTitleSpan);
rankRow.append(subtitleDiv, summaryDiv);
return rankRow;
}
function createPlatformRow(isNative) {
const platformRow = document.createElement("div");
platformRow.classList.add("user_reviews_summary_row");
const createDivWithClasses = (...classes) => {
const div = document.createElement("div");
div.classList.add(...classes);
return div;
};
const icon = isNative ? "✔" : "✘";
const subtitleDiv = createDivWithClasses("subtitle", "column");
subtitleDiv.textContent = "Native Linux?";
const summaryDiv = createDivWithClasses(
"summary",
"column",
"protondb_isNative"
);
const platformValueSpan = document.createElement("span");
platformValueSpan.classList.add("protondb_isNative_value");
platformValueSpan.textContent = icon;
platformValueSpan.style.color = "#67c1f5";
platformValueSpan.setAttribute("data-isNative", isNative);
const link = document.createElement("a");
link.href = `https://protondb.com/app/${getAppId()}`;
link.textContent = "(?)";
const rankTitleSpan = document.createElement("span");
rankTitleSpan.classList.add("responsive_reviewdesc_short");
rankTitleSpan.textContent = isNative ? "Yes" : "No";
summaryDiv.append(platformValueSpan, link, rankTitleSpan);
platformRow.append(subtitleDiv, summaryDiv);
return platformRow;
}
function createIconElement(status, iconUrl) {
const icon = document.createElement("img");
var imgURL = chrome.runtime.getURL(`assets/${iconUrl}.png`);
icon.src = imgURL;
icon.classList.add("deck-status-icon");
icon.setAttribute("data-ds-deck-tier", status);
return icon;
}
function createLineElement() {
const line = document.createElement("div");
line.classList.add("vertical-line");
return line;
}
function insertLineBeforeItem(line, item) {
if (item.parentNode) item.parentNode.insertBefore(line, item);
item.prepend(line);
}
function insertIconBeforeItem(icon, item) {
if (item.parentNode) item.parentNode.insertBefore(icon, item);
item.prepend(icon);
}
function insertIconInList(icon, item) {
if (!item) return;
const gameListing = item.querySelector(".responsive_search_name_combined");
const reviewBox = gameListing.querySelector(
".col.search_reviewscore.responsive_secondrow"
);
makeItemRelative(reviewBox);
const review = reviewBox.querySelector(".search_review_summary");
icon.classList.add("on-list");
if (review) review.prepend(icon);
}
function colorLineElement(line, color) {
line.style.backgroundColor = color;
}
function insertIconInTabList(icon, item) {
if (item.classList.contains("tab_item_overlay")) {
item = item.parentNode;
}
const details = item.querySelector(".tab_item_details");
if (!details) return false;
icon.classList.add("on-tab-list");
const platformImgs = details.querySelectorAll(".platform_img");
if (platformImgs.length > 0) {
const latestPlatformImg = platformImgs[platformImgs.length - 1];
latestPlatformImg.insertAdjacentElement("afterend", icon);
}
}
@@ -0,0 +1,103 @@
// Browser API compatibility
const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
// Mutation Observers
const observers = [];
// Shared Sets
const items = new Set();
const processedItems = new Set();
// Shared Colors
const colors = {};
//css classes ought to change time to time, examples given for that to remember them.
//to skip adding anything in parent
const LIST_ITEM_TITLE = "._3rrH9dPdtHVRMzAEw82AId";
const PAGE_TITLE = ".page_title_area.game_title_area.page_content";
const BUNDLE_CONTENTS = ".bundle_contents_preview";
const DLC_LIST = ".game_area_dlc_list";
const HOVER_MODAL = "._2VvFLg2irh9gPAhhxE4Kpo"; //try to find games that are hoverable with purchase option
const MINI_WIDGET_TITLE = "._9VjYX3CYMn2y-wWpAn00Y"; //check event page with stream
const FLOATING_STREAM = "._28O6dX6-Xf37oViWRRhvjz"; //check event page with stream
const PACKAGE_CONTENTS_CAPSULES = ".package_contents_capsules";
const BASE_GAME_OF_DLC = ".glance_details";
const GAME_AREA_DESCRIPTION = ".game_area_description";
const RECOMMENDATION_LINK = ".recommendation_link"; //check franchise page
const VALVE_INDEX = ".valveindex_purchase_grid";
const VALVE_INDEX_ALYX_PROMO = ".hlvr_included_sidebar";
const PURCHASE_NOTE = "#purchase_note"; //they sometimes put game hyperlink, eg: cod hq app
const DISCOVERY_QUEUE = ".discovery_queue_static";
const RECOMMENDED_GAME_REASON = ".home_content_reason"; //check game category
const BREADCRUMBS = "breadcrumbs"; // Skip breadcrumbs navigation - removed dot for class name
//to skip add anything if <a> tag has one of those classes
const RECOMMENDED_GAME_ON_STORE_BUTTON_HOVER = "btnv6_blue_hoverfade";
const RECOMMENDED_GAME_ON_STORE_BUTTON_SMALL = "btn_small_tall";
const SCREENSHOT = "screenshot";
const SIMILIAR_APP = "similarapp"; //check game page when logged in and find similiar to games you have played
const BUNDLE_ITEM = "bundle_contents_preview_item";
//Relative Classes
const PACKAGE_ITEM = ".bundle_package_item";
const STORE_SALE_WIDGET_MINI = ".StoreSaleWidgetContainer_mini";
const LARGE_GAME_ITEM = "._3DkfNrtTOLjNYd3yZliMzy"; //check ea play for this class
const IMPRESSION_TRACKED_ELEMENT = ".ImpressionTrackedElement"; //login and go category page
//banner game;
//21/07/2024
//we only need title for icon for aesthetic purposes but no icon on game cover art
const BANNER_GAME_TITLE = "_26-ICqi8UhxutiYJyQ7jw"; //check game category, prevent adding icons on game title on the right side
const BANNER_GAME_COVER_ART = "_3htJKARnLcsZPlG3Sqlvkg"; //check game category, prevent adding icons on game cover art
//skip adding icons
const HOVER_MODEL_BOTTOM_SECTION = "Xwe3c0LwQGRjmLBfrpFu6"; // hoverable games with purchase option
const ClassNamesToCheck = [
RECOMMENDED_GAME_ON_STORE_BUTTON_HOVER,
RECOMMENDED_GAME_ON_STORE_BUTTON_SMALL,
SCREENSHOT,
SIMILIAR_APP,
BUNDLE_ITEM,
BREADCRUMBS // Add breadcrumbs to direct class check
];
const ClassNamesToCheckInParent = [
LIST_ITEM_TITLE,
PAGE_TITLE,
BUNDLE_CONTENTS,
DLC_LIST,
HOVER_MODAL,
MINI_WIDGET_TITLE,
FLOATING_STREAM,
PACKAGE_CONTENTS_CAPSULES,
BASE_GAME_OF_DLC,
GAME_AREA_DESCRIPTION,
RECOMMENDATION_LINK,
VALVE_INDEX,
VALVE_INDEX_ALYX_PROMO,
PURCHASE_NOTE,
DISCOVERY_QUEUE,
RECOMMENDED_GAME_REASON,
BREADCRUMBS // Add breadcrumbs to parent check
];
const shouldSkipAddingLineGlobals = {
specificClassesToCheck: [BANNER_GAME_TITLE],
specificClassesToCheckInParent: [],
};
const shouldSkipAddingIconGlobals = {
specificClassesToCheck: [BANNER_GAME_COVER_ART, HOVER_MODEL_BOTTOM_SECTION],
specificClassesToCheckInParent: [],
};
const RelativeClassCheck = [];
const ParentRelativeClassCheck = [
PACKAGE_ITEM,
STORE_SALE_WIDGET_MINI,
LARGE_GAME_ITEM,
IMPRESSION_TRACKED_ELEMENT,
];
@@ -0,0 +1,67 @@
function setColors() {
chrome.storage.local.get(
{
borkedColor: "#FF0000",
bronzeColor: "#CD7F32",
silverColor: "#A6A6A6",
goldColor: "#CFB53B",
platinumColor: "#B4C7DC",
unknownColor: "#3D3D3D",
},
(result) => {
colors.borked = result.borkedColor;
colors.bronze = result.bronzeColor;
colors.silver = result.silverColor;
colors.gold = result.goldColor;
colors.platinum = result.platinumColor;
colors.unknown = result.unknownColor;
}
);
}
function parseAppId(item) {
const link = item.getAttribute("href");
const linkMatch = link ? link.match(/\/app\/(\d+)/) : null;
const dataAppId = item ? item.getAttribute("data-ds-appid") : null; // Get the value of data-ds-appid attribute
return linkMatch && linkMatch[1]
? linkMatch[1]
: dataAppId !== null
? dataAppId
: null;
}
function getAppId() {
var url = document.URL;
// Extract the number after "/app/"
var match = url.match(/\/app\/(\d+)/);
var appId = match ? match[1] : null;
return appId;
}
function getLineColor(tier) {
return colors[tier] || colors.unknown;
}
function hasAnyClass(element, classNames) {
const elementClassName = element.className;
for (const className of classNames) {
if (elementClassName.includes(className)) {
return true;
}
}
return false;
}
function hasAnyParentClass(element, classNames) {
let parent = element.parentElement;
for (const className of classNames) {
if (element.closest(className)) {
return true;
}
}
return false;
}
@@ -0,0 +1,149 @@
function shouldSkipAddingLine(item) {
if (!item) return true;
const parent = item?.parentNode ? item.parentNode : item;
// Skip if parent is a paragraph or item already has a line
if (parent?.tagName === "P" || item.querySelector(".vertical-line")) {
return true;
}
// Skip if item matches specific conditions
if (item.matches(".gutter_item") ||
hasAnyClass(item, shouldSkipAddingLineGlobals.specificClassesToCheck) ||
hasAnyParentClass(item, shouldSkipAddingLineGlobals.specificClassesToCheckInParent)) {
return true;
}
shouldMakeItemRelative(item);
return false;
}
function shouldSkipAddingIcon(item) {
if (!item) return true;
const parent = item?.parentNode ? item.parentNode : item;
// Skip if parent is a paragraph or item already has an icon
if (parent?.tagName === "P" || item.querySelector(".deck-status-icon")) {
return true;
}
// Skip if item matches specific conditions
if (item.matches(".gutter_item") ||
hasAnyClass(item, shouldSkipAddingIconGlobals.specificClassesToCheck) ||
hasAnyParentClass(item, shouldSkipAddingIconGlobals.specificClassesToCheckInParent)) {
return true;
}
shouldMakeItemRelative(item);
return false;
}
function shouldMakeItemRelative(item) {
if (!item || item.style.position === "relative") return false;
if (hasAnyClass(item, RelativeClassCheck)) {
makeItemRelative(item);
}
if (hasAnyParentClass(item, ParentRelativeClassCheck)) {
makeItemRelative(item.parentNode);
}
return false;
}
function makeItemRelative(item) {
if (item) item.style.position = "relative";
}
async function addLineToItem(item) {
if (shouldSkipAddingLine(item)) return;
const appId = parseAppId(item);
if (!appId) return;
try {
const data = await new Promise((resolve, reject) => {
browserAPI.runtime.sendMessage(
{ contentScriptQuery: "queryGame", appId },
(response) => {
if (browserAPI.runtime.lastError) {
reject(browserAPI.runtime.lastError);
return;
}
resolve(response);
}
);
});
const tier = data?.tier || "unknown";
const color = getLineColor(tier);
const line = createLineElement();
colorLineElement(line, color);
line.dataset.tier = tier;
insertLineBeforeItem(line, item);
} catch (error) {
console.error('[ProtonDB] Error adding line:', error.message);
}
}
async function addIconToItem(item, isItemInSearchBox = false) {
if (shouldSkipAddingIcon(item)) return;
const appId = parseAppId(item);
if (!appId) return;
try {
const data = await new Promise((resolve, reject) => {
browserAPI.runtime.sendMessage(
{ contentScriptQuery: "queryDeckStats", appId },
(response) => {
if (browserAPI.runtime.lastError) {
reject(browserAPI.runtime.lastError);
return;
}
resolve(response);
}
);
});
if (!data?.icon) return;
const icon = createIconElement(data.status, data.icon);
const url = document.URL;
if (isItemInSearchBox) {
insertIconBeforeItem(icon, item);
} else if (url.includes("https://store.steampowered.com/search/")) {
insertIconInList(icon, item);
} else if (isItemInTabList(item)) {
insertIconInTabList(icon, item);
} else {
insertIconBeforeItem(icon, item);
}
} catch (error) {
console.error('[ProtonDB] Error adding icon:', error.message);
}
}
function isItemInTabList(item) {
return item.classList.contains("tab_item") || item.classList.contains("tab_item_overlay");
}
function addElementsToItems(item, isItemInSearchBox = false) {
if (!item) return;
addLineToItem(item);
addIconToItem(item, isItemInSearchBox);
items.add(item);
}
function getNewItems() {
const selector = 'a[href^="https://store.steampowered.com/app/"]:not([class*="ReviewScore"]):not(a[class*="ReviewScore"]), a.home_marketing_message.small.app_impression_tracked';
return Array.from(document.querySelectorAll(selector))
.filter(item =>
!items.has(item) &&
!hasAnyClass(item, ClassNamesToCheck) &&
!hasAnyParentClass(item, ClassNamesToCheckInParent)
);
}
@@ -0,0 +1,72 @@
function handleContentMutation(mutationsList) {
mutationsList.forEach((mutation) => {
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const newItems = getNewItems(node);
if (newItems.length > 0) {
newItems.forEach((item) => {
addElementsToItems(
item,
item.parentNode?.id === "search_suggestion_contents"
? true
: false
);
});
}
}
});
}
});
}
function handleStorageMutation(changes, namespace) {
// Identify tiers that have changed color
const changedTiers = [];
for (let [key, { oldValue, newValue }] of Object.entries(changes)) {
if (key.endsWith("Color")) {
const tier = key.replace("Color", "");
colors[tier] = newValue;
changedTiers.push(tier);
}
}
// If any tiers had their colors changed, update the lines and rank value spans
if (changedTiers.length > 0) {
const selector = changedTiers
.map((tier) => `.vertical-line[data-tier="${tier}"]`)
.join(",");
const lines = document.querySelectorAll(selector);
lines.forEach((line) => {
const tier = line.dataset.tier;
line.style.backgroundColor = colors[tier];
// Update the rank value spans with the changed tier
const lowercaseTier = tier.toLowerCase();
const rankValueSpans = document.querySelectorAll(
`.protondb_rank_value[data-tier="${lowercaseTier}"]`
);
if (rankValueSpans.length === 0) return;
rankValueSpans.forEach((rankValueSpan) => {
if (colors.hasOwnProperty(lowercaseTier)) {
rankValueSpan.style.color = colors[lowercaseTier];
}
});
});
}
}
const contentObserver = new MutationObserver(handleContentMutation);
observers.push(contentObserver);
function startObservers() {
setColors();
observers.forEach((observer) => {
observer.observe(document, { childList: true, subtree: true });
});
}
// Listen to changes in the local storage
chrome.storage.onChanged.addListener(handleStorageMutation);