PixelPiggy/CS_float
0
1const winston = require('winston'),2 SteamUser = require('steam-user'),3 GlobalOffensive = require('globaloffensive'),4 SteamTotp = require('steam-totp'),5 EventEmitter = require('events').EventEmitter;6 7class Bot extends EventEmitter {8 /**9 * Sets the ready status and sends a 'ready' or 'unready' event if it has changed10 * @param {*|boolean} val New ready status11 */12 set ready(val) {13 const prev = this.ready;14 this.ready_ = val;15 16 if (val !== prev) {17 this.emit(val ? 'ready' : 'unready');18 }19 }20 21 /**22 * Returns the current ready status23 * @return {*|boolean} Ready status24 */25 get ready() {26 return this.ready_ || false;27 }28 29 constructor(settings) {30 super();31 32 this.settings = settings;33 this.busy = false;34 35 this.steamClient = new SteamUser(Object.assign({36 promptSteamGuardCode: false,37 enablePicsCache: true // Required to check if we own CSGO with ownsApp38 }, this.settings.steam_user));39 40 this.csgoClient = new GlobalOffensive(this.steamClient);41 42 // set up event handlers43 this.bindEventHandlers();44 45 // Variance to apply so that each bot relogins at different times46 const variance = parseInt(Math.random() * 4 * 60 * 1000);47 48 // As of 7/10/2020, GC inspect calls can timeout repeatedly for whatever reason49 setInterval(() => {50 if (this.csgoClient.haveGCSession) {51 this.relogin = true;52 this.steamClient.relog();53 }54 }, 30 * 60 * 1000 + variance);55 }56 57 logIn(username, password, auth) {58 this.ready = false;59 60 // Save these parameters if we login later61 if (arguments.length === 3) {62 this.username = username;63 this.password = password;64 this.auth = auth;65 }66 67 winston.info(`Logging in ${this.username}`);68 69 // If there is a steam client, make sure it is disconnected70 if (this.steamClient) this.steamClient.logOff();71 72 this.loginData = {73 accountName: this.username,74 password: this.password,75 rememberPassword: true,76 };77 78 if (this.auth && this.auth !== '') {79 // Check if it is a shared_secret80 if (this.auth.length <= 5) this.loginData.authCode = this.auth;81 else {82 // Generate the code from the shared_secret83 winston.debug(`${this.username} Generating TOTP Code from shared_secret`);84 this.loginData.twoFactorCode = SteamTotp.getAuthCode(this.auth);85 }86 }87 88 winston.debug(`${this.username} About to connect`);89 this.steamClient.logOn(this.loginData);90 }91 92 bindEventHandlers() {93 this.steamClient.on('error', (err) => {94 winston.error(`Error logging in ${this.username}:`, err);95 96 let login_error_msgs = {97 61: 'Invalid Password',98 63: 'Account login denied due to 2nd factor authentication failure. ' +99 'If using email auth, an email has been sent.',100 65: 'Account login denied due to auth code being invalid',101 66: 'Account login denied due to 2nd factor auth failure and no mail has been sent'102 };103 104 if (err.eresult && login_error_msgs[err.eresult] !== undefined) {105 winston.error(this.username + ': ' + login_error_msgs[err.eresult]);106 }107 108 // Yes, checking for string errors sucks, but we have no other attributes to check109 // this error against.110 if (err.toString().includes('Proxy connection timed out')) {111 this.logIn();112 }113 });114 115 this.steamClient.on('disconnected', (eresult, msg) => {116 winston.warn(`${this.username} Logged off, reconnecting! (${eresult}, ${msg})`);117 });118 119 this.steamClient.on('loggedOn', (details, parental) => {120 winston.info(`${this.username} Log on OK`);121 122 // Fixes reconnecting to CS:GO GC since node-steam-user still assumes we're playing 730123 // and never sends the appLaunched event to node-globaloffensive124 this.steamClient.gamesPlayed([], true);125 126 if (this.relogin) {127 // Don't check ownership cache since the event isn't always emitted on relogin128 winston.info(`${this.username} Initiating GC Connection, Relogin`);129 this.steamClient.gamesPlayed([730], true);130 return;131 }132 133 // Ensure we own CSGO134 // We have to wait until app ownership is cached to safely check135 this.steamClient.once('ownershipCached', () => {136 if (!this.steamClient.ownsApp(730)) {137 winston.info(`${this.username} doesn't own CS:GO, retrieving free license`);138 139 // Request a license for CS:GO140 this.steamClient.requestFreeLicense([730], (err, grantedPackages, grantedAppIDs) => {141 winston.debug(`${this.username} Granted Packages`, grantedPackages);142 winston.debug(`${this.username} Granted App IDs`, grantedAppIDs);143 144 if (err) {145 winston.error(`${this.username} Failed to obtain free CS:GO license`);146 } else {147 winston.info(`${this.username} Initiating GC Connection`);148 this.steamClient.gamesPlayed([730], true);149 }150 });151 } else {152 winston.info(`${this.username} Initiating GC Connection`);153 this.steamClient.gamesPlayed([730], true);154 }155 });156 });157 158 this.csgoClient.on('inspectItemInfo', (itemData) => {159 if (this.resolve && this.currentRequest) {160 itemData = {iteminfo: itemData};161 162 // Ensure the received itemid is the same as what we want163 if (itemData.iteminfo.itemid !== this.currentRequest.a) return;164 165 // Clear any TTL timeout166 if (this.ttlTimeout) {167 clearTimeout(this.ttlTimeout);168 this.ttlTimeout = false;169 }170 171 // GC requires a delay between subsequent requests172 // Figure out how long to delay until this bot isn't busy anymore173 let offset = new Date().getTime() - this.currentRequest.time;174 let delay = this.settings.request_delay - offset;175 176 // If we're past the request delay, don't delay177 if (delay < 0) delay = 0;178 179 itemData.delay = delay;180 itemData.iteminfo.s = this.currentRequest.s;181 itemData.iteminfo.a = this.currentRequest.a;182 itemData.iteminfo.d = this.currentRequest.d;183 itemData.iteminfo.m = this.currentRequest.m;184 185 // If the paintseed is 0, the proto returns null, force 0186 itemData.iteminfo.paintseed = itemData.iteminfo.paintseed || 0;187 188 // paintwear -> floatvalue to match previous API version response189 itemData.iteminfo.floatvalue = itemData.iteminfo.paintwear;190 delete itemData.iteminfo.paintwear;191 192 // Backwards compatibility with previous node-globaloffensive versions193 for (const sticker of itemData.iteminfo.stickers) {194 sticker.stickerId = sticker.sticker_id;195 delete sticker.sticker_id;196 }197 198 this.resolve(itemData);199 this.resolve = false;200 this.currentRequest = false;201 202 setTimeout(() => {203 // We're no longer busy (satisfied request delay)204 this.busy = false;205 }, delay);206 }207 });208 209 this.csgoClient.on('connectedToGC', () => {210 winston.info(`${this.username} CSGO Client Ready!`);211 212 this.ready = true;213 });214 215 this.csgoClient.on('disconnectedFromGC', (reason) => {216 winston.warn(`${this.username} CSGO unready (${reason}), trying to reconnect!`);217 this.ready = false;218 219 // node-globaloffensive will automatically try to reconnect220 });221 222 this.csgoClient.on('connectionStatus', (status) => {223 winston.debug(`${this.username} GC Connection Status Update ${status}`);224 });225 226 this.csgoClient.on('debug', (msg) => {227 winston.debug(msg);228 });229 }230 231 sendFloatRequest(link) {232 return new Promise((resolve, reject) => {233 this.resolve = resolve;234 this.busy = true;235 236 const params = link.getParams();237 winston.debug(`${this.username} Fetching for ${params.a}`);238 239 this.currentRequest = {s: params.s, a: params.a, d: params.d, m: params.m, time: new Date().getTime()};240 241 if (!this.ready) {242 reject('This bot is not ready');243 }244 else {245 // The first param (owner) depends on the type of inspect link246 this.csgoClient.inspectItem(params.s !== '0' ? params.s : params.m, params.a, params.d);247 }248 249 // Set a timeout in case the GC takes too long to respond250 this.ttlTimeout = setTimeout(() => {251 // GC didn't respond in time, reset and reject252 this.busy = false;253 this.currentRequest = false;254 reject('ttl exceeded');255 }, this.settings.request_ttl);256 });257 }258}259 260module.exports = Bot;261 