From d48ea04763129825cd597c984bdb937d38bc0fd8 Mon Sep 17 00:00:00 2001 From: IdiNium <47635037+idinium96@users.noreply.github.com> Date: Fri, 25 Nov 2022 16:40:21 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E2=86=A9=EF=B8=8F=20partially=20revert=20c?= =?UTF-8?q?hanges=20made=20in=20#1171?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 71 ++++++ package.json | 5 + src/app.ts | 72 +++--- src/classes/Bot.ts | 384 +++++++++++++++++++++++++++-- src/classes/BotManager.ts | 76 +++--- src/classes/Commands/Commands.ts | 54 ++-- src/classes/Listings.ts | 4 +- src/classes/MyHandler/MyHandler.ts | 4 +- src/classes/Trades.ts | 2 +- 9 files changed, 558 insertions(+), 114 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6f6dcdd8c..a720a0723 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,10 @@ "@tf2autobot/tf2-schema": "^4.2.6", "@tf2autobot/tf2-sku": "^2.0.3", "@tf2autobot/tradeoffer-manager": "^2.14.1", + "async": "^3.2.4", "axios": "^0.27.2", + "bluebird": "^3.7.2", + "bluebird-global": "^1.0.1", "body-parser": "^1.20.0", "callback-queue": "^3.0.0", "change-case": "^4.1.2", @@ -53,6 +56,8 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.18.6", + "@types/async": "^3.2.15", + "@types/bluebird-global": "^3.5.13", "@types/cheerio": "^0.22.31", "@types/death": "^1.1.2", "@types/express": "^4.17.14", @@ -2200,6 +2205,12 @@ "node": ">= 6" } }, + "node_modules/@types/async": { + "version": "3.2.15", + "resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.15.tgz", + "integrity": "sha512-PAmPfzvFA31mRoqZyTVsgJMsvbynR429UTTxhmfsUCrWGh3/fxOrzqBtaTPJsn4UtzTv4Vb0+/O7CARWb69N4g==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -2241,6 +2252,21 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/bluebird": { + "version": "3.5.37", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.37.tgz", + "integrity": "sha512-g2qEd+zkfkTEudA2SrMAeAvY7CrFqtbsLILm2dT2VIeKTqMqVzcdfURlvu6FU3srRgbmXN1Srm94pg34EIehww==", + "dev": true + }, + "node_modules/@types/bluebird-global": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bluebird-global/-/bluebird-global-3.5.13.tgz", + "integrity": "sha512-jmq47VdRYy8KPjXDlJ6zO5Ie+l5j0X2fGdSbfCS3mGdM93MTy50dh/EfltIv/QD15hCYTY+0lm/C0Bou1tPYnQ==", + "dev": true, + "dependencies": { + "@types/bluebird": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", @@ -3178,6 +3204,19 @@ "node": ">= 0.8.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/bluebird-global": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bluebird-global/-/bluebird-global-1.0.1.tgz", + "integrity": "sha512-llTREi0V3EUM150DKdMLlXN/X2QsQ/hWJDiaGnivqeDwE32I0dmivYLU9/VvhotTzvvbv7OiM6zjVXYJ2dCmKQ==", + "peerDependencies": { + "bluebird": "*" + } + }, "node_modules/bodec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", @@ -12837,6 +12876,12 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, + "@types/async": { + "version": "3.2.15", + "resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.15.tgz", + "integrity": "sha512-PAmPfzvFA31mRoqZyTVsgJMsvbynR429UTTxhmfsUCrWGh3/fxOrzqBtaTPJsn4UtzTv4Vb0+/O7CARWb69N4g==", + "dev": true + }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -12878,6 +12923,21 @@ "@babel/types": "^7.3.0" } }, + "@types/bluebird": { + "version": "3.5.37", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.37.tgz", + "integrity": "sha512-g2qEd+zkfkTEudA2SrMAeAvY7CrFqtbsLILm2dT2VIeKTqMqVzcdfURlvu6FU3srRgbmXN1Srm94pg34EIehww==", + "dev": true + }, + "@types/bluebird-global": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bluebird-global/-/bluebird-global-3.5.13.tgz", + "integrity": "sha512-jmq47VdRYy8KPjXDlJ6zO5Ie+l5j0X2fGdSbfCS3mGdM93MTy50dh/EfltIv/QD15hCYTY+0lm/C0Bou1tPYnQ==", + "dev": true, + "requires": { + "@types/bluebird": "*" + } + }, "@types/body-parser": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", @@ -13608,6 +13668,17 @@ "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", "integrity": "sha1-+WLWh+wsNpVwrnGvhDJW5tDKESk=" }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "bluebird-global": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bluebird-global/-/bluebird-global-1.0.1.tgz", + "integrity": "sha512-llTREi0V3EUM150DKdMLlXN/X2QsQ/hWJDiaGnivqeDwE32I0dmivYLU9/VvhotTzvvbv7OiM6zjVXYJ2dCmKQ==", + "requires": {} + }, "bodec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", diff --git a/package.json b/package.json index f0d55e3d1..a32a58be0 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,10 @@ "@tf2autobot/tf2-schema": "^4.2.6", "@tf2autobot/tf2-sku": "^2.0.3", "@tf2autobot/tradeoffer-manager": "^2.14.1", + "async": "^3.2.4", "axios": "^0.27.2", + "bluebird": "^3.7.2", + "bluebird-global": "^1.0.1", "body-parser": "^1.20.0", "callback-queue": "^3.0.0", "change-case": "^4.1.2", @@ -70,6 +73,8 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.18.6", + "@types/async": "^3.2.15", + "@types/bluebird-global": "^3.5.13", "@types/cheerio": "^0.22.31", "@types/death": "^1.1.2", "@types/express": "^4.17.14", diff --git a/src/app.ts b/src/app.ts index 180aeec5b..ce0542801 100644 --- a/src/app.ts +++ b/src/app.ts @@ -24,6 +24,8 @@ if (process.env.BOT_VERSION !== pjson.version) { process.exit(1); } +import 'bluebird-global'; + import dotenv from 'dotenv'; dotenv.config({ path: path.join(__dirname, '../.env') }); @@ -142,46 +144,40 @@ process.on('message', message => { } }); -botManager - .start(options) - .then(async () => { - if (options.enableHttpApi) { - const { default: HttpManager } = await import('./classes/HttpManager'); - const httpManager = new HttpManager(options); - await httpManager.start(); - } - }) - .catch(err => { - if (err) { - // https://stackoverflow.com/questions/30715367/why-can-i-not-throw-inside-a-promise-catch-handler - setTimeout(() => { - /*eslint-disable */ - if (err.response || err.name === 'AxiosError') { - // if it's Axios error, filter the error - - const e = new Error(err.message); - - e['code'] = err.code; - e['status'] = err.response?.status ?? err.status; - e['method'] = err.config?.method ?? err.method; - e['url'] = err.config?.url?.replace(/\?.+/, '') ?? err.baseURL?.replace(/\?.+/, ''); // Ignore parameters - - if (typeof err.response?.data === 'string' && err.response?.data?.includes('')) { - return throwErr(e); - } +void botManager.start(options).asCallback(err => { + if (err) { + /*eslint-disable */ + if (err.response || err.name === 'AxiosError') { + // if it's Axios error, filter the error - e['data'] = err.response?.data; + const e = new Error(err.message); - return throwErr(e); - } - /*eslint-enable */ + e['code'] = err.code; + e['status'] = err.response?.status ?? err.status; + e['method'] = err.config?.method ?? err.method; + e['url'] = err.config?.url?.replace(/\?.+/, '') ?? err.baseURL?.replace(/\?.+/, ''); // Ignore parameters + + if (typeof err.response?.data === 'string' && err.response?.data?.includes('')) { + throw e; + } + + e['data'] = err.response?.data; - return throwErr(err); - }, 10); + throw e; } - }); + /*eslint-enable */ -function throwErr(err): void { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - process.emit('uncaughtException', err); -} + throw err; + } + + if (options.enableHttpApi) { + void import('./classes/HttpManager').then(({ default: HttpManager }) => { + const httpManager = new HttpManager(options); + void httpManager.start().asCallback(err => { + if (err) { + throw err; + } + }); + }); + } +}); diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index fcea139d6..7c225a020 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -9,6 +9,7 @@ import SchemaManager, { Effect, StrangeParts } from '@tf2autobot/tf2-schema'; import BptfLogin from '@tf2autobot/bptf-login'; import TF2 from '@tf2autobot/tf2'; import dayjs, { Dayjs } from 'dayjs'; +import async from 'async'; import semver from 'semver'; import axios, { AxiosError } from 'axios'; import pluralize from 'pluralize'; @@ -766,7 +767,7 @@ export default class Bot { }); } - async start(): Promise { + start(): Promise { let data: { loginAttempts?: number[]; pricelist?: PricesDataObject; @@ -784,10 +785,10 @@ export default class Bot { this.addListener(this.client, 'webSession', this.onWebSession.bind(this), false); this.addListener(this.client, 'steamGuard', this.onSteamGuard.bind(this), false); this.addListener(this.client, 'loginKey', this.handler.onLoginKey.bind(this.handler), false); - this.addAsyncListener(this.client, 'error', this.onError.bind(this), false); + this.addListener(this.client, 'error', this.onError.bind(this), false); this.addListener(this.community, 'sessionExpired', this.onSessionExpired.bind(this), false); - this.addAsyncListener(this.community, 'confKeyNeeded', this.onConfKeyNeeded.bind(this), false); + this.addListener(this.community, 'confKeyNeeded', this.onConfKeyNeeded.bind(this), false); this.addListener(this.manager, 'pollData', this.handler.onPollData.bind(this.handler), false); this.addListener(this.manager, 'newOffer', this.trades.onNewOffer.bind(this.trades), true); @@ -795,6 +796,349 @@ export default class Bot { this.addListener(this.manager, 'receivedOfferChanged', this.trades.onOfferChanged.bind(this.trades), true); this.addListener(this.manager, 'offerList', this.trades.onOfferList.bind(this.trades), true); + return new Promise((resolve, reject) => { + async.eachSeries( + [ + (callback): void => { + log.debug('Calling onRun'); + void this.handler.onRun().asCallback((err, v) => { + if (err) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(err); + } + + data = v; + if (data.pollData) { + log.debug('Setting poll data'); + this.manager.pollData = data.pollData; + } + + if (data.loginAttempts) { + log.debug('Setting login attempts'); + this.setLoginAttempts = data.loginAttempts; + } + + if (data.blockedList) { + log.debug('Loading blocked list data'); + this.blockedList = data.blockedList; + } + + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + }); + }, + (callback): void => { + log.info('Signing in to Steam...'); + + let lastLoginFailed = false; + const loginResponse = (err: CustomError): void => { + if (err) { + this.handler.onLoginError(err); + if (!lastLoginFailed && err.eresult === EResult.InvalidPassword) { + lastLoginFailed = true; + // Try and sign in without login key + log.warn('Failed to sign in to Steam, retrying without login key...'); + void this.login(null).asCallback(loginResponse); + return; + } else { + log.warn('Failed to sign in to Steam: ', err); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(err); + } + } + + log.info('Signed in to Steam!'); + + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + }; + + void this.login(data.loginKey || null).asCallback(loginResponse); + }, + async (callback): Promise => { + if (this.options.discordBotToken) { + log.info(`Initializing Discord bot...`); + this.discordBot = new DiscordBot(this.options, this); + try { + await this.discordBot.start(); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + } catch (err) { + this.discordBot = null; + log.warn('Failed to start Discord bot: ', err); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(err); + } + } else { + log.info('Discord api key is not set, ignoring.'); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + } + }, + (callback): void => { + log.debug('Waiting for web session'); + void this.getWebSession().asCallback((err, v) => { + if (err) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(err); + } + + cookies = v; + this.bptf.setCookies(cookies); + + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + }); + }, + (callback): void => { + if (this.options.bptfApiKey && this.options.bptfAccessToken) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + } + + log.warn( + 'You have not included the backpack.tf API key or access token in the environment variables' + ); + void this.getBptfAPICredentials.asCallback(err => { + if (err) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(err); + } + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + }); + }, + (callback): void => { + log.info('Getting Steam API key...'); + void this.setCookies(cookies).asCallback(callback); + }, + (callback): void => { + void this.checkAdminBanned() + .then(banned => { + if (banned) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(new Error('Not allowed')); + } + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + }) + .catch(err => { + if (err) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(err); + } + }); + + this.periodicCheck(); + }, + (callback): void => { + this.schemaManager = new SchemaManager({ + apiKey: this.manager.apiKey, + updateTime: 24 * 60 * 60 * 1000 + }); + + log.info('Getting TF2 schema...'); + void this.initializeSchema().asCallback(callback); + }, + (callback): void => { + log.info('Initializing inventory, bptf-listings, and profile settings'); + async.parallel( + [ + (callback): void => { + log.debug('Initializing inventory...'); + this.inventoryManager = new InventoryManager(this.pricelist); + // only call this here, and in Commands/Options + Inventory.setOptions(this.schema.paints, this.strangeParts, this.options.highValue); + this.inventoryManager.setInventory = new Inventory( + this.client.steamID, + this, + 'our' + ); + void this.inventoryManager.getInventory.fetch().asCallback(callback); + }, + (callback): void => { + log.debug('Initializing bptf-listings...'); + this.userID = this.bptf._getUserID(); + this.listingManager = new ListingManager({ + token: this.options.bptfAccessToken, + userID: this.userID, + userAgent: + 'TF2Autobot' + + (this.options.useragentHeaderCustom !== '' + ? ` - ${this.options.useragentHeaderCustom}` + : ' - Run your own bot for free'), + schema: this.schema + }); + + this.listingManager.token = this.options.bptfAccessToken; + this.listingManager.steamid = this.client.steamID; + + this.addListener( + this.listingManager, + 'pulse', + this.handler.onUserAgent.bind(this), + true + ); + this.addListener( + this.listingManager, + 'createListingsSuccessful', + this.handler.onCreateListingsSuccessful.bind(this), + true + ); + this.addListener( + this.listingManager, + 'updateListingsSuccessful', + this.handler.onUpdateListingsSuccessful.bind(this), + true + ); + this.addListener( + this.listingManager, + 'deleteListingsSuccessful', + this.handler.onDeleteListingsSuccessful.bind(this), + true + ); + this.addListener( + this.listingManager, + 'deleteArchivedListingSuccessful', + this.handler.onDeleteArchivedListingSuccessful.bind(this), + true + ); + this.addListener( + this.listingManager, + 'createListingsError', + this.handler.onCreateListingsError.bind(this), + true + ); + this.addListener( + this.listingManager, + 'updateListingsError', + this.handler.onUpdateListingsError.bind(this), + true + ); + this.addListener( + this.listingManager, + 'deleteListingsError', + this.handler.onDeleteListingsError.bind(this), + true + ); + this.addListener( + this.listingManager, + 'deleteArchivedListingError', + this.handler.onDeleteArchivedListingError.bind(this), + true + ); + + this.listingManager.init(callback); + }, + (callback): void => { + if (this.options.skipUpdateProfileSettings) { + return callback(null); + } + + log.debug('Updating profile settings...'); + + this.community.profileSettings( + { + profile: 3, + inventory: 3, + inventoryGifts: false + }, + callback + ); + } + ], + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + callback + ); + }, + (callback: (err?) => void): void => { + log.info('Initializing and setting up pricelist...'); + + this.pricelist = new Pricelist(this.priceSource, this.schema, this.options, this); + this.addListener( + this.pricelist, + 'pricelist', + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.handler.onPricelist.bind(this.handler), + false + ); + this.addListener(this.pricelist, 'price', this.handler.onPriceChange.bind(this.handler), true); + + this.pricelist.init(); + + const pricelist = Array.isArray(data.pricelist) + ? (data.pricelist.reduce((buff: Record, e: EntryData) => { + buff[e.sku] = e; + return buff; + }, {}) as PricesDataObject) + : data.pricelist || {}; + + this.pricelist + .setPricelist(pricelist, this) + .then(() => { + callback(null); + }) + .catch(err => { + callback(err); + }); + }, + (callback): void => { + log.debug('Getting max friends...'); + void this.friends.getMaxFriends.asCallback(callback); + }, + (callback): void => { + log.debug('Creating listings...'); + void this.listings.redoListings().asCallback(callback); + }, + (callback): void => { + this.community.getTradeURL((err, url) => { + if (err) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(err); + } + + this.tradeOfferUrl = url; + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ + return callback(null); + }); + } + ], + (item, callback) => { + if (this.botManager.isStopping) { + // Shutdown is requested, break out of the startup process + return resolve(); + } + item(callback); + }, + err => { + if (err) { + return reject(err); + } + if (this.botManager.isStopping) { + // Shutdown is requested, break out of the startup process + return resolve(); + } + + if (this.options.discordBotToken && this.discordBot) { + this.discordBot?.setPresence('online'); + } + + this.manager.pollInterval = 5 * 1000; + this.setReady = true; + this.handler.onReady(); + this.manager.doPoll(); + this.startVersionChecker(); + this.initResetCacheInterval(); + + if (this.options.discordBotToken && this.discordBot) { + this.discordBot?.setPresence('online'); + } + + return resolve(); + } + ); + }); + const firstTwoChain = [ async () => { log.debug('Calling onRun'); @@ -1100,7 +1444,7 @@ export default class Bot { }, 24 * 60 * 60 * 1000); } - async setCookies(cookies: string[]): Promise { + setCookies(cookies: string[]): Promise { this.community.setCookies(cookies); if (this.isReady) { @@ -1109,7 +1453,15 @@ export default class Bot { this.listingManager.setUserID(this.userID); } - await promisify(this.manager.setCookies.bind(this.manager))(cookies); + return new Promise((resolve, reject) => { + this.manager.setCookies(cookies, err => { + if (err) { + return reject(err); + } + + resolve(); + }); + }); } getWebSession(eventOnly = false): Promise { @@ -1388,17 +1740,15 @@ export default class Bot { if (this.client.steamID) this.client.webLogOn(); } - private async onConfKeyNeeded( - tag: string, - callback: (err: Error | null, time: number, confKey: string) => void - ): Promise { + private onConfKeyNeeded(tag: string, callback: (err: Error | null, time: number, confKey: string) => void): void { log.debug('Conf key needed'); - const offset = await this.getTimeOffset; - const time = SteamTotp.time(offset); - const confKey = SteamTotp.getConfirmationKey(this.options.steamIdentitySecret, time, tag); + void this.getTimeOffset.asCallback((err, offset) => { + const time = SteamTotp.time(offset); + const confKey = SteamTotp.getConfirmationKey(this.options.steamIdentitySecret, time, tag); - callback(null, time, confKey); + return callback(null, time, confKey); + }); } private onSteamGuard(domain: string, callback: (authCode: string) => void, lastCodeWrong: boolean): void { @@ -1430,7 +1780,7 @@ export default class Bot { }); } - private async onError(err: CustomError): Promise { + private onError(err: CustomError): void { if (err.eresult === EResult.LoggedInElsewhere) { log.warn('Signed in elsewhere, stopping the bot...'); this.botManager.stop(err, false, true); @@ -1445,7 +1795,11 @@ export default class Bot { log.warn('Login session replaced, relogging...'); - await this.login(); + void this.login().asCallback(err => { + if (err) { + throw err; + } + }); } else { throw err; } diff --git a/src/classes/BotManager.ts b/src/classes/BotManager.ts index faf7a4ed9..a9ee56ead 100644 --- a/src/classes/BotManager.ts +++ b/src/classes/BotManager.ts @@ -1,3 +1,4 @@ +import async from 'async'; import SchemaManager from '@tf2autobot/tf2-schema'; import pm2 from 'pm2'; import Bot from './Bot'; @@ -53,35 +54,54 @@ export default class BotManager { return this.bot !== null && this.bot.isReady; } - async start(options: Options): Promise { - REQUIRED_OPTS.forEach(optName => { - if (!process.env[optName] && !options[camelCase(optName)]) { - throw new Error(`Missing required environment variable "${optName}"`); - } - }); - - try { - log.debug('Connecting to PM2...'); - await this.connectToPM2(); - - log.info('Starting bot...'); - this.pricer.init(options.enableSocket); - this.bot = new Bot(this, options, this.pricer); - - await this.bot.start(); - - this.pricer.connect(this.bot?.options.enableSocket); - - this.schemaManager = this.bot.schemaManager; - } catch (err) { - if (this.isStopping) { - return Promise.resolve(this.stop(null, false, false)); - } + start(options: Options): Promise { + return new Promise((resolve, reject) => { + REQUIRED_OPTS.forEach(optName => { + if (!process.env[optName] && !options[camelCase(optName)]) { + return reject(new Error(`Missing required environment variable "${optName}"`)); + } + }); - if (err) { - throw err; - } - } + async.eachSeries( + [ + (callback): void => { + log.debug('Connecting to PM2...'); + void this.connectToPM2().asCallback(callback); + }, + (callback): void => { + log.info('Starting bot...'); + this.pricer.init(options.enableSocket); + this.bot = new Bot(this, options, this.pricer); + + void this.bot.start().asCallback(callback); + } + ], + (item, callback) => { + if (this.isStopping) { + // Shutdown is requested, stop the bot + return this.stop(null, false, false); + } + + item(callback); + }, + err => { + if (err) { + return reject(err); + } + + if (this.isStopping) { + // Shutdown is requested, stop the bot + return this.stop(null, false, false); + } + + this.pricer.connect(this.bot?.options.enableSocket); + + this.schemaManager = this.bot.schemaManager; + + return resolve(); + } + ); + }); } stop(err: Error | null, checkIfReady = true, rudely = false): void { diff --git a/src/classes/Commands/Commands.ts b/src/classes/Commands/Commands.ts index 583b52dc5..7de5cbfd6 100644 --- a/src/classes/Commands/Commands.ts +++ b/src/classes/Commands/Commands.ts @@ -3,7 +3,6 @@ import SKU from '@tf2autobot/tf2-sku'; import pluralize from 'pluralize'; import Currencies from '@tf2autobot/tf2-currencies'; import dayjs from 'dayjs'; -import { promisify } from 'util'; import * as c from './sub-classes/export'; import { removeLinkProtocol, getItemFromParams, getItemAndAmount } from './functions/utils'; @@ -716,38 +715,37 @@ export default class Commands { ); } - void this.bot.trades - .getOffer(activeOffer) - .then(offer => { - if (!offer) throw new Error('Offer might already be canceled'); - offer.data('canceledByUser', true); - - void promisify(offer.cancel.bind(offer))() - .then(() => { - this.bot.sendMessage( - steamID, - `✅ Offer sent (${offer.id}) has been successfully cancelled.` - ); - }) - .catch((err: Error) => { - // Only react to error, if the offer is canceled then the user - // will get an alert from the onTradeOfferChanged handler - - log.warn('Error while trying to cancel an offer: ', err); - return this.bot.sendMessage( - steamID, - `❌ Ohh nooooes! Something went wrong while trying to cancel the offer: ${err.message}` - ); - }); - }) - .catch((err: Error) => { + void this.bot.trades.getOffer(activeOffer).asCallback((err, offer) => { + if (err || !offer) { const errStringify = JSON.stringify(err); - const errMessage = errStringify === '' ? err?.message : errStringify; + const errMessage = errStringify === '' ? (err as Error)?.message : errStringify; return this.bot.sendMessage( steamID, - `❌ Ohh nooooes! Something went wrong while trying to get the offer: ${errMessage}` + `❌ Ohh nooooes! Something went wrong while trying to get the offer: ${errMessage}` + + (!offer ? ` (or the offer might already be canceled)` : '') + ); + } + + offer.data('canceledByUser', true); + + offer.cancel(err => { + // Only react to error, if the offer is canceled then the user + // will get an alert from the onTradeOfferChanged handler + + if (err) { + log.warn('Error while trying to cancel an offer: ', err); + return this.bot.sendMessage( + steamID, + `❌ Ohh nooooes! Something went wrong while trying to cancel the offer: ${err.message}` + ); + } + + return this.bot.sendMessage( + steamID, + `✅ Offer sent (${offer.id}) has been successfully cancelled.` ); }); + }); } } diff --git a/src/classes/Listings.ts b/src/classes/Listings.ts index a0977a819..97c1e1800 100644 --- a/src/classes/Listings.ts +++ b/src/classes/Listings.ts @@ -356,7 +356,7 @@ export default class Listings { log.debug('Checking listings for ' + pluralize('item', priceKeys.length, true) + '...'); - void this.recursiveCheckPricelist(priceKeys, pricelist).finally(() => { + void this.recursiveCheckPricelist(priceKeys, pricelist).asCallback(() => { log.debug('Done checking all'); // Done checking all listings this.checkingAllListings = false; @@ -435,7 +435,7 @@ export default class Listings { return; } - void this.removeAllListings().finally(next); + void this.removeAllListings().asCallback(next); }); } diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index c3e86ba9f..2045cfa02 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -411,7 +411,7 @@ export default class MyHandler extends Handler { return resolve(); } - void this.bot.listings + this.bot.listings .removeAll() .catch((err: Error) => log.warn('Failed to remove all listings on shutdown (autokeys was enabled): ', err) @@ -424,7 +424,7 @@ export default class MyHandler extends Handler { return resolve(); } - void this.bot.listings + this.bot.listings .removeAll() .catch((err: Error) => log.warn('Failed to remove all listings on shutdown: ', err)) .finally(() => resolve()); diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index d3ba844b0..8a3a33250 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -536,7 +536,7 @@ export default class Trades { log.verbose(`Handling offer #${offerId}...`); - void this.getOffer(offerId) + this.getOffer(offerId) .then(offer => { if (!offer) { log.debug('Failed to get offer'); From 10561699b5d3b8df64b44ff03447cdbc0f708d53 Mon Sep 17 00:00:00 2001 From: IdiNium <47635037+idinium96@users.noreply.github.com> Date: Mon, 2 Jan 2023 20:34:40 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=94=A8=20fix=20missing=20instruction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Bot.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index a190832dc..38a3318ef 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -993,6 +993,7 @@ export default class Bot { }, (callback): void => { log.info('Initializing inventory, bptf-listings, and profile settings'); + this.setProperties(); async.parallel( [ (callback): void => { From 87d808970c66b8f34e45c651d8aef1baf1678e52 Mon Sep 17 00:00:00 2001 From: IdiNium <47635037+idinium96@users.noreply.github.com> Date: Mon, 2 Jan 2023 20:39:45 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=94=A8=20remove=20unnecessary=20void?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Bot.ts | 280 +-------------------------------------------- 1 file changed, 1 insertion(+), 279 deletions(-) diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index 38a3318ef..5d2a69e47 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -14,7 +14,6 @@ import semver from 'semver'; import axios, { AxiosError } from 'axios'; import pluralize from 'pluralize'; import * as timersPromises from 'timers/promises'; -import { promisify } from 'util'; import fs from 'fs'; import path from 'path'; @@ -964,7 +963,7 @@ export default class Bot { void this.setCookies(cookies).asCallback(callback); }, (callback): void => { - void this.checkAdminBanned() + this.checkAdminBanned() .then(banned => { if (banned) { /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */ @@ -1190,283 +1189,6 @@ export default class Bot { } ); }); - - const firstTwoChain = [ - async () => { - log.debug('Calling onRun'); - - data = await this.handler.onRun(); - - if (data.pollData) { - log.debug('Setting poll data'); - this.manager.pollData = data.pollData; - } - - if (data.loginAttempts) { - log.debug('Setting login attempts'); - this.setLoginAttempts = data.loginAttempts; - } - - if (data.blockedList) { - log.debug('Loading blocked list data'); - this.blockedList = data.blockedList; - } - }, - async () => { - log.info('Signing in to Steam...'); - - return await this.login(data.loginKey || null); - } - ]; - - const promisesChain = [ - async () => { - log.debug('Waiting for web session'); - cookies = await this.getWebSession(); - this.bptf.setCookies(cookies); - }, - async () => { - if (this.options.discordBotToken) { - log.info(`Initializing Discord bot...`); - this.discordBot = new DiscordBot(this.options, this); - try { - await this.discordBot.start(); - } catch (err) { - this.discordBot = null; - log.warn('Failed to start Discord bot: ', err); - throw err; - } - } else { - log.info('Discord api key is not set, ignoring.'); - } - }, - async () => { - if (this.options.bptfApiKey && this.options.bptfAccessToken) return; - - log.warn('You have not included the backpack.tf API key or access token in the environment variables'); - - await this.getBptfAPICredentials; - }, - async () => { - log.info('Getting Steam API key...'); - await this.setCookies(cookies); - }, - async () => { - const banned = await this.checkAdminBanned(); - if (banned) throw new Error('Not allowed'); - - this.periodicCheck(); - }, - async () => { - this.schemaManager = new SchemaManager({ - apiKey: this.manager.apiKey, - updateTime: 24 * 60 * 60 * 1000, - lite: true - }); - - log.info('Getting TF2 schema...'); - await this.initializeSchema(); - }, - () => { - log.info('Setting pricelist and inventory...'); - - this.pricelist = new Pricelist(this.priceSource, this.schema, this.options, this); - this.pricelist.init(); - - this.addListener( - this.pricelist, - 'pricelist', - // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.handler.onPricelist.bind(this.handler), - false - ); - this.addListener(this.pricelist, 'price', this.handler.onPriceChange.bind(this.handler), true); - - this.setProperties(); - }, - async () => { - log.debug('Initializing inventory...'); - this.inventoryManager = new InventoryManager(this.pricelist); - - // only call this here, and in Commands/Options - Inventory.setOptions(this.schema.paints, this.strangeParts, this.options.highValue); - - this.inventoryManager.setInventory = new Inventory(this.client.steamID, this, 'our'); - await this.inventoryManager.getInventory.fetch(); - }, - async () => { - log.debug('Initializing bptf-listings...'); - - this.userID = this.bptf._getUserID(); - - this.listingManager = new ListingManager({ - token: this.options.bptfAccessToken, - userID: this.userID, - userAgent: - `TF2Autobot${this.options.useragentHeaderShowVersion ? `@v${process.env.VERSION}` : ''}` + - (this.options.useragentHeaderCustom !== '' - ? ` - ${this.options.useragentHeaderCustom}` - : ' - Run your own bot for free'), - schema: this.schema, - steamid: this.client.steamID.getSteamID64() - }); - - this.addListener(this.listingManager, 'pulse', this.handler.onUserAgent.bind(this), true); - this.addListener( - this.listingManager, - 'createListingsSuccessful', - this.handler.onCreateListingsSuccessful.bind(this), - true - ); - this.addListener( - this.listingManager, - 'updateListingsSuccessful', - this.handler.onUpdateListingsSuccessful.bind(this), - true - ); - this.addListener( - this.listingManager, - 'deleteListingsSuccessful', - this.handler.onDeleteListingsSuccessful.bind(this), - true - ); - this.addListener( - this.listingManager, - 'deleteArchivedListingSuccessful', - this.handler.onDeleteArchivedListingSuccessful.bind(this), - true - ); - this.addListener( - this.listingManager, - 'createListingsError', - this.handler.onCreateListingsError.bind(this), - true - ); - this.addListener( - this.listingManager, - 'updateListingsError', - this.handler.onUpdateListingsError.bind(this), - true - ); - this.addListener( - this.listingManager, - 'deleteListingsError', - this.handler.onDeleteListingsError.bind(this), - true - ); - this.addListener( - this.listingManager, - 'deleteArchivedListingError', - this.handler.onDeleteArchivedListingError.bind(this), - true - ); - await promisify(this.listingManager.init.bind(this.listingManager))(); - }, - async () => { - if (this.options.skipUpdateProfileSettings) return; - - log.debug('Updating profile settings...'); - - await promisify(this.community.profileSettings.bind(this.community))({ - profile: 3, - inventory: 3, - inventoryGifts: false - }); - }, - async () => { - log.info('Setting up pricelist...'); - - const pricelist = Array.isArray(data.pricelist) - ? (data.pricelist.reduce((buff: Record, e: EntryData) => { - buff[e.sku] = e; - return buff; - }, {}) as PricesDataObject) - : data.pricelist || {}; - - await this.pricelist.setPricelist(pricelist, this); - }, - async () => { - log.debug('Getting max friends...'); - await this.friends.getMaxFriends; - }, - async () => { - log.debug('Creating listings...'); - await this.listings.redoListings(); - } - ]; - - let promiseFirstTwo = Promise.resolve(); - let promise = Promise.resolve(); - - return new Promise((resolve, reject) => { - const checkIfStopping = () => { - if (this.botManager.isStopping) return reject(); - }; - - for (const promiseToChainFirstTwo of firstTwoChain) { - promiseFirstTwo = promiseFirstTwo.then(promiseToChainFirstTwo).then(checkIfStopping); - } - - let lastLoginFailed = false; - - const successResponse = () => { - log.info('Signed in to Steam!'); - }; - - const failResponse = (err: CustomError) => { - this.handler.onLoginError(err); - log.warn('Failed to sign in to Steam: ', err); - return reject(err); - }; - - promiseFirstTwo - .then(() => { - successResponse(); - }) - .catch(async (err: CustomError) => { - if (!lastLoginFailed && err.eresult === EResult.InvalidPassword) { - this.handler.onLoginError(err); - lastLoginFailed = true; - // Try and sign in without login key - log.warn('Failed to sign in to Steam, retrying without login key...'); - await this.login(null).then(successResponse).catch(failResponse); - } else { - failResponse(err); - } - }) - .finally(() => { - for (const promiseToChain of promisesChain) { - promise = promise.then(promiseToChain).then(checkIfStopping); - } - - promise - .then(() => { - this.community.getTradeURL((err, url) => { - if (err) { - throw err; - } - - this.tradeOfferUrl = url; - - if (this.options.discordBotToken && this.discordBot) { - this.discordBot?.setPresence('online'); - } - - this.manager.pollInterval = 5 * 1000; - this.setReady = true; - this.handler.onReady(); - this.manager.doPoll(); - this.startVersionChecker(); - this.initResetCacheInterval(); - - resolve(); - }); - }) - .catch(err => { - return reject(err); - }); - }); - }); } setProperties(): void { From ad339d6b2d355f5a6d4be4361e2be389dd723865 Mon Sep 17 00:00:00 2001 From: IdiNium <47635037+idinium96@users.noreply.github.com> Date: Mon, 2 Jan 2023 20:40:07 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=94=A8=20add=20missing=20`lite`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Bot.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index 5d2a69e47..53309b207 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -984,7 +984,8 @@ export default class Bot { (callback): void => { this.schemaManager = new SchemaManager({ apiKey: this.manager.apiKey, - updateTime: 24 * 60 * 60 * 1000 + updateTime: 24 * 60 * 60 * 1000, + lite: true }); log.info('Getting TF2 schema...'); From 37d7ca57ac07706fc8d7bd782f6f54696db05efc Mon Sep 17 00:00:00 2001 From: IdiNium <47635037+idinium96@users.noreply.github.com> Date: Mon, 2 Jan 2023 20:41:26 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=94=A8=20initialize=20pricelist=20fir?= =?UTF-8?q?st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Bot.ts | 62 +++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index 53309b207..de5ee68dd 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -991,6 +991,37 @@ export default class Bot { log.info('Getting TF2 schema...'); void this.initializeSchema().asCallback(callback); }, + (callback: (err?) => void): void => { + log.info('Initializing and setting up pricelist...'); + + this.pricelist = new Pricelist(this.priceSource, this.schema, this.options, this); + this.addListener( + this.pricelist, + 'pricelist', + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.handler.onPricelist.bind(this.handler), + false + ); + this.addListener(this.pricelist, 'price', this.handler.onPriceChange.bind(this.handler), true); + + this.pricelist.init(); + + const pricelist = Array.isArray(data.pricelist) + ? (data.pricelist.reduce((buff: Record, e: EntryData) => { + buff[e.sku] = e; + return buff; + }, {}) as PricesDataObject) + : data.pricelist || {}; + + this.pricelist + .setPricelist(pricelist, this) + .then(() => { + callback(null); + }) + .catch(err => { + callback(err); + }); + }, (callback): void => { log.info('Initializing inventory, bptf-listings, and profile settings'); this.setProperties(); @@ -1103,37 +1134,6 @@ export default class Bot { callback ); }, - (callback: (err?) => void): void => { - log.info('Initializing and setting up pricelist...'); - - this.pricelist = new Pricelist(this.priceSource, this.schema, this.options, this); - this.addListener( - this.pricelist, - 'pricelist', - // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.handler.onPricelist.bind(this.handler), - false - ); - this.addListener(this.pricelist, 'price', this.handler.onPriceChange.bind(this.handler), true); - - this.pricelist.init(); - - const pricelist = Array.isArray(data.pricelist) - ? (data.pricelist.reduce((buff: Record, e: EntryData) => { - buff[e.sku] = e; - return buff; - }, {}) as PricesDataObject) - : data.pricelist || {}; - - this.pricelist - .setPricelist(pricelist, this) - .then(() => { - callback(null); - }) - .catch(err => { - callback(err); - }); - }, (callback): void => { log.debug('Getting max friends...'); void this.friends.getMaxFriends.asCallback(callback); From 54b502b0f8abc508da13787afbe199f877f22338 Mon Sep 17 00:00:00 2001 From: IdiNium <47635037+idinium96@users.noreply.github.com> Date: Mon, 2 Jan 2023 20:51:10 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=94=A8=20setup=20pricelist=20after=20?= =?UTF-8?q?inventory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Bot.ts | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index de5ee68dd..763ad60b8 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -992,7 +992,7 @@ export default class Bot { void this.initializeSchema().asCallback(callback); }, (callback: (err?) => void): void => { - log.info('Initializing and setting up pricelist...'); + log.info('Initializing pricelist...'); this.pricelist = new Pricelist(this.priceSource, this.schema, this.options, this); this.addListener( @@ -1006,21 +1006,7 @@ export default class Bot { this.pricelist.init(); - const pricelist = Array.isArray(data.pricelist) - ? (data.pricelist.reduce((buff: Record, e: EntryData) => { - buff[e.sku] = e; - return buff; - }, {}) as PricesDataObject) - : data.pricelist || {}; - - this.pricelist - .setPricelist(pricelist, this) - .then(() => { - callback(null); - }) - .catch(err => { - callback(err); - }); + callback(null); }, (callback): void => { log.info('Initializing inventory, bptf-listings, and profile settings'); @@ -1134,6 +1120,25 @@ export default class Bot { callback ); }, + (callback: (err?) => void): void => { + log.info('Setting up pricelist...'); + + const pricelist = Array.isArray(data.pricelist) + ? (data.pricelist.reduce((buff: Record, e: EntryData) => { + buff[e.sku] = e; + return buff; + }, {}) as PricesDataObject) + : data.pricelist || {}; + + this.pricelist + .setPricelist(pricelist, this) + .then(() => { + callback(null); + }) + .catch(err => { + callback(err); + }); + }, (callback): void => { log.debug('Getting max friends...'); void this.friends.getMaxFriends.asCallback(callback); From 977e33d25009e897259b3b0748c58f840d25243f Mon Sep 17 00:00:00 2001 From: IdiNium <47635037+idinium96@users.noreply.github.com> Date: Mon, 2 Jan 2023 20:53:54 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=94=8E=20check=20if=20defined=20first?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/MyHandler/MyHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index dd715de3c..cccdc2bf5 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -410,7 +410,7 @@ export default class MyHandler extends Handler { log.warn('Unable to disable Mann Co. Supply Crate Key...'); }) .finally(() => { - if (this.bot.listingManager.ready !== true) { + if (!this.bot.listingManager || this.bot.listingManager.ready !== true) { // We have not set up the listing manager, don't try and remove listings return resolve(); } @@ -423,7 +423,7 @@ export default class MyHandler extends Handler { .finally(() => resolve()); }); } else { - if (this.bot.listingManager.ready !== true) { + if (!this.bot.listingManager || this.bot.listingManager.ready !== true) { // We have not set up the listing manager, don't try and remove listings return resolve(); }