From b284d5c27720d01a7aa006f4c2314b70fe58bba4 Mon Sep 17 00:00:00 2001 From: Antoine Rybacki <15911822+Lifeismana@users.noreply.github.com> Date: Wed, 16 Oct 2024 00:27:39 +0200 Subject: [PATCH 1/3] Move token fetching to its own function --- scripts/background.js | 76 +++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/scripts/background.js b/scripts/background.js index f94dd5017..6501c15b8 100644 --- a/scripts/background.js +++ b/scripts/background.js @@ -5,6 +5,7 @@ let checkoutSessionId; let userDataCache = null; let userFamilyDataCache = null; let userFamilySemaphore = null; +let tokenSemaphore = null; let nextAllowedRequest = 0; /** @type {browser} ExtensionApi */ @@ -196,24 +197,9 @@ async function FetchSteamUserFamilyData( callback ) try { - const tokenResponseFetch = await fetch( - `https://store.steampowered.com/pointssummary/ajaxgetasyncconfig`, - { - credentials: 'include', - headers: { - Accept: 'application/json', - }, - } - ); - const token = await tokenResponseFetch.json(); - - if( !token || !token.success || !token.data || !token.data.webapi_token ) - { - throw new Error( 'Are you logged on the Steam Store in this browser?' ); - } - + const token = await GetStoreToken(); const paramsSharedLibrary = new URLSearchParams(); - paramsSharedLibrary.set( 'access_token', token.data.webapi_token ); + paramsSharedLibrary.set( 'access_token', token ); paramsSharedLibrary.set( 'family_groupid', '0' ); // family_groupid is ignored paramsSharedLibrary.set( 'include_excluded', 'true' ); paramsSharedLibrary.set( 'include_free', 'true' ); @@ -402,6 +388,62 @@ function GetAchievementsGroups( appid, callback ) .catch( ( error ) => callback( { success: false, error: error.message } ) ); } +/** + * @return {Promise} + */ +async function GetStoreToken() +{ + if( tokenSemaphore !== null ) + { + return await tokenSemaphore ; + } + let token = null; + let semaphoreResolve = null; + tokenSemaphore = new Promise( resolve => + { + semaphoreResolve = resolve; + } ); + + try + { + token = await GetLocalOption( { storetoken: false } ).then( data => data.storetoken ); + if( token ) + { + const jwt = token.split( '.' ); + const payload = JSON.parse( atob( jwt[ 1 ] ) ); + const expiration = payload.exp * 1000; + if( Date.now() < expiration ) + { + return token; + } + } + + token = await fetch( + `https://store.steampowered.com/pointssummary/ajaxgetasyncconfig`, + { + credentials: 'include', + headers: { + Accept: 'application/json', + }, + } + ).then( response =>response.json() ); + + if( !token || !token.success || !token.data || !token.data.webapi_token ) + { + throw new Error( 'Are you logged on the Steam Store in this browser?' ); + } + + await SetLocalOption( 'storetoken', token.data.webapi_token ); + + return token.data.webapi_token; + } + finally + { + semaphoreResolve( token ); + tokenSemaphore = null; + }; +} + /** * @param {Function} callback */ From 91cedb8ed6146739e9a4df37d1d1d3f00718c8ad Mon Sep 17 00:00:00 2001 From: Antoine Rybacki <15911822+Lifeismana@users.noreply.github.com> Date: Wed, 16 Oct 2024 00:39:31 +0200 Subject: [PATCH 2/3] Add Fetching of private apps --- scripts/background.js | 96 +++++++++++++++++++++++++++++++++++++++ scripts/steamdb/global.js | 22 ++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/scripts/background.js b/scripts/background.js index 6501c15b8..9f065ee62 100644 --- a/scripts/background.js +++ b/scripts/background.js @@ -4,7 +4,9 @@ let storeSessionId; let checkoutSessionId; let userDataCache = null; let userFamilyDataCache = null; +let userPrivateAppsCache = null; let userFamilySemaphore = null; +let userPrivateAppsSemaphore = null; let tokenSemaphore = null; let nextAllowedRequest = 0; @@ -48,6 +50,7 @@ ExtensionApi.runtime.onMessage.addListener( ( request, sender, callback ) => switch( request.contentScriptQuery ) { case 'InvalidateCache': InvalidateCache(); callback(); return true; + case 'FetchPrivateApps': FetchPrivateApps( callback ); return true; case 'FetchSteamUserData': FetchSteamUserData( callback ); return true; case 'FetchSteamUserFamilyData': FetchSteamUserFamilyData( callback ); return true; case 'GetApp': GetApp( request.appid, callback ); return true; @@ -78,6 +81,99 @@ function InvalidateCache() SetLocalOption( 'userfamilydata', '{}' ); } +/** + * @param {Function} callback + */ +async function FetchPrivateApps( callback ) +{ + if( userPrivateAppsCache !== null ) + { + callback( { data: userPrivateAppsCache } ); + return; + } + + if( userPrivateAppsSemaphore !== null ) + { + callback( await userFamilySemaphore ); + return; + } + + const now = Date.now(); + const cacheData = await GetLocalOption( { privateappsdata: false } ); + const cache = cacheData.userfamilydata && cacheData.userfamilydata; + + if( cache && cache.cached && cache.data && now < cache.cached + 21600000 ) + { + callback( { data: cache.data } ); + return; + } + + let callbackResponse = null; + let semaphoreResolve = null; + userPrivateAppsSemaphore = new Promise( resolve => + { + semaphoreResolve = resolve; + } ); + + try + { + const token = await GetStoreToken(); + const paramsPrivateApps = new URLSearchParams(); + paramsPrivateApps.set( 'access_token', token ); + const responseFetch = await fetch( + `https://api.steampowered.com/IAccountPrivateAppsService/GetPrivateAppList/v1/?${paramsPrivateApps.toString()}`, + { + headers: { + Accept: 'application/json', + } + } + ); + const response = await responseFetch.json(); + + if( !response || !response.response || !response.response.private_apps ) + { + throw new Error( 'Is Steam okay?' ); + } + + userPrivateAppsCache = + { + rgPrivateApps: response.response.private_apps.appids || [], + }; + + callbackResponse = + { + data: userPrivateAppsCache + }; + + callback( callbackResponse ); + + await SetLocalOption( 'privateappsdata', JSON.stringify( { + data: userPrivateAppsCache, + cached: now + } ) ); + } + catch( error ) + { + callbackResponse = + { + error: error.message, + }; + + if( cache && cache.data ) + { + callbackResponse.data = cache.data; + } + + callback( callbackResponse ); + } + finally + { + semaphoreResolve( callbackResponse ); + userPrivateAppsSemaphore = null; + } + +}; + /** * @param {Function} callback */ diff --git a/scripts/steamdb/global.js b/scripts/steamdb/global.js index 634827141..601dcae55 100644 --- a/scripts/steamdb/global.js +++ b/scripts/steamdb/global.js @@ -80,6 +80,14 @@ GetOption( { 'steamdb-highlight': true, 'steamdb-highlight-family': true }, asyn }, resolve ); } ); + /** @type {Promise<{data?: object, error?: string}>} */ + const PrivateDataPromise = new Promise( ( resolve ) => + { + SendMessageToBackgroundScript( { + contentScriptQuery: 'FetchPrivateApps', + }, resolve ); + } ); + /** @type {Promise<{data?: object, error?: string}>} */ const familyDataPromise = new Promise( ( resolve ) => { @@ -103,7 +111,7 @@ GetOption( { 'steamdb-highlight': true, 'steamdb-highlight-family': true }, asyn }, 10000 ); // 10 seconds } ); - const userData = await userDataPromise; + const [ userData, privateData ] = await Promise.all( [ userDataPromise, PrivateDataPromise ] ); // If family data does not load fast enough, assume it failed const familyData = await Promise.race( [ @@ -116,6 +124,11 @@ GetOption( { 'steamdb-highlight': true, 'steamdb-highlight-family': true }, asyn WriteLog( 'Failed to load userdata', userData.error ); } + if( privateData.error ) + { + WriteLog( 'Failed to load privatedata', privateData.error ); + } + if( familyData.error ) { WriteLog( 'Failed to load family userdata', familyData.error ); @@ -128,6 +141,11 @@ GetOption( { 'steamdb-highlight': true, 'steamdb-highlight-family': true }, asyn { response = userData.data; + if( privateData.data ) + { + response.rgPrivateApps = privateData.data.rgPrivateApps; + } + if( familyData.data ) { response.rgFamilySharedApps = familyData.data.rgFamilySharedApps; @@ -159,6 +177,8 @@ GetOption( { 'steamdb-highlight': true, 'steamdb-highlight-family': true }, asyn beforeDom ? '(before dom completed)' : '', 'Packages', response.rgOwnedPackages?.length || 0, + 'Private Apps', + response.rgPrivateApps?.length || 0, 'Family Apps', response.rgFamilySharedApps?.length || 0, ); From c1c260636870ca2bab91be216206b60e9a1b7944 Mon Sep 17 00:00:00 2001 From: Antoine Rybacki <15911822+Lifeismana@users.noreply.github.com> Date: Wed, 16 Oct 2024 01:07:30 +0200 Subject: [PATCH 3/3] Add toggling private an app --- scripts/background.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scripts/background.js b/scripts/background.js index 9f065ee62..de07ef9fd 100644 --- a/scripts/background.js +++ b/scripts/background.js @@ -56,6 +56,7 @@ ExtensionApi.runtime.onMessage.addListener( ( request, sender, callback ) => case 'GetApp': GetApp( request.appid, callback ); return true; case 'GetAppPrice': GetAppPrice( request, callback ); return true; case 'GetAchievementsGroups': GetAchievementsGroups( request.appid, callback ); return true; + case 'SetAppPrivate': SetAppPrivate( request.appids, request.private, callback ); return true; case 'StoreWishlistAdd': StoreWishlistAdd( request.appid, callback ); return true; case 'StoreWishlistRemove': StoreWishlistRemove( request.appid, callback ); return true; case 'StoreFollow': StoreFollow( request.appid, callback ); return true; @@ -484,6 +485,27 @@ function GetAchievementsGroups( appid, callback ) .catch( ( error ) => callback( { success: false, error: error.message } ) ); } +/** + * @param {Array} appids + * @param {Boolean} privateState + * @param {Function} callback + */ +// ? Api supports setting multiple apps at once (Are they even using that feature?), do we really need that? +async function SetAppPrivate( appids, privateState, callback ) +{ + const token = await GetStoreToken(); + const paramsSetPrivate = new URLSearchParams(); + paramsSetPrivate.set( 'access_token', token ); + appids.forEach( ( appid, index ) => + { + paramsSetPrivate.set( `appids[${index}]`, appid ); + } ); + paramsSetPrivate.set( 'private', privateState ); + const responseFetch = await fetch( + `https://api.steampowered.com/IAccountPrivateAppsService/ToggleAppPrivacy/v1/?${paramsSetPrivate.toString()}` + ); +} + /** * @return {Promise} */