Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default defineConfig([
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-namespace': 'off',
},
},
]);
32 changes: 24 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@
"webpack-cli": "^6.0.1"
},
"dependencies": {
"@csfloat/cs2-inspect-serializer": "^1.1.0",
"@csfloat/tlsn-wasm": "0.1.0-alpha.14",
"buffer": "^6.0.3",
"comlink": "^4.4.2",
"@csfloat/tlsn-wasm": "0.1.0-alpha.14"
"comlink": "^4.4.2"
}
}
141 changes: 123 additions & 18 deletions src/lib/bridge/handlers/fetch_inspect_info.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {decodeLink, CEconItemPreviewDataBlock} from '@csfloat/cs2-inspect-serializer';
import {SimpleHandler} from './main';
import {RequestType} from './types';
import {gSchemaFetcher} from '../../services/schema_fetcher';
import type {ItemSchema} from '../../types/schema';

interface Sticker {
slot: number;
stickerId: number;
codename?: string;
material?: string;
name?: string;
wear?: number;
}
Expand All @@ -25,12 +26,7 @@ export interface ItemInfo {
paintseed: number;
inventory: number;
origin: number;
s: string;
a: string;
d: string;
m: string;
floatvalue: number;
imageurl: string;
min: number;
max: number;
weapon_type?: string;
Expand All @@ -56,16 +52,125 @@ export interface FetchInspectInfoResponse {

export const FetchInspectInfo = new SimpleHandler<FetchInspectInfoRequest, FetchInspectInfoResponse>(
RequestType.FETCH_INSPECT_INFO,
(req) => {
const apiUrl = `https://api.csfloat.com/?url=${req.link}&minimal=true${req.listPrice ? '&listPrice=' + req.listPrice : ''}`;
return fetch(apiUrl).then((resp) => {
return resp.json().then((json: FetchInspectInfoResponse) => {
if (resp.ok) {
return json;
} else {
throw Error(json.error);
}
}) as Promise<FetchInspectInfoResponse>;
});
async (req) => {
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 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;
}

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);
}

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),
},
};
}
);

function getSchemaPaint(weapon: ItemSchema.RawWeapon | undefined, paintIndex: number): ItemSchema.RawPaint | undefined {
if (!weapon) {
return undefined;
}

if (weapon.paints[paintIndex] !== undefined) {
return weapon.paints[paintIndex];
}

if (weapon.paints?.['0'] !== undefined) {
return weapon.paints['0'];
}

const availablePaintIndexes = Object.keys(weapon.paints || {});
if (availablePaintIndexes.length === 1) {
return weapon.paints[availablePaintIndexes[0]];
}
}

function getWearName(floatvalue: number): string | undefined {
if (floatvalue <= 0.07) {
return 'Factory New';
}

if (floatvalue <= 0.15) {
return 'Minimal Wear';
}

if (floatvalue <= 0.38) {
return 'Field-Tested';
}

if (floatvalue <= 0.45) {
return 'Well-Worn';
}

if (floatvalue <= 1) {
return 'Battle-Scarred';
}
}
10 changes: 7 additions & 3 deletions src/lib/components/common/item_holder_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,13 @@ export abstract class ItemHolderMetadata extends FloatElement {
return;
}

return this.asset
?.actions![0].link.replace('%owner_steamid%', this.ownerSteamId)
.replace('%assetid%', this.assetId!);
const link = this.asset?.actions![0].link;
if (link.includes('%propid:6%')) {
const propId = this.asset.asset_properties?.find((p) => p.propertyid === 6)?.string_value;
if (!propId || !link) return;
return link.replace('%propid:6%', propId);
}
return link;
}

protected render(): HTMLTemplateResult {
Expand Down
12 changes: 11 additions & 1 deletion src/lib/components/inventory/inventory_item_holder_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,18 @@ export class InventoryItemHolderMetadata extends ItemHolderMetadata {
if (isCAppwideInventory(g_ActiveInventory)) {
const contextId = this.isTradeProtected ? ContextId.PROTECTED : ContextId.PRIMARY;

return g_ActiveInventory.m_rgChildInventories[contextId]?.m_rgAssets[this.assetId]?.description;
const invAsset = g_ActiveInventory.m_rgChildInventories[contextId]?.m_rgAssets[this.assetId];
if (invAsset && !invAsset.description.asset_properties) {
// due to inconsistencies in Steam's data structure, we sometimes need to manually populate this field here
invAsset.description.asset_properties = invAsset.asset_properties;
}
return invAsset?.description;
} else {
const invAsset = g_ActiveInventory.m_rgAssets[this.assetId];
if (invAsset && !invAsset.description.asset_properties) {
// due to inconsistencies in Steam's data structure, we sometimes need to manually populate this field here
invAsset.description.asset_properties = g_ActiveInventory.m_rgAssetProperties[this.assetId];
}
return g_ActiveInventory.m_rgAssets[this.assetId]?.description;
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/lib/components/inventory/selected_item_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,13 @@ export class SelectedItemInfo extends FloatElement {
return;
}

return this.asset.description
?.actions![0].link.replace('%owner_steamid%', g_ActiveInventory.m_owner.strSteamId!)
.replace('%assetid%', this.asset.assetid!);
const link = this.asset.description?.actions![0].link;
if (link.includes('%propid:6%')) {
const propId = this.asset.asset_properties?.find((p) => p.propertyid === 6)?.string_value;
if (!propId || !link) return;
return link.replace('%propid:6%', propId);
}
return link;
}

get stallListing(): Contract | undefined {
Expand Down
8 changes: 7 additions & 1 deletion src/lib/components/market/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ export function getMarketInspectLink(listingId: string): string | undefined {
const asset = g_rgAssets[AppId.CSGO][ContextId.PRIMARY][listingInfo.asset.id!];
if (!asset || !asset.market_actions?.length) return;

return asset.market_actions[0].link.replace('%listingid%', listingId).replace('%assetid%', asset.id);
const link = asset.market_actions[0].link;
if (link.includes('%propid:6%')) {
const propId = asset.asset_properties?.find((p) => p.propertyid === 6)?.string_value;
if (!propId || !link) return;
return link.replace('%propid:6%', propId);
}
return link;
}

/**
Expand Down
Loading
Loading