|
const https = require('https'); |
|
|
|
const fs = require('fs'); |
|
|
|
const ids = { |
|
target: '76561197966563795' |
|
}; |
|
|
|
class AssetCache { |
|
cache = new Map() |
|
|
|
cacheName = 'assetCache.json'; |
|
|
|
async init() { |
|
try { |
|
const file = await readFile(this.cacheName); |
|
// const output = JSON.parse(file); |
|
const parsed = new Map(Object.entries(file)); |
|
this.cache = parsed; |
|
} catch {} |
|
} |
|
|
|
async save() { |
|
await saveFile(JSON.stringify(Object.fromEntries(this.cache.entries())), this.cacheName); |
|
} |
|
|
|
get(key) { |
|
return this.cache.get(key); |
|
} |
|
|
|
set(key, value) { |
|
this.cache.set(key, value); |
|
} |
|
} |
|
|
|
async function getUrl(url) { |
|
return new Promise((resolve, reject) => { |
|
const callUrl = async (url) => { |
|
https.get(url, (res) => { |
|
console.log('statusCode:', res.statusCode); |
|
console.log('headers:', res.headers); |
|
let data = "" |
|
res.on('data', (d) => { |
|
data += d; |
|
}); |
|
res.on('end', () => { |
|
if (200 !== res.statusCode) { |
|
console.log(data); |
|
setTimeout(() => callUrl(url), 50); |
|
} else { |
|
resolve(data); |
|
} |
|
}) |
|
}).on('error', (e) => { |
|
if(e.code !== undefined && e.code === "ECONNRESET") { |
|
setTimeout(() => callUrl(url), 50); |
|
} else { |
|
reject(e); |
|
} |
|
}); |
|
} |
|
callUrl(url).catch((e) => reject(e)); |
|
}); |
|
} |
|
|
|
let key = null; |
|
async function apiKey() { |
|
if (!key) key = JSON.parse((await fs.promises.readFile('.env.json')).toString()); |
|
return key; |
|
} |
|
|
|
async function getAssetClassInfo(queries, classCount) { |
|
let url = `https://api.steampowered.com/ISteamEconomy/GetAssetClassInfo/v0001/?key=${key.key}&format=json&language=en&appid=440`; |
|
url += `&class_count=${classCount}${queries}`; |
|
const result = JSON.parse(await getUrl(url)); |
|
if (result.result === undefined || result.result.success === false) { |
|
await new Promise(r => setTimeout(r, 20)); |
|
return await getAssetClassInfo(queries, classCount); |
|
} |
|
return result; |
|
} |
|
|
|
async function getAndCacheAssetClassInfo(queries, classCount, assetCache) { |
|
const classesInfo = await getAssetClassInfo(queries, classCount); |
|
for (const k in classesInfo.result) { |
|
if('success' !== k) { |
|
let key; |
|
if(classesInfo.result[k].instanceid) { |
|
key = `${k}_${classesInfo.result[k].instanceid}` |
|
} else { |
|
key = `${k}_0` |
|
} |
|
assetCache.set(key, classesInfo.result[k]); |
|
} |
|
} |
|
return classesInfo.result; |
|
} |
|
|
|
async function getAssetClassInfoForTrades({trades, assetCache}) { |
|
let results = []; |
|
for await (const trade of trades) { |
|
if(trade.assets_received !== undefined) { |
|
let classCount = 0; |
|
let pending = new Set(); |
|
let queries = ''; |
|
for await (const asset of trade.assets_received) { |
|
const key = `${asset.classid}_${asset.instanceid}`; |
|
const classInfo = assetCache.get(key); |
|
if(classInfo !== undefined && !pending.has(key)) { |
|
if(asset.appid === 440) { |
|
pending.add(key); |
|
queries += `&classid${classCount}=${asset.classid}&instanceid${classCount}=${asset.instanceid}` |
|
classCount += 1; |
|
if(classCount === 30) { |
|
results = results.concat(await getAndCacheAssetClassInfo(queries, classCount, assetCache)); |
|
classCount = 0; |
|
queries = ''; |
|
pending = new Set(); |
|
} |
|
} |
|
} |
|
} |
|
if(classCount > 0) { |
|
results = results.concat(await getAndCacheAssetClassInfo(queries, classCount, assetCache)); |
|
} |
|
} |
|
if(trade.assets_given !== undefined) { |
|
let classCount = 0; |
|
let pending = new Set(); |
|
let queries = ''; |
|
for await (const asset of trade.assets_given) { |
|
const key = `${asset.classid}_${asset.instanceid}`; |
|
const classInfo = assetCache.get(key); |
|
if(classInfo !== undefined && !pending.has(key)) { |
|
if(asset.appid === 440) { |
|
pending.add(key); |
|
queries += `&classid${classCount}=${asset.classid}&instanceid${classCount}=${asset.instanceid}` |
|
classCount += 1; |
|
if(classCount === 30) { |
|
results = results.concat(await getAndCacheAssetClassInfo(queries, classCount, assetCache)); |
|
classCount = 0; |
|
queries = ''; |
|
pending = new Set(); |
|
} |
|
} |
|
} |
|
} |
|
if(classCount > 0) { |
|
results = results.concat(await getAndCacheAssetClassInfo(queries, classCount, assetCache)); |
|
} |
|
} |
|
} |
|
return results; |
|
} |
|
|
|
async function getTF2TradeHistory(options) { |
|
if(undefined === options) options = {}; |
|
if(options.maxTrades === undefined || (1 > options.maxTrades && 30 < options.maxTrades)) options.maxTrades = 30; |
|
let url = `https://api.steampowered.com/IEconService/GetTradeHistory/v1/?key=${key.key}&max_trades=${options.maxTrades}`; |
|
if(options.startAfterTime !== undefined) url = `${url}&start_after_time=${options.startAfterTime}`; |
|
const result = JSON.parse(await getUrl(url)); |
|
if (result.response === undefined || result.response.more === undefined) { |
|
console.log(`failed to get trade history ${url}. sleeping`); |
|
await new Promise(r => setTimeout(r, 20)); |
|
return getTF2TradeHistory(options); |
|
} |
|
return result; |
|
} |
|
|
|
async function saveFile(data, f) { |
|
return new Promise((r, e) => { |
|
fs.writeFile(f, data, (err) => { |
|
if (err) e(err); |
|
console.log('The file has been saved!'); |
|
r(data); |
|
}) |
|
}); |
|
} |
|
|
|
async function readDir(d) { |
|
return new Promise((r, e) => { |
|
fs.readdir(d, (err, files) => { |
|
if (err) e(err); |
|
r(files); |
|
}); |
|
}) |
|
} |
|
|
|
function tradeHistoryDir(steamID) { |
|
return `tradehistory/${steamID}`; |
|
} |
|
|
|
async function writeHistory({history, targetDir, startAfterTime}) { |
|
await fs.promises.mkdir(targetDir, { recursive: true }) |
|
await saveFile(JSON.stringify(history, null, 2), `${targetDir}/${startAfterTime}.json`, { encoding: 'utf-8' }); |
|
} |
|
|
|
async function writeHistoryDetails({details, targetDir, startAfterTime}) { |
|
await fs.promises.mkdir(targetDir, { recursive: true }) |
|
await saveFile(JSON.stringify(details, null, 2), `${targetDir}/${startAfterTime}-details.json`, { encoding: 'utf-8' }); |
|
} |
|
|
|
async function readFile(filePath) { |
|
return JSON.parse((await fs.promises.readFile(filePath, { encoding: 'utf-8' }))); |
|
} |
|
|
|
async function dumpHistoryUntil({targetDir, startAfterTime, maxTimeInit, assetCache}) { |
|
let result = await getTF2TradeHistory({ startAfterTime }); |
|
let trades = result.response.trades; |
|
const firstResultTimeInit = trades[0].time_init; |
|
const lastResultTimeInit = trades[trades.length - 1].time_init; |
|
if(maxTimeInit) { |
|
if(maxTimeInit <= firstResultTimeInit) return; |
|
if(maxTimeInit <= lastResultTimeInit) { |
|
trades = trades.slice(0, trades.findIndex((e) => e.time_init > maxTimeInit ) - 1); |
|
} |
|
} |
|
if(trades.length > 0) { |
|
let details = await getAssetClassInfoForTrades({trades, assetCache}); |
|
await writeHistory({history: trades, targetDir: targetDir, startAfterTime: firstResultTimeInit}); |
|
await writeHistoryDetails({details: details, targetDir: targetDir, startAfterTime: firstResultTimeInit}); |
|
if (result.response.more) { |
|
return dumpHistoryUntil({targetDir: targetDir, startAfterTime: lastResultTimeInit, maxTimeInit: maxTimeInit, assetCache}); |
|
} |
|
} |
|
} |
|
|
|
async function dumpAllHistory({targetDir, startAfterTime, assetCache}) { |
|
let latestTimeInit, oldestTimeInit; |
|
try { |
|
const histories = (await readDir(targetDir)).map((fn) => Number(fn.substring(0, fn.length - 5))); |
|
latestTimeInit = histories[histories.length - 1]; |
|
const oldestHistoryResults = await readFile(`${targetDir}/${histories[0]}.json`); |
|
oldestTimeInit = oldestHistoryResults[oldestHistoryResults.length - 1].time_init; |
|
} catch {} |
|
// get everything new |
|
await dumpHistoryUntil({targetDir: targetDir, startAfterTime: startAfterTime, maxTimeInit: latestTimeInit, assetCache}); |
|
// get everything old.. just in case |
|
await dumpHistoryUntil({targetDir: targetDir, startAfterTime: oldestTimeInit, assetCache}); |
|
} |
|
|
|
async function run() { |
|
const assetCache = new AssetCache(); |
|
await assetCache.init(); |
|
await apiKey(); |
|
const result = await dumpAllHistory({targetDir: tradeHistoryDir(ids.target), assetCache}); |
|
await assetCache.save(); |
|
return result; |
|
} |
|
|
|
run().then((result) => console.log(result)).catch((e) => console.log(e)); |