diff --git a/src/lib/alarms/trade_history.ts b/src/lib/alarms/trade_history.ts index 9b076e5c..c96c4438 100644 --- a/src/lib/alarms/trade_history.ts +++ b/src/lib/alarms/trade_history.ts @@ -1,6 +1,7 @@ import {Trade} from '../types/float_market'; import {TradeHistoryStatus} from '../bridge/handlers/trade_history_status'; import cheerio from 'cheerio'; +import {AppId} from '../types/steam_constants'; export async function pingTradeHistory(pendingTrades: Trade[]) { const history = await getTradeHistory(); @@ -13,7 +14,9 @@ export async function pingTradeHistory(pendingTrades: Trade[]) { // We only want to send history that is relevant to verifying trades on CSFloat const historyForCSFloat = history.filter((e) => { - return !![...e.received_asset_ids, ...e.given_asset_ids].find((e) => { + const received_ids = e.received_assets.map((e) => e.asset_id); + const given_ids = e.given_assets.map((e) => e.asset_id); + return !![...received_ids, ...given_ids].find((e) => { return assetsToFind[e]; }); }); @@ -33,10 +36,76 @@ async function getTradeHistory(): Promise { }); const body = await resp.text(); + const webAPIToken = /data-loyalty_webapi_token=\""([a-zA-Z0-9\_\.-]+)"\"/.exec(body); + if (webAPIToken && webAPIToken.length > 1) { + try { + const history = await getTradeHistoryFromAPI(webAPIToken[1]); + if (history.length > 0) { + // Hedge in case this endpoint gets killed, only return if there are results, fallback to HTML parser + return history; + } + } catch (e) { + console.error(e); + } + + // Fallback to HTML parsing + } + if (body.includes('too many requests')) { throw 'Too many requests'; } + return parseTradeHistoryHTML(body); +} + +interface HistoryAsset { + assetid: string; + appid: AppId; + new_assetid: string; +} + +interface TradeHistoryAPIResponse { + response: { + trades: { + tradeid: string; + steamid_other: string; + status: number; + assets_given: HistoryAsset[]; + assets_received: HistoryAsset[]; + }[]; + }; +} + +async function getTradeHistoryFromAPI(accessToken: string): Promise { + // This only works if they have granted permission for https://api.steampowered.com + const resp = await fetch( + `https://api.steampowered.com/IEconService/GetTradeHistory/v1/?access_token=${accessToken}&max_trades=50`, + { + credentials: 'include', + // Expect redirect since we're using `me` above + redirect: 'follow', + } + ); + + if (resp.status !== 200) { + throw new Error('invalid status'); + } + + const data = (await resp.json()) as TradeHistoryAPIResponse; + return data.response.trades.map((e) => { + return { + other_party_url: `https://steamcommunity.com/profiles/${e.steamid_other}`, + received_assets: e.assets_received.map((e) => { + return {asset_id: e.assetid, new_asset_id: e.new_assetid}; + }), + given_assets: e.assets_given.map((e) => { + return {asset_id: e.assetid, new_asset_id: e.new_assetid}; + }), + } as TradeHistoryStatus; + }); +} + +function parseTradeHistoryHTML(body: string): TradeHistoryStatus[] { const doc = cheerio.load(body); const statuses = doc('.tradehistoryrow .tradehistory_event_description a') @@ -44,8 +113,8 @@ async function getTradeHistory(): Promise { .map((row) => { return { other_party_url: doc(row).attr('href'), - received_asset_ids: [], - given_asset_ids: [], + received_assets: [], + given_assets: [], } as TradeHistoryStatus; }); @@ -56,9 +125,9 @@ async function getTradeHistory(): Promise { const [text, index, type, assetId] = match; const tradeIndex = parseInt(index); if (type === 'received') { - statuses[tradeIndex].received_asset_ids.push(assetId); + statuses[tradeIndex].received_assets.push({asset_id: assetId}); } else if (type === 'given') { - statuses[tradeIndex].given_asset_ids.push(assetId); + statuses[tradeIndex].given_assets.push({asset_id: assetId}); } } diff --git a/src/lib/alarms/trade_offer.ts b/src/lib/alarms/trade_offer.ts index 88ef13bd..18dd7a93 100644 --- a/src/lib/alarms/trade_offer.ts +++ b/src/lib/alarms/trade_offer.ts @@ -39,6 +39,16 @@ export async function pingSentTradeOffers(pendingTrades: Trade[]) { await TradeOfferStatus.handleRequest({sent_offers: offersForCSFloat}, {}); } +interface TradeOffersAPIResponse { + response: { + trade_offers_sent: { + tradeofferid: string; + accountid_other: string; + trade_offer_state: TradeOfferState; + }[]; + }; +} + async function getEnglishSentTradeOffersHTML(): Promise { const resp = await fetch(`https://steamcommunity.com/id/me/tradeoffers/sent`, { credentials: 'include', diff --git a/src/lib/bridge/handlers/trade_history_status.ts b/src/lib/bridge/handlers/trade_history_status.ts index c04d9956..e0cf4680 100644 --- a/src/lib/bridge/handlers/trade_history_status.ts +++ b/src/lib/bridge/handlers/trade_history_status.ts @@ -1,10 +1,15 @@ import {SimpleHandler} from './main'; import {RequestType} from './types'; +export interface TradeHistoryAsset { + asset_id: string; + new_asset_id?: string; +} + export interface TradeHistoryStatus { other_party_url: string; - received_asset_ids: string[]; - given_asset_ids: string[]; + received_assets: TradeHistoryAsset[]; + given_assets: TradeHistoryAsset[]; } export interface TradeHistoryStatusRequest { diff --git a/src/lib/page_scripts/trade_offers.ts b/src/lib/page_scripts/trade_offers.ts index 077bf466..ab6a260f 100644 --- a/src/lib/page_scripts/trade_offers.ts +++ b/src/lib/page_scripts/trade_offers.ts @@ -1,5 +1,13 @@ import {init} from './utils'; +import {inPageContext} from '../utils/snips'; +import {pingTradeHistory} from '../alarms/trade_history'; init('src/lib/page_scripts/trade_offers.js', main); function main() {} + +if (!inPageContext()) { + pingTradeHistory([]) + .then((r) => console.log(r)) + .catch((e) => console.log(e)); +}