commit f42fecb1373cb3d683b522279e3a9e4295fdb37f Author: gsd Date: Wed Nov 1 22:23:47 2023 +0300 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd64e04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/auth_data/ +/package-lock.json +/Facti13.SteamTradeBot.JS.v1.iml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f3069ab --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM node:18.18-bullseye + +RUN git clone https://git.pblr-nyk.pro/gsd/Facti13.SteamTradeBot.JS.v1 && \ +mv Facti13.SteamTradeBot.JS.v1 /app && chown node:node -R /app && chmod 770 -R /app && \ + echo "const VERSION = $(date +%s);module.exports = VERSION;" > /app/version.js + +USER node +WORKDIR /app +RUN npm install + +CMD ["nodejs", "tradebot.js"] \ No newline at end of file diff --git a/backend_integration.js b/backend_integration.js new file mode 100644 index 0000000..9e3a914 --- /dev/null +++ b/backend_integration.js @@ -0,0 +1,56 @@ +const axios = require("axios"); +class BackendIntegration { + + url = ""; + secret_key = ""; + + up = false; + prices = {}; + + constructor() { + this.url = process.env.BACKEND_URL; + this.secret_key = process.env.SECRET_KEY; + } + + async pulse() { + console.log("request pulse"); + return await axios.get(`${this.url}/api/pulse/db`, {headers:{Cookie:`secretkey=${this.secret_key};`}}).then( + response => { + this.up = response.status === 200; + } + ).catch((e)=>{console.log("pulse failed!")}) + } + + getPrices(tc) { + axios.get(`${this.url}/api/external/vip`, {headers:{Cookie:`secretkey=${this.secret_key};`}}).then( + response => { + if (response.status === 200) { + tc.setPrices(response.data); + } else {console.log("cannot get prices"); process.exit(200)} + } + ).catch(()=>{this.prices = {}; console.log("set prices to null");}); + } + + vip(steam64, amount, extra, uniq) { + axios.post(`${this.url}/api/external/vip?steam=${steam64}&amount=${amount}&service=steam&extra=${extra}&unique=${uniq}`, {}, {headers:{Cookie:`secretkey=${this.secret_key};`}}).then( + response => { + if (response.status === 200) { + if (response.data === 0) { + console.log(`[S64:${steam64}] VIP as not be added, maybe permition already exists`) + return 99; + } else if (response.data > 0) { + console.log(`[S64:${steam64}] VIP has be added!`); + return 100; + } else if (response.data < 0) { + console.log(`[S64:${steam64}] VIP has be extends!`); + return 101; + } + } else { + console.log(`[S64:${steam64}] Cannot add VIP`); + } + } + ).catch((e)=>{console.log("cannot add vip"); console.log(e)}); + } +} + +module.exports = BackendIntegration; \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..04ef881 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,5 @@ +services: + facti13_steambot_js: + build: ./ + container_name: facti13_steambot_js + restart: always \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d98f7e3 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "Facti13.SteamTradeBot.JS.v1", + "version": "1.0.0", + "dependencies": { + "axios": "^1.6.0", + "steam-totp": "^2.1.2", + "steam-tradeoffer-manager": "^2.10.6", + "steam-user": "^5.0.4", + "steamcommunity": "^3.47.1" + } +} diff --git a/trade_checker.js b/trade_checker.js new file mode 100644 index 0000000..ef2d5a9 --- /dev/null +++ b/trade_checker.js @@ -0,0 +1,62 @@ +class TradeChecker { + APP_ID = 440; + MONTH = 2678400; + WEEK = 604800; + DAY = 86400; + + prices_map = {} + + constructor() { + } + + setPrices(prices) { + for (const pos in prices) { + if (prices[pos]["money_price"] === 0){ + continue + } + this.prices_map[prices[pos]['period']] = parseInt(prices[pos]["item_price"].split()[0]) + } + if (this.prices_map.length === 0) { + console.log("cannot parse prices"); + } + } + + mannco_key(items) { + const class_id = 101785959; + const instance_id = 11040578; + let count = 0; + for (const item_id in items) { + if (items[item_id].appid === this.APP_ID && parseInt(items[item_id].classid) === class_id && parseInt(items[item_id].instanceid) === instance_id) count++; + } + return count; + } + + pure_metal(items) { + const class_id = 2674; + const instance_id = 11040547; + let count = 0; + for (const item_id in items) { + if (items[item_id].appid === this.APP_ID && parseInt(items[item_id].classid) === class_id && parseInt(items[item_id].instanceid) === instance_id) count++; + } + return count; + } + + Items2Seconds(items) { + const key_count = this.mannco_key(items); + const metal_count = this.pure_metal(items); + let final_amount = 0; + + if(key_count >= this.prices_map["month"]) + final_amount += key_count * this.MONTH + + if(metal_count >= this.prices_map["week"]) + final_amount += (metal_count / self.prices_map["week"]) * this.WEEK; + else if(metal_count >= this.prices_map["day"]) + final_amount += (metal_count / this.prices_map["day"]) * this.DAY; + + return final_amount; + } + +} + +module.exports = TradeChecker; \ No newline at end of file diff --git a/tradebot.js b/tradebot.js new file mode 100644 index 0000000..93ef8e1 --- /dev/null +++ b/tradebot.js @@ -0,0 +1,131 @@ +'use strict'; +//////////////////////////////////////////////////////////////// +var backend_integration = require("./backend_integration") +var trade_checker_imp = require("./trade_checker") +var backend = new backend_integration(); +var trade_checker = new trade_checker_imp(); +const VERSION = require("./version"); +console.log(`Build date: ${VERSION}`); + +async function sync(timeout) { + let sleep = function sleep (howLong) { + return new Promise(function (resolve) { + setTimeout(() => {resolve()}, howLong) + }) + } + while (true) { + try { + await backend.pulse().then(() => { + if (backend.up) { + backend.getPrices(trade_checker); + } + }); + } catch (e) { + console.log(e); + } finally { + if (!backend.up) {console.log(`wait ${timeout} seconds after destroy, where pulse failed`)} + await sleep(timeout * 1000); + if (!backend.up) {process.exit(228);} + } + } +} +sync(60); + +///////////////////////////////////////////////////////////////// +var SteamUser = require('steam-user'); +var SteamCommunity = require('steamcommunity'); +var SteamTradeOfferManager = require('steam-tradeoffer-manager'); +var SteamTOTP = require('steam-totp'); + +///////////////////////////////////////////////////////////////// +var client = new SteamUser(); +var community = new SteamCommunity(); +var manager = new SteamTradeOfferManager({ + steam: client, + language: 'en' +}); +console.log('connecting to steam account...'); +const config = require("./auth_data/auth.json") +client.logOn({ + accountName: config.login, + password: config.password, + twoFactorCode: SteamTOTP.getAuthCode(config.shared_secret) +}); +///////////////////////////////////////////////////////////////// +client.on('loggedOn', function(details, parental) { + console.log('SteamID64 :', client.steamID.getSteamID64()); + client.setPersona(SteamUser.EPersonaState.Online); +}); + +client.on('webSession', function(sessionID, cookies) { + manager.setCookies(cookies, function(err) { + if (err) { + console.log(err); + process.exit(1); // Fatal error since we couldn't get our API key + return; + } + + console.log("Got API key: " + manager.apiKey); + }); + + community.setCookies(cookies); +}); + +client.on('error', function(error) { + + console.log('connection error\n'); + console.log(error); +}); +//////////////////////////////////////////////////////////////////// + +function calculate(offer) { + + if (offer.state !== SteamTradeOfferManager.ETradeOfferState.Active) { + console.log(`[${offer.id}] trade have not active stats`) + return false; + } + + if (offer.itemsToGive.length>0) { + console.log(`[${offer.id}] partner wanna get bot items, decine trade`); + return false; + } + + if (offer.itemsToReceive.length>50) { + console.log(`[${offer.id}] cannot accept trade with more 50 items`) + return false; + } + + const seconds = trade_checker.Items2Seconds(offer.itemsToReceive); + if (seconds === 0) { + console.log(`[${offer.id}] cannot accept trade not valid items`) + return false; + } + + return seconds > 0; +} + +manager.on('newOffer', function(offer) { + console.log(`[${offer.id}] Incoming trade from ${offer.partner.getSteamID64()}`); + if (calculate(offer)) { + console.log(`[${offer.id}] Try accept trade`); + offer.accept(false, function(error, status) {}); + } else { + console.log(`[${offer.id}] Try decline trade`); + offer.decline(function(error, status) {}) + } +}) + +manager.on('receivedOfferChanged', function(offer, oldState) { + console.log(`[${offer.id}] changed: ${SteamTradeOfferManager.ETradeOfferState[oldState]} -> ${SteamTradeOfferManager.ETradeOfferState[offer.state]}`); + if (offer.state === SteamTradeOfferManager.ETradeOfferState.Accepted) { + offer.getExchangeDetails((err, status, tradeInitTime, receivedItems, sentItems) => { + const seconds = trade_checker.Items2Seconds(receivedItems); + if (seconds > 0) { + const extra = `keys=${trade_checker.mannco_key(receivedItems)};metal=${trade_checker.pure_metal(receivedItems)};`; + backend.vip(offer.partner.getSteamID64(), seconds, extra, offer.id); + } else { + console.log(`[${offer.id}] Trade accepted, but vip not gived!`); + } + }) + } +}); \ No newline at end of file diff --git a/version.js b/version.js new file mode 100644 index 0000000..225958b --- /dev/null +++ b/version.js @@ -0,0 +1 @@ +const VERSION = 0;module.exports = VERSION; \ No newline at end of file