Files
TurboTrades/import-market-prices.js
2026-01-10 04:57:43 +00:00

389 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import mongoose from "mongoose";
import axios from "axios";
import dotenv from "dotenv";
dotenv.config();
/**
* Import Market Prices Script
* Downloads all Steam market items and stores them as reference data
* for quick price lookups when loading inventory or updating prices
*/
const MONGODB_URI =
process.env.MONGODB_URI || "mongodb://localhost:27017/turbotrades";
const STEAM_API_KEY =
process.env.STEAM_APIS_KEY || process.env.STEAM_API_KEY;
const BASE_URL = "https://api.steamapis.com";
// Define market price schema
const marketPriceSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
index: true,
},
game: {
type: String,
required: true,
enum: ["cs2", "rust"],
index: true,
},
appId: {
type: Number,
required: true,
},
marketHashName: {
type: String,
required: true,
unique: true,
},
price: {
type: Number,
required: true,
},
priceType: {
type: String,
enum: ["safe", "median", "mean", "avg", "latest"],
default: "safe",
},
image: {
type: String,
default: null,
},
borderColor: {
type: String,
default: null,
},
nameId: {
type: Number,
default: null,
},
lastUpdated: {
type: Date,
default: Date.now,
},
},
{
timestamps: true,
collection: "marketprices",
}
);
// Compound index for fast lookups
marketPriceSchema.index({ game: 1, name: 1 });
marketPriceSchema.index({ game: 1, marketHashName: 1 });
console.log("\n╔═══════════════════════════════════════════════╗");
console.log("║ Steam Market Price Import Script ║");
console.log("╚═══════════════════════════════════════════════╝\n");
async function fetchMarketData(game, appId) {
console.log(`\n📡 Fetching ${game.toUpperCase()} market data...`);
console.log(` App ID: ${appId}`);
console.log(` URL: ${BASE_URL}/market/items/${appId}\n`);
try {
const response = await axios.get(`${BASE_URL}/market/items/${appId}`, {
params: {
api_key: STEAM_API_KEY,
},
timeout: 60000, // 60 second timeout
});
if (!response.data || !response.data.data) {
console.error(`❌ No data returned for ${game}`);
return [];
}
const items = response.data.data;
const itemCount = Object.keys(items).length;
console.log(`✅ Received ${itemCount} items from API`);
// Transform API data to our format
const marketItems = [];
Object.values(items).forEach((item) => {
// Get the best available price
const price =
item.prices?.safe ||
item.prices?.median ||
item.prices?.mean ||
item.prices?.avg ||
item.prices?.latest;
if (!price || price <= 0) {
return; // Skip items without valid prices
}
const marketHashName = item.market_hash_name || item.market_name;
const marketName = item.market_name || item.market_hash_name;
if (!marketHashName || !marketName) {
return; // Skip items without names
}
// Determine which price type was used
let priceType = "safe";
if (item.prices?.safe) priceType = "safe";
else if (item.prices?.median) priceType = "median";
else if (item.prices?.mean) priceType = "mean";
else if (item.prices?.avg) priceType = "avg";
else if (item.prices?.latest) priceType = "latest";
marketItems.push({
name: marketName,
game: game,
appId: appId,
marketHashName: marketHashName,
price: price,
priceType: priceType,
image: item.image || null,
borderColor: item.border_color || null,
nameId: item.nameID || null,
lastUpdated: new Date(),
});
});
console.log(`✅ Processed ${marketItems.length} items with valid prices`);
return marketItems;
} catch (error) {
console.error(`❌ Error fetching ${game} market data:`, error.message);
if (error.response?.status === 401) {
console.error(" 🔑 API key is invalid or expired");
} else if (error.response?.status === 429) {
console.error(" ⏱️ Rate limit exceeded");
} else if (error.response?.status === 403) {
console.error(" 🚫 Access forbidden - check API subscription");
}
throw error;
}
}
async function importToDatabase(MarketPrice, items, game) {
console.log(`\n💾 Importing ${game.toUpperCase()} items to database...`);
let inserted = 0;
let updated = 0;
let errors = 0;
let skipped = 0;
// Use bulk operations for better performance
const bulkOps = [];
for (const item of items) {
bulkOps.push({
updateOne: {
filter: { marketHashName: item.marketHashName },
update: { $set: item },
upsert: true,
},
});
// Execute in batches of 1000
if (bulkOps.length >= 1000) {
try {
const result = await MarketPrice.bulkWrite(bulkOps);
inserted += result.upsertedCount;
updated += result.modifiedCount;
console.log(
` 📦 Batch complete: ${inserted} inserted, ${updated} updated`
);
bulkOps.length = 0; // Clear array
} catch (error) {
console.error(` ❌ Batch error:`, error.message);
errors += bulkOps.length;
bulkOps.length = 0;
}
}
}
// Execute remaining items
if (bulkOps.length > 0) {
try {
const result = await MarketPrice.bulkWrite(bulkOps);
inserted += result.upsertedCount;
updated += result.modifiedCount;
} catch (error) {
console.error(` ❌ Final batch error:`, error.message);
errors += bulkOps.length;
}
}
console.log(`\n${game.toUpperCase()} import complete:`);
console.log(` 📥 Inserted: ${inserted}`);
console.log(` 🔄 Updated: ${updated}`);
if (errors > 0) {
console.log(` ❌ Errors: ${errors}`);
}
if (skipped > 0) {
console.log(` ⏭️ Skipped: ${skipped}`);
}
return { inserted, updated, errors, skipped };
}
async function main() {
// Check API key
if (!STEAM_API_KEY) {
console.error("❌ ERROR: Steam API key not configured!\n");
console.error("Please set one of these environment variables:");
console.error(" - STEAM_APIS_KEY (recommended)");
console.error(" - STEAM_API_KEY (fallback)\n");
console.error("Get your API key from: https://steamapis.com/\n");
process.exit(1);
}
console.log("🔑 API Key: ✓ Configured");
console.log(` First 10 chars: ${STEAM_API_KEY.substring(0, 10)}...`);
console.log(`📡 Database: ${MONGODB_URI}\n`);
try {
// Connect to MongoDB
console.log("🔌 Connecting to MongoDB...");
await mongoose.connect(MONGODB_URI);
console.log("✅ Connected to database\n");
// Create or get MarketPrice model
const MarketPrice =
mongoose.models.MarketPrice ||
mongoose.model("MarketPrice", marketPriceSchema);
console.log("─────────────────────────────────────────────────");
// Get current counts
const cs2Count = await MarketPrice.countDocuments({ game: "cs2" });
const rustCount = await MarketPrice.countDocuments({ game: "rust" });
console.log("\n📊 Current Database Status:");
console.log(` CS2: ${cs2Count} items`);
console.log(` Rust: ${rustCount} items`);
console.log("\n─────────────────────────────────────────────────");
// Fetch and import CS2 items
console.log("\n🎮 COUNTER-STRIKE 2 (CS2)");
console.log("─────────────────────────────────────────────────");
const cs2Items = await fetchMarketData("cs2", 730);
const cs2Results = await importToDatabase(MarketPrice, cs2Items, "cs2");
console.log("\n─────────────────────────────────────────────────");
// Fetch and import Rust items
console.log("\n🔧 RUST");
console.log("─────────────────────────────────────────────────");
const rustItems = await fetchMarketData("rust", 252490);
const rustResults = await importToDatabase(MarketPrice, rustItems, "rust");
console.log("\n═════════════════════════════════════════════════");
console.log("\n📊 FINAL SUMMARY\n");
console.log("🎮 CS2:");
console.log(` Total Items: ${cs2Items.length}`);
console.log(` Inserted: ${cs2Results.inserted}`);
console.log(` Updated: ${cs2Results.updated}`);
console.log(` Errors: ${cs2Results.errors}`);
console.log("\n🔧 Rust:");
console.log(` Total Items: ${rustItems.length}`);
console.log(` Inserted: ${rustResults.inserted}`);
console.log(` Updated: ${rustResults.updated}`);
console.log(` Errors: ${rustResults.errors}`);
const totalItems = cs2Items.length + rustItems.length;
const totalInserted = cs2Results.inserted + rustResults.inserted;
const totalUpdated = cs2Results.updated + rustResults.updated;
const totalErrors = cs2Results.errors + rustResults.errors;
console.log("\n🎉 Grand Total:");
console.log(` Total Items: ${totalItems}`);
console.log(` Inserted: ${totalInserted}`);
console.log(` Updated: ${totalUpdated}`);
console.log(` Errors: ${totalErrors}`);
// Get final counts
const finalCs2Count = await MarketPrice.countDocuments({ game: "cs2" });
const finalRustCount = await MarketPrice.countDocuments({ game: "rust" });
const finalTotal = await MarketPrice.countDocuments();
console.log("\n📦 Database Now Contains:");
console.log(` CS2: ${finalCs2Count} items`);
console.log(` Rust: ${finalRustCount} items`);
console.log(` Total: ${finalTotal} items`);
console.log("\n─────────────────────────────────────────────────");
// Show sample items
console.log("\n💎 Sample Items (Highest Priced):\n");
const sampleItems = await MarketPrice.find()
.sort({ price: -1 })
.limit(5)
.select("name game price priceType");
sampleItems.forEach((item, index) => {
console.log(` ${index + 1}. [${item.game.toUpperCase()}] ${item.name}`);
console.log(` Price: $${item.price.toFixed(2)} (${item.priceType})`);
});
console.log("\n═════════════════════════════════════════════════");
console.log("\n✅ Import completed successfully!\n");
console.log("💡 Next Steps:");
console.log(" 1. Use these prices for inventory loading");
console.log(" 2. Query by: MarketPrice.findOne({ marketHashName: name })");
console.log(" 3. Update periodically with: node import-market-prices.js\n");
console.log("📚 Usage Example:");
console.log(' const price = await MarketPrice.findOne({ ');
console.log(' marketHashName: "AK-47 | Redline (Field-Tested)"');
console.log(" });");
console.log(" console.log(price.price); // e.g., 12.50\n");
// Disconnect
await mongoose.disconnect();
console.log("👋 Disconnected from database\n");
process.exit(0);
} catch (error) {
console.error("\n❌ FATAL ERROR:");
console.error(` ${error.message}\n`);
if (error.message.includes("ECONNREFUSED")) {
console.error("🔌 MongoDB Connection Failed:");
console.error(" - Is MongoDB running?");
console.error(" - Check MONGODB_URI in .env");
console.error(` - Current URI: ${MONGODB_URI}\n`);
}
console.error("Stack trace:");
console.error(error.stack);
console.error();
if (mongoose.connection.readyState === 1) {
await mongoose.disconnect();
console.log("👋 Disconnected from database\n");
}
process.exit(1);
}
}
// Handle ctrl+c gracefully
process.on("SIGINT", async () => {
console.log("\n\n⚠ Import interrupted by user");
if (mongoose.connection.readyState === 1) {
await mongoose.disconnect();
console.log("👋 Disconnected from database");
}
process.exit(0);
});
// Run the script
main();