Skip to content

Commit

Permalink
Merge pull request #234 from csfloat/feature/trade-cancel-pings
Browse files Browse the repository at this point in the history
Implements Cancel Trade Pings
  • Loading branch information
Step7750 authored May 21, 2024
2 parents 87dce81 + 9967ea1 commit bc01dd5
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 19 deletions.
8 changes: 7 additions & 1 deletion src/lib/alarms/csfloat_trade_pings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Trade} from '../types/float_market';
import {FetchPendingTrades} from '../bridge/handlers/fetch_pending_trades';
import {pingTradeHistory} from './trade_history';
import {pingSentTradeOffers} from './trade_offer';
import {pingCancelTrades, pingSentTradeOffers} from './trade_offer';
import {HasPermissions} from '../bridge/handlers/has_permissions';
import {PingExtensionStatus} from '../bridge/handlers/ping_extension_status';

Expand Down Expand Up @@ -53,4 +53,10 @@ export async function pingTradeStatus() {
} catch (e) {
console.error('failed to ping sent trade offer state', e);
}

try {
await pingCancelTrades(pendingTrades);
} catch (e) {
console.error('failed to ping cancel ping trade offers', e);
}
}
101 changes: 83 additions & 18 deletions src/lib/alarms/trade_offer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {TradeOfferState} from '../types/steam_constants';
import {Trade} from '../types/float_market';
import {Trade, TradeState} from '../types/float_market';
import {TradeOfferStatus, TradeOffersType} from '../bridge/handlers/trade_offer_status';
import {clearAccessTokenFromStorage, getAccessToken} from './access_token';
import {AnnotateOffer} from '../bridge/handlers/annotate_offer';
import {PingCancelTrade} from '../bridge/handlers/ping_cancel_trade';

interface OfferStatus {
offer_id: string;
Expand Down Expand Up @@ -64,6 +65,44 @@ export async function pingSentTradeOffers(pendingTrades: Trade[]) {
}
}

