From 7b696faf972a964c5a92c914a5c5348f80526d25 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 30 Mar 2024 18:05:59 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=94=84=EF=B8=8F=20redo=20logging=20in?= =?UTF-8?q?=20(credit=20@Nicklason)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Bot.ts | 187 ++++++++++------------------- src/classes/BotManager.ts | 5 - src/classes/Handler.ts | 4 +- src/classes/MyHandler/MyHandler.ts | 14 +-- src/resources/paths.ts | 4 +- 5 files changed, 76 insertions(+), 138 deletions(-) diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index 3474bfe3c..b32fe464c 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -18,9 +18,6 @@ import fs from 'fs'; import path from 'path'; import * as files from '../lib/files'; -// Reference: https://github.com/tf2-automatic/tf2-automatic/commit/cf7b807cae11eb172a78ef184bbafdb4ebe86501#diff-58f39591209025b16105c9f25a34c119332983a0d8cea7819b534d9d408324c4L329 -// Credit to @Nicklason -import { EAuthSessionGuardType, EAuthTokenPlatformType, LoginSession } from 'steam-session'; import jwt from 'jsonwebtoken'; import DiscordBot from './DiscordBot'; import { Message as DiscordMessage } from 'discord.js'; @@ -65,8 +62,6 @@ export default class Bot { readonly manager: TradeOfferManager; - session: LoginSession | null = null; - readonly community: SteamCommunity; tradeOfferUrl: string; @@ -839,13 +834,14 @@ export default class Bot { let cookies: string[]; this.addListener(this.client, 'loggedOn', this.handler.onLoggedOn.bind(this.handler), false); + this.addListener(this.client, 'refreshToken', this.handler.onRefreshToken.bind(this.handler), false); this.addAsyncListener(this.client, 'friendMessage', this.onMessage.bind(this), true); this.addListener(this.client, 'friendRelationship', this.handler.onFriendRelationship.bind(this.handler), true); this.addListener(this.client, 'groupRelationship', this.handler.onGroupRelationship.bind(this.handler), true); this.addListener(this.client, 'newItems', this.onNewItems.bind(this), true); this.addListener(this.client, 'webSession', this.onWebSession.bind(this), false); this.addListener(this.client, 'steamGuard', this.onSteamGuard.bind(this), false); - this.addListener(this.client, 'error', this.onError.bind(this), false); + this.addAsyncListener(this.client, 'error', this.onError.bind(this), false); this.addListener(this.community, 'sessionExpired', this.onSessionExpired.bind(this), false); this.addListener(this.community, 'confKeyNeeded', this.onConfKeyNeeded.bind(this), false); @@ -1373,113 +1369,7 @@ export default class Bot { }); } - private async startSession(): Promise { - this.session = new LoginSession(EAuthTokenPlatformType.SteamClient); - // will think about proxy later - //,{ - // httpProxy: this.configService.getOrThrow('steam').proxyUrl - // }); - - this.session.on('debug', (message: string) => { - log.debug(`Session debug: ${message}`); - }); - - const oldTokens = (await files.readFile(this.handler.getPaths.files.loginToken, true).catch(err => { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access - log.warn(`Failed to read tokens: ${err.message}`); - return null; - })) as SteamTokens; - - if (oldTokens !== null) { - // Figure out if the refresh token expired - const { refreshToken, accessToken } = oldTokens; - - this.session.refreshToken = refreshToken; - this.session.accessToken = accessToken; - - const decoded = jwt.decode(refreshToken, { - complete: true - }); - - if (decoded) { - const { exp } = decoded.payload as { exp: number }; - - if (exp < Date.now() / 1000) { - // Refresh token expired, log in again - log.debug('Refresh token expired, logging in again'); - } else { - // Refresh token is still valid, use it - return refreshToken; - } - } - } - - const result = await this.session.startWithCredentials({ - accountName: this.options.steamAccountName, - password: this.options.steamPassword - }); - - if (result.actionRequired) { - const actions = result.validActions ?? []; - - if (actions.length !== 1) { - throw new Error(`Unexpected number of valid actions: ${actions.length}`); - } - - const action = actions[0]; - - if (action.type !== EAuthSessionGuardType.DeviceCode) { - throw new Error(`Unexpected action type: ${action.type}`); - } - - await this.session.submitSteamGuardCode(SteamTotp.generateAuthCode(this.options.steamSharedSecret)); - } - - this.session.on('error', err => { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access - log.warn(`Error in session: ${err.message}`); - }); - - /* eslint-disable @typescript-eslint/no-non-null-assertion */ - return new Promise((resolve, reject) => { - const handleAuth = () => { - log.debug('Session authenticated'); - removeListeners(); - resolve(); - }; - - const handleTimeout = () => { - removeListeners(); - reject(new Error('Login session timed out')); - }; - - const handleError = (err: Error) => { - removeListeners(); - reject(err); - }; - - const removeListeners = () => { - this.session.removeListener('authenticated', handleAuth); - this.session.removeListener('timeout', handleTimeout); - this.session.removeListener('error', handleError); - }; - - this.session.once('authenticated', handleAuth); - this.session.once('error', handleError); - }).then(() => { - const refreshToken = this.session.refreshToken; - - this.handler.onLoginToken({ refreshToken, accessToken: this.session.accessToken }); - - this.session.removeAllListeners(); - this.session = null; - - return refreshToken; - }); - /* eslint-enable @typescript-eslint/no-non-null-assertion */ - } - - private async login(): Promise { + private async login(refreshToken?: string): Promise { log.debug('Starting login attempt'); // loginKey: loginKey, // private: true @@ -1489,19 +1379,12 @@ export default class Bot { this.handler.onLoginThrottle(wait); } - const refreshToken = await this.startSession(); - return new Promise((resolve, reject) => { setTimeout(() => { const listeners = this.client.listeners('error'); this.client.removeAllListeners('error'); - const details = { refreshToken }; - - this.newLoginAttempt(); - this.client.logOn(details); - const gotEvent = (): void => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -1535,16 +1418,74 @@ export default class Bot { this.client.removeListener('error', errorEvent); log.debug('Did not get login response from Steam'); + this.client.logOff(); reject(new Error('Did not get login response (Steam might be down)')); }, 60 * 1000); this.client.once('loggedOn', loggedOnEvent); this.client.once('error', errorEvent); + + let loginDetails: { refreshToken: string } | { accountName: string; password: string }; + + if (refreshToken) { + log.debug('Attempting to login to Steam with refresh token...'); + loginDetails = { refreshToken }; + } else { + log.debug('Attempting to login to Steam...'); + loginDetails = { + accountName: this.options.steamAccountName, + password: this.options.steamPassword + }; + } + + this.newLoginAttempt(); + this.client.logOn(loginDetails); }, wait); }); } + private calculateBackoff(delay: number, attempts: number): number { + return delay * Math.pow(2, attempts - 1) + Math.floor(Math.random() * 1000); + } + + private async getRefreshToken(): Promise { + const tokenPath = this.handler.getPaths.files.refreshToken; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const refreshToken = (await files.readFile(tokenPath, false).catch(err => null)) as string; + + if (!refreshToken) { + return null; + } + + const decoded = jwt.decode(refreshToken, { + complete: true + }); + + if (!decoded) { + // Invalid token + return null; + } + + const { exp } = decoded.payload as { exp: number }; + + if (exp < Date.now() / 1000) { + // Refresh token expired + return null; + } + + return refreshToken; + } + + private async deleteRefreshToken(): Promise { + const tokenPath = this.handler.getPaths.files.refreshToken; + + await files.writeFile(tokenPath, '', false).catch(() => { + // Ignore error + }); + } + sendMessage(steamID: SteamID | string, message: string): void { if (steamID instanceof SteamID && steamID.redirectAnswerTo) { const origMessage = steamID.redirectAnswerTo; @@ -1667,7 +1608,7 @@ export default class Bot { }); } - private onError(err: CustomError): void { + private async onError(err: CustomError): Promise { if (err.eresult === EResult.LoggedInElsewhere) { log.warn('Signed in elsewhere, stopping the bot...'); this.botManager.stop(err, false, true); @@ -1682,7 +1623,9 @@ export default class Bot { log.warn('Login session replaced, relogging...'); - this.login().catch(err => { + await this.deleteRefreshToken(); + + this.login(await this.getRefreshToken()).catch(err => { if (err) { throw err; } diff --git a/src/classes/BotManager.ts b/src/classes/BotManager.ts index 7ebf38fb1..d7bde2474 100644 --- a/src/classes/BotManager.ts +++ b/src/classes/BotManager.ts @@ -195,11 +195,6 @@ export default class BotManager { this.bot.client.setPersona(EPersonaState.Snooze); this.bot.client.autoRelogin = false; - if (this.bot.session) { - this.bot.session.removeAllListeners(); - this.bot.session.cancelLoginAttempt(); - } - // Stop polling offers this.bot.manager.pollInterval = -1; diff --git a/src/classes/Handler.ts b/src/classes/Handler.ts index 7c4e6eb29..1a4271e32 100644 --- a/src/classes/Handler.ts +++ b/src/classes/Handler.ts @@ -2,7 +2,7 @@ import SteamID from 'steamid'; import TradeOfferManager, { PollData, Meta, CustomError } from '@tf2autobot/tradeoffer-manager'; -import Bot, { SteamTokens } from './Bot'; +import Bot from './Bot'; import { Entry, PricesDataObject, PricesObject } from './Pricelist'; import { Blocked } from './MyHandler/interfaces'; @@ -47,7 +47,7 @@ export default abstract class Handler { * Called when a new login key has been issued * @param loginKey - The new login key */ - abstract onLoginToken(loginToken: SteamTokens): void; + abstract onRefreshToken(token: string): void; /** * Called when a new trade offer is being processed diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index 4d9c149db..0517301bf 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -26,7 +26,7 @@ import { keepMetalSupply, craftDuplicateWeapons, craftClassWeapons } from './uti import { Blocked, BPTFGetUserInfo } from './interfaces'; import Handler, { OnRun } from '../Handler'; -import Bot, { SteamTokens } from '../Bot'; +import Bot from '../Bot'; import Pricelist, { Entry, PricesDataObject, PricesObject } from '../Pricelist'; import Commands from '../Commands/Commands'; import CartQueue from '../Carts/CartQueue'; @@ -493,19 +493,19 @@ export default class MyHandler extends Handler { await this.commands.processMessage(steamID, message); } - onLoginToken(loginToken: SteamTokens): void { - log.debug('New login key'); + onRefreshToken(token: string): void { + log.debug('New refresh key'); - files.writeFile(this.paths.files.loginToken, loginToken, true).catch(err => { - log.warn('Failed to save login token: ', err); + files.writeFile(this.paths.files.refreshToken, token, false).catch(err => { + log.warn('Failed to save refresh token: ', err); }); } onLoginError(err: CustomError): void { if (err.eresult === EResult.AccessDenied) { // Access denied during login - files.deleteFile(this.paths.files.loginToken).catch(err => { - log.warn('Failed to delete login token file: ', err); + files.deleteFile(this.paths.files.refreshToken).catch(err => { + log.warn('Failed to delete refresh token file: ', err); }); } } diff --git a/src/resources/paths.ts b/src/resources/paths.ts index 5c5f9e0c6..5398d765e 100644 --- a/src/resources/paths.ts +++ b/src/resources/paths.ts @@ -2,7 +2,7 @@ import path from 'path'; import fs from 'fs'; interface FilePaths { - loginToken: string; + refreshToken: string; pollData: string; loginAttempts: string; pricelist: string; @@ -35,7 +35,7 @@ export default function genPaths(steamAccountName: string, maxPollDataSizeMB = 5 return { files: { - loginToken: path.join(__dirname, `../../files/${steamAccountName}/loginToken.json`), + refreshToken: path.join(__dirname, `../../files/${steamAccountName}/refreshToken.txt`), pollData: pollDataPath, loginAttempts: path.join(__dirname, `../../files/${steamAccountName}/loginattempts.json`), pricelist: path.join(__dirname, `../../files/${steamAccountName}/pricelist.json`), From ef5e7de44f55da5f03fe43298eb6e7c1797428e6 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 30 Mar 2024 18:14:33 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=9A=AE=20remove=20steam-session?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3938d45f7..9cf9ab0cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,6 @@ "retry": "^0.13.1", "semver": "^7.5.4", "socket.io-client": "^4.7.2", - "steam-session": "^1.7.1", "steam-totp": "^2.1.2", "steam-user": "^5.0.4", "steamid": "^2.0.0", diff --git a/package.json b/package.json index c1bf1a995..0a3a5121c 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "retry": "^0.13.1", "semver": "^7.5.4", "socket.io-client": "^4.7.2", - "steam-session": "^1.7.1", "steam-totp": "^2.1.2", "steam-user": "^5.0.4", "steamid": "^2.0.0", From 27eb2a2e4c3933e1b66307d465b49ff78081402e Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 31 Mar 2024 07:41:13 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=94=A8=20force=20intent=20sell=20for?= =?UTF-8?q?=20item=20added=20with=20assetid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Commands/sub-classes/PricelistManager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/classes/Commands/sub-classes/PricelistManager.ts b/src/classes/Commands/sub-classes/PricelistManager.ts index 3b7f5d563..50127b118 100644 --- a/src/classes/Commands/sub-classes/PricelistManager.ts +++ b/src/classes/Commands/sub-classes/PricelistManager.ts @@ -176,6 +176,8 @@ export default class PricelistManagerCommands { if (params.id) { priceKey = String(params.id); params.id = String(params.id); + // force intent sell for assetid added + params.intent = 1; } priceKey = priceKey ? priceKey : params.sku; return this.bot.pricelist From 1ff8931484cdcf26bd3abeebbe0818f2effcffe7 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:28:19 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=94=A8=20use=20refreshToken=20if=20ex?= =?UTF-8?q?ists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Bot.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index b32fe464c..0104b4a51 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -887,10 +887,10 @@ export default class Bot { return callback(null); }); }, - (callback): void => { + async (callback): Promise => { log.info('Signing in to Steam...'); - this.login() + this.login(await this.getRefreshToken()) .then(() => { log.info('Signed in to Steam!'); From 23923d02f70566e0687f38e92fd58781be637f69 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:35:14 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20v5.11.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9cf9ab0cb..81729cc36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tf2autobot", - "version": "5.10.0", + "version": "5.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tf2autobot", - "version": "5.10.0", + "version": "5.11.0", "license": "MIT", "dependencies": { "@tf2autobot/bptf-listings": "^5.7.6", diff --git a/package.json b/package.json index 0a3a5121c..3369a8344 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tf2autobot", - "version": "5.10.0", + "version": "5.11.0", "description": "Fully automated TF2 trading bot advertising on www.backpack.tf using prices from www.prices.tf, Originally made by Nicklason.", "main": "dist/app.js", "scripts": {