Skip to content
Merged
1 change: 1 addition & 0 deletions src/environment.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export const environment = {
loggingLevel: 'Error',
},
reverse_watch_base_api_url: 'http://localhost:3434/api',
floatdb_gateway_url: 'https://gateway.floatdb.com',
};
1 change: 1 addition & 0 deletions src/environment.staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export const environment = {
loggingLevel: 'Error',
},
reverse_watch_base_api_url: 'https://reverse.watch/api',
floatdb_gateway_url: 'https://gateway.floatdb.com',
};
1 change: 1 addition & 0 deletions src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export const environment = {
loggingLevel: 'Warn',
},
reverse_watch_base_api_url: 'https://reverse.watch/api',
floatdb_gateway_url: 'https://gateway.floatdb.com',
};
203 changes: 127 additions & 76 deletions src/lib/bridge/handlers/fetch_inspect_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {decodeLink, CEconItemPreviewDataBlock} from '@csfloat/cs2-inspect-serial
import {SimpleHandler} from './main';
import {RequestType} from './types';
import {gSchemaFetcher} from '../../services/schema_fetcher';
import {gThresholdFetcher} from '../../services/threshold_fetcher';
import {gRankBatcher} from '../../services/rank_batcher';
import type {ItemSchema} from '../../types/schema';

