adds .local/share/millennium and easyeffects
@@ -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 |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
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
|
||||
}
|
||||
});
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 277 B |
|
After Width: | Height: | Size: 886 B |
|
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"}
|
||||
|
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);
|
||||