added steambot, trades and trasctions.
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import axios from "axios";
|
||||
import { authenticate } from "../middleware/auth.js";
|
||||
import Item from "../models/Item.js";
|
||||
import Trade from "../models/Trade.js";
|
||||
import Transaction from "../models/Transaction.js";
|
||||
import { config } from "../config/index.js";
|
||||
import pricingService from "../services/pricing.js";
|
||||
import marketPriceService from "../services/marketPrice.js";
|
||||
import { getSteamBotManager } from "../services/steamBot.js";
|
||||
|
||||
/**
|
||||
* Inventory routes for fetching and listing Steam items
|
||||
@@ -108,6 +111,8 @@ export default async function inventoryRoutes(fastify, options) {
|
||||
// Parse item details
|
||||
const item = {
|
||||
assetid: asset.assetid,
|
||||
appid: appId,
|
||||
contextid: contextId,
|
||||
classid: asset.classid,
|
||||
instanceid: asset.instanceid,
|
||||
name: desc.market_hash_name || desc.name || "Unknown Item",
|
||||
@@ -346,7 +351,7 @@ export default async function inventoryRoutes(fastify, options) {
|
||||
}
|
||||
);
|
||||
|
||||
// POST /inventory/sell - Sell items to the site
|
||||
// POST /inventory/sell - Create Steam trade offer to sell items
|
||||
fastify.post(
|
||||
"/sell",
|
||||
{
|
||||
@@ -354,15 +359,24 @@ export default async function inventoryRoutes(fastify, options) {
|
||||
schema: {
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["items"],
|
||||
required: ["items", "tradeUrl"],
|
||||
properties: {
|
||||
items: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
required: ["assetid", "name", "price", "image"],
|
||||
required: [
|
||||
"assetid",
|
||||
"appid",
|
||||
"contextid",
|
||||
"name",
|
||||
"price",
|
||||
"image",
|
||||
],
|
||||
properties: {
|
||||
assetid: { type: "string" },
|
||||
appid: { type: "number" },
|
||||
contextid: { type: "string" },
|
||||
name: { type: "string" },
|
||||
price: { type: "number" },
|
||||
image: { type: "string" },
|
||||
@@ -374,15 +388,29 @@ export default async function inventoryRoutes(fastify, options) {
|
||||
},
|
||||
},
|
||||
},
|
||||
tradeUrl: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
// Declare variables outside try block for catch block access
|
||||
let userId, steamId, items, tradeUrl, botManager, bypassBots;
|
||||
|
||||
try {
|
||||
const { items } = request.body;
|
||||
const userId = request.user._id;
|
||||
const steamId = request.user.steamId;
|
||||
items = request.body.items;
|
||||
tradeUrl = request.body.tradeUrl;
|
||||
userId = request.user._id;
|
||||
steamId = request.user.steamId;
|
||||
|
||||
console.log("🔍 Sell endpoint called:", {
|
||||
userId,
|
||||
steamId,
|
||||
itemCount: items?.length,
|
||||
hasTradeUrl: !!tradeUrl,
|
||||
nodeEnv: process.env.NODE_ENV,
|
||||
bypassBot: process.env.BYPASS_BOT_REQUIREMENT,
|
||||
});
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
return reply.status(400).send({
|
||||
@@ -391,103 +419,477 @@ export default async function inventoryRoutes(fastify, options) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!tradeUrl) {
|
||||
return reply.status(400).send({
|
||||
success: false,
|
||||
message:
|
||||
"Trade URL is required. Please set your trade URL in your profile.",
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate total value
|
||||
const totalValue = items.reduce((sum, item) => sum + item.price, 0);
|
||||
|
||||
// Add items to marketplace
|
||||
const createdItems = [];
|
||||
for (const item of items) {
|
||||
// Determine game based on item characteristics
|
||||
const game = item.category?.includes("Rust") ? "rust" : "cs2";
|
||||
// Get bot manager
|
||||
botManager = getSteamBotManager();
|
||||
|
||||
// Map category
|
||||
let categoryMapped = "other";
|
||||
if (item.category) {
|
||||
const cat = item.category.toLowerCase();
|
||||
if (cat.includes("rifle")) categoryMapped = "rifles";
|
||||
else if (cat.includes("pistol")) categoryMapped = "pistols";
|
||||
else if (cat.includes("knife")) categoryMapped = "knives";
|
||||
else if (cat.includes("glove")) categoryMapped = "gloves";
|
||||
else if (cat.includes("smg")) categoryMapped = "smgs";
|
||||
else if (cat.includes("sticker")) categoryMapped = "stickers";
|
||||
}
|
||||
// In development mode, allow bypassing bot requirement
|
||||
const isDevelopmentMode = process.env.NODE_ENV === "development";
|
||||
bypassBots = process.env.BYPASS_BOT_REQUIREMENT === "true";
|
||||
|
||||
// Map rarity
|
||||
let rarityMapped = "common";
|
||||
if (item.rarity) {
|
||||
const rar = item.rarity.toLowerCase();
|
||||
if (rar.includes("contraband") || rar.includes("ancient"))
|
||||
rarityMapped = "exceedingly";
|
||||
else if (rar.includes("covert") || rar.includes("legendary"))
|
||||
rarityMapped = "legendary";
|
||||
else if (rar.includes("classified") || rar.includes("mythical"))
|
||||
rarityMapped = "mythical";
|
||||
else if (rar.includes("restricted") || rar.includes("rare"))
|
||||
rarityMapped = "rare";
|
||||
else if (rar.includes("mil-spec") || rar.includes("uncommon"))
|
||||
rarityMapped = "uncommon";
|
||||
}
|
||||
|
||||
const newItem = new Item({
|
||||
name: item.name,
|
||||
description: `Listed from Steam inventory`,
|
||||
image: item.image,
|
||||
game: game,
|
||||
category: categoryMapped,
|
||||
rarity: rarityMapped,
|
||||
wear: item.wear || null,
|
||||
statTrak: item.statTrak || false,
|
||||
souvenir: item.souvenir || false,
|
||||
price: item.price,
|
||||
seller: userId,
|
||||
status: "active",
|
||||
featured: false,
|
||||
if (!botManager.isInitialized && !bypassBots) {
|
||||
return reply.status(503).send({
|
||||
success: false,
|
||||
message:
|
||||
"Trade system is currently unavailable. Please try again later.",
|
||||
});
|
||||
|
||||
await newItem.save();
|
||||
createdItems.push(newItem);
|
||||
}
|
||||
|
||||
// Update user balance
|
||||
request.user.balance += totalValue;
|
||||
await request.user.save();
|
||||
// Development mode: Mock trade creation
|
||||
if (bypassBots || !botManager.isInitialized) {
|
||||
console.log(
|
||||
"⚠️ DEVELOPMENT MODE: Creating mock trade (no real Steam bot)"
|
||||
);
|
||||
|
||||
console.log(
|
||||
`✅ User ${request.user.username} sold ${
|
||||
items.length
|
||||
} items for $${totalValue.toFixed(2)}`
|
||||
);
|
||||
// Generate mock verification code
|
||||
const mockCode = Math.random()
|
||||
.toString(36)
|
||||
.substring(2, 8)
|
||||
.toUpperCase();
|
||||
const mockOfferId = `DEV_${Date.now()}`;
|
||||
const mockTradeOfferUrl = `https://steamcommunity.com/tradeoffer/${mockOfferId}`;
|
||||
|
||||
// Broadcast to WebSocket if available
|
||||
if (fastify.websocketManager) {
|
||||
// Update user's balance
|
||||
fastify.websocketManager.sendToUser(steamId, {
|
||||
type: "balance_update",
|
||||
data: {
|
||||
balance: request.user.balance,
|
||||
},
|
||||
// Create trade record in database
|
||||
const trade = new Trade({
|
||||
offerId: mockOfferId,
|
||||
botId: "dev-bot",
|
||||
userId: userId,
|
||||
steamId: steamId,
|
||||
tradeUrl: tradeUrl,
|
||||
tradeOfferUrl: mockTradeOfferUrl,
|
||||
items: items.map((item) => ({
|
||||
assetId: item.assetid,
|
||||
name: item.name,
|
||||
price: item.price,
|
||||
image: item.image,
|
||||
game: item.appid === 730 ? "cs2" : "rust",
|
||||
wear: item.wear,
|
||||
rarity: item.rarity,
|
||||
category: item.category,
|
||||
statTrak: item.statTrak,
|
||||
souvenir: item.souvenir,
|
||||
})),
|
||||
totalValue,
|
||||
userReceives: totalValue,
|
||||
verificationCode: mockCode,
|
||||
state: "pending",
|
||||
});
|
||||
|
||||
// Broadcast new items to marketplace
|
||||
fastify.websocketManager.broadcastPublic("new_items", {
|
||||
count: createdItems.length,
|
||||
await trade.save();
|
||||
|
||||
console.log(
|
||||
`✅ Mock trade created: ${mockOfferId} with code: ${mockCode}`
|
||||
);
|
||||
|
||||
// Send WebSocket notification
|
||||
if (fastify.websocketManager) {
|
||||
fastify.websocketManager.sendToUser(steamId, {
|
||||
type: "trade_created",
|
||||
data: {
|
||||
tradeId: trade._id,
|
||||
offerId: mockOfferId,
|
||||
verificationCode: mockCode,
|
||||
tradeOfferUrl: mockTradeOfferUrl,
|
||||
itemCount: items.length,
|
||||
totalValue,
|
||||
botId: "dev-bot",
|
||||
status: "pending",
|
||||
timestamp: Date.now(),
|
||||
isDevelopment: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
message: "Mock trade created (Development Mode)",
|
||||
trade: {
|
||||
tradeId: trade._id,
|
||||
offerId: mockOfferId,
|
||||
verificationCode: mockCode,
|
||||
tradeOfferUrl: mockTradeOfferUrl,
|
||||
itemCount: items.length,
|
||||
totalValue,
|
||||
status: "pending",
|
||||
botId: "dev-bot",
|
||||
isDevelopment: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare items for Steam trade (format required by steam-tradeoffer-manager)
|
||||
const itemsToReceive = items.map((item) => ({
|
||||
assetid: item.assetid,
|
||||
appid: item.appid,
|
||||
contextid: item.contextid,
|
||||
}));
|
||||
|
||||
console.log(
|
||||
`📤 Creating trade offer for user ${request.user.username} (${
|
||||
items.length
|
||||
} items, $${totalValue.toFixed(2)})`
|
||||
);
|
||||
|
||||
// Create trade offer via bot
|
||||
let tradeResult;
|
||||
try {
|
||||
tradeResult = await botManager.createTradeOffer({
|
||||
tradeUrl,
|
||||
itemsToReceive,
|
||||
userId: steamId,
|
||||
metadata: {
|
||||
username: request.user.username,
|
||||
itemCount: items.length,
|
||||
totalValue,
|
||||
},
|
||||
});
|
||||
} catch (botError) {
|
||||
console.error("Bot trade creation error:", botError);
|
||||
return reply.status(503).send({
|
||||
success: false,
|
||||
message:
|
||||
botError.message ||
|
||||
"Failed to create trade offer. Please try again.",
|
||||
});
|
||||
}
|
||||
|
||||
// Create trade record in database
|
||||
const trade = new Trade({
|
||||
offerId: tradeResult.offerId,
|
||||
botId: tradeResult.botId,
|
||||
userId: userId,
|
||||
steamId: steamId,
|
||||
tradeUrl: tradeUrl,
|
||||
tradeOfferUrl: tradeResult.tradeOfferUrl,
|
||||
items: items.map((item) => ({
|
||||
assetId: item.assetid,
|
||||
name: item.name,
|
||||
price: item.price,
|
||||
image: item.image,
|
||||
game: item.appid === 730 ? "cs2" : "rust",
|
||||
wear: item.wear,
|
||||
rarity: item.rarity,
|
||||
category: item.category,
|
||||
statTrak: item.statTrak,
|
||||
souvenir: item.souvenir,
|
||||
})),
|
||||
totalValue,
|
||||
userReceives: totalValue,
|
||||
verificationCode: tradeResult.code,
|
||||
state: "pending",
|
||||
});
|
||||
|
||||
await trade.save();
|
||||
|
||||
console.log(
|
||||
`✅ Trade offer created: ${tradeResult.offerId} with verification code: ${tradeResult.code}`
|
||||
);
|
||||
|
||||
// Send immediate WebSocket notification with verification code
|
||||
if (fastify.websocketManager) {
|
||||
fastify.websocketManager.sendToUser(steamId, {
|
||||
type: "trade_created",
|
||||
data: {
|
||||
tradeId: trade._id,
|
||||
offerId: tradeResult.offerId,
|
||||
verificationCode: tradeResult.code,
|
||||
tradeOfferUrl: tradeResult.tradeOfferUrl,
|
||||
itemCount: items.length,
|
||||
totalValue,
|
||||
botId: tradeResult.botId,
|
||||
status: "pending",
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
message: `Successfully sold ${items.length} item${
|
||||
items.length > 1 ? "s" : ""
|
||||
} for $${totalValue.toFixed(2)}`,
|
||||
itemsListed: createdItems.length,
|
||||
totalEarned: totalValue,
|
||||
newBalance: request.user.balance,
|
||||
message: "Trade offer created successfully",
|
||||
trade: {
|
||||
tradeId: trade._id,
|
||||
offerId: tradeResult.offerId,
|
||||
verificationCode: tradeResult.code,
|
||||
tradeOfferUrl: tradeResult.tradeOfferUrl,
|
||||
itemCount: items.length,
|
||||
totalValue,
|
||||
status: "pending",
|
||||
botId: tradeResult.botId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error selling items:", error);
|
||||
console.error("❌ Error creating sell trade:", error);
|
||||
console.error("Error stack:", error.stack);
|
||||
console.error("Error details:", {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
userId,
|
||||
steamId,
|
||||
itemCount: items?.length,
|
||||
tradeUrl: tradeUrl ? "present" : "missing",
|
||||
bypassBots,
|
||||
isInitialized: botManager?.isInitialized,
|
||||
});
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: "Failed to process sale. Please try again.",
|
||||
message:
|
||||
error.message || "Failed to create trade offer. Please try again.",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// GET /inventory/trades - Get user's trades
|
||||
fastify.get(
|
||||
"/trades",
|
||||
{
|
||||
preHandler: authenticate,
|
||||
},
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const userId = request.user._id;
|
||||
|
||||
const trades = await Trade.find({ user: userId })
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(50);
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
trades,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching trades:", error);
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: "Failed to fetch trades",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// GET /inventory/trade/:tradeId - Get specific trade details
|
||||
fastify.get(
|
||||
"/trade/:tradeId",
|
||||
{
|
||||
preHandler: authenticate,
|
||||
},
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const { tradeId } = request.params;
|
||||
const userId = request.user._id;
|
||||
|
||||
const trade = await Trade.findOne({ _id: tradeId, userId: userId });
|
||||
|
||||
if (!trade) {
|
||||
return reply.status(404).send({
|
||||
success: false,
|
||||
message: "Trade not found",
|
||||
});
|
||||
}
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
trade,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching trade:", error);
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: "Failed to fetch trade details",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// POST /inventory/trade/:tradeId/cancel - Cancel a pending trade
|
||||
fastify.post(
|
||||
"/trade/:tradeId/cancel",
|
||||
{
|
||||
preHandler: authenticate,
|
||||
},
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const { tradeId } = request.params;
|
||||
const userId = request.user._id;
|
||||
const steamId = request.user.steamId;
|
||||
|
||||
const trade = await Trade.findOne({ _id: tradeId, userId: userId });
|
||||
|
||||
if (!trade) {
|
||||
return reply.status(404).send({
|
||||
success: false,
|
||||
message: "Trade not found",
|
||||
});
|
||||
}
|
||||
|
||||
if (trade.state !== "pending") {
|
||||
return reply.status(400).send({
|
||||
success: false,
|
||||
message: "Only pending trades can be cancelled",
|
||||
});
|
||||
}
|
||||
|
||||
// Cancel via bot manager
|
||||
const botManager = getSteamBotManager();
|
||||
await botManager.cancelTradeOffer(trade.offerId, trade.botId);
|
||||
|
||||
// Update trade status
|
||||
trade.state = "canceled";
|
||||
await trade.save();
|
||||
|
||||
// Notify via WebSocket
|
||||
if (fastify.websocketManager) {
|
||||
fastify.websocketManager.sendToUser(steamId, {
|
||||
type: "trade_cancelled",
|
||||
data: {
|
||||
tradeId: trade._id,
|
||||
offerId: trade.offerId,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
message: "Trade cancelled successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error cancelling trade:", error);
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: "Failed to cancel trade",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// POST /inventory/trade/:tradeId/complete - Complete trade (development mode only)
|
||||
fastify.post(
|
||||
"/trade/:tradeId/complete",
|
||||
{
|
||||
preHandler: authenticate,
|
||||
},
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const { tradeId } = request.params;
|
||||
const userId = request.user._id;
|
||||
const steamId = request.user.steamId;
|
||||
|
||||
// Only allow in development mode
|
||||
if (
|
||||
process.env.NODE_ENV !== "development" &&
|
||||
process.env.BYPASS_BOT_REQUIREMENT !== "true"
|
||||
) {
|
||||
return reply.status(403).send({
|
||||
success: false,
|
||||
message: "This endpoint is only available in development mode",
|
||||
});
|
||||
}
|
||||
|
||||
const trade = await Trade.findOne({ _id: tradeId, user: userId });
|
||||
|
||||
if (!trade) {
|
||||
return reply.status(404).send({
|
||||
success: false,
|
||||
message: "Trade not found",
|
||||
});
|
||||
}
|
||||
|
||||
if (trade.state !== "pending") {
|
||||
return reply.status(400).send({
|
||||
success: false,
|
||||
message: "Only pending trades can be completed",
|
||||
});
|
||||
}
|
||||
|
||||
// Simulate trade acceptance
|
||||
console.log(
|
||||
`🧪 DEV MODE: Manually completing trade ${tradeId} for user ${request.user.username}`
|
||||
);
|
||||
|
||||
// Credit user balance
|
||||
request.user.balance += trade.totalValue;
|
||||
await request.user.save();
|
||||
|
||||
// Update trade status
|
||||
trade.state = "accepted";
|
||||
trade.completedAt = new Date();
|
||||
await trade.save();
|
||||
|
||||
// Create transaction record
|
||||
const transaction = new Transaction({
|
||||
user: userId,
|
||||
type: "sale",
|
||||
amount: trade.totalValue,
|
||||
description: `Sold ${trade.items.length} item(s) (DEV MODE)`,
|
||||
status: "completed",
|
||||
metadata: {
|
||||
tradeId: trade._id,
|
||||
offerId: trade.offerId,
|
||||
botId: trade.botId,
|
||||
itemCount: trade.items.length,
|
||||
verificationCode: trade.verificationCode,
|
||||
isDevelopment: true,
|
||||
},
|
||||
});
|
||||
await transaction.save();
|
||||
|
||||
console.log(
|
||||
`✅ DEV MODE: Credited $${trade.totalValue.toFixed(2)} to user ${
|
||||
request.user.username
|
||||
} (Balance: $${request.user.balance.toFixed(2)})`
|
||||
);
|
||||
|
||||
// Notify via WebSocket
|
||||
if (fastify.websocketManager) {
|
||||
fastify.websocketManager.sendToUser(steamId, {
|
||||
type: "trade_completed",
|
||||
data: {
|
||||
tradeId: trade._id,
|
||||
offerId: trade.offerId,
|
||||
amount: trade.totalValue,
|
||||
newBalance: request.user.balance,
|
||||
itemCount: trade.items.length,
|
||||
timestamp: Date.now(),
|
||||
isDevelopment: true,
|
||||
},
|
||||
});
|
||||
|
||||
fastify.websocketManager.sendToUser(steamId, {
|
||||
type: "balance_update",
|
||||
data: {
|
||||
balance: request.user.balance,
|
||||
change: trade.totalValue,
|
||||
reason: "trade_completed",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
message: "Trade completed successfully (Development Mode)",
|
||||
trade: {
|
||||
tradeId: trade._id,
|
||||
status: "completed",
|
||||
amount: trade.totalValue,
|
||||
newBalance: request.user.balance,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error completing trade:", error);
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: "Failed to complete trade",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user