Skip to content

Commit 10fcaa6

Browse files
authored
Merge pull request #373 from csfloat/fix/inspect-link-parsing
Fix: Local Parsing for new Inspect Link Format
2 parents 8b0a508 + c38f348 commit 10fcaa6

12 files changed

Lines changed: 311 additions & 36 deletions

File tree

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default defineConfig([
4646
'@typescript-eslint/no-empty-function': 'off',
4747
'@typescript-eslint/no-unused-vars': 'off',
4848
'@typescript-eslint/no-empty-object-type': 'off',
49+
'@typescript-eslint/no-namespace': 'off',
4950
},
5051
},
5152
]);

package-lock.json

Lines changed: 24 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@
6868
"webpack-cli": "^6.0.1"
6969
},
7070
"dependencies": {
71+
"@csfloat/cs2-inspect-serializer": "^1.1.0",
72+
"@csfloat/tlsn-wasm": "0.1.0-alpha.14",
7173
"buffer": "^6.0.3",
72-
"comlink": "^4.4.2",
73-
"@csfloat/tlsn-wasm": "0.1.0-alpha.14"
74+
"comlink": "^4.4.2"
7475
}
7576
}

src/lib/bridge/handlers/fetch_inspect_info.ts

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import {decodeLink, CEconItemPreviewDataBlock} from '@csfloat/cs2-inspect-serializer';
12
import {SimpleHandler} from './main';
23
import {RequestType} from './types';
4+
import {gSchemaFetcher} from '../../services/schema_fetcher';
5+
import type {ItemSchema} from '../../types/schema';
36

47
interface Sticker {
58
slot: number;
69
stickerId: number;
7-
codename?: string;
8-
material?: string;
910
name?: string;
1011
wear?: number;
1112
}
@@ -25,12 +26,7 @@ export interface ItemInfo {
2526
paintseed: number;
2627
inventory: number;
2728
origin: number;
28-
s: string;
29-
a: string;
30-
d: string;
31-
m: string;
3229
floatvalue: number;
33-
imageurl: string;
3430
min: number;
3531
max: number;
3632
weapon_type?: string;
@@ -56,16 +52,125 @@ export interface FetchInspectInfoResponse {
5652

5753
export const FetchInspectInfo = new SimpleHandler<FetchInspectInfoRequest, FetchInspectInfoResponse>(
5854
RequestType.FETCH_INSPECT_INFO,
59-
(req) => {
60-
const apiUrl = `https://api.csfloat.com/?url=${req.link}&minimal=true${req.listPrice ? '&listPrice=' + req.listPrice : ''}`;
61-
return fetch(apiUrl).then((resp) => {
62-
return resp.json().then((json: FetchInspectInfoResponse) => {
63-
if (resp.ok) {
64-
return json;
65-
} else {
66-
throw Error(json.error);
67-
}
68-
}) as Promise<FetchInspectInfoResponse>;
69-
});
55+
async (req) => {
56+
let decoded: CEconItemPreviewDataBlock;
57+
try {
58+
decoded = decodeLink(req.link);
59+
} catch (error) {
60+
throw new Error('Failed to decode inspect link');
61+
}
62+
63+
const defindex = decoded.defindex ?? 0;
64+
const paintindex = decoded.paintindex ?? 0;
65+
const floatvalue = decoded.paintwear ?? 0;
66+
67+
let min = 0;
68+
let max = 1;
69+
let weaponType: string | undefined;
70+
let itemName: string | undefined;
71+
let rarityName: string | undefined;
72+
let stickers: Sticker[] = [];
73+
let keychains: Keychain[] = [];
74+
75+
try {
76+
const schema = await gSchemaFetcher.getSchema();
77+
const weapon = schema.weapons[defindex];
78+
const paint = getSchemaPaint(weapon, paintindex);
79+
80+
weaponType = weapon?.name;
81+
rarityName = schema.rarities.find((rarity) => rarity.value === (paint?.rarity ?? decoded.rarity))?.name;
82+
83+
if (paint) {
84+
itemName = paint.name;
85+
min = paint.min;
86+
max = paint.max;
87+
}
88+
89+
stickers = decoded.stickers.map((sticker) => {
90+
const schemaSticker = schema.stickers[sticker.stickerId?.toString() ?? ''];
91+
return {
92+
slot: sticker.slot ?? 0,
93+
stickerId: sticker.stickerId ?? 0,
94+
wear: sticker.wear,
95+
name: schemaSticker?.market_hash_name,
96+
};
97+
});
98+
99+
keychains = decoded.keychains.map((keychain) => {
100+
const schemaKeychain = schema.keychains[keychain.stickerId?.toString() ?? ''];
101+
return {
102+
slot: keychain.slot ?? 0,
103+
stickerId: keychain.stickerId ?? 0,
104+
wear: keychain.wear,
105+
pattern: keychain.pattern ?? 0,
106+
name: schemaKeychain?.market_hash_name,
107+
};
108+
});
109+
} catch (error) {
110+
console.error('Failed to fetch schema item metadata:', error);
111+
}
112+
113+
return {
114+
iteminfo: {
115+
stickers,
116+
keychains,
117+
itemid: decoded.itemid?.toString() ?? '',
118+
defindex,
119+
paintindex,
120+
rarity: decoded.rarity ?? 0,
121+
quality: decoded.quality ?? 0,
122+
paintseed: decoded.paintseed ?? 0,
123+
inventory: decoded.inventory ?? 0,
124+
origin: decoded.origin ?? 0,
125+
floatvalue,
126+
min,
127+
max,
128+
weapon_type: weaponType,
129+
item_name: itemName,
130+
rarity_name: rarityName,
131+
wear_name: getWearName(floatvalue),
132+
},
133+
};
70134
}
71135
);
136+
137+
function getSchemaPaint(weapon: ItemSchema.RawWeapon | undefined, paintIndex: number): ItemSchema.RawPaint | undefined {
138+
if (!weapon) {
139+
return undefined;
140+
}
141+
142+
if (weapon.paints[paintIndex] !== undefined) {
143+
return weapon.paints[paintIndex];
144+
}
145+
146+
if (weapon.paints?.['0'] !== undefined) {
147+
return weapon.paints['0'];
148+
}
149+
150+
const availablePaintIndexes = Object.keys(weapon.paints || {});
151+
if (availablePaintIndexes.length === 1) {
152+
return weapon.paints[availablePaintIndexes[0]];
153+
}
154+
}
155+
156+
function getWearName(floatvalue: number): string | undefined {
157+
if (floatvalue <= 0.07) {
158+
return 'Factory New';
159+
}
160+
161+
if (floatvalue <= 0.15) {
162+
return 'Minimal Wear';
163+
}
164+
165+
if (floatvalue <= 0.38) {
166+
return 'Field-Tested';
167+
}
168+
169+
if (floatvalue <= 0.45) {
170+
return 'Well-Worn';
171+
}
172+
173+
if (floatvalue <= 1) {
174+
return 'Battle-Scarred';
175+
}
176+
}

src/lib/components/common/item_holder_metadata.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,13 @@ export abstract class ItemHolderMetadata extends FloatElement {
9595
return;
9696
}
9797

98-
return this.asset
99-
?.actions![0].link.replace('%owner_steamid%', this.ownerSteamId)
100-
.replace('%assetid%', this.assetId!);
98+
const link = this.asset?.actions![0].link;
99+
if (link.includes('%propid:6%')) {
100+
const propId = this.asset.asset_properties?.find((p) => p.propertyid === 6)?.string_value;
101+
if (!propId || !link) return;
102+
return link.replace('%propid:6%', propId);
103+
}
104+
return link;
101105
}
102106

103107
protected render(): HTMLTemplateResult {

src/lib/components/inventory/inventory_item_holder_metadata.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,18 @@ export class InventoryItemHolderMetadata extends ItemHolderMetadata {
1818
if (isCAppwideInventory(g_ActiveInventory)) {
1919
const contextId = this.isTradeProtected ? ContextId.PROTECTED : ContextId.PRIMARY;
2020

21-
return g_ActiveInventory.m_rgChildInventories[contextId]?.m_rgAssets[this.assetId]?.description;
21+
const invAsset = g_ActiveInventory.m_rgChildInventories[contextId]?.m_rgAssets[this.assetId];
22+
if (invAsset && !invAsset.description.asset_properties) {
23+
// due to inconsistencies in Steam's data structure, we sometimes need to manually populate this field here
24+
invAsset.description.asset_properties = invAsset.asset_properties;
25+
}
26+
return invAsset?.description;
2227
} else {
28+
const invAsset = g_ActiveInventory.m_rgAssets[this.assetId];
29+
if (invAsset && !invAsset.description.asset_properties) {
30+
// due to inconsistencies in Steam's data structure, we sometimes need to manually populate this field here
31+
invAsset.description.asset_properties = g_ActiveInventory.m_rgAssetProperties[this.assetId];
32+
}
2333
return g_ActiveInventory.m_rgAssets[this.assetId]?.description;
2434
}
2535
}

src/lib/components/inventory/selected_item_info.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,13 @@ export class SelectedItemInfo extends FloatElement {
9494
return;
9595
}
9696

97-
return this.asset.description
98-
?.actions![0].link.replace('%owner_steamid%', g_ActiveInventory.m_owner.strSteamId!)
99-
.replace('%assetid%', this.asset.assetid!);
97+
const link = this.asset.description?.actions![0].link;
98+
if (link.includes('%propid:6%')) {
99+
const propId = this.asset.asset_properties?.find((p) => p.propertyid === 6)?.string_value;
100+
if (!propId || !link) return;
101+
return link.replace('%propid:6%', propId);
102+
}
103+
return link;
100104
}
101105

102106
get stallListing(): Contract | undefined {

src/lib/components/market/helpers.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ export function getMarketInspectLink(listingId: string): string | undefined {
1212
const asset = g_rgAssets[AppId.CSGO][ContextId.PRIMARY][listingInfo.asset.id!];
1313
if (!asset || !asset.market_actions?.length) return;
1414

15-
return asset.market_actions[0].link.replace('%listingid%', listingId).replace('%assetid%', asset.id);
15+
const link = asset.market_actions[0].link;
16+
if (link.includes('%propid:6%')) {
17+
const propId = asset.asset_properties?.find((p) => p.propertyid === 6)?.string_value;
18+
if (!propId || !link) return;
19+
return link.replace('%propid:6%', propId);
20+
}
21+
return link;
1622
}
1723

1824
/**

0 commit comments

Comments
 (0)