export async function pingCancelTrades(pendingTrades: Trade[]) {
const hasWaitForCancelPing = pendingTrades.find((e) => e.state === TradeState.PENDING && e.wait_for_cancel_ping);
if (!hasWaitForCancelPing) {
// Nothing to process/ping, exit
return;
}

const tradeOffers = await getSentAndReceivedTradeOffersFromAPI();

const allTradeOffers = [...(tradeOffers.sent || []), ...(tradeOffers.received || [])];

for (const trade of pendingTrades) {
if (trade.state !== TradeState.PENDING) {
continue;
}

if (!trade.wait_for_cancel_ping) {
continue;
}

const tradeOffer = allTradeOffers.find((e) => e.offer_id === trade.steam_offer.id);
if (
tradeOffer &&
(tradeOffer.state === TradeOfferState.Active ||
tradeOffer.state === TradeOfferState.Accepted ||
tradeOffer.state === TradeOfferState.CreatedNeedsConfirmation)
) {
// We don't want to send a cancel ping if the offer is active or valid
continue;
}

try {
await PingCancelTrade.handleRequest({trade_id: trade.id}, {});
} catch (e) {
console.error(`failed to send cancel ping for trade ${trade.id}`, e);
}
}
}
async function getEnglishSentTradeOffersHTML(): Promise<string> {
const resp = await fetch(`https://steamcommunity.com/id/me/tradeoffers/sent`, {
credentials: 'include',
Expand All @@ -90,7 +129,7 @@ async function getEnglishSentTradeOffersHTML(): Promise<string> {

async function getSentTradeOffers(): Promise<{offers: OfferStatus[]; type: TradeOffersType}> {
try {
const offers = await getTradeOffersFromAPI();
const offers = await getSentTradeOffersFromAPI();
if (offers.length > 0) {
// Hedge in case this endpoint gets killed, only return if there are results, fallback to HTML parser
return {offers, type: TradeOffersType.API};
Expand All @@ -109,19 +148,31 @@ interface TradeOfferItem {
assetid: string;
}

interface TradeOffersAPIOffer {
tradeofferid: string;
accountid_other: string;
trade_offer_state: TradeOfferState;
items_to_give?: TradeOfferItem[];
items_to_receive?: TradeOfferItem[];
}

interface TradeOffersAPIResponse {
response: {
trade_offers_sent: {
tradeofferid: string;
accountid_other: string;
trade_offer_state: TradeOfferState;
items_to_give?: TradeOfferItem[];
items_to_receive?: TradeOfferItem[];
}[];
trade_offers_sent: TradeOffersAPIOffer[];
trade_offers_received: TradeOffersAPIOffer[];
};
}

async function getTradeOffersFromAPI(): Promise<OfferStatus[]> {
function offerStateMapper(e: TradeOffersAPIOffer): OfferStatus {
return {
offer_id: e.tradeofferid,
state: e.trade_offer_state,
given_asset_ids: (e.items_to_give || []).map((e) => e.assetid),
received_asset_ids: (e.items_to_receive || []).map((e) => e.assetid),
} as OfferStatus;
}

async function getSentTradeOffersFromAPI(): Promise<OfferStatus[]> {
const accessToken = await getAccessToken();

const resp = await fetch(
Expand All @@ -136,14 +187,28 @@ async function getTradeOffersFromAPI(): Promise<OfferStatus[]> {
}

const data = (await resp.json()) as TradeOffersAPIResponse;
return data.response.trade_offers_sent.map((e) => {
return {
offer_id: e.tradeofferid,
state: e.trade_offer_state,
given_asset_ids: (e.items_to_give || []).map((e) => e.assetid),
received_asset_ids: (e.items_to_receive || []).map((e) => e.assetid),
} as OfferStatus;
});
return data.response.trade_offers_sent.map(offerStateMapper);
}

async function getSentAndReceivedTradeOffersFromAPI(): Promise<{received: OfferStatus[]; sent: OfferStatus[]}> {
const accessToken = await getAccessToken();

const resp = await fetch(
`https://api.steampowered.com/IEconService/GetTradeOffers/v1/?access_token=${accessToken}&get_received_offers=true&get_sent_offers=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(offerStateMapper),
sent: data.response.trade_offers_sent.map(offerStateMapper),
};
}

const BANNER_TO_STATE: {[banner: string]: TradeOfferState} = {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/bridge/handlers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {TradeOfferStatus} from './trade_offer_status';
import {HasPermissions} from './has_permissions';
import {PingSetupExtension} from './ping_setup_extension';
import {PingExtensionStatus} from './ping_extension_status';
import {PingCancelTrade} from './ping_cancel_trade';

export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
[RequestType.EXECUTE_SCRIPT_ON_PAGE]: ExecuteScriptOnPage,
Expand All @@ -34,4 +35,5 @@ export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
[RequestType.HAS_PERMISSIONS]: HasPermissions,
[RequestType.PING_SETUP_EXTENSION]: PingSetupExtension,
[RequestType.PING_EXTENSION_STATUS]: PingExtensionStatus,
[RequestType.PING_CANCEL_TRADE]: PingCancelTrade,
};
31 changes: 31 additions & 0 deletions src/lib/bridge/handlers/ping_cancel_trade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {SimpleHandler} from './main';
import {RequestType} from './types';
import {environment} from '../../../environment';
import {Trade} from '../../types/float_market';

export interface PingCancelTradeRequest {
trade_id: string;
}

export interface PingCancelTradeResponse {
trade: Trade;
}

export const PingCancelTrade = new SimpleHandler<PingCancelTradeRequest, PingCancelTradeResponse>(
RequestType.PING_CANCEL_TRADE,
async (req) => {
const resp = await fetch(`${environment.csfloat_base_api_url}/v1/trades/${req.trade_id}/cancel-ping`, {
credentials: 'include',
method: 'POST',
});

if (resp.status !== 200) {
throw new Error('invalid status');
}

const trade = (await resp.json()) as Trade;
return {
trade,
};
}
);
1 change: 1 addition & 0 deletions src/lib/bridge/handlers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export enum RequestType {
HAS_PERMISSIONS,
PING_SETUP_EXTENSION,
PING_EXTENSION_STATUS,
PING_CANCEL_TRADE,
}
1 change: 1 addition & 0 deletions src/lib/types/float_market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,5 @@ export interface Trade {
state: TradeState;
trade_url: string;
steam_offer: SteamOffer;
wait_for_cancel_ping?: boolean;
}

0 comments on commit bc01dd5

Please sign in to comment.