interface Sticker {
Expand Down Expand Up @@ -42,95 +44,144 @@ export interface ItemInfo {

export interface FetchInspectInfoRequest {
link: string;
listPrice?: number;
asset_id: string;
}

export interface FetchInspectInfoResponse {
iteminfo: ItemInfo;
error?: string;
}

async function processInspectItem(req: FetchInspectInfoRequest, schema: ItemSchema.Response): Promise<ItemInfo> {
let decoded: CEconItemPreviewDataBlock;
try {
decoded = decodeLink(req.link);
} catch (error) {
throw new Error('Failed to decode inspect link');
}

const defindex = decoded.defindex ?? 0;
const paintindex = decoded.paintindex ?? 0;
const floatvalue = decoded.paintwear ?? 0;

let min = 0;
let max = 1;
let weaponType: string | undefined;
let itemName: string | undefined;
let rarityName: string | undefined;
let stickers: Sticker[] = [];
let keychains: Keychain[] = [];

try {
const weapon = schema.weapons[defindex];
const paint = getSchemaPaint(weapon, paintindex);

weaponType = weapon?.name;
rarityName = schema.rarities.find((rarity) => rarity.value === (paint?.rarity ?? decoded.rarity))?.name;

if (paint) {
itemName = paint.name;
min = paint.min;
max = paint.max;
}

stickers = decoded.stickers.map((sticker) => {
const schemaSticker = schema.stickers[sticker.stickerId?.toString() ?? ''];
return {
slot: sticker.slot ?? 0,
stickerId: sticker.stickerId ?? 0,
wear: sticker.wear,
name: schemaSticker?.market_hash_name,
};
});

keychains = decoded.keychains.map((keychain) => {
const schemaKeychain = schema.keychains[keychain.stickerId?.toString() ?? ''];
return {
slot: keychain.slot ?? 0,
stickerId: keychain.stickerId ?? 0,
wear: keychain.wear,
pattern: keychain.pattern ?? 0,
name: schemaKeychain?.market_hash_name,
};
});
} catch (error) {
console.error('Failed to fetch schema item metadata:', error);
}

const iteminfo: ItemInfo = {
stickers,
keychains,
itemid: decoded.itemid?.toString() ?? '',
defindex,
paintindex,
rarity: decoded.rarity ?? 0,
quality: decoded.quality ?? 0,
paintseed: decoded.paintseed ?? 0,
inventory: decoded.inventory ?? 0,
origin: decoded.origin ?? 0,
floatvalue,
min,
max,
weapon_type: weaponType,
item_name: itemName,
rarity_name: rarityName,
wear_name: getWearName(floatvalue),
};

try {
if (decoded.itemid != null && decoded.paintwear != null) {
const stattrak = decoded.killeaterscoretype !== undefined;
const souvenir = decoded.quality === 12;

if (await gThresholdFetcher.qualifiesForRankCheck(defindex, paintindex, stattrak, souvenir, floatvalue)) {
const rankResult = await gRankBatcher.check(req.link, decoded.itemid.toString());
if (rankResult) {
iteminfo.low_rank = rankResult.low_rank;
iteminfo.high_rank = rankResult.high_rank;
}
}
}
} catch (e) {
console.error('Failed to check rank:', e);
}

return iteminfo;
}

export const FetchInspectInfo = new SimpleHandler<FetchInspectInfoRequest, FetchInspectInfoResponse>(
RequestType.FETCH_INSPECT_INFO,
async (req) => {
let decoded: CEconItemPreviewDataBlock;
try {
decoded = decodeLink(req.link);
} catch (error) {
throw new Error('Failed to decode inspect link');
}
const schema = await gSchemaFetcher.getSchema();
return {iteminfo: await processInspectItem(req, schema)};
}
);

const defindex = decoded.defindex ?? 0;
const paintindex = decoded.paintindex ?? 0;
const floatvalue = decoded.paintwear ?? 0;

let min = 0;
let max = 1;
let weaponType: string | undefined;
let itemName: string | undefined;
let rarityName: string | undefined;
let stickers: Sticker[] = [];
let keychains: Keychain[] = [];

try {
const schema = await gSchemaFetcher.getSchema();
const weapon = schema.weapons[defindex];
const paint = getSchemaPaint(weapon, paintindex);

weaponType = weapon?.name;
rarityName = schema.rarities.find((rarity) => rarity.value === (paint?.rarity ?? decoded.rarity))?.name;

if (paint) {
itemName = paint.name;
min = paint.min;
max = paint.max;
}
export interface FetchInspectInfoBatchRequest {
requests: FetchInspectInfoRequest[];
}

stickers = decoded.stickers.map((sticker) => {
const schemaSticker = schema.stickers[sticker.stickerId?.toString() ?? ''];
return {
slot: sticker.slot ?? 0,
stickerId: sticker.stickerId ?? 0,
wear: sticker.wear,
name: schemaSticker?.market_hash_name,
};
});

keychains = decoded.keychains.map((keychain) => {
const schemaKeychain = schema.keychains[keychain.stickerId?.toString() ?? ''];
return {
slot: keychain.slot ?? 0,
stickerId: keychain.stickerId ?? 0,
wear: keychain.wear,
pattern: keychain.pattern ?? 0,
name: schemaKeychain?.market_hash_name,
};
});
} catch (error) {
console.error('Failed to fetch schema item metadata:', error);
}
export interface FetchInspectInfoBatchResponse {
results: Record<string, FetchInspectInfoResponse>;
}

return {
iteminfo: {
stickers,
keychains,
itemid: decoded.itemid?.toString() ?? '',
defindex,
paintindex,
rarity: decoded.rarity ?? 0,
quality: decoded.quality ?? 0,
paintseed: decoded.paintseed ?? 0,
inventory: decoded.inventory ?? 0,
origin: decoded.origin ?? 0,
floatvalue,
min,
max,
weapon_type: weaponType,
item_name: itemName,
rarity_name: rarityName,
wear_name: getWearName(floatvalue),
},
};
export const FetchInspectInfoBatch = new SimpleHandler<FetchInspectInfoBatchRequest, FetchInspectInfoBatchResponse>(
RequestType.FETCH_INSPECT_INFO_BATCH,
async (req) => {
const schema = await gSchemaFetcher.getSchema();

const results: Record<string, FetchInspectInfoResponse> = {};
await Promise.all(
req.requests.map(async (itemReq) => {
try {
results[itemReq.asset_id] = {iteminfo: await processInspectItem(itemReq, schema)};
} catch (e) {
results[itemReq.asset_id] = {iteminfo: {} as ItemInfo, error: (e as any).toString()};
}
})
);

return {results};
}
);

Expand Down
3 changes: 2 additions & 1 deletion src/lib/bridge/handlers/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ExecuteScriptOnPage} from './execute_script';
import {FetchStall} from './fetch_stall';
import {FetchInspectInfo} from './fetch_inspect_info';
import {FetchInspectInfo, FetchInspectInfoBatch} from './fetch_inspect_info';
import {ExecuteCssOnPage} from './execute_css';
import {StorageGet} from './storage_get';
import {StorageSet} from './storage_set';
Expand Down Expand Up @@ -78,4 +78,5 @@ export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
[RequestType.FETCH_NOTARY_TOKEN]: FetchNotaryToken,
[RequestType.FETCH_STEAM_POWERED_INVENTORY]: FetchSteamPoweredInventory,
[RequestType.FETCH_REVERSAL_STATUS]: FetchReversalStatus,
[RequestType.FETCH_INSPECT_INFO_BATCH]: FetchInspectInfoBatch,
};
1 change: 1 addition & 0 deletions src/lib/bridge/handlers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ export enum RequestType {
FETCH_NOTARY_TOKEN = 35,
FETCH_STEAM_POWERED_INVENTORY = 36,
FETCH_REVERSAL_STATUS = 37,
FETCH_INSPECT_INFO_BATCH = 38,
}
3 changes: 2 additions & 1 deletion src/lib/components/common/item_holder_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,12 @@ export abstract class ItemHolderMetadata extends FloatElement {
if (!isSkin(this.asset) && !isCharm(this.asset)) return;

// Commodities won't have inspect links
if (!this.inspectLink) return;
if (!this.inspectLink || !this.assetId) return;

try {
this.itemInfo = await gFloatFetcher.fetch({
link: this.inspectLink,
asset_id: this.assetId,
});
} catch (e: any) {
console.error(`Failed to fetch float for ${this.assetId}: ${e.toString()}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import {ContextId} from '../../types/steam_constants';
import {isCAppwideInventory} from '../../utils/checkers';

@CustomElement()
@InjectAppend(
'#active_inventory_page div.inventory_page:not([style*="display: none"]) .itemHolder div.app730',
InjectionMode.CONTINUOUS
)
@InjectAppend('#active_inventory_page div.inventory_page .itemHolder div.app730', InjectionMode.CONTINUOUS)
export class InventoryItemHolderMetadata extends ItemHolderMetadata {
get asset(): rgAsset | undefined {
if (!this.assetId) return;
Expand Down
1 change: 1 addition & 0 deletions src/lib/components/inventory/selected_item_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ export class SelectedItemInfo extends FloatElement {
) {
try {
this.itemInfo = await gFloatFetcher.fetch({
asset_id: this.asset.assetid,
link: this.inspectLink,
});
} catch (e: any) {
Expand Down
14 changes: 5 additions & 9 deletions src/lib/components/market/item_row_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ export class ItemRowWrapper extends FloatElement {
}

async fetchFloat(): Promise<ItemInfo> {
if (!this.listingInfo?.asset.id) {
throw new Error('Missing asset ID');
}

return gFloatFetcher.fetch({
link: this.inspectLink!,
listPrice: this.usdPrice,
asset_id: this.listingInfo?.asset.id,
});
}

Expand All @@ -111,14 +115,6 @@ export class ItemRowWrapper extends FloatElement {
return (this.listingInfo.converted_price + this.listingInfo.converted_fee) / 100;
}

get usdPrice(): number | undefined {
if (this.listingInfo?.currencyid === Currency.USD) {
return this.listingInfo.price + this.listingInfo.fee;
} else if (this.listingInfo?.converted_currencyid === Currency.USD) {
return this.listingInfo.converted_price! + this.listingInfo.converted_fee!;
}
}

@state()
private itemInfo: ItemInfo | undefined;
@state()
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/market/sort_listings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ export class SortListings extends FloatElement {
// Catch error to prevent one failure from stopping the Promise.all() later
try {
const link = getMarketInspectLink(listingId);
const info = await gFloatFetcher.fetch({link: link!});
const listingInfo = g_rgListingInfo[listingId];
const asset = g_rgAssets[AppId.CSGO][ContextId.PRIMARY][listingInfo.asset.id];
const info = await gFloatFetcher.fetch({link: link!, asset_id: listingInfo.asset.id});
return {
failed: false,
info,
Expand Down
Loading