Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions _locales/de/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,9 @@
"options_builtin_achievements_csrating": {
"message": "Verlauf der CS-Wertung bei den CS2-Errungenschaften anzeigen"
},
"options_profile_gamecovers": {
"message": "Spielcover mit Regionsbeschränkung anzeigen"
},
"boostercreator_available_at_date": {
"message": "Du wirst bis zum $available_date$ kein weiteres Booster-Pack für dieses Spiel herstellen können.",
"description": "\"Booster Pack\" should match how Steam itself names it at: https://steamcommunity.com/tradingcards/boostercreator/",
Expand Down
4 changes: 3 additions & 1 deletion _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,9 @@
"options_builtin_achievements_csrating": {
"message": "Display CS rating history on CS2 achievements page"
},

"options_profile_gamecovers": {
"message": "Display game covers with country restriction"
},
"boostercreator_available_at_date": {
"message": "You will not be able to create another Booster Pack for this game until $available_date$.",
"description": "\"Booster Pack\" should match how Steam itself names it at: https://steamcommunity.com/tradingcards/boostercreator/",
Expand Down
3 changes: 3 additions & 0 deletions _locales/ru/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,9 @@
"options_builtin_achievements_csrating": {
"message": "Отображать историю рейтинга CS на странице достижений CS2"
},
"options_profile_gamecovers": {
"message": "Отображать обложки игр c региональным ограничением"
},
"boostercreator_available_at_date": {
"message": "Вы не сможете создать дополнительный набор карточек этой игры до $available_date$.",
"description": "\"Booster Pack\" should match how Steam itself names it at: https://steamcommunity.com/tradingcards/boostercreator/",
Expand Down
3 changes: 3 additions & 0 deletions _locales/uk/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,9 @@
"options_builtin_achievements_csrating": {
"message": "Показувати історію рейтингу CS на сторінці досягнень CS2"
},
"options_profile_gamecovers": {
"message": "Показувати обкладинки ігор з регіональним обмеженням"
},
"boostercreator_available_at_date": {
"message": "Ви не зможете створити інший комплект для цієї гри до $available_date$.",
"description": "\"Booster Pack\" should match how Steam itself names it at: https://steamcommunity.com/tradingcards/boostercreator/",
Expand Down
4 changes: 4 additions & 0 deletions icons/applogo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"icons/steamhunters.svg",
"icons/image.svg",
"icons/achievements_completed.svg",
"icons/applogo.svg",

"styles/appicon.css",
"styles/inventory-sidebar.css",
Expand All @@ -82,6 +83,7 @@
"scripts/community/agecheck_injected.js",
"scripts/community/filedetails_award_injected.js",
"scripts/community/profile_award_injected.js",
"scripts/community/profile_gamecovers_injected.js",
"scripts/community/tradeoffer_injected.js",
"scripts/community/boostercreator_injected.js",
"scripts/store/app_collapse_long_strings.js",
Expand Down
6 changes: 5 additions & 1 deletion options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,13 @@ <h3 data-msg="options_header_enhancements"></h3>
<div><a href="https://github.com/SteamDatabase/BrowserExtension#automatically-open-grant-an-award-popup-from-a-link-using-award" target="_blank" class="muted" data-msg="options_github_explanation"></a></div>
</div>
</label>
<label class="checkbox">
<input type="checkbox" class="option-check" data-option="profile-gamecovers" checked>
<div data-msg="options_profile_gamecovers"></div>
</label>
<div class="checkbox">
<input type="checkbox" class="option-check" checked disabled>
<div data-msg="options_builtin_collapse_library"></div>
<div data-msg="options_builtin_collapse_library"></div>
</div>
<div class="checkbox">
<input type="checkbox" class="option-check" checked disabled>
Expand Down
11 changes: 11 additions & 0 deletions scripts/community/profile.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

GetOption( {
'profile-gamecovers': true,
'profile-calculator': true,
'enhancement-award-popup-url': true,
}, ( items ) =>
Expand All @@ -14,6 +15,16 @@ GetOption( {
document.head.appendChild( script );
}

if( items[ 'profile-gamecovers' ] )
{
const script = document.createElement( 'script' );
script.id = 'steamdb_profile_gamecovers';
script.type = 'text/javascript';
script.dataset.appLogo = GetLocalResource( 'icons/applogo.svg' );
script.src = GetLocalResource( 'scripts/community/profile_gamecovers_injected.js' );
document.head.appendChild( script );
}

if( !items[ 'profile-calculator' ] )
{
return;
Expand Down
242 changes: 242 additions & 0 deletions scripts/community/profile_gamecovers_injected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
'use strict';

( () =>
{
const profilePagesRegex = /^https:\/\/steamcommunity\.com\/(id|profiles)\/[^/]+(\/games(\/[^/]+)?)?\/?$/;
if( !profilePagesRegex.test( window.location.href ) )
{
return;
}

// Check if "games" part
const isGamesPage = window.location.href.match( profilePagesRegex )[ 2 ] !== undefined;

/** @type {HTMLScriptElement} */
const currentScript = document.querySelector( '#steamdb_profile_gamecovers' );
const fallbackCoverImage = currentScript.dataset.appLogo;

/** @type {Map<number, HTMLImageElement>} */
const appsImageStore = new Map();

/**
* @param {string} url
* @returns {number}
*/
function GetAppIDFromUrl( url )
{
const appid = url.match( /\/(?:app|sub|bundle|friendsthatplay|gamecards|recommended|widget)\/(?<id>[0-9]+)/ );
return appid ? Number.parseInt( appid.groups.id, 10 ) : -1;
}

/** @param {Record<string, any>} node */
function GetReactFiber( node )
{
const reactFiberKey = Object.keys( node ).find( key => key.startsWith( '__reactFiber' ) );
return node[ reactFiberKey ];
}

/** @param {HTMLImageElement} image */
function CheckValidImg( image )
{
if( image.complete && image.naturalWidth === 0 )
{
return false;
}

const imageSrc = image.src;

if( imageSrc === undefined || imageSrc === null )
{
return false;
}

if( imageSrc === fallbackCoverImage )
{
return true;
}

// Empty src is equal to the current location
return imageSrc !== ""
&& imageSrc !== window.location.href
// Skip fallback cover from Steam CDN
&& !imageSrc.includes( 'public/ssr' );
}

/**
* @param {number} appId
* @param {string} path
* @returns {string}
*/
function GetCoverUrl( appId, path )
{
return `https://shared.fastly.steamstatic.com/store_item_assets/steam/apps/${appId}/${path}?t=${Date.now()}`;
}

/**
* @param {number} appId
* @param {HTMLImageElement} img
*/
function StoreCoverImage( appId, img )
{
img.src = fallbackCoverImage;

// Handle load error
img.addEventListener( 'error', () =>
{
// Rollback
img.src = fallbackCoverImage;
appsImageStore.delete( appId );
}, { once: true } );

appsImageStore.set( appId, img );
}

function ParseGamePictureCovers()
{
const gamePictures = document.querySelectorAll( 'picture' );
for( const picture of gamePictures )
{
const fiber = GetReactFiber( picture );
if( !fiber )
{
continue;
}

const gameProps = fiber.return.memoizedProps?.game;
if( !gameProps )
{
continue;
}

const appId = gameProps.appid;
if( appsImageStore.has( appId ) )
{
continue;
}

const coverImg = picture.querySelector( 'img' );
if( !coverImg )
{
continue;
}

if( CheckValidImg( coverImg ) )
{
continue;
}

const spanPicture = picture.nextSibling;
if( spanPicture instanceof HTMLSpanElement )
{
spanPicture.style.display = 'none';
}

StoreCoverImage( appId, coverImg );
}
}

function ParseGameCoverCapsules()
{
/** @type {NodeListOf<HTMLImageElement>} */
const gameCovers = document.querySelectorAll( 'img.game_capsule' );
for( const cover of gameCovers )
{
if( CheckValidImg( cover ) )
{
continue;
}

const parent = cover.parentElement;
if( parent instanceof HTMLAnchorElement )
{
const appId = GetAppIDFromUrl( parent.href );
if( appsImageStore.has( appId ) )
{
continue;
}

StoreCoverImage( appId, cover );
}
}
}

async function LoadGameCovers()
{
if( appsImageStore.size === 0 )
{
return;
}

const appIds = Array.from( appsImageStore.keys() ).slice( 0, 50 );
console.log( '[SteamDB]: Loading apps', appIds );

// https://api.steampowered.com/IStoreBrowseService/GetItems/v1/?input_json={"ids":[{"appid":"654310"},{"appid":"1091500"},{"appid":"730"}],"context":{"country_code":"US"},"data_request":{"include_assets":true}}
const url = new URL( 'https://api.steampowered.com/IStoreBrowseService/GetItems/v1/' );
url.searchParams.set( 'input_json', JSON.stringify( {
ids: appIds.slice( 0, 50 ).map( ( appid ) => ( { appid } ) ),
context: {
country_code: 'US',
},
data_request: {
include_assets: true,
},
} ) );

try
{
const req = await fetch( url , {
headers: {
'X-Requested-With': 'SteamDB',
},
} );

const res = await req.json();

for( const item of res.response.store_items )
{
const appImage = appsImageStore.get( item.appid );
if( !appImage )
{
continue;
}

const headerAsset = item.assets?.header;
if( headerAsset )
{
appImage.src = GetCoverUrl( item.appid, headerAsset );
}
else
{
appImage.src = GetCoverUrl( item.appid, 'header.jpg' );
}

appsImageStore.delete( item.appid );
}
}
catch( err )
{
console.error( '[SteamDB]: Failed to load app', err );
}
}

function InvokeParseCovers()
{
if( isGamesPage )
{
ParseGamePictureCovers();
}
else
{
ParseGameCoverCapsules();
}

LoadGameCovers();
}

InvokeParseCovers();

setInterval( () =>
{
InvokeParseCovers();
}, 10_000 );
} )();