From 27b7e077d80f98010fa9d0ee01afc8d6a768b9eb Mon Sep 17 00:00:00 2001 From: GODrums Date: Fri, 13 Dec 2024 21:14:26 +0100 Subject: [PATCH 01/14] Display item metadata on /tradeoffers --- src/lib/alarms/trade_offer.ts | 81 +++++++++++++++++++ src/lib/bridge/handlers/fetch_steam_trades.ts | 42 ++++++++++ src/lib/bridge/handlers/handlers.ts | 2 + src/lib/bridge/handlers/types.ts | 1 + .../trade_offer_holder_metadata.ts | 21 +++++ src/lib/page_scripts/trade_offers.ts | 75 +++++++++++++++++ 6 files changed, 222 insertions(+) create mode 100644 src/lib/bridge/handlers/fetch_steam_trades.ts create mode 100644 src/lib/components/trade_offers/trade_offer_holder_metadata.ts diff --git a/src/lib/alarms/trade_offer.ts b/src/lib/alarms/trade_offer.ts index 5e46a0d0..8353d242 100644 --- a/src/lib/alarms/trade_offer.ts +++ b/src/lib/alarms/trade_offer.ts @@ -6,6 +6,9 @@ import {AnnotateOffer} from '../bridge/handlers/annotate_offer'; import {PingCancelTrade} from '../bridge/handlers/ping_cancel_trade'; import {CancelTradeOffer} from '../bridge/handlers/cancel_trade_offer'; import {FetchSteamUser} from '../bridge/handlers/fetch_steam_user'; +import { rgDescription } from '../types/steam'; +import { HasPermissions } from '../bridge/handlers/has_permissions'; +import { ExtendedOfferStatus, ExtendedSingleOffer } from '../bridge/handlers/fetch_steam_trades'; export async function pingSentTradeOffers(pendingTrades: Trade[]) { const {offers, type} = await getSentTradeOffers(); @@ -191,6 +194,13 @@ async function getSentTradeOffers(): Promise<{offers: OfferStatus[]; type: Trade interface TradeOfferItem { assetid: string; + appid: number; + contextid: string; + classid: string; + instanceid: string; + amount: string; + missing: boolean; + est_usd: string; } interface TradeOffersAPIOffer { @@ -207,6 +217,7 @@ interface TradeOffersAPIResponse { response: { trade_offers_sent: TradeOffersAPIOffer[]; trade_offers_received: TradeOffersAPIOffer[]; + descriptions?: rgDescription[]; }; } @@ -222,6 +233,30 @@ function offerStateMapper(e: TradeOffersAPIOffer): OfferStatus { } as OfferStatus; } +function extendedOfferStateMapper(e: TradeOffersAPIOffer) { + return { + offer_id: e.tradeofferid, + state: e.trade_offer_state, + given_asset_ids: (e.items_to_give || []).map((e) => { + return { + assetid: e.assetid, + classid: e.classid, + instanceid: e.instanceid, + } as ExtendedSingleOffer; + }), + received_asset_ids: (e.items_to_receive || []).map((e) => { + return { + assetid: e.assetid, + classid: e.classid, + instanceid: e.instanceid, + } as ExtendedSingleOffer; + }), + time_created: e.time_created, + time_updated: e.time_updated, + other_steam_id64: (BigInt('76561197960265728') + BigInt(e.accountid_other)).toString(), + }; +} + async function getSentTradeOffersFromAPI(): Promise { const access = await getAccessToken(); @@ -266,6 +301,52 @@ async function getSentAndReceivedTradeOffersFromAPI(): Promise<{ }; } + +export async function getTradeOffersWithDescriptionFromAPI(): Promise<{ + received: ExtendedOfferStatus[]; + sent: ExtendedOfferStatus[]; + descriptions: rgDescription[]; + steam_id?: string | null; +}> { + const access = await getAccessToken(); + + // check if permissions are granted + const steamPoweredPermissions = await HasPermissions.handleRequest( + { + permissions: [], + origins: ['https://api.steampowered.com/*'], + }, + {} + ); + if (!steamPoweredPermissions.granted) { + return { + received: [], + sent: [], + descriptions: [], + steam_id: access.steam_id, + } + } + + const resp = await fetch( + `https://api.steampowered.com/IEconService/GetTradeOffers/v1/?access_token=${access.token}&get_received_offers=true&get_sent_offers=true&get_descriptions=true`, + { + credentials: 'include', + } + ); + + if (resp.status !== 200) { + throw new Error('invalid status'); + } + + const data = (await resp.json()) as TradeOffersAPIResponse; + return { + received: (data.response?.trade_offers_received || []).map(extendedOfferStateMapper), + sent: (data.response?.trade_offers_sent || []).map(extendedOfferStateMapper), + steam_id: access.steam_id, + descriptions: data.response?.descriptions || [], + }; +} + const BANNER_TO_STATE: {[banner: string]: TradeOfferState} = { accepted: TradeOfferState.Accepted, counter: TradeOfferState.Countered, diff --git a/src/lib/bridge/handlers/fetch_steam_trades.ts b/src/lib/bridge/handlers/fetch_steam_trades.ts new file mode 100644 index 00000000..c346b64b --- /dev/null +++ b/src/lib/bridge/handlers/fetch_steam_trades.ts @@ -0,0 +1,42 @@ +import { getTradeOffersWithDescriptionFromAPI } from '../../alarms/trade_offer'; +import { rgDescription } from '../../types/steam'; +import { TradeOfferState } from '../../types/steam_constants'; +import {SimpleHandler} from './main'; +import {RequestType} from './types'; + +interface FetchSteamTradesRequest {} + +export interface FetchSteamTradesResponse { + received: ExtendedOfferStatus[]; + sent: ExtendedOfferStatus[]; + descriptions: rgDescription[]; + steam_id?: string | null; +} + +export interface ExtendedOfferStatus { + offer_id: string; + state: TradeOfferState; + given_asset_ids?: ExtendedSingleOffer[]; + received_asset_ids?: ExtendedSingleOffer[]; + time_created?: number; + time_updated?: number; + other_steam_id64?: string; +} + +export interface ExtendedSingleOffer { + assetid: string; + classid: string; + instanceid: string; +} + +export const FetchSteamTrades = new SimpleHandler( + RequestType.FETCH_STEAM_TRADES, + async (req) => { + const resp = await getTradeOffersWithDescriptionFromAPI(); + if (!resp) { + throw new Error('Error fetching Steam trade offers from API'); + } + + return resp; + } +); diff --git a/src/lib/bridge/handlers/handlers.ts b/src/lib/bridge/handlers/handlers.ts index 6ae8d08f..d5c55f0a 100644 --- a/src/lib/bridge/handlers/handlers.ts +++ b/src/lib/bridge/handlers/handlers.ts @@ -23,6 +23,7 @@ import {PingTradeStatus} from './ping_trade_status'; import {PingStatus} from './ping_status'; import {FetchOwnInventory} from './fetch_own_inventory'; import {CancelTradeOffer} from './cancel_trade_offer'; +import { FetchSteamTrades } from './fetch_steam_trades'; export const HANDLERS_MAP: {[key in RequestType]: RequestHandler} = { [RequestType.EXECUTE_SCRIPT_ON_PAGE]: ExecuteScriptOnPage, @@ -48,4 +49,5 @@ export const HANDLERS_MAP: {[key in RequestType]: RequestHandler} = { [RequestType.PING_STATUS]: PingStatus, [RequestType.FETCH_OWN_INVENTORY]: FetchOwnInventory, [RequestType.CANCEL_TRADE_OFFER]: CancelTradeOffer, + [RequestType.FETCH_STEAM_TRADES]: FetchSteamTrades, }; diff --git a/src/lib/bridge/handlers/types.ts b/src/lib/bridge/handlers/types.ts index c389f9c5..a50e8273 100644 --- a/src/lib/bridge/handlers/types.ts +++ b/src/lib/bridge/handlers/types.ts @@ -22,4 +22,5 @@ export enum RequestType { PING_STATUS = 20, FETCH_OWN_INVENTORY = 21, CANCEL_TRADE_OFFER = 22, + FETCH_STEAM_TRADES = 23, } diff --git a/src/lib/components/trade_offers/trade_offer_holder_metadata.ts b/src/lib/components/trade_offers/trade_offer_holder_metadata.ts new file mode 100644 index 00000000..f9263bbe --- /dev/null +++ b/src/lib/components/trade_offers/trade_offer_holder_metadata.ts @@ -0,0 +1,21 @@ +import {CustomElement, InjectAppend, InjectionMode} from '../injectors'; +import {ItemHolderMetadata} from '../common/item_holder_metadata'; +import {rgAsset} from '../../types/steam'; + +// Annotates item info (float, seed, etc...) in boxes on the Trade Offers Page +@CustomElement() +// Items in received/sent trade offers +@InjectAppend('.tradeoffer .trade_item', InjectionMode.CONTINUOUS) +export class TradeOfferHolderMetadata extends ItemHolderMetadata { + get assetId(): string | undefined { + return $J(this).parent().attr('data-assetid'); + } + + get asset(): rgAsset | undefined { + return JSON.parse($J(this).parent().attr('data-description') ?? '{}') as rgAsset; + } + + get ownerSteamId(): string | undefined { + return $J(this).parent().attr('data-owner-steamid'); + } +} diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index c9be74c0..8c0cf87c 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -1,15 +1,90 @@ import {init} from './utils'; import '../components/trade_offers/better_tracking'; +import '../components/trade_offers/trade_offer_holder_metadata'; import {inPageContext} from '../utils/snips'; import {ClientSend} from '../bridge/client'; import {PingSetupExtension} from '../bridge/handlers/ping_setup_extension'; import {PingExtensionStatus} from '../bridge/handlers/ping_extension_status'; +import { FetchSteamTrades, FetchSteamTradesResponse } from '../bridge/handlers/fetch_steam_trades'; init('src/lib/page_scripts/trade_offers.js', main); function main() {} +/** + * Gets the trade offers from the local storage or fetches them from the API. + * Local storage serves as a cache here. + * @param isSentPage if the current page is the sent trade offers page + * @returns the trade offers + */ +async function fetchTradeOffers(isSentPage: boolean) { + const g_steamTrades = JSON.parse(localStorage.getItem('g_steamTrades') || '{}') as FetchSteamTradesResponse; + let refetchRequired = true; + if (g_steamTrades.sent || g_steamTrades.received) { + const latestTradeId = Number.parseInt(g_steamTrades[isSentPage ? "sent" : "received"]?.[0].offer_id); + const latestTradeElement = Number.parseInt(document.querySelector('.tradeoffer')?.id.split('_')[1] ?? '0'); + + refetchRequired = Number.isNaN(latestTradeId) || latestTradeId !== latestTradeElement; + } + + if (!refetchRequired) { + return g_steamTrades; + } + + const steamTrades = await ClientSend(FetchSteamTrades, {}); + + localStorage.setItem('g_steamTrades', JSON.stringify(steamTrades)); + return steamTrades; +} + +/** + * Fetches the api data for trade offers and stores relevant data in the DOM to be used by Lit components. + */ +async function getAndStoreTradeOffers() { + const isSentPage = location.pathname.includes("sent"); + + const steamTrades = await fetchTradeOffers(isSentPage); + + const tradeOffers = document.querySelectorAll('.tradeoffer'); + + for (const tradeOffer of tradeOffers) { + const tradeId = tradeOffer.id.split('_')[1]; + const tradeItems = tradeOffer.querySelectorAll('.trade_item'); + const trade = isSentPage ? steamTrades.sent.find(t => t.offer_id === tradeId) : steamTrades.received.find(t => t.offer_id === tradeId); + + for (const tradeItem of tradeItems) { + const economyItemParts = tradeItem.getAttribute('data-economy-item')?.split('/'); + const classId = economyItemParts?.[2]; + const instanceId = economyItemParts?.[3]; + + if (!instanceId) { + continue; + } + + const description = steamTrades.descriptions.find(d => d.classid === classId && d.instanceid === instanceId); + if (description) { + tradeItem.setAttribute('data-description', JSON.stringify(description)); + } + + let isOwnItem = true; + let apiItem = trade?.given_asset_ids?.find(a => a.classid === classId && a.instanceid === instanceId); + if (!apiItem) { + isOwnItem = false; + apiItem = trade?.received_asset_ids?.find(a => a.classid === classId && a.instanceid === instanceId); + } + const ownerId = isOwnItem ? JSON.parse(document.getElementById('application_config')?.dataset?.userinfo || '{}').steamid : trade?.other_steam_id64; + + tradeItem.setAttribute('data-owner-steamid', ownerId); + if (apiItem?.assetid) { + tradeItem.setAttribute('data-assetid', apiItem.assetid); + } + } + } +} + if (!inPageContext()) { + getAndStoreTradeOffers(); + const refresh = setInterval(() => { const widget = document.getElementsByTagName('csfloat-better-tracking-widget'); if (!widget || widget.length === 0) { From c1a09b7ad8de9da842fb4870ccb12ba61fe842a7 Mon Sep 17 00:00:00 2001 From: GODrums Date: Fri, 13 Dec 2024 21:15:10 +0100 Subject: [PATCH 02/14] chore: run format --- src/lib/alarms/trade_offer.ts | 9 +++---- src/lib/bridge/handlers/fetch_steam_trades.ts | 6 ++--- src/lib/bridge/handlers/handlers.ts | 2 +- src/lib/page_scripts/trade_offers.ts | 26 ++++++++++++------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/lib/alarms/trade_offer.ts b/src/lib/alarms/trade_offer.ts index 8353d242..074273de 100644 --- a/src/lib/alarms/trade_offer.ts +++ b/src/lib/alarms/trade_offer.ts @@ -6,9 +6,9 @@ import {AnnotateOffer} from '../bridge/handlers/annotate_offer'; import {PingCancelTrade} from '../bridge/handlers/ping_cancel_trade'; import {CancelTradeOffer} from '../bridge/handlers/cancel_trade_offer'; import {FetchSteamUser} from '../bridge/handlers/fetch_steam_user'; -import { rgDescription } from '../types/steam'; -import { HasPermissions } from '../bridge/handlers/has_permissions'; -import { ExtendedOfferStatus, ExtendedSingleOffer } from '../bridge/handlers/fetch_steam_trades'; +import {rgDescription} from '../types/steam'; +import {HasPermissions} from '../bridge/handlers/has_permissions'; +import {ExtendedOfferStatus, ExtendedSingleOffer} from '../bridge/handlers/fetch_steam_trades'; export async function pingSentTradeOffers(pendingTrades: Trade[]) { const {offers, type} = await getSentTradeOffers(); @@ -301,7 +301,6 @@ async function getSentAndReceivedTradeOffersFromAPI(): Promise<{ }; } - export async function getTradeOffersWithDescriptionFromAPI(): Promise<{ received: ExtendedOfferStatus[]; sent: ExtendedOfferStatus[]; @@ -324,7 +323,7 @@ export async function getTradeOffersWithDescriptionFromAPI(): Promise<{ sent: [], descriptions: [], steam_id: access.steam_id, - } + }; } const resp = await fetch( diff --git a/src/lib/bridge/handlers/fetch_steam_trades.ts b/src/lib/bridge/handlers/fetch_steam_trades.ts index c346b64b..f47180d3 100644 --- a/src/lib/bridge/handlers/fetch_steam_trades.ts +++ b/src/lib/bridge/handlers/fetch_steam_trades.ts @@ -1,6 +1,6 @@ -import { getTradeOffersWithDescriptionFromAPI } from '../../alarms/trade_offer'; -import { rgDescription } from '../../types/steam'; -import { TradeOfferState } from '../../types/steam_constants'; +import {getTradeOffersWithDescriptionFromAPI} from '../../alarms/trade_offer'; +import {rgDescription} from '../../types/steam'; +import {TradeOfferState} from '../../types/steam_constants'; import {SimpleHandler} from './main'; import {RequestType} from './types'; diff --git a/src/lib/bridge/handlers/handlers.ts b/src/lib/bridge/handlers/handlers.ts index d5c55f0a..78ef8ffa 100644 --- a/src/lib/bridge/handlers/handlers.ts +++ b/src/lib/bridge/handlers/handlers.ts @@ -23,7 +23,7 @@ import {PingTradeStatus} from './ping_trade_status'; import {PingStatus} from './ping_status'; import {FetchOwnInventory} from './fetch_own_inventory'; import {CancelTradeOffer} from './cancel_trade_offer'; -import { FetchSteamTrades } from './fetch_steam_trades'; +import {FetchSteamTrades} from './fetch_steam_trades'; export const HANDLERS_MAP: {[key in RequestType]: RequestHandler} = { [RequestType.EXECUTE_SCRIPT_ON_PAGE]: ExecuteScriptOnPage, diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index 8c0cf87c..52e1a989 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -5,7 +5,7 @@ import {inPageContext} from '../utils/snips'; import {ClientSend} from '../bridge/client'; import {PingSetupExtension} from '../bridge/handlers/ping_setup_extension'; import {PingExtensionStatus} from '../bridge/handlers/ping_extension_status'; -import { FetchSteamTrades, FetchSteamTradesResponse } from '../bridge/handlers/fetch_steam_trades'; +import {FetchSteamTrades, FetchSteamTradesResponse} from '../bridge/handlers/fetch_steam_trades'; init('src/lib/page_scripts/trade_offers.js', main); @@ -21,7 +21,7 @@ async function fetchTradeOffers(isSentPage: boolean) { const g_steamTrades = JSON.parse(localStorage.getItem('g_steamTrades') || '{}') as FetchSteamTradesResponse; let refetchRequired = true; if (g_steamTrades.sent || g_steamTrades.received) { - const latestTradeId = Number.parseInt(g_steamTrades[isSentPage ? "sent" : "received"]?.[0].offer_id); + const latestTradeId = Number.parseInt(g_steamTrades[isSentPage ? 'sent' : 'received']?.[0].offer_id); const latestTradeElement = Number.parseInt(document.querySelector('.tradeoffer')?.id.split('_')[1] ?? '0'); refetchRequired = Number.isNaN(latestTradeId) || latestTradeId !== latestTradeElement; @@ -41,16 +41,18 @@ async function fetchTradeOffers(isSentPage: boolean) { * Fetches the api data for trade offers and stores relevant data in the DOM to be used by Lit components. */ async function getAndStoreTradeOffers() { - const isSentPage = location.pathname.includes("sent"); + const isSentPage = location.pathname.includes('sent'); const steamTrades = await fetchTradeOffers(isSentPage); - + const tradeOffers = document.querySelectorAll('.tradeoffer'); for (const tradeOffer of tradeOffers) { const tradeId = tradeOffer.id.split('_')[1]; const tradeItems = tradeOffer.querySelectorAll('.trade_item'); - const trade = isSentPage ? steamTrades.sent.find(t => t.offer_id === tradeId) : steamTrades.received.find(t => t.offer_id === tradeId); + const trade = isSentPage + ? steamTrades.sent.find((t) => t.offer_id === tradeId) + : steamTrades.received.find((t) => t.offer_id === tradeId); for (const tradeItem of tradeItems) { const economyItemParts = tradeItem.getAttribute('data-economy-item')?.split('/'); @@ -61,19 +63,23 @@ async function getAndStoreTradeOffers() { continue; } - const description = steamTrades.descriptions.find(d => d.classid === classId && d.instanceid === instanceId); + const description = steamTrades.descriptions.find( + (d) => d.classid === classId && d.instanceid === instanceId + ); if (description) { tradeItem.setAttribute('data-description', JSON.stringify(description)); } let isOwnItem = true; - let apiItem = trade?.given_asset_ids?.find(a => a.classid === classId && a.instanceid === instanceId); + let apiItem = trade?.given_asset_ids?.find((a) => a.classid === classId && a.instanceid === instanceId); if (!apiItem) { isOwnItem = false; - apiItem = trade?.received_asset_ids?.find(a => a.classid === classId && a.instanceid === instanceId); + apiItem = trade?.received_asset_ids?.find((a) => a.classid === classId && a.instanceid === instanceId); } - const ownerId = isOwnItem ? JSON.parse(document.getElementById('application_config')?.dataset?.userinfo || '{}').steamid : trade?.other_steam_id64; - + const ownerId = isOwnItem + ? JSON.parse(document.getElementById('application_config')?.dataset?.userinfo || '{}').steamid + : trade?.other_steam_id64; + tradeItem.setAttribute('data-owner-steamid', ownerId); if (apiItem?.assetid) { tradeItem.setAttribute('data-assetid', apiItem.assetid); From 869b3c918019ab3aa68c4f6396fc7adbfdc8ee2e Mon Sep 17 00:00:00 2001 From: GODrums Date: Fri, 13 Dec 2024 21:30:13 +0100 Subject: [PATCH 03/14] Remove csfloat check for offer tracking --- .../trade_offers/better_tracking.ts | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/lib/components/trade_offers/better_tracking.ts b/src/lib/components/trade_offers/better_tracking.ts index 9dbe5560..be4da692 100644 --- a/src/lib/components/trade_offers/better_tracking.ts +++ b/src/lib/components/trade_offers/better_tracking.ts @@ -53,29 +53,19 @@ export class BetterTrackingWidget extends FloatElement { async connectedCallback() { super.connectedCallback(); - try { - // Used for api.steampowered.com requests, all tokens stay on the users' device - const hasPermissions = await ClientSend(HasPermissions, { - permissions: ['alarms'], - origins: ['*://*.steampowered.com/*'], - }); + // Used for api.steampowered.com requests, all tokens stay on the users' device + const hasPermissions = await ClientSend(HasPermissions, { + permissions: ['alarms'], + origins: ['*://*.steampowered.com/*'], + }); - if (hasPermissions.granted) { - // In case they switched accounts on CSFloat or Steam or initial ping was lost, send redundant pings - ClientSend(PingSetupExtension, {}); - return; - } - - const trades = await ClientSend(FetchPendingTrades, {state: 'queued,pending,verified', limit: 1}); - if (trades.count === 0) { - // They aren't actively using CSFloat Market, no need to show this - return; - } - - this.show = true; - } catch (e) { - console.info('user is not logged into CSFloat'); + if (hasPermissions.granted) { + // In case they switched accounts on CSFloat or Steam or initial ping was lost, send redundant pings + ClientSend(PingSetupExtension, {}); + return; } + + this.show = true; } render() { From 32673f58c96bd7155806245595237748a7bc4e1d Mon Sep 17 00:00:00 2001 From: GODrums Date: Fri, 13 Dec 2024 21:38:30 +0100 Subject: [PATCH 04/14] fix: avoid circular dependency --- src/lib/alarms/trade_offer.ts | 17 ++++++++++++++++- src/lib/bridge/handlers/fetch_steam_trades.ts | 19 +------------------ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/lib/alarms/trade_offer.ts b/src/lib/alarms/trade_offer.ts index 074273de..12c53022 100644 --- a/src/lib/alarms/trade_offer.ts +++ b/src/lib/alarms/trade_offer.ts @@ -8,7 +8,22 @@ import {CancelTradeOffer} from '../bridge/handlers/cancel_trade_offer'; import {FetchSteamUser} from '../bridge/handlers/fetch_steam_user'; import {rgDescription} from '../types/steam'; import {HasPermissions} from '../bridge/handlers/has_permissions'; -import {ExtendedOfferStatus, ExtendedSingleOffer} from '../bridge/handlers/fetch_steam_trades'; + +export interface ExtendedOfferStatus { + offer_id: string; + state: TradeOfferState; + given_asset_ids?: ExtendedSingleOffer[]; + received_asset_ids?: ExtendedSingleOffer[]; + time_created?: number; + time_updated?: number; + other_steam_id64?: string; +} + +export interface ExtendedSingleOffer { + assetid: string; + classid: string; + instanceid: string; +} export async function pingSentTradeOffers(pendingTrades: Trade[]) { const {offers, type} = await getSentTradeOffers(); diff --git a/src/lib/bridge/handlers/fetch_steam_trades.ts b/src/lib/bridge/handlers/fetch_steam_trades.ts index f47180d3..6b3660ec 100644 --- a/src/lib/bridge/handlers/fetch_steam_trades.ts +++ b/src/lib/bridge/handlers/fetch_steam_trades.ts @@ -1,6 +1,5 @@ -import {getTradeOffersWithDescriptionFromAPI} from '../../alarms/trade_offer'; +import {ExtendedOfferStatus, getTradeOffersWithDescriptionFromAPI} from '../../alarms/trade_offer'; import {rgDescription} from '../../types/steam'; -import {TradeOfferState} from '../../types/steam_constants'; import {SimpleHandler} from './main'; import {RequestType} from './types'; @@ -13,22 +12,6 @@ export interface FetchSteamTradesResponse { steam_id?: string | null; } -export interface ExtendedOfferStatus { - offer_id: string; - state: TradeOfferState; - given_asset_ids?: ExtendedSingleOffer[]; - received_asset_ids?: ExtendedSingleOffer[]; - time_created?: number; - time_updated?: number; - other_steam_id64?: string; -} - -export interface ExtendedSingleOffer { - assetid: string; - classid: string; - instanceid: string; -} - export const FetchSteamTrades = new SimpleHandler( RequestType.FETCH_STEAM_TRADES, async (req) => { From fbf851702d13dabbfdf825a61001a587cf2422c8 Mon Sep 17 00:00:00 2001 From: GODrums Date: Wed, 18 Dec 2024 20:18:23 +0100 Subject: [PATCH 05/14] clarify variable names --- .../trade_offer_holder_metadata.ts | 6 ++-- src/lib/page_scripts/trade_offers.ts | 31 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/lib/components/trade_offers/trade_offer_holder_metadata.ts b/src/lib/components/trade_offers/trade_offer_holder_metadata.ts index f9263bbe..b28af10d 100644 --- a/src/lib/components/trade_offers/trade_offer_holder_metadata.ts +++ b/src/lib/components/trade_offers/trade_offer_holder_metadata.ts @@ -8,14 +8,14 @@ import {rgAsset} from '../../types/steam'; @InjectAppend('.tradeoffer .trade_item', InjectionMode.CONTINUOUS) export class TradeOfferHolderMetadata extends ItemHolderMetadata { get assetId(): string | undefined { - return $J(this).parent().attr('data-assetid'); + return $J(this).parent().attr('data-csfloat-assetid'); } get asset(): rgAsset | undefined { - return JSON.parse($J(this).parent().attr('data-description') ?? '{}') as rgAsset; + return JSON.parse($J(this).parent().attr('data-csfloat-description') ?? '{}') as rgAsset; } get ownerSteamId(): string | undefined { - return $J(this).parent().attr('data-owner-steamid'); + return $J(this).parent().attr('data-csfloat-owner-steamid'); } } diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index 52e1a989..35a38b32 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -22,9 +22,9 @@ async function fetchTradeOffers(isSentPage: boolean) { let refetchRequired = true; if (g_steamTrades.sent || g_steamTrades.received) { const latestTradeId = Number.parseInt(g_steamTrades[isSentPage ? 'sent' : 'received']?.[0].offer_id); - const latestTradeElement = Number.parseInt(document.querySelector('.tradeoffer')?.id.split('_')[1] ?? '0'); + const latestTradeIDFromPage = Number.parseInt(document.querySelector('.tradeoffer')?.id.split('_')[1] ?? '0'); - refetchRequired = Number.isNaN(latestTradeId) || latestTradeId !== latestTradeElement; + refetchRequired = Number.isNaN(latestTradeId) || latestTradeId !== latestTradeIDFromPage; } if (!refetchRequired) { @@ -40,7 +40,7 @@ async function fetchTradeOffers(isSentPage: boolean) { /** * Fetches the api data for trade offers and stores relevant data in the DOM to be used by Lit components. */ -async function getAndStoreTradeOffers() { +async function annotateTradeOfferItemElements() { const isSentPage = location.pathname.includes('sent'); const steamTrades = await fetchTradeOffers(isSentPage); @@ -48,18 +48,18 @@ async function getAndStoreTradeOffers() { const tradeOffers = document.querySelectorAll('.tradeoffer'); for (const tradeOffer of tradeOffers) { - const tradeId = tradeOffer.id.split('_')[1]; - const tradeItems = tradeOffer.querySelectorAll('.trade_item'); + const tradeOfferID = tradeOffer.id.split('_')[1]; + const tradeItemElements = tradeOffer.querySelectorAll('.trade_item'); const trade = isSentPage - ? steamTrades.sent.find((t) => t.offer_id === tradeId) - : steamTrades.received.find((t) => t.offer_id === tradeId); + ? steamTrades.sent.find((t) => t.offer_id === tradeOfferID) + : steamTrades.received.find((t) => t.offer_id === tradeOfferID); - for (const tradeItem of tradeItems) { - const economyItemParts = tradeItem.getAttribute('data-economy-item')?.split('/'); + for (const tradeItemElement of tradeItemElements) { + const economyItemParts = tradeItemElement.getAttribute('data-economy-item')?.split('/'); const classId = economyItemParts?.[2]; const instanceId = economyItemParts?.[3]; - if (!instanceId) { + if (!classId || !instanceId) { continue; } @@ -67,7 +67,7 @@ async function getAndStoreTradeOffers() { (d) => d.classid === classId && d.instanceid === instanceId ); if (description) { - tradeItem.setAttribute('data-description', JSON.stringify(description)); + tradeItemElement.setAttribute('data-csfloat-description', JSON.stringify(description)); } let isOwnItem = true; @@ -76,20 +76,23 @@ async function getAndStoreTradeOffers() { isOwnItem = false; apiItem = trade?.received_asset_ids?.find((a) => a.classid === classId && a.instanceid === instanceId); } + const ownerId = isOwnItem ? JSON.parse(document.getElementById('application_config')?.dataset?.userinfo || '{}').steamid : trade?.other_steam_id64; - tradeItem.setAttribute('data-owner-steamid', ownerId); + if (ownerId) { + tradeItemElement.setAttribute('data-csfloat-owner-steamid', ownerId); + } if (apiItem?.assetid) { - tradeItem.setAttribute('data-assetid', apiItem.assetid); + tradeItemElement.setAttribute('data-csfloat-assetid', apiItem.assetid); } } } } if (!inPageContext()) { - getAndStoreTradeOffers(); + annotateTradeOfferItemElements(); const refresh = setInterval(() => { const widget = document.getElementsByTagName('csfloat-better-tracking-widget'); From 16d228b5b48906cc370d73d9dda6aeb98e2569c3 Mon Sep 17 00:00:00 2001 From: GODrums Date: Wed, 18 Dec 2024 21:10:09 +0100 Subject: [PATCH 06/14] Move config parsing to utility --- src/lib/page_scripts/trade_offers.ts | 7 +++---- src/lib/utils/userinfo.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 src/lib/utils/userinfo.ts diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index 35a38b32..5f85399a 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -6,6 +6,7 @@ import {ClientSend} from '../bridge/client'; import {PingSetupExtension} from '../bridge/handlers/ping_setup_extension'; import {PingExtensionStatus} from '../bridge/handlers/ping_extension_status'; import {FetchSteamTrades, FetchSteamTradesResponse} from '../bridge/handlers/fetch_steam_trades'; +import {getUserSteamID} from '../utils/userinfo'; init('src/lib/page_scripts/trade_offers.js', main); @@ -76,10 +77,8 @@ async function annotateTradeOfferItemElements() { isOwnItem = false; apiItem = trade?.received_asset_ids?.find((a) => a.classid === classId && a.instanceid === instanceId); } - - const ownerId = isOwnItem - ? JSON.parse(document.getElementById('application_config')?.dataset?.userinfo || '{}').steamid - : trade?.other_steam_id64; + + const ownerId = isOwnItem ? getUserSteamID() : trade?.other_steam_id64; if (ownerId) { tradeItemElement.setAttribute('data-csfloat-owner-steamid', ownerId); diff --git a/src/lib/utils/userinfo.ts b/src/lib/utils/userinfo.ts new file mode 100644 index 00000000..98d8b63a --- /dev/null +++ b/src/lib/utils/userinfo.ts @@ -0,0 +1,26 @@ +interface UserInfo { + account_name: string; + accountid: number; + country_code: string; + is_limited: boolean; + is_partner_member: boolean; + is_support: boolean; + logged_in: boolean; + steamid: string; +} + +export function getUserInfo() { + const configUserInfo = document.getElementById('application_config')?.dataset?.userinfo; + if (!configUserInfo) { + return null; + } + return JSON.parse(configUserInfo) as UserInfo; +} + +export function getUserSteamID() { + const userInfo = getUserInfo(); + if (!userInfo?.logged_in) { + return null; + } + return userInfo.steamid; +} From bbb6d72c92ba97f5e40a9b5677f0c67434ae7814 Mon Sep 17 00:00:00 2001 From: GODrums Date: Thu, 19 Dec 2024 01:04:45 +0100 Subject: [PATCH 07/14] Handle access token caching --- src/lib/alarms/trade_offer.ts | 8 ++++---- src/lib/bridge/handlers/fetch_steam_trades.ts | 6 ++++-- src/lib/page_scripts/trade_offers.ts | 17 +++++++++++++---- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/lib/alarms/trade_offer.ts b/src/lib/alarms/trade_offer.ts index 12c53022..2d864804 100644 --- a/src/lib/alarms/trade_offer.ts +++ b/src/lib/alarms/trade_offer.ts @@ -316,14 +316,12 @@ async function getSentAndReceivedTradeOffersFromAPI(): Promise<{ }; } -export async function getTradeOffersWithDescriptionFromAPI(): Promise<{ +export async function getTradeOffersWithDescriptionFromAPI(steam_id?: string): Promise<{ received: ExtendedOfferStatus[]; sent: ExtendedOfferStatus[]; descriptions: rgDescription[]; steam_id?: string | null; }> { - const access = await getAccessToken(); - // check if permissions are granted const steamPoweredPermissions = await HasPermissions.handleRequest( { @@ -337,10 +335,12 @@ export async function getTradeOffersWithDescriptionFromAPI(): Promise<{ received: [], sent: [], descriptions: [], - steam_id: access.steam_id, + steam_id: steam_id, }; } + const access = await getAccessToken(steam_id); + const resp = await fetch( `https://api.steampowered.com/IEconService/GetTradeOffers/v1/?access_token=${access.token}&get_received_offers=true&get_sent_offers=true&get_descriptions=true`, { diff --git a/src/lib/bridge/handlers/fetch_steam_trades.ts b/src/lib/bridge/handlers/fetch_steam_trades.ts index 6b3660ec..05e338f5 100644 --- a/src/lib/bridge/handlers/fetch_steam_trades.ts +++ b/src/lib/bridge/handlers/fetch_steam_trades.ts @@ -3,7 +3,9 @@ import {rgDescription} from '../../types/steam'; import {SimpleHandler} from './main'; import {RequestType} from './types'; -interface FetchSteamTradesRequest {} +interface FetchSteamTradesRequest { + steam_id?: string; +} export interface FetchSteamTradesResponse { received: ExtendedOfferStatus[]; @@ -15,7 +17,7 @@ export interface FetchSteamTradesResponse { export const FetchSteamTrades = new SimpleHandler( RequestType.FETCH_STEAM_TRADES, async (req) => { - const resp = await getTradeOffersWithDescriptionFromAPI(); + const resp = await getTradeOffersWithDescriptionFromAPI(req.steam_id); if (!resp) { throw new Error('Error fetching Steam trade offers from API'); } diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index 5f85399a..4879f979 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -18,7 +18,7 @@ function main() {} * @param isSentPage if the current page is the sent trade offers page * @returns the trade offers */ -async function fetchTradeOffers(isSentPage: boolean) { +async function fetchTradeOffers(steam_id: string, isSentPage: boolean) { const g_steamTrades = JSON.parse(localStorage.getItem('g_steamTrades') || '{}') as FetchSteamTradesResponse; let refetchRequired = true; if (g_steamTrades.sent || g_steamTrades.received) { @@ -32,7 +32,7 @@ async function fetchTradeOffers(isSentPage: boolean) { return g_steamTrades; } - const steamTrades = await ClientSend(FetchSteamTrades, {}); + const steamTrades = await ClientSend(FetchSteamTrades, {steam_id}); localStorage.setItem('g_steamTrades', JSON.stringify(steamTrades)); return steamTrades; @@ -42,9 +42,16 @@ async function fetchTradeOffers(isSentPage: boolean) { * Fetches the api data for trade offers and stores relevant data in the DOM to be used by Lit components. */ async function annotateTradeOfferItemElements() { + const steam_id = getUserSteamID(); + + if (!steam_id) { + console.error('Failed to get steam_id', steam_id); + return; + } + const isSentPage = location.pathname.includes('sent'); - const steamTrades = await fetchTradeOffers(isSentPage); + const steamTrades = await fetchTradeOffers(steam_id, isSentPage); const tradeOffers = document.querySelectorAll('.tradeoffer'); @@ -56,6 +63,8 @@ async function annotateTradeOfferItemElements() { : steamTrades.received.find((t) => t.offer_id === tradeOfferID); for (const tradeItemElement of tradeItemElements) { + // Format: classinfo/{appid}/{classid}/{instanceid} + // Example: data-economy-item="classinfo/730/310777185/302028390" const economyItemParts = tradeItemElement.getAttribute('data-economy-item')?.split('/'); const classId = economyItemParts?.[2]; const instanceId = economyItemParts?.[3]; @@ -78,7 +87,7 @@ async function annotateTradeOfferItemElements() { apiItem = trade?.received_asset_ids?.find((a) => a.classid === classId && a.instanceid === instanceId); } - const ownerId = isOwnItem ? getUserSteamID() : trade?.other_steam_id64; + const ownerId = isOwnItem ? steam_id : trade?.other_steam_id64; if (ownerId) { tradeItemElement.setAttribute('data-csfloat-owner-steamid', ownerId); From a652c77474a23c44f7a1a808bba6ce4256e846ea Mon Sep 17 00:00:00 2001 From: GODrums Date: Thu, 19 Dec 2024 02:27:23 +0100 Subject: [PATCH 08/14] simplify tradeoffer types --- src/lib/alarms/trade_offer.ts | 55 +++---------------- src/lib/bridge/handlers/fetch_steam_trades.ts | 6 +- src/lib/page_scripts/trade_offers.ts | 18 +++--- src/lib/utils/userinfo.ts | 9 +++ 4 files changed, 31 insertions(+), 57 deletions(-) diff --git a/src/lib/alarms/trade_offer.ts b/src/lib/alarms/trade_offer.ts index 2d864804..023cc6c5 100644 --- a/src/lib/alarms/trade_offer.ts +++ b/src/lib/alarms/trade_offer.ts @@ -8,22 +8,7 @@ import {CancelTradeOffer} from '../bridge/handlers/cancel_trade_offer'; import {FetchSteamUser} from '../bridge/handlers/fetch_steam_user'; import {rgDescription} from '../types/steam'; import {HasPermissions} from '../bridge/handlers/has_permissions'; - -export interface ExtendedOfferStatus { - offer_id: string; - state: TradeOfferState; - given_asset_ids?: ExtendedSingleOffer[]; - received_asset_ids?: ExtendedSingleOffer[]; - time_created?: number; - time_updated?: number; - other_steam_id64?: string; -} - -export interface ExtendedSingleOffer { - assetid: string; - classid: string; - instanceid: string; -} +import { convertToSteamID64 } from '../utils/userinfo'; export async function pingSentTradeOffers(pendingTrades: Trade[]) { const {offers, type} = await getSentTradeOffers(); @@ -218,9 +203,9 @@ interface TradeOfferItem { est_usd: string; } -interface TradeOffersAPIOffer { +export interface TradeOffersAPIOffer { tradeofferid: string; - accountid_other: string; + accountid_other: number; trade_offer_state: TradeOfferState; items_to_give?: TradeOfferItem[]; items_to_receive?: TradeOfferItem[]; @@ -244,34 +229,10 @@ function offerStateMapper(e: TradeOffersAPIOffer): OfferStatus { received_asset_ids: (e.items_to_receive || []).map((e) => e.assetid), time_created: e.time_created, time_updated: e.time_updated, - other_steam_id64: (BigInt('76561197960265728') + BigInt(e.accountid_other)).toString(), + other_steam_id64: convertToSteamID64(e.accountid_other), } as OfferStatus; } -function extendedOfferStateMapper(e: TradeOffersAPIOffer) { - return { - offer_id: e.tradeofferid, - state: e.trade_offer_state, - given_asset_ids: (e.items_to_give || []).map((e) => { - return { - assetid: e.assetid, - classid: e.classid, - instanceid: e.instanceid, - } as ExtendedSingleOffer; - }), - received_asset_ids: (e.items_to_receive || []).map((e) => { - return { - assetid: e.assetid, - classid: e.classid, - instanceid: e.instanceid, - } as ExtendedSingleOffer; - }), - time_created: e.time_created, - time_updated: e.time_updated, - other_steam_id64: (BigInt('76561197960265728') + BigInt(e.accountid_other)).toString(), - }; -} - async function getSentTradeOffersFromAPI(): Promise { const access = await getAccessToken(); @@ -317,8 +278,8 @@ async function getSentAndReceivedTradeOffersFromAPI(): Promise<{ } export async function getTradeOffersWithDescriptionFromAPI(steam_id?: string): Promise<{ - received: ExtendedOfferStatus[]; - sent: ExtendedOfferStatus[]; + received: TradeOffersAPIOffer[]; + sent: TradeOffersAPIOffer[]; descriptions: rgDescription[]; steam_id?: string | null; }> { @@ -354,8 +315,8 @@ export async function getTradeOffersWithDescriptionFromAPI(steam_id?: string): P const data = (await resp.json()) as TradeOffersAPIResponse; return { - received: (data.response?.trade_offers_received || []).map(extendedOfferStateMapper), - sent: (data.response?.trade_offers_sent || []).map(extendedOfferStateMapper), + received: data.response?.trade_offers_received || [], + sent: data.response?.trade_offers_sent || [], steam_id: access.steam_id, descriptions: data.response?.descriptions || [], }; diff --git a/src/lib/bridge/handlers/fetch_steam_trades.ts b/src/lib/bridge/handlers/fetch_steam_trades.ts index 05e338f5..13694abd 100644 --- a/src/lib/bridge/handlers/fetch_steam_trades.ts +++ b/src/lib/bridge/handlers/fetch_steam_trades.ts @@ -1,4 +1,4 @@ -import {ExtendedOfferStatus, getTradeOffersWithDescriptionFromAPI} from '../../alarms/trade_offer'; +import {getTradeOffersWithDescriptionFromAPI, TradeOffersAPIOffer} from '../../alarms/trade_offer'; import {rgDescription} from '../../types/steam'; import {SimpleHandler} from './main'; import {RequestType} from './types'; @@ -8,8 +8,8 @@ interface FetchSteamTradesRequest { } export interface FetchSteamTradesResponse { - received: ExtendedOfferStatus[]; - sent: ExtendedOfferStatus[]; + received: TradeOffersAPIOffer[]; + sent: TradeOffersAPIOffer[]; descriptions: rgDescription[]; steam_id?: string | null; } diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index 4879f979..c390828f 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -6,7 +6,7 @@ import {ClientSend} from '../bridge/client'; import {PingSetupExtension} from '../bridge/handlers/ping_setup_extension'; import {PingExtensionStatus} from '../bridge/handlers/ping_extension_status'; import {FetchSteamTrades, FetchSteamTradesResponse} from '../bridge/handlers/fetch_steam_trades'; -import {getUserSteamID} from '../utils/userinfo'; +import {convertToSteamID64, getUserSteamID} from '../utils/userinfo'; init('src/lib/page_scripts/trade_offers.js', main); @@ -22,7 +22,7 @@ async function fetchTradeOffers(steam_id: string, isSentPage: boolean) { const g_steamTrades = JSON.parse(localStorage.getItem('g_steamTrades') || '{}') as FetchSteamTradesResponse; let refetchRequired = true; if (g_steamTrades.sent || g_steamTrades.received) { - const latestTradeId = Number.parseInt(g_steamTrades[isSentPage ? 'sent' : 'received']?.[0].offer_id); + const latestTradeId = Number.parseInt(g_steamTrades[isSentPage ? 'sent' : 'received']?.[0].tradeofferid); const latestTradeIDFromPage = Number.parseInt(document.querySelector('.tradeoffer')?.id.split('_')[1] ?? '0'); refetchRequired = Number.isNaN(latestTradeId) || latestTradeId !== latestTradeIDFromPage; @@ -58,9 +58,13 @@ async function annotateTradeOfferItemElements() { for (const tradeOffer of tradeOffers) { const tradeOfferID = tradeOffer.id.split('_')[1]; const tradeItemElements = tradeOffer.querySelectorAll('.trade_item'); + const trade = isSentPage - ? steamTrades.sent.find((t) => t.offer_id === tradeOfferID) - : steamTrades.received.find((t) => t.offer_id === tradeOfferID); + ? steamTrades.sent.find((t) => t.tradeofferid === tradeOfferID) + : steamTrades.received.find((t) => t.tradeofferid === tradeOfferID); + if (!trade) { + continue; + } for (const tradeItemElement of tradeItemElements) { // Format: classinfo/{appid}/{classid}/{instanceid} @@ -81,13 +85,13 @@ async function annotateTradeOfferItemElements() { } let isOwnItem = true; - let apiItem = trade?.given_asset_ids?.find((a) => a.classid === classId && a.instanceid === instanceId); + let apiItem = trade?.items_to_give?.find((a) => a.classid === classId && a.instanceid === instanceId); if (!apiItem) { isOwnItem = false; - apiItem = trade?.received_asset_ids?.find((a) => a.classid === classId && a.instanceid === instanceId); + apiItem = trade?.items_to_receive?.find((a) => a.classid === classId && a.instanceid === instanceId); } - const ownerId = isOwnItem ? steam_id : trade?.other_steam_id64; + const ownerId = isOwnItem ? steam_id : convertToSteamID64(trade.accountid_other); if (ownerId) { tradeItemElement.setAttribute('data-csfloat-owner-steamid', ownerId); diff --git a/src/lib/utils/userinfo.ts b/src/lib/utils/userinfo.ts index 98d8b63a..addb6aa1 100644 --- a/src/lib/utils/userinfo.ts +++ b/src/lib/utils/userinfo.ts @@ -24,3 +24,12 @@ export function getUserSteamID() { } return userInfo.steamid; } + +/** + * Converts a SteamID32 to a SteamID64 + * @param steamID32 number + * @returns SteamID64 + */ +export function convertToSteamID64(steamID32: number) { + return (BigInt('76561197960265728') + BigInt(steamID32)).toString(); +} \ No newline at end of file From 6923b4307acade2ba1aa6b0532e0f4a43ba06695 Mon Sep 17 00:00:00 2001 From: GODrums Date: Thu, 19 Dec 2024 03:00:10 +0100 Subject: [PATCH 09/14] update variable names --- src/lib/page_scripts/trade_offers.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index c390828f..93c4d2b0 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -22,10 +22,10 @@ async function fetchTradeOffers(steam_id: string, isSentPage: boolean) { const g_steamTrades = JSON.parse(localStorage.getItem('g_steamTrades') || '{}') as FetchSteamTradesResponse; let refetchRequired = true; if (g_steamTrades.sent || g_steamTrades.received) { - const latestTradeId = Number.parseInt(g_steamTrades[isSentPage ? 'sent' : 'received']?.[0].tradeofferid); - const latestTradeIDFromPage = Number.parseInt(document.querySelector('.tradeoffer')?.id.split('_')[1] ?? '0'); + const latestTradeOfferID = Number.parseInt(g_steamTrades[isSentPage ? 'sent' : 'received']?.[0].tradeofferid); + const latestTradeOfferIDFromPage = Number.parseInt(document.querySelector('.tradeoffer')?.id.split('_')[1] ?? '0'); - refetchRequired = Number.isNaN(latestTradeId) || latestTradeId !== latestTradeIDFromPage; + refetchRequired = Number.isNaN(latestTradeOfferID) || latestTradeOfferID !== latestTradeOfferIDFromPage; } if (!refetchRequired) { @@ -59,10 +59,10 @@ async function annotateTradeOfferItemElements() { const tradeOfferID = tradeOffer.id.split('_')[1]; const tradeItemElements = tradeOffer.querySelectorAll('.trade_item'); - const trade = isSentPage + const tradeOfferAPI = isSentPage ? steamTrades.sent.find((t) => t.tradeofferid === tradeOfferID) : steamTrades.received.find((t) => t.tradeofferid === tradeOfferID); - if (!trade) { + if (!tradeOfferAPI) { continue; } @@ -85,13 +85,13 @@ async function annotateTradeOfferItemElements() { } let isOwnItem = true; - let apiItem = trade?.items_to_give?.find((a) => a.classid === classId && a.instanceid === instanceId); + let apiItem = tradeOfferAPI?.items_to_give?.find((a) => a.classid === classId && a.instanceid === instanceId); if (!apiItem) { isOwnItem = false; - apiItem = trade?.items_to_receive?.find((a) => a.classid === classId && a.instanceid === instanceId); + apiItem = tradeOfferAPI?.items_to_receive?.find((a) => a.classid === classId && a.instanceid === instanceId); } - const ownerId = isOwnItem ? steam_id : convertToSteamID64(trade.accountid_other); + const ownerId = isOwnItem ? steam_id : convertToSteamID64(tradeOfferAPI.accountid_other); if (ownerId) { tradeItemElement.setAttribute('data-csfloat-owner-steamid', ownerId); From 0d4620b56d0266ce2a4eae250d2c71749e0c0e94 Mon Sep 17 00:00:00 2001 From: GODrums Date: Thu, 19 Dec 2024 04:22:01 +0100 Subject: [PATCH 10/14] rework caching with CachedHandler --- src/lib/alarms/trade_offer.ts | 2 +- src/lib/bridge/handlers/fetch_steam_trades.ts | 24 +++++++---- src/lib/page_scripts/trade_offers.ts | 43 +++++++------------ src/lib/utils/userinfo.ts | 2 +- 4 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/lib/alarms/trade_offer.ts b/src/lib/alarms/trade_offer.ts index 023cc6c5..dcb72f6a 100644 --- a/src/lib/alarms/trade_offer.ts +++ b/src/lib/alarms/trade_offer.ts @@ -8,7 +8,7 @@ import {CancelTradeOffer} from '../bridge/handlers/cancel_trade_offer'; import {FetchSteamUser} from '../bridge/handlers/fetch_steam_user'; import {rgDescription} from '../types/steam'; import {HasPermissions} from '../bridge/handlers/has_permissions'; -import { convertToSteamID64 } from '../utils/userinfo'; +import {convertToSteamID64} from '../utils/userinfo'; export async function pingSentTradeOffers(pendingTrades: Trade[]) { const {offers, type} = await getSentTradeOffers(); diff --git a/src/lib/bridge/handlers/fetch_steam_trades.ts b/src/lib/bridge/handlers/fetch_steam_trades.ts index 13694abd..7af84ab8 100644 --- a/src/lib/bridge/handlers/fetch_steam_trades.ts +++ b/src/lib/bridge/handlers/fetch_steam_trades.ts @@ -1,10 +1,12 @@ import {getTradeOffersWithDescriptionFromAPI, TradeOffersAPIOffer} from '../../alarms/trade_offer'; import {rgDescription} from '../../types/steam'; +import {CachedHandler} from '../wrappers/cached'; import {SimpleHandler} from './main'; import {RequestType} from './types'; interface FetchSteamTradesRequest { steam_id?: string; + trade_offer_id?: number; } export interface FetchSteamTradesResponse { @@ -14,14 +16,18 @@ export interface FetchSteamTradesResponse { steam_id?: string | null; } -export const FetchSteamTrades = new SimpleHandler( - RequestType.FETCH_STEAM_TRADES, - async (req) => { - const resp = await getTradeOffersWithDescriptionFromAPI(req.steam_id); - if (!resp) { - throw new Error('Error fetching Steam trade offers from API'); - } +export const FetchSteamTrades = new CachedHandler( + new SimpleHandler( + RequestType.FETCH_STEAM_TRADES, + async (req) => { + const resp = await getTradeOffersWithDescriptionFromAPI(req.steam_id); + if (!resp) { + throw new Error('Error fetching Steam trade offers from API'); + } - return resp; - } + return resp; + } + ), + 1, + 10 * 60 * 1000 ); diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index 93c4d2b0..cde71fc0 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -15,27 +15,15 @@ function main() {} /** * Gets the trade offers from the local storage or fetches them from the API. * Local storage serves as a cache here. - * @param isSentPage if the current page is the sent trade offers page + * @param steam_id the steam id of logged in user * @returns the trade offers */ -async function fetchTradeOffers(steam_id: string, isSentPage: boolean) { - const g_steamTrades = JSON.parse(localStorage.getItem('g_steamTrades') || '{}') as FetchSteamTradesResponse; - let refetchRequired = true; - if (g_steamTrades.sent || g_steamTrades.received) { - const latestTradeOfferID = Number.parseInt(g_steamTrades[isSentPage ? 'sent' : 'received']?.[0].tradeofferid); - const latestTradeOfferIDFromPage = Number.parseInt(document.querySelector('.tradeoffer')?.id.split('_')[1] ?? '0'); - - refetchRequired = Number.isNaN(latestTradeOfferID) || latestTradeOfferID !== latestTradeOfferIDFromPage; - } - - if (!refetchRequired) { - return g_steamTrades; - } - - const steamTrades = await ClientSend(FetchSteamTrades, {steam_id}); +async function fetchTradeOffers(steam_id: string) { + const latestTradeIDFromPage = document.querySelector('.tradeoffer')?.id.split('_')[1]; + const trade_offer_id = latestTradeIDFromPage ? Number.parseInt(latestTradeIDFromPage) : undefined; - localStorage.setItem('g_steamTrades', JSON.stringify(steamTrades)); - return steamTrades; + console.log('Fetching trade offers', steam_id, trade_offer_id); + return await ClientSend(FetchSteamTrades, {steam_id, trade_offer_id}); } /** @@ -49,19 +37,16 @@ async function annotateTradeOfferItemElements() { return; } - const isSentPage = location.pathname.includes('sent'); - - const steamTrades = await fetchTradeOffers(steam_id, isSentPage); + const steamTrades = await fetchTradeOffers(steam_id); const tradeOffers = document.querySelectorAll('.tradeoffer'); for (const tradeOffer of tradeOffers) { const tradeOfferID = tradeOffer.id.split('_')[1]; const tradeItemElements = tradeOffer.querySelectorAll('.trade_item'); - - const tradeOfferAPI = isSentPage - ? steamTrades.sent.find((t) => t.tradeofferid === tradeOfferID) - : steamTrades.received.find((t) => t.tradeofferid === tradeOfferID); + const tradeOfferAPI = + steamTrades.sent.find((t) => t.tradeofferid === tradeOfferID) ?? + steamTrades.received.find((t) => t.tradeofferid === tradeOfferID); if (!tradeOfferAPI) { continue; } @@ -85,10 +70,14 @@ async function annotateTradeOfferItemElements() { } let isOwnItem = true; - let apiItem = tradeOfferAPI?.items_to_give?.find((a) => a.classid === classId && a.instanceid === instanceId); + let apiItem = tradeOfferAPI?.items_to_give?.find( + (a) => a.classid === classId && a.instanceid === instanceId + ); if (!apiItem) { isOwnItem = false; - apiItem = tradeOfferAPI?.items_to_receive?.find((a) => a.classid === classId && a.instanceid === instanceId); + apiItem = tradeOfferAPI?.items_to_receive?.find( + (a) => a.classid === classId && a.instanceid === instanceId + ); } const ownerId = isOwnItem ? steam_id : convertToSteamID64(tradeOfferAPI.accountid_other); diff --git a/src/lib/utils/userinfo.ts b/src/lib/utils/userinfo.ts index addb6aa1..6b0085fc 100644 --- a/src/lib/utils/userinfo.ts +++ b/src/lib/utils/userinfo.ts @@ -32,4 +32,4 @@ export function getUserSteamID() { */ export function convertToSteamID64(steamID32: number) { return (BigInt('76561197960265728') + BigInt(steamID32)).toString(); -} \ No newline at end of file +} From 48bf2b69dd5bfdd2b840ee49344b88bb9fe47e5f Mon Sep 17 00:00:00 2001 From: GODrums Date: Tue, 7 Jan 2025 23:25:25 +0100 Subject: [PATCH 11/14] Handle case of no open trades --- src/lib/page_scripts/trade_offers.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index cde71fc0..e670eb01 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -5,7 +5,7 @@ import {inPageContext} from '../utils/snips'; import {ClientSend} from '../bridge/client'; import {PingSetupExtension} from '../bridge/handlers/ping_setup_extension'; import {PingExtensionStatus} from '../bridge/handlers/ping_extension_status'; -import {FetchSteamTrades, FetchSteamTradesResponse} from '../bridge/handlers/fetch_steam_trades'; +import {FetchSteamTrades} from '../bridge/handlers/fetch_steam_trades'; import {convertToSteamID64, getUserSteamID} from '../utils/userinfo'; init('src/lib/page_scripts/trade_offers.js', main); @@ -22,6 +22,10 @@ async function fetchTradeOffers(steam_id: string) { const latestTradeIDFromPage = document.querySelector('.tradeoffer')?.id.split('_')[1]; const trade_offer_id = latestTradeIDFromPage ? Number.parseInt(latestTradeIDFromPage) : undefined; + if (!trade_offer_id) { + return; + } + console.log('Fetching trade offers', steam_id, trade_offer_id); return await ClientSend(FetchSteamTrades, {steam_id, trade_offer_id}); } @@ -39,6 +43,10 @@ async function annotateTradeOfferItemElements() { const steamTrades = await fetchTradeOffers(steam_id); + if (!steamTrades) { + return; + } + const tradeOffers = document.querySelectorAll('.tradeoffer'); for (const tradeOffer of tradeOffers) { From 3806f9e3e233c80f8b682c664a5b0f643f8e2776 Mon Sep 17 00:00:00 2001 From: GODrums Date: Thu, 9 Jan 2025 00:03:46 +0100 Subject: [PATCH 12/14] Rename variables and update references --- src/lib/alarms/trade_offer.ts | 4 +-- src/lib/bridge/handlers/fetch_steam_trades.ts | 1 + src/lib/page_scripts/trade_offers.ts | 25 +++++++++---------- src/lib/utils/userinfo.ts | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/lib/alarms/trade_offer.ts b/src/lib/alarms/trade_offer.ts index dcb72f6a..c5eb0128 100644 --- a/src/lib/alarms/trade_offer.ts +++ b/src/lib/alarms/trade_offer.ts @@ -8,7 +8,7 @@ import {CancelTradeOffer} from '../bridge/handlers/cancel_trade_offer'; import {FetchSteamUser} from '../bridge/handlers/fetch_steam_user'; import {rgDescription} from '../types/steam'; import {HasPermissions} from '../bridge/handlers/has_permissions'; -import {convertToSteamID64} from '../utils/userinfo'; +import {convertSteamID32To64} from '../utils/userinfo'; export async function pingSentTradeOffers(pendingTrades: Trade[]) { const {offers, type} = await getSentTradeOffers(); @@ -229,7 +229,7 @@ function offerStateMapper(e: TradeOffersAPIOffer): OfferStatus { received_asset_ids: (e.items_to_receive || []).map((e) => e.assetid), time_created: e.time_created, time_updated: e.time_updated, - other_steam_id64: convertToSteamID64(e.accountid_other), + other_steam_id64: convertSteamID32To64(e.accountid_other), } as OfferStatus; } diff --git a/src/lib/bridge/handlers/fetch_steam_trades.ts b/src/lib/bridge/handlers/fetch_steam_trades.ts index 7af84ab8..bf6731bf 100644 --- a/src/lib/bridge/handlers/fetch_steam_trades.ts +++ b/src/lib/bridge/handlers/fetch_steam_trades.ts @@ -6,6 +6,7 @@ import {RequestType} from './types'; interface FetchSteamTradesRequest { steam_id?: string; + // Used for caching the request uniquely, does not affect the return results trade_offer_id?: number; } diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index e670eb01..a1b789e7 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -6,7 +6,7 @@ import {ClientSend} from '../bridge/client'; import {PingSetupExtension} from '../bridge/handlers/ping_setup_extension'; import {PingExtensionStatus} from '../bridge/handlers/ping_extension_status'; import {FetchSteamTrades} from '../bridge/handlers/fetch_steam_trades'; -import {convertToSteamID64, getUserSteamID} from '../utils/userinfo'; +import {convertSteamID32To64, getUserSteamID} from '../utils/userinfo'; init('src/lib/page_scripts/trade_offers.js', main); @@ -18,7 +18,7 @@ function main() {} * @param steam_id the steam id of logged in user * @returns the trade offers */ -async function fetchTradeOffers(steam_id: string) { +function fetchTradeOffers(steam_id: string) { const latestTradeIDFromPage = document.querySelector('.tradeoffer')?.id.split('_')[1]; const trade_offer_id = latestTradeIDFromPage ? Number.parseInt(latestTradeIDFromPage) : undefined; @@ -26,8 +26,7 @@ async function fetchTradeOffers(steam_id: string) { return; } - console.log('Fetching trade offers', steam_id, trade_offer_id); - return await ClientSend(FetchSteamTrades, {steam_id, trade_offer_id}); + return ClientSend(FetchSteamTrades, {steam_id, trade_offer_id}); } /** @@ -47,15 +46,15 @@ async function annotateTradeOfferItemElements() { return; } - const tradeOffers = document.querySelectorAll('.tradeoffer'); + const tradeOfferElements = document.querySelectorAll('.tradeoffer'); - for (const tradeOffer of tradeOffers) { - const tradeOfferID = tradeOffer.id.split('_')[1]; - const tradeItemElements = tradeOffer.querySelectorAll('.trade_item'); - const tradeOfferAPI = + for (const tradeOfferElement of tradeOfferElements) { + const tradeOfferID = tradeOfferElement.id.split('_')[1]; + const tradeItemElements = tradeOfferElement.querySelectorAll('.trade_item'); + const tradeOffer = steamTrades.sent.find((t) => t.tradeofferid === tradeOfferID) ?? steamTrades.received.find((t) => t.tradeofferid === tradeOfferID); - if (!tradeOfferAPI) { + if (!tradeOffer) { continue; } @@ -78,17 +77,17 @@ async function annotateTradeOfferItemElements() { } let isOwnItem = true; - let apiItem = tradeOfferAPI?.items_to_give?.find( + let apiItem = tradeOffer?.items_to_give?.find( (a) => a.classid === classId && a.instanceid === instanceId ); if (!apiItem) { isOwnItem = false; - apiItem = tradeOfferAPI?.items_to_receive?.find( + apiItem = tradeOffer?.items_to_receive?.find( (a) => a.classid === classId && a.instanceid === instanceId ); } - const ownerId = isOwnItem ? steam_id : convertToSteamID64(tradeOfferAPI.accountid_other); + const ownerId = isOwnItem ? steam_id : convertSteamID32To64(tradeOffer.accountid_other); if (ownerId) { tradeItemElement.setAttribute('data-csfloat-owner-steamid', ownerId); diff --git a/src/lib/utils/userinfo.ts b/src/lib/utils/userinfo.ts index 6b0085fc..124fdd54 100644 --- a/src/lib/utils/userinfo.ts +++ b/src/lib/utils/userinfo.ts @@ -30,6 +30,6 @@ export function getUserSteamID() { * @param steamID32 number * @returns SteamID64 */ -export function convertToSteamID64(steamID32: number) { +export function convertSteamID32To64(steamID32: number) { return (BigInt('76561197960265728') + BigInt(steamID32)).toString(); } From 42685e6cbb8cd537a8d8b09519d3818f8bcede5c Mon Sep 17 00:00:00 2001 From: GODrums Date: Thu, 9 Jan 2025 00:35:40 +0100 Subject: [PATCH 13/14] Revert tracking prerequisite --- .../trade_offers/better_tracking.ts | 32 ++++++++++++------- src/lib/page_scripts/trade_offers.ts | 4 +-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/lib/components/trade_offers/better_tracking.ts b/src/lib/components/trade_offers/better_tracking.ts index be4da692..9dbe5560 100644 --- a/src/lib/components/trade_offers/better_tracking.ts +++ b/src/lib/components/trade_offers/better_tracking.ts @@ -53,19 +53,29 @@ export class BetterTrackingWidget extends FloatElement { async connectedCallback() { super.connectedCallback(); - // Used for api.steampowered.com requests, all tokens stay on the users' device - const hasPermissions = await ClientSend(HasPermissions, { - permissions: ['alarms'], - origins: ['*://*.steampowered.com/*'], - }); + try { + // Used for api.steampowered.com requests, all tokens stay on the users' device + const hasPermissions = await ClientSend(HasPermissions, { + permissions: ['alarms'], + origins: ['*://*.steampowered.com/*'], + }); - if (hasPermissions.granted) { - // In case they switched accounts on CSFloat or Steam or initial ping was lost, send redundant pings - ClientSend(PingSetupExtension, {}); - return; - } + if (hasPermissions.granted) { + // In case they switched accounts on CSFloat or Steam or initial ping was lost, send redundant pings + ClientSend(PingSetupExtension, {}); + return; + } - this.show = true; + const trades = await ClientSend(FetchPendingTrades, {state: 'queued,pending,verified', limit: 1}); + if (trades.count === 0) { + // They aren't actively using CSFloat Market, no need to show this + return; + } + + this.show = true; + } catch (e) { + console.info('user is not logged into CSFloat'); + } } render() { diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index a1b789e7..3962098e 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -77,9 +77,7 @@ async function annotateTradeOfferItemElements() { } let isOwnItem = true; - let apiItem = tradeOffer?.items_to_give?.find( - (a) => a.classid === classId && a.instanceid === instanceId - ); + let apiItem = tradeOffer?.items_to_give?.find((a) => a.classid === classId && a.instanceid === instanceId); if (!apiItem) { isOwnItem = false; apiItem = tradeOffer?.items_to_receive?.find( From c47c675fffbe132933b5827ab6c767d1bf198bc3 Mon Sep 17 00:00:00 2001 From: GODrums Date: Fri, 10 Jan 2025 16:29:10 +0100 Subject: [PATCH 14/14] Apply review suggestions --- .../components/trade_offers/trade_offer_holder_metadata.ts | 6 +++++- src/lib/page_scripts/trade_offers.ts | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/components/trade_offers/trade_offer_holder_metadata.ts b/src/lib/components/trade_offers/trade_offer_holder_metadata.ts index b28af10d..17fae1a6 100644 --- a/src/lib/components/trade_offers/trade_offer_holder_metadata.ts +++ b/src/lib/components/trade_offers/trade_offer_holder_metadata.ts @@ -12,7 +12,11 @@ export class TradeOfferHolderMetadata extends ItemHolderMetadata { } get asset(): rgAsset | undefined { - return JSON.parse($J(this).parent().attr('data-csfloat-description') ?? '{}') as rgAsset; + const dataDescription = $J(this).parent().attr('data-csfloat-description'); + + if (!dataDescription) return undefined; + + return JSON.parse(dataDescription) as rgAsset; } get ownerSteamId(): string | undefined { diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index 3962098e..f90fc742 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -5,7 +5,7 @@ import {inPageContext} from '../utils/snips'; import {ClientSend} from '../bridge/client'; import {PingSetupExtension} from '../bridge/handlers/ping_setup_extension'; import {PingExtensionStatus} from '../bridge/handlers/ping_extension_status'; -import {FetchSteamTrades} from '../bridge/handlers/fetch_steam_trades'; +import {FetchSteamTrades, FetchSteamTradesResponse} from '../bridge/handlers/fetch_steam_trades'; import {convertSteamID32To64, getUserSteamID} from '../utils/userinfo'; init('src/lib/page_scripts/trade_offers.js', main); @@ -18,7 +18,7 @@ function main() {} * @param steam_id the steam id of logged in user * @returns the trade offers */ -function fetchTradeOffers(steam_id: string) { +function fetchTradeOffers(steam_id: string): Promise | undefined { const latestTradeIDFromPage = document.querySelector('.tradeoffer')?.id.split('_')[1]; const trade_offer_id = latestTradeIDFromPage ? Number.parseInt(latestTradeIDFromPage) : undefined;