Skip to content

Commit 6d9f2a8

Browse files
rainerstudiosclaude
andcommitted
Add: Full inventory sync without Steam 10-day restriction
Created inventorySync.js that: - Fetches FULL CS2 inventory using authenticated Steam API - No 10-day restriction (user is logged in to their own account) - Filters CS2 skins only (excludes cases, stickers, etc.) - Sends inventory to backend /api/inventory/sync endpoint - Shows success/error notifications - Can be triggered via window.CS2SyncInventory() This allows users to import ALL their inventory items, including items purchased less than 10 days ago, WITHOUT needing SteamAuth. Next steps: - Add script to manifest.json - Create backend /api/inventory/sync endpoint - Add sync button to extension popup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 400dba5 commit 6d9f2a8

1 file changed

Lines changed: 286 additions & 0 deletions

File tree

src/inventorySync.js

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
// Steam Inventory Sync - Fetch full inventory and send to backend
2+
(() => {
3+
'use strict';
4+
5+
const InventorySyncer = {
6+
initialized: false,
7+
syncing: false,
8+
9+
log(...args) {
10+
console.log('[CS2 Inventory Sync]', ...args);
11+
},
12+
13+
init() {
14+
if (this.initialized) return;
15+
this.initialized = true;
16+
17+
this.log('Initializing inventory syncer...');
18+
19+
// Listen for sync triggers from popup/content script
20+
window.addEventListener('message', (event) => {
21+
if (event.data && event.data.type === 'CS2_SYNC_INVENTORY') {
22+
this.log('📨 Received sync inventory request');
23+
this.syncInventory();
24+
}
25+
});
26+
27+
// Also expose global function for manual triggers
28+
window.CS2SyncInventory = () => this.syncInventory();
29+
30+
this.log('✅ Inventory syncer initialized');
31+
},
32+
33+
async syncInventory() {
34+
if (this.syncing) {
35+
this.log('⏸️ Sync already in progress, skipping...');
36+
return {
37+
success: false,
38+
error: 'Sync already in progress'
39+
};
40+
}
41+
42+
this.syncing = true;
43+
this.log('🔄 Starting inventory sync...');
44+
45+
try {
46+
// Step 1: Get Steam ID
47+
const steamId = await this.getSteamId();
48+
if (!steamId) {
49+
throw new Error('Could not determine Steam ID');
50+
}
51+
52+
this.log(`✅ Steam ID: ${steamId}`);
53+
54+
// Step 2: Fetch CS2 inventory
55+
const inventory = await this.fetchCS2Inventory(steamId);
56+
this.log(`✅ Fetched ${inventory.length} CS2 items`);
57+
58+
// Step 3: Send to backend
59+
const result = await this.sendToBackend(inventory, steamId);
60+
this.log('✅ Sync complete:', result);
61+
62+
// Notify user
63+
this.showNotification('success', `Synced ${inventory.length} items`);
64+
65+
return {
66+
success: true,
67+
itemCount: inventory.length,
68+
result
69+
};
70+
71+
} catch (error) {
72+
this.log('❌ Sync failed:', error);
73+
this.showNotification('error', error.message);
74+
75+
return {
76+
success: false,
77+
error: error.message
78+
};
79+
} finally {
80+
this.syncing = false;
81+
}
82+
},
83+
84+
async getSteamId() {
85+
// Try to get from g_steamID global
86+
if (typeof g_steamID !== 'undefined' && g_steamID) {
87+
return g_steamID;
88+
}
89+
90+
// Try to parse from page URL (e.g., /profiles/76561199094452064)
91+
const urlMatch = window.location.pathname.match(/\/profiles\/(\d+)/);
92+
if (urlMatch) {
93+
return urlMatch[1];
94+
}
95+
96+
// Try to get from profile data
97+
if (typeof UserYou !== 'undefined' && UserYou?.strSteamId) {
98+
return UserYou.strSteamId;
99+
}
100+
101+
return null;
102+
},
103+
104+
async fetchCS2Inventory(steamId) {
105+
this.log('📡 Fetching CS2 inventory from Steam...');
106+
107+
// Steam Inventory API - authenticated endpoint (no 10-day restriction!)
108+
const url = `https://steamcommunity.com/inventory/${steamId}/730/2?l=english&count=5000`;
109+
110+
const response = await fetch(url, {
111+
credentials: 'include', // Important: includes auth cookies
112+
headers: {
113+
'Accept': 'application/json'
114+
}
115+
});
116+
117+
if (!response.ok) {
118+
throw new Error(`Steam API returned ${response.status}`);
119+
}
120+
121+
const data = await response.json();
122+
123+
if (!data.assets || !data.descriptions) {
124+
throw new Error('Invalid inventory response from Steam');
125+
}
126+
127+
this.log(`📦 Raw inventory: ${data.assets.length} assets, ${data.descriptions.length} descriptions`);
128+
129+
// Map assets to descriptions
130+
const inventory = data.assets.map(asset => {
131+
// Find matching description
132+
const desc = data.descriptions.find(d =>
133+
d.classid === asset.classid &&
134+
d.instanceid === asset.instanceid
135+
);
136+
137+
if (!desc) return null;
138+
139+
// Only include skins with float values (exclude cases, stickers, etc.)
140+
if (!this.isCS2Skin(desc.market_hash_name)) {
141+
return null;
142+
}
143+
144+
return {
145+
assetid: asset.assetid,
146+
classid: asset.classid,
147+
instanceid: asset.instanceid,
148+
amount: asset.amount,
149+
name: desc.name,
150+
market_hash_name: desc.market_hash_name,
151+
type: desc.type,
152+
icon_url: desc.icon_url,
153+
tradable: desc.tradable ? 1 : 0,
154+
marketable: desc.marketable ? 1 : 0,
155+
commodity: desc.commodity ? 1 : 0,
156+
market_actions: desc.market_actions,
157+
descriptions: desc.descriptions,
158+
tags: desc.tags
159+
};
160+
}).filter(item => item !== null);
161+
162+
return inventory;
163+
},
164+
165+
isCS2Skin(marketHashName) {
166+
if (!marketHashName) return false;
167+
168+
const conditions = [
169+
'Factory New',
170+
'Minimal Wear',
171+
'Field-Tested',
172+
'Well-Worn',
173+
'Battle-Scarred'
174+
];
175+
176+
return conditions.some(condition => marketHashName.includes(condition));
177+
},
178+
179+
async sendToBackend(inventory, steamId) {
180+
this.log('📤 Sending inventory to backend...');
181+
182+
// Get backend URL from storage or use default
183+
const backendUrl = await this.getBackendUrl();
184+
185+
const payload = {
186+
steamId: steamId,
187+
inventory: inventory,
188+
timestamp: new Date().toISOString(),
189+
appId: 730,
190+
contextId: 2
191+
};
192+
193+
this.log(`📤 Sending ${inventory.length} items to ${backendUrl}`);
194+
195+
const response = await fetch(`${backendUrl}/api/inventory/sync`, {
196+
method: 'POST',
197+
headers: {
198+
'Content-Type': 'application/json'
199+
},
200+
body: JSON.stringify(payload),
201+
credentials: 'include' // Include session cookies
202+
});
203+
204+
if (!response.ok) {
205+
const errorText = await response.text();
206+
throw new Error(`Backend returned ${response.status}: ${errorText}`);
207+
}
208+
209+
const result = await response.json();
210+
return result;
211+
},
212+
213+
async getBackendUrl() {
214+
// Try to get from chrome storage
215+
if (typeof chrome !== 'undefined' && chrome.storage) {
216+
return new Promise((resolve) => {
217+
chrome.storage.sync.get(['backendUrl'], (result) => {
218+
resolve(result.backendUrl || 'https://steamledger.com');
219+
});
220+
});
221+
}
222+
223+
return 'https://steamledger.com';
224+
},
225+
226+
showNotification(type, message) {
227+
// Create a toast notification
228+
const notification = document.createElement('div');
229+
notification.style.cssText = `
230+
position: fixed;
231+
top: 20px;
232+
right: 20px;
233+
background: ${type === 'success' ? '#4CAF50' : '#f44336'};
234+
color: white;
235+
padding: 16px 24px;
236+
border-radius: 4px;
237+
font-family: Arial, sans-serif;
238+
font-size: 14px;
239+
z-index: 999999;
240+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
241+
animation: slideIn 0.3s ease;
242+
`;
243+
244+
notification.innerHTML = `
245+
<strong>CS2 Inventory Sync</strong><br/>
246+
${message}
247+
`;
248+
249+
// Add slide-in animation
250+
const style = document.createElement('style');
251+
style.textContent = `
252+
@keyframes slideIn {
253+
from {
254+
transform: translateX(400px);
255+
opacity: 0;
256+
}
257+
to {
258+
transform: translateX(0);
259+
opacity: 1;
260+
}
261+
}
262+
`;
263+
document.head.appendChild(style);
264+
265+
document.body.appendChild(notification);
266+
267+
// Remove after 3 seconds
268+
setTimeout(() => {
269+
notification.style.animation = 'slideIn 0.3s ease reverse';
270+
setTimeout(() => notification.remove(), 300);
271+
}, 3000);
272+
}
273+
};
274+
275+
// Initialize when script loads
276+
if (document.readyState === 'loading') {
277+
document.addEventListener('DOMContentLoaded', () => InventorySyncer.init());
278+
} else {
279+
InventorySyncer.init();
280+
}
281+
282+
// Expose for debugging
283+
window.InventorySyncer = InventorySyncer;
284+
285+
console.log('🔄 CS2 Inventory Sync Script Loaded!', new Date().toLocaleTimeString());
286+
})();

0 commit comments

Comments
 (0)