From 4ec5be8116ea3f2175afb3191079701f1a9df8ad Mon Sep 17 00:00:00 2001 From: Bonfire Date: Wed, 10 Feb 2021 00:12:26 -0500 Subject: [PATCH 01/43] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20always=20show?= =?UTF-8?q?=20group=20on=20item=20add?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change makes it so that whenever an item is added, it will always show which group the item is in (even if that group is all) āœ… Closes: #324 --- src/classes/Commands/functions/pricelistManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/Commands/functions/pricelistManager.ts b/src/classes/Commands/functions/pricelistManager.ts index f2ebf5e85..35a52c424 100644 --- a/src/classes/Commands/functions/pricelistManager.ts +++ b/src/classes/Commands/functions/pricelistManager.ts @@ -365,7 +365,7 @@ function generateAddedReply(bot: Bot, isPremium: boolean, entry: Entry): string `\nšŸ“‹ Enabled: ${entry.enabled ? 'āœ…' : 'āŒ'}` + `\nšŸ”„ Autoprice: ${entry.autoprice ? 'āœ…' : 'āŒ'}` + (isPremium ? `\nšŸ“¢ Promoted: ${entry.promoted === 1 ? 'āœ…' : 'āŒ'}` : '') + - `${entry.group !== 'all' ? `\nšŸ”° Group: ${entry.group}` : ''}` + + `\nšŸ”° Group: ${entry.group}` + `${entry.note.buy !== null ? `\nšŸ“„ Custom buying note: ${entry.note.buy}` : ''}` + `${entry.note.sell !== null ? `\nšŸ“¤ Custom selling note: ${entry.note.sell}` : ''}` ); From 172e0cf134bb9c9ed3d5673539ece37ac523e52e Mon Sep 17 00:00:00 2001 From: Bonfire Date: Wed, 10 Feb 2021 00:18:09 -0500 Subject: [PATCH 02/43] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20always=20show?= =?UTF-8?q?=20group=20on=20item=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change makes it so that whenever an item is updated, it will always show which group the item is in (even if that group is all) āœ… Closes: #324 --- src/classes/Commands/functions/pricelistManager.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/classes/Commands/functions/pricelistManager.ts b/src/classes/Commands/functions/pricelistManager.ts index 35a52c424..08a47b2b3 100644 --- a/src/classes/Commands/functions/pricelistManager.ts +++ b/src/classes/Commands/functions/pricelistManager.ts @@ -850,12 +850,8 @@ export async function updateCommand(steamID: SteamID, message: string, bot: Bot) : `${newEntry.promoted === 1 ? 'āœ…' : 'āŒ'}` }` : '') + - `${ - newEntry.group !== 'all' - ? `\nšŸ”° Group: ${ - oldEntry.group !== newEntry.group ? `${oldEntry.group} ā†’ ${newEntry.group}` : newEntry.group - }` - : '' + `\nšŸ”° Group: ${ + oldEntry.group !== newEntry.group ? `${oldEntry.group} ā†’ ${newEntry.group}` : newEntry.group }` + `${newEntry.note.buy !== null ? `\nšŸ“„ Custom buying note: ${newEntry.note.buy}` : ''}` + `${newEntry.note.sell !== null ? `\nšŸ“¤ Custom selling note: ${newEntry.note.sell}` : ''}` From 17c7064a633f9bdf0287d6b9b484b9df481c6e23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Feb 2021 06:40:11 +0000 Subject: [PATCH 03/43] Bump ts-jest from 26.5.0 to 26.5.1 Bumps [ts-jest](https://github.com/kulshekhar/ts-jest) from 26.5.0 to 26.5.1. - [Release notes](https://github.com/kulshekhar/ts-jest/releases) - [Changelog](https://github.com/kulshekhar/ts-jest/blob/master/CHANGELOG.md) - [Commits](https://github.com/kulshekhar/ts-jest/compare/v26.5.0...v26.5.1) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 972ebd118..15929b928 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10963,9 +10963,9 @@ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, "ts-jest": { - "version": "26.5.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.0.tgz", - "integrity": "sha512-Ya4IQgvIFNa2Mgq52KaO8yBw2W8tWp61Ecl66VjF0f5JaV8u50nGoptHVILOPGoI7SDnShmEqnYQEmyHdQ+56g==", + "version": "26.5.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.1.tgz", + "integrity": "sha512-G7Rmo3OJMvlqE79amJX8VJKDiRcd7/r61wh9fnvvG8cAjhA9edklGw/dCxRSQmfZ/z8NDums5srSVgwZos1qfg==", "dev": true, "requires": { "@types/jest": "26.x", diff --git a/package.json b/package.json index bce502d0c..53d4517ac 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "nodemon": "^2.0.7", "prettier": "^2.2.1", "socket.io-mock": "^1.3.1", - "ts-jest": "^26.5.0", + "ts-jest": "^26.5.1", "typescript": "^4.1.3" }, "private": true, From 94d6f3e86108d0789f6a58464ce736419a1e3e6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Feb 2021 06:42:19 +0000 Subject: [PATCH 04/43] Bump graceful-fs from 4.2.5 to 4.2.6 Bumps [graceful-fs](https://github.com/isaacs/node-graceful-fs) from 4.2.5 to 4.2.6. - [Release notes](https://github.com/isaacs/node-graceful-fs/releases) - [Commits](https://github.com/isaacs/node-graceful-fs/compare/v4.2.5...v4.2.6) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 972ebd118..9abb52f01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5248,9 +5248,9 @@ } }, "graceful-fs": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.5.tgz", - "integrity": "sha512-kBBSQbz2K0Nyn+31j/w36fUfxkBW9/gfwRWdUY1ULReH3iokVJgddZAFcD1D0xlgTmFxJCbUkUclAlc6/IDJkw==" + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, "growly": { "version": "1.3.0", diff --git a/package.json b/package.json index bce502d0c..28186dcb8 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "dot-prop": "^6.0.1", "dotenv": "^8.2.0", "eslint-config-prettier": "^7.2.0", - "graceful-fs": "^4.2.5", + "graceful-fs": "^4.2.6", "isobject": "^4.0.0", "js-levenshtein": "^1.1.6", "jsonlint": "^1.6.3", From 2a5731c14d4b7187f7ba333b88198195f8d9adb1 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Thu, 11 Feb 2021 03:11:50 +0800 Subject: [PATCH 05/43] =?UTF-8?q?=F0=9F=94=84=20update=20`bptf-listings-2`?= =?UTF-8?q?=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 972ebd118..e773d743e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2870,9 +2870,9 @@ } }, "bptf-listings-2": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.6.tgz", - "integrity": "sha512-r0AVY0j9uWj3nYjp8cQpvSpBP4ehjD+ct4QDHuwcUWjzXrx2q93EsELsmk4G7ER1wILBNcyz+N2cF+k2bpJNWg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.7.tgz", + "integrity": "sha512-0PsyvLtj7i06qyQVLbVKzr7ooEiLB1PTQ0LejMBdBlfWJeedt9+3yuwfcLL2wOd8LAWa6Gj9g0Z/EEgvDnE7ZQ==", "requires": { "async": "^3.2.0", "events": "^3.2.0", diff --git a/package.json b/package.json index bce502d0c..2b2956756 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "async": "^3.2.0", "bluebird": "^3.7.2", "bluebird-global": "^1.0.1", - "bptf-listings-2": "^1.1.6", + "bptf-listings-2": "^1.1.7", "bptf-login-2": "^1.0.0", "callback-queue": "^3.0.0", "change-case": "^4.1.2", From 1c8d8dd113b19374402578bdb7e8818a53f69d96 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Thu, 11 Feb 2021 04:09:25 +0800 Subject: [PATCH 06/43] =?UTF-8?q?=F0=9F=94=84=20update=20`bptf-listings-2`?= =?UTF-8?q?=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index e773d743e..36ded1632 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2870,9 +2870,9 @@ } }, "bptf-listings-2": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.7.tgz", - "integrity": "sha512-0PsyvLtj7i06qyQVLbVKzr7ooEiLB1PTQ0LejMBdBlfWJeedt9+3yuwfcLL2wOd8LAWa6Gj9g0Z/EEgvDnE7ZQ==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.8.tgz", + "integrity": "sha512-n0nyX9mJxYSVr0OrTJksXFrcRxBCvTC/A/SzMJ3E6B6ZZPMaHlOQu9nuQWfi2BecvyhYryeEObKO/aEshwLheg==", "requires": { "async": "^3.2.0", "events": "^3.2.0", diff --git a/package.json b/package.json index 2b2956756..0a00487a8 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "async": "^3.2.0", "bluebird": "^3.7.2", "bluebird-global": "^1.0.1", - "bptf-listings-2": "^1.1.7", + "bptf-listings-2": "^1.1.8", "bptf-login-2": "^1.0.0", "callback-queue": "^3.0.0", "change-case": "^4.1.2", From dfa9868615b52cc4f94f247049bcde8c8611bd31 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Thu, 11 Feb 2021 07:52:21 +0800 Subject: [PATCH 07/43] =?UTF-8?q?=F0=9F=94=84=20update=20`bptf-listings-2`?= =?UTF-8?q?=20library=20(3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 36ded1632..761f3ca3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2870,9 +2870,9 @@ } }, "bptf-listings-2": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.8.tgz", - "integrity": "sha512-n0nyX9mJxYSVr0OrTJksXFrcRxBCvTC/A/SzMJ3E6B6ZZPMaHlOQu9nuQWfi2BecvyhYryeEObKO/aEshwLheg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.9.tgz", + "integrity": "sha512-746+JXgOGm8J3HL4Ju8N68g8k33GdV4/jZGNpZuAfkZUY9S/UZ35W4Hvo1YHv3n+OSfJVnuLI9N+K4KDxDVXtw==", "requires": { "async": "^3.2.0", "events": "^3.2.0", diff --git a/package.json b/package.json index 0a00487a8..1258d7651 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "async": "^3.2.0", "bluebird": "^3.7.2", "bluebird-global": "^1.0.1", - "bptf-listings-2": "^1.1.8", + "bptf-listings-2": "^1.1.9", "bptf-login-2": "^1.0.0", "callback-queue": "^3.0.0", "change-case": "^4.1.2", From 9be6b0f34d6a9f6fc4db62146904a2472109febb Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Thu, 11 Feb 2021 09:28:10 +0800 Subject: [PATCH 08/43] =?UTF-8?q?=F0=9F=94=84=20update=20`bptf-listings-2`?= =?UTF-8?q?=20library=20(4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 761f3ca3c..29dd7dbcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2870,9 +2870,9 @@ } }, "bptf-listings-2": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.9.tgz", - "integrity": "sha512-746+JXgOGm8J3HL4Ju8N68g8k33GdV4/jZGNpZuAfkZUY9S/UZ35W4Hvo1YHv3n+OSfJVnuLI9N+K4KDxDVXtw==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.10.tgz", + "integrity": "sha512-yrrksUauwcJs058V0coPte1K/HJnoKRSDglbbQGvOpsK1fRFiLJPsuSitj4gjeVB9E0DrG9/FIaBjyi/tqC22A==", "requires": { "async": "^3.2.0", "events": "^3.2.0", diff --git a/package.json b/package.json index 1258d7651..ccc3749e1 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "async": "^3.2.0", "bluebird": "^3.7.2", "bluebird-global": "^1.0.1", - "bptf-listings-2": "^1.1.9", + "bptf-listings-2": "^1.1.10", "bptf-login-2": "^1.0.0", "callback-queue": "^3.0.0", "change-case": "^4.1.2", From 692333eaac6499bc73d7872cfd635ab3940b7269 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Thu, 11 Feb 2021 19:25:56 +0800 Subject: [PATCH 09/43] =?UTF-8?q?=F0=9F=94=A8=20add=20missing=20retry=20to?= =?UTF-8?q?=20accept=20here?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Trades.ts | 70 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index 17970bb2b..0cfdaec89 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -569,7 +569,75 @@ export default class Trades { // Maybe wait for confirmation to be accepted and then resolve? this.acceptConfirmation(offer).catch(err => { log.debug(`Error while trying to accept mobile confirmation on offer #${offer.id}: `, err); - return reject(err); + + if (!(err as Error)?.message?.includes('Could not find confirmation for object')) { + // If error other than invalidItems state, retry to accept + // i.e. "Could not act on confirmation" or "HTTP error 502" errors. + // Maybe this can prevent the trade from getting cancelled? + + const opt = this.bot.options; + if (opt.sendAlert.failedAccept) { + const keyPrices = this.bot.pricelist.getKeyPrices; + const value = t.valueDiff( + offer, + keyPrices, + false, + opt.miscSettings.showOnlyMetal.enable + ); + + if (opt.discordWebhook.sendAlert.enable && opt.discordWebhook.sendAlert.url !== '') { + const summary = t.summarizeToChat( + offer, + this.bot, + 'summary-accepting', + true, + value, + keyPrices, + false, + false + ); + sendAlert( + `failed-accept` as 'failed-accept' | 'failed-decline', + this.bot, + `Failed to accept on the offer #${offer.id}` + + summary + + `\n\nRetrying in 3 seconds, or you can try to force accept this trade, send "!faccept ${offer.id}" now.`, + null, + err, + [offer.id] + ); + } else { + const summary = t.summarizeToChat( + offer, + this.bot, + 'summary-accepting', + false, + value, + keyPrices, + true, + false + ); + + this.bot.messageAdmins( + `Failed to accept on the offer #${offer.id}:` + + summary + + `\n\nRetrying in 3 seconds, you can try to force accept this trade, reply "!faccept ${offer.id}" now.` + + `\n\nError: ${ + TradeOfferManager.EResult[(err as CustomError).eresult] as string + } (https://steamerrors.com/${(err as CustomError).eresult})`, + [] + ); + } + } + + setTimeout(() => { + // Auto-retry after 3 seconds + void this.retryActionAfterFailure(offer.id, 'accept'); + }, 3 * 1000); + } else { + // else just reject + return reject(err); + } }); } From a67faa68e138af107206d815abf66fc6e328d5b7 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Thu, 11 Feb 2021 19:35:38 +0800 Subject: [PATCH 10/43] =?UTF-8?q?=F0=9F=8E=A8=20improve=20error=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Trades.ts | 18 ++++++++++++++---- src/lib/DiscordWebhook/sendAlert.ts | 20 ++++++++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index 0cfdaec89..25f1794d4 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -359,8 +359,12 @@ export default class Trades { summary + `\n\nRetrying in 3 seconds, you can try to force ${action} this trade, reply "!f${action} ${offer.id}" now.` + `\n\nError: ${ - TradeOfferManager.EResult[(err as CustomError).eresult] as string - } (https://steamerrors.com/${(err as CustomError).eresult})`, + (err as CustomError).eresult + ? `${ + TradeOfferManager.EResult[(err as CustomError).eresult] as string + } (https://steamerrors.com/${(err as CustomError).eresult})` + : JSON.stringify(err, null, 4) + }`, [] ); } @@ -623,8 +627,14 @@ export default class Trades { summary + `\n\nRetrying in 3 seconds, you can try to force accept this trade, reply "!faccept ${offer.id}" now.` + `\n\nError: ${ - TradeOfferManager.EResult[(err as CustomError).eresult] as string - } (https://steamerrors.com/${(err as CustomError).eresult})`, + (err as CustomError).eresult + ? `${ + TradeOfferManager.EResult[ + (err as CustomError).eresult + ] as string + } (https://steamerrors.com/${(err as CustomError).eresult})` + : JSON.stringify(err, null, 4) + }`, [] ); } diff --git a/src/lib/DiscordWebhook/sendAlert.ts b/src/lib/DiscordWebhook/sendAlert.ts index cbf2917ec..130812ed3 100644 --- a/src/lib/DiscordWebhook/sendAlert.ts +++ b/src/lib/DiscordWebhook/sendAlert.ts @@ -131,18 +131,26 @@ export default function sendAlert( title = 'Failed to accept trade'; description = msg + - `\n\nError: [${ - TradeOfferManager.EResult[(err as CustomError).eresult] as string - }](https://steamerrors.com/${(err as CustomError).eresult})`; + `\n\nError: ${ + (err as CustomError).eresult + ? `[${TradeOfferManager.EResult[(err as CustomError).eresult] as string}](https://steamerrors.com/${ + (err as CustomError).eresult + })` + : JSON.stringify(err, null, 4) + }`; content = items[0]; // offer id color = '16711680'; // red } else if (type === 'failed-decline') { title = 'Failed to decline trade'; description = msg + - `\n\nError: [${ - TradeOfferManager.EResult[(err as CustomError).eresult] as string - }](https://steamerrors.com/${(err as CustomError).eresult})`; + `\n\nError: ${ + (err as CustomError).eresult + ? `[${TradeOfferManager.EResult[(err as CustomError).eresult] as string}](https://steamerrors.com/${ + (err as CustomError).eresult + })` + : JSON.stringify(err, null, 4) + }`; content = items[0]; // offer id color = '16711680'; // red } else if (type === 'failed-processing-offer') { From dcce96f9135818c4528061bb656eb4105b23e69a Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Thu, 11 Feb 2021 19:48:20 +0800 Subject: [PATCH 11/43] =?UTF-8?q?=F0=9F=94=A8=20only=20retry=20to=20accept?= =?UTF-8?q?=20ONCE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/MyHandler/MyHandler.ts | 5 +++++ src/classes/Trades.ts | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index 124282721..bef5796f4 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -1805,6 +1805,11 @@ export default class MyHandler extends Handler { highValue.theirItems = result.theirHighValuedItems; highValue.items = result.items; } + + if (this.bot.trades.recentlyRetryToAccept[offer.id] !== undefined) { + // delete if exist + delete this.bot.trades.recentlyRetryToAccept[offer.id]; + } } if (offer.state === TradeOfferManager.ETradeOfferState['Accepted']) { diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index 25f1794d4..0fa6fd6f7 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -26,6 +26,8 @@ export default class Trades { private restartOnEscrowCheckFailed: NodeJS.Timeout; + recentlyRetryToAccept: UnknownDictionary = {}; + constructor(bot: Bot) { this.bot = bot; } @@ -640,10 +642,20 @@ export default class Trades { } } - setTimeout(() => { - // Auto-retry after 3 seconds - void this.retryActionAfterFailure(offer.id, 'accept'); - }, 3 * 1000); + this.recentlyRetryToAccept[offer.id] = + this.recentlyRetryToAccept[offer.id] === undefined + ? 0 + : this.recentlyRetryToAccept[offer.id] + 1; + + if (this.recentlyRetryToAccept[offer.id] === 0) { + // Just retry to accept ONCE + setTimeout(() => { + // Auto-retry after 3 seconds + void this.retryActionAfterFailure(offer.id, 'accept'); + }, 3 * 1000); + } else { + return reject(err); + } } else { // else just reject return reject(err); From 8c2648516559a757675bfd84eb05c8f34a00b075 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Feb 2021 14:26:43 +0000 Subject: [PATCH 12/43] Bump typescript from 4.1.3 to 4.1.5 Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.1.3 to 4.1.5. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v4.1.3...v4.1.5) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85232a11d..bd9dd0ecf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11060,9 +11060,9 @@ } }, "typescript": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", - "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", + "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", "dev": true }, "undefsafe": { diff --git a/package.json b/package.json index 93f7d98e3..9c8da6ed6 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "prettier": "^2.2.1", "socket.io-mock": "^1.3.1", "ts-jest": "^26.5.1", - "typescript": "^4.1.3" + "typescript": "^4.1.5" }, "private": true, "_moduleAliases": { From 28cf618ef8d5b7ceb9bf3593dcbb19583e8fb25e Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 02:32:59 +0800 Subject: [PATCH 13/43] =?UTF-8?q?=E2=AC=86=20increase=20setTimeout=20retry?= =?UTF-8?q?=20time=20to=2030=20seconds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Trades.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index 0fa6fd6f7..7983f472f 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -339,7 +339,7 @@ export default class Trades { this.bot, `Failed to ${action} on the offer #${offer.id}` + summary + - `\n\nRetrying in 3 seconds, or you can try to force ${action} this trade, send "!f${action} ${offer.id}" now.`, + `\n\nRetrying in 30 seconds, or you can try to force ${action} this trade, send "!f${action} ${offer.id}" now.`, null, err, [offer.id] @@ -359,7 +359,7 @@ export default class Trades { this.bot.messageAdmins( `Failed to ${action} on the offer #${offer.id}:` + summary + - `\n\nRetrying in 3 seconds, you can try to force ${action} this trade, reply "!f${action} ${offer.id}" now.` + + `\n\nRetrying in 30 seconds, you can try to force ${action} this trade, reply "!f${action} ${offer.id}" now.` + `\n\nError: ${ (err as CustomError).eresult ? `${ @@ -374,9 +374,9 @@ export default class Trades { if (!['MANUAL-FORCE', 'AUTO-RETRY'].includes(reason) && ['accept', 'decline'].includes(action)) { setTimeout(() => { - // Auto-retry after 3 seconds + // Auto-retry after 30 seconds void this.retryActionAfterFailure(offer.id, action as 'accept' | 'decline'); - }, 3 * 1000); + }, 30 * 1000); } }) .finally(() => { @@ -607,7 +607,7 @@ export default class Trades { this.bot, `Failed to accept on the offer #${offer.id}` + summary + - `\n\nRetrying in 3 seconds, or you can try to force accept this trade, send "!faccept ${offer.id}" now.`, + `\n\nRetrying in 30 seconds, or you can try to force accept this trade, send "!faccept ${offer.id}" now.`, null, err, [offer.id] @@ -627,7 +627,7 @@ export default class Trades { this.bot.messageAdmins( `Failed to accept on the offer #${offer.id}:` + summary + - `\n\nRetrying in 3 seconds, you can try to force accept this trade, reply "!faccept ${offer.id}" now.` + + `\n\nRetrying in 30 seconds, you can try to force accept this trade, reply "!faccept ${offer.id}" now.` + `\n\nError: ${ (err as CustomError).eresult ? `${ @@ -650,9 +650,9 @@ export default class Trades { if (this.recentlyRetryToAccept[offer.id] === 0) { // Just retry to accept ONCE setTimeout(() => { - // Auto-retry after 3 seconds + // Auto-retry after 30 seconds void this.retryActionAfterFailure(offer.id, 'accept'); - }, 3 * 1000); + }, 30 * 1000); } else { return reject(err); } From 2cd69635cfaf7eb5ace92d11a3f6f7d741e01f03 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 07:46:38 +0800 Subject: [PATCH 14/43] =?UTF-8?q?=F0=9F=94=82=20revert=20retry=20accept=20?= =?UTF-8?q?on=20mobile=20confirm=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/MyHandler/MyHandler.ts | 5 -- src/classes/Trades.ts | 125 +++++++++++----------------- src/lib/DiscordWebhook/sendAlert.ts | 20 ++++- 3 files changed, 64 insertions(+), 86 deletions(-) diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index bef5796f4..124282721 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -1805,11 +1805,6 @@ export default class MyHandler extends Handler { highValue.theirItems = result.theirHighValuedItems; highValue.items = result.items; } - - if (this.bot.trades.recentlyRetryToAccept[offer.id] !== undefined) { - // delete if exist - delete this.bot.trades.recentlyRetryToAccept[offer.id]; - } } if (offer.state === TradeOfferManager.ETradeOfferState['Accepted']) { diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index 7983f472f..1f0cdab7b 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -26,8 +26,6 @@ export default class Trades { private restartOnEscrowCheckFailed: NodeJS.Timeout; - recentlyRetryToAccept: UnknownDictionary = {}; - constructor(bot: Bot) { this.bot = bot; } @@ -576,89 +574,60 @@ export default class Trades { this.acceptConfirmation(offer).catch(err => { log.debug(`Error while trying to accept mobile confirmation on offer #${offer.id}: `, err); - if (!(err as Error)?.message?.includes('Could not find confirmation for object')) { - // If error other than invalidItems state, retry to accept - // i.e. "Could not act on confirmation" or "HTTP error 502" errors. - // Maybe this can prevent the trade from getting cancelled? + const opt = this.bot.options; + if (opt.sendAlert.failedAccept) { + const keyPrices = this.bot.pricelist.getKeyPrices; + const value = t.valueDiff(offer, keyPrices, false, opt.miscSettings.showOnlyMetal.enable); - const opt = this.bot.options; - if (opt.sendAlert.failedAccept) { - const keyPrices = this.bot.pricelist.getKeyPrices; - const value = t.valueDiff( + if (opt.discordWebhook.sendAlert.enable && opt.discordWebhook.sendAlert.url !== '') { + const summary = t.summarizeToChat( offer, + this.bot, + 'summary-accepting', + true, + value, keyPrices, false, - opt.miscSettings.showOnlyMetal.enable + false + ); + sendAlert( + `error-accept`, + this.bot, + `Error while trying to accept mobile confirmation on offer #${offer.id}` + + summary + + `\n\nThe offer might already get cancelled. You can check if this offer is still active by` + + ` sending "!trade ${offer.id}"`, + null, + err, + [offer.id] ); - - if (opt.discordWebhook.sendAlert.enable && opt.discordWebhook.sendAlert.url !== '') { - const summary = t.summarizeToChat( - offer, - this.bot, - 'summary-accepting', - true, - value, - keyPrices, - false, - false - ); - sendAlert( - `failed-accept` as 'failed-accept' | 'failed-decline', - this.bot, - `Failed to accept on the offer #${offer.id}` + - summary + - `\n\nRetrying in 30 seconds, or you can try to force accept this trade, send "!faccept ${offer.id}" now.`, - null, - err, - [offer.id] - ); - } else { - const summary = t.summarizeToChat( - offer, - this.bot, - 'summary-accepting', - false, - value, - keyPrices, - true, - false - ); - - this.bot.messageAdmins( - `Failed to accept on the offer #${offer.id}:` + - summary + - `\n\nRetrying in 30 seconds, you can try to force accept this trade, reply "!faccept ${offer.id}" now.` + - `\n\nError: ${ - (err as CustomError).eresult - ? `${ - TradeOfferManager.EResult[ - (err as CustomError).eresult - ] as string - } (https://steamerrors.com/${(err as CustomError).eresult})` - : JSON.stringify(err, null, 4) - }`, - [] - ); - } - } - - this.recentlyRetryToAccept[offer.id] = - this.recentlyRetryToAccept[offer.id] === undefined - ? 0 - : this.recentlyRetryToAccept[offer.id] + 1; - - if (this.recentlyRetryToAccept[offer.id] === 0) { - // Just retry to accept ONCE - setTimeout(() => { - // Auto-retry after 30 seconds - void this.retryActionAfterFailure(offer.id, 'accept'); - }, 30 * 1000); } else { - return reject(err); + const summary = t.summarizeToChat( + offer, + this.bot, + 'summary-accepting', + false, + value, + keyPrices, + true, + false + ); + + this.bot.messageAdmins( + `Error while trying to accept mobile confirmation on offer #${offer.id}:` + + summary + + `\n\nThe offer might already get cancelled. You can check if this offer is still active by` + + ` sending "!trade ${offer.id}` + + `\n\nError: ${ + (err as CustomError).eresult + ? `${ + TradeOfferManager.EResult[(err as CustomError).eresult] as string + } (https://steamerrors.com/${(err as CustomError).eresult})` + : (err as Error).message + }`, + [] + ); } - } else { - // else just reject - return reject(err); } }); } diff --git a/src/lib/DiscordWebhook/sendAlert.ts b/src/lib/DiscordWebhook/sendAlert.ts index 130812ed3..695d6d5d8 100644 --- a/src/lib/DiscordWebhook/sendAlert.ts +++ b/src/lib/DiscordWebhook/sendAlert.ts @@ -33,7 +33,8 @@ type AlertType = | 'failed-decline' | 'failed-processing-offer' | 'retry-success' - | 'retry-failed'; + | 'retry-failed' + | 'error-accept'; export default function sendAlert( type: AlertType, @@ -136,7 +137,7 @@ export default function sendAlert( ? `[${TradeOfferManager.EResult[(err as CustomError).eresult] as string}](https://steamerrors.com/${ (err as CustomError).eresult })` - : JSON.stringify(err, null, 4) + : (err as Error).message }`; content = items[0]; // offer id color = '16711680'; // red @@ -149,7 +150,20 @@ export default function sendAlert( ? `[${TradeOfferManager.EResult[(err as CustomError).eresult] as string}](https://steamerrors.com/${ (err as CustomError).eresult })` - : JSON.stringify(err, null, 4) + : (err as Error).message + }`; + content = items[0]; // offer id + color = '16711680'; // red + } else if (type === 'error-accept') { + title = 'Error while trying to accept mobile confirmation'; + description = + msg + + `\n\nError: ${ + (err as CustomError).eresult + ? `[${TradeOfferManager.EResult[(err as CustomError).eresult] as string}](https://steamerrors.com/${ + (err as CustomError).eresult + })` + : (err as Error).message }`; content = items[0]; // offer id color = '16711680'; // red From 79fe05a32e37d9d7891d18438eb96d82dc64377e Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 09:26:43 +0800 Subject: [PATCH 15/43] =?UTF-8?q?=F0=9F=94=84=20update=20`options`=20schem?= =?UTF-8?q?a=20-=20remove=20`allOf`,=20etc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schemas/options-json/array-string-url.ts | 3 +- src/schemas/options-json/options.ts | 285 ++++++++++++------- 2 files changed, 183 insertions(+), 105 deletions(-) diff --git a/src/schemas/options-json/array-string-url.ts b/src/schemas/options-json/array-string-url.ts index d07bc6dad..568426e1a 100644 --- a/src/schemas/options-json/array-string-url.ts +++ b/src/schemas/options-json/array-string-url.ts @@ -7,5 +7,6 @@ export const stringArrayURLSchema: jsonschema.Schema = { type: 'string', pattern: '^$|https://discord(app)?.com/api/webhooks/[0-9]+/(.)+' }, - required: false + required: false, + additionalProperties: false }; diff --git a/src/schemas/options-json/options.ts b/src/schemas/options-json/options.ts index a13a1adf6..2c4057c58 100644 --- a/src/schemas/options-json/options.ts +++ b/src/schemas/options-json/options.ts @@ -9,7 +9,8 @@ export const optionsSchema: jsonschema.Schema = { items: { type: 'string' }, - required: false + required: false, + additionalProperties: false }, 'only-enable': { type: 'object', @@ -18,7 +19,8 @@ export const optionsSchema: jsonschema.Schema = { type: 'boolean' } }, - required: ['enable'] + required: ['enable'], + additionalProperties: false }, 'normalize-which': { type: 'object', @@ -65,7 +67,8 @@ export const optionsSchema: jsonschema.Schema = { $ref: '#/definitions/only-enable-declineReply' } }, - required: ['autoAcceptOverpay', 'autoDecline'] + required: ['autoAcceptOverpay', 'autoDecline'], + additionalProperties: false }, 'only-ignore-failed': { type: 'object', @@ -84,7 +87,8 @@ export const optionsSchema: jsonschema.Schema = { type: 'string' } }, - required: ['note'] + required: ['note'], + additionalProperties: false }, 'discord-webhook-misc': { type: 'object', @@ -102,7 +106,8 @@ export const optionsSchema: jsonschema.Schema = { type: 'boolean' } }, - required: ['showQuickLinks', 'showKeyRate', 'showPureStock', 'showInventory'] + required: ['showQuickLinks', 'showKeyRate', 'showPureStock', 'showInventory'], + additionalProperties: false }, 'discord-webhook-enable-url': { type: 'object', @@ -115,7 +120,8 @@ export const optionsSchema: jsonschema.Schema = { pattern: '^$|https://discord(app)?.com/api/webhooks/[0-9]+/(.)+' } }, - required: ['enable', 'url'] + required: ['enable', 'url'], + additionalProperties: false }, 'only-customReply-reply': { type: 'object', @@ -141,7 +147,8 @@ export const optionsSchema: jsonschema.Schema = { type: 'string' } }, - required: ['disabled'] + required: ['disabled'], + additionalProperties: false }, 'only-enable-customReply-disabled': { type: 'object', @@ -223,7 +230,8 @@ export const optionsSchema: jsonschema.Schema = { type: 'string' } }, - required: ['disabled', 'dontHave', 'have'] + required: ['disabled', 'dontHave', 'have'], + additionalProperties: false } }, required: ['enable', 'customReply'], @@ -401,7 +409,8 @@ export const optionsSchema: jsonschema.Schema = { 'weaponsAsCurrency', 'checkUses', 'game' - ] + ], + additionalProperties: false }, sendAlert: { properties: { @@ -424,7 +433,8 @@ export const optionsSchema: jsonschema.Schema = { type: 'boolean' } }, - required: ['lowPure', 'failedToAdd', 'failedToUpdate', 'failedToDisable'] + required: ['lowPure', 'failedToAdd', 'failedToUpdate', 'failedToDisable'], + additionalProperties: false }, backpackFull: { type: 'boolean' @@ -442,7 +452,8 @@ export const optionsSchema: jsonschema.Schema = { type: 'boolean' } }, - required: ['gotDisabled', 'receivedNotInPricelist', 'tryingToTake'] + required: ['gotDisabled', 'receivedNotInPricelist', 'tryingToTake'], + additionalProperties: false }, autoRemoveIntentSellFailed: { type: 'boolean' @@ -493,7 +504,8 @@ export const optionsSchema: jsonschema.Schema = { additionalProperties: false } }, - required: ['autoRemoveIntentSell', 'autoAddInvalidItems', 'autoAddPaintedItems', 'priceAge'] + required: ['autoRemoveIntentSell', 'autoAddInvalidItems', 'autoAddPaintedItems', 'priceAge'], + additionalProperties: false }, bypass: { type: 'object', @@ -572,11 +584,26 @@ export const optionsSchema: jsonschema.Schema = { type: 'string' } }, - required: ['spells', 'strangeParts', 'killstreaker', 'sheen', 'painted'], + required: [ + 'summary', + 'asked', + 'offered', + 'profitFromOverpay', + 'lossFromUnderpay', + 'timeTaken', + 'keyRate', + 'pureStock', + 'totalItems', + 'spells', + 'strangeParts', + 'killstreaker', + 'sheen', + 'painted' + ], additionalProperties: false } }, - required: ['showStockChanges', 'showTimeTakenInMS', 'showItemPrices'], + required: ['showStockChanges', 'showTimeTakenInMS', 'showItemPrices', 'customText'], additionalProperties: false }, @@ -899,19 +926,20 @@ export const optionsSchema: jsonschema.Schema = { additionalProperties: false }, invalidItems: { - allOf: [ - { - properties: { - givePrice: { - type: 'boolean' - } - }, - required: ['givePrice'] + type: 'object', + properties: { + givePrice: { + type: 'boolean' + }, + autoAcceptOverpay: { + type: 'boolean' }, - { - $ref: '#/definitions/only-autoAcceptOverpay-autoDecline' + autoDecline: { + $ref: '#/definitions/only-enable-declineReply' } - ] + }, + required: ['givePrice', 'autoAcceptOverpay', 'autoDecline'], + additionalProperties: false }, disabledItems: { $ref: '#/definitions/only-autoAcceptOverpay-autoDecline' @@ -1052,14 +1080,26 @@ export const optionsSchema: jsonschema.Schema = { $ref: 'array-string-url' }, misc: { - allOf: [ - { - $ref: '#/definitions/discord-webhook-misc' + type: 'object', + properties: { + showQuickLinks: { + type: 'boolean' }, - { - $ref: '#/definitions/only-note' + showKeyRate: { + type: 'boolean' + }, + showPureStock: { + type: 'boolean' + }, + showInventory: { + type: 'boolean' + }, + note: { + type: 'string' } - ] + }, + required: ['showQuickLinks', 'showKeyRate', 'showPureStock', 'showInventory', 'note'], + additionalProperties: false }, mentionOwner: { properties: { @@ -1082,71 +1122,97 @@ export const optionsSchema: jsonschema.Schema = { additionalProperties: false }, offerReview: { - allOf: [ - { - $ref: '#/definitions/discord-webhook-enable-url' + type: 'object', + properties: { + enable: { + type: 'boolean' }, - { + url: { + type: 'string', + pattern: '^$|https://discord(app)?.com/api/webhooks/[0-9]+/(.)+' + }, + mentionInvalidValue: { + type: 'boolean' + }, + isMention: { + type: 'boolean' + }, + misc: { type: 'object', properties: { - mentionInvalidValue: { + showQuickLinks: { + type: 'boolean' + }, + showKeyRate: { type: 'boolean' }, - isMention: { + showPureStock: { type: 'boolean' }, - misc: { - $ref: '#/definitions/discord-webhook-misc' + showInventory: { + type: 'boolean' } }, - required: ['mentionInvalidValue', 'isMention', 'misc'] + required: ['showQuickLinks', 'showKeyRate', 'showPureStock', 'showInventory'], + additionalProperties: false } - ] + }, + required: ['enable', 'url', 'mentionInvalidValue', 'isMention', 'misc'], + additionalProperties: false }, messages: { - allOf: [ - { - $ref: '#/definitions/discord-webhook-enable-url' + type: 'object', + properties: { + enable: { + type: 'boolean' }, - { - type: 'object', - properties: { - isMention: { - type: 'boolean' - }, - showQuickLinks: { - type: 'boolean' - } - }, - required: ['isMention', 'showQuickLinks'] + url: { + type: 'string', + pattern: '^$|https://discord(app)?.com/api/webhooks/[0-9]+/(.)+' + }, + isMention: { + type: 'boolean' + }, + showQuickLinks: { + type: 'boolean' } - ] + }, + required: ['enable', 'url', 'isMention', 'showQuickLinks'], + additionalProperties: false }, priceUpdate: { - allOf: [ - { - $ref: '#/definitions/discord-webhook-enable-url' + type: 'object', + properties: { + enable: { + type: 'boolean' }, - { - $ref: '#/definitions/only-note' + url: { + type: 'string', + pattern: '^$|https://discord(app)?.com/api/webhooks/[0-9]+/(.)+' + }, + note: { + type: 'string' } - ] + }, + required: ['enable', 'url', 'note'], + additionalProperties: false }, sendAlert: { - allOf: [ - { - $ref: '#/definitions/discord-webhook-enable-url' + type: 'object', + properties: { + enable: { + type: 'boolean' }, - { - type: 'object', - properties: { - isMention: { - type: 'boolean' - } - }, - required: ['isMention'] + url: { + type: 'string', + pattern: '^$|https://discord(app)?.com/api/webhooks/[0-9]+/(.)+' + }, + isMention: { + type: 'boolean' } - ] + }, + required: ['enable', 'url', 'isMention'], + additionalProperties: false }, sendStats: { $ref: '#/definitions/discord-webhook-enable-url' @@ -1277,6 +1343,7 @@ export const optionsSchema: jsonschema.Schema = { 'success', 'successEscrow', 'decline', + 'accepted', 'tradedAway', 'failedMobileConfirmation', 'cancelledActiveForAwhile', @@ -1449,7 +1516,7 @@ export const optionsSchema: jsonschema.Schema = { additionalProperties: false }, owner: { - $ref: '#/definitions/only-enable-customReply-disabled' + $ref: '#/definitions/only-enabled-disabled-reply' }, discord: { type: 'object', @@ -1458,7 +1525,17 @@ export const optionsSchema: jsonschema.Schema = { type: 'boolean' }, customReply: { - $ref: '#/definitions/only-disabled' + type: 'object', + properties: { + disabled: { + type: 'string' + }, + reply: { + type: 'string' + } + }, + required: ['disabled', 'reply'], + additionalProperties: false }, inviteURL: { type: 'string' @@ -1731,88 +1808,88 @@ export const optionsSchema: jsonschema.Schema = { $ref: '#/definitions/painted-properties' }, 'A Deep Commitment to Purple': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'A Distinctive Lack of Hue': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, "A Mann's Mint": { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'After Eight': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Aged Moustache Grey': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'An Extraordinary Abundance of Tinge': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Australium Gold': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Color No. 216-190-216': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Dark Salmon Injustice': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Drably Olive': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Indubitably Green': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Mann Co. Orange': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, Muskelmannbraun: { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, "Noble Hatter's Violet": { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Peculiarly Drab Tincture': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Pink as Hell': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Radigan Conagher Brown': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'The Bitter Taste of Defeat and Lime': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, "The Color of a Gentlemann's Business Pants": { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Ye Olde Rustic Colour': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, "Zepheniah's Greed": { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'An Air of Debonair': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Balaclavas Are Forever': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, "Operator's Overalls": { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Cream Spirit': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Team Spirit': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'The Value of Teamwork': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' }, 'Waterlogged Lab Coat': { - type: '#/definitions/painted-properties' + $ref: '#/definitions/painted-properties' } }, required: [ From 2aaca0a618f67ef6845208ab0f4cf470ba547251 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 09:47:39 +0800 Subject: [PATCH 16/43] =?UTF-8?q?=E2=9C=A8=20option=20to=20filter=20buy=20?= =?UTF-8?q?order=20that=20can't=20afford?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Commands/functions/manager.ts | 188 ++++++++++++++-------- src/classes/InventoryManager.ts | 5 +- src/classes/Listings.ts | 62 +++++-- src/classes/MyHandler/MyHandler.ts | 180 ++++++++++++--------- src/classes/Options.ts | 4 + src/schemas/options-json/options.ts | 11 +- 6 files changed, 293 insertions(+), 157 deletions(-) diff --git a/src/classes/Commands/functions/manager.ts b/src/classes/Commands/functions/manager.ts index bafd1d855..5edc9b895 100644 --- a/src/classes/Commands/functions/manager.ts +++ b/src/classes/Commands/functions/manager.ts @@ -7,6 +7,7 @@ import child from 'child_process'; import fs from 'graceful-fs'; import sleepasync from 'sleep-async'; import path from 'path'; +import dayjs from 'dayjs'; import { EPersonaState } from 'steam-user'; import { utils } from './export'; import Bot from '../../Bot'; @@ -551,90 +552,137 @@ export function refreshAutokeysCommand(steamID: SteamID, bot: Bot): void { bot.sendMessage(steamID, 'āœ… Successfully refreshed Autokeys.'); } +let pricelistLength = 0; +let executed = false; +let lastExecutedTime: number | null = null; +let executeTimeout: NodeJS.Timeout; + export function refreshListingsCommand(steamID: SteamID, bot: Bot): void { - const listingsSKUs: string[] = []; - bot.listingManager.getListings(async err => { - if (err) { - return bot.sendMessage( - steamID, - 'āŒ Unable to refresh listings, please try again later: ' + JSON.stringify(err) - ); - } - bot.listingManager.listings.forEach(listing => { - let listingSKU = listing.getSKU(); - if (listing.intent === 1) { - if (bot.options.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) { - listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); - } + const newExecutedTime = dayjs().valueOf(); + const timeDiff = newExecutedTime - lastExecutedTime; - if (bot.options.normalize.festivized.our && listingSKU.includes(';festive')) { - listingSKU = listingSKU.replace(';festive', ''); - } + if (executed === true) { + return bot.sendMessage( + steamID, + `āš ļø You need to wait ${Math.trunc( + ((pricelistLength > 1000 ? 60 : 30) * 60 * 1000 - timeDiff) / (1000 * 60) + )} minutes before you run refresh listings command again.` + ); + } else { + const listingsSKUs: string[] = []; + bot.listingManager.getListings(async err => { + if (err) { + return bot.sendMessage( + steamID, + 'āŒ Unable to refresh listings, please try again later: ' + JSON.stringify(err) + ); + } - if (bot.options.normalize.strangeAsSecondQuality.our && listingSKU.includes(';strange')) { - listingSKU = listingSKU.replace(';strange', ''); + const inventory = bot.inventoryManager; + const isFilterCantAfford = bot.options.pricelist.filterCantAfford.enable; + + bot.listingManager.listings.forEach(listing => { + let listingSKU = listing.getSKU(); + if (listing.intent === 1) { + if (bot.options.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) { + listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); + } + + if (bot.options.normalize.festivized.our && listingSKU.includes(';festive')) { + listingSKU = listingSKU.replace(';festive', ''); + } + + if (bot.options.normalize.strangeAsSecondQuality.our && listingSKU.includes(';strange')) { + listingSKU = listingSKU.replace(';strange', ''); + } + } else { + if (/;[p][0-9]+/.test(listingSKU)) { + listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); + } } - } else { - if (/;[p][0-9]+/.test(listingSKU)) { - listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); + + const match = bot.pricelist.getPrice(listingSKU); + + if (isFilterCantAfford && listing.intent === 0 && match !== null) { + const canAffordToBuy = inventory.isCanAffordToBuy(match.buy, inventory.getInventory); + + if (!canAffordToBuy) { + // Listing for buying exist but we can't afford to buy, remove. + log.debug(`Intent buy, removed because can't afford: ${match.sku}`); + listing.remove(); + } } - } - listingsSKUs.push(listingSKU); - }); + listingsSKUs.push(listingSKU); + }); - // Remove duplicate elements - const newlistingsSKUs: string[] = []; - listingsSKUs.forEach(sku => { - if (!newlistingsSKUs.includes(sku)) { - newlistingsSKUs.push(sku); - } - }); + // Remove duplicate elements + const newlistingsSKUs: string[] = []; + listingsSKUs.forEach(sku => { + if (!newlistingsSKUs.includes(sku)) { + newlistingsSKUs.push(sku); + } + }); - const inventory = bot.inventoryManager; - const pricelist = bot.pricelist.getPrices.filter(entry => { - // First find out if lising for this item from bptf already exist. - const isExist = newlistingsSKUs.find(sku => entry.sku === sku); - - if (!isExist) { - // undefined - listing does not exist but item is in the pricelist - - // Get amountCanBuy and amountCanSell (already cover intent and so on) - const amountCanBuy = inventory.amountCanTrade(entry.sku, true); - const amountCanSell = inventory.amountCanTrade(entry.sku, false); - - if ( - (amountCanBuy > 0 && inventory.isCanAffordToBuy(entry.buy, inventory.getInventory)) || - amountCanSell > 0 - ) { - // if can amountCanBuy is more than 0 and isCanAffordToBuy is true OR amountCanSell is more than 0 - // return this entry - return true; + const pricelist = bot.pricelist.getPrices.filter(entry => { + // First find out if lising for this item from bptf already exist. + const isExist = newlistingsSKUs.find(sku => entry.sku === sku); + + if (!isExist) { + // undefined - listing does not exist but item is in the pricelist + + // Get amountCanBuy and amountCanSell (already cover intent and so on) + const amountCanBuy = inventory.amountCanTrade(entry.sku, true); + const amountCanSell = inventory.amountCanTrade(entry.sku, false); + + if ( + (amountCanBuy > 0 && inventory.isCanAffordToBuy(entry.buy, inventory.getInventory)) || + amountCanSell > 0 + ) { + // if can amountCanBuy is more than 0 and isCanAffordToBuy is true OR amountCanSell is more than 0 + // return this entry + log.debug(`Missing${isFilterCantAfford ? '/Re-adding can afford' : ' listings'}: ${entry.sku}`); + return true; + } + + // Else ignore + return false; } - // Else ignore + // Else if listing already exist on backpack.tf, ignore return false; - } + }); - // Else if listing already exist on backpack.tf, ignore - return false; - }); + if (pricelist.length > 0) { + clearTimeout(executeTimeout); + lastExecutedTime = dayjs().valueOf(); - if (pricelist.length > 0) { - log.debug( - 'Checking listings for ' + - pluralize('item', pricelist.length, true) + - ` [${pricelist.map(entry => entry.sku).join(', ')}] ...` - ); + log.debug( + 'Checking listings for ' + + pluralize('item', pricelist.length, true) + + ` [${pricelist.map(entry => entry.sku).join(', ')}]...` + ); - bot.sendMessage(steamID, 'Refreshing listings for ' + pluralize('item', pricelist.length, true) + '...'); + bot.sendMessage( + steamID, + 'Refreshing listings for ' + pluralize('item', pricelist.length, true) + '...' + ); - await bot.listings.recursiveCheckPricelist(pricelist, true); + pricelistLength = pricelist.length; + executed = true; + executeTimeout = setTimeout(() => { + lastExecutedTime = null; + executed = false; + clearTimeout(executeTimeout); + }, (pricelistLength > 1000 ? 60 : 30) * 60 * 1000); - log.debug('Done checking ' + pluralize('item', pricelist.length, true)); - bot.sendMessage(steamID, 'āœ… Done refreshing ' + pluralize('item', pricelist.length, true)); - } else { - bot.sendMessage(steamID, 'āŒ Nothing to refresh.'); - } - }); + await bot.listings.recursiveCheckPricelist(pricelist, true); + + log.debug('Done checking ' + pluralize('item', pricelist.length, true)); + bot.sendMessage(steamID, 'āœ… Done refreshing ' + pluralize('item', pricelist.length, true)); + } else { + bot.sendMessage(steamID, 'āŒ Nothing to refresh.'); + } + }); + } } diff --git a/src/classes/InventoryManager.ts b/src/classes/InventoryManager.ts index 6955acbdc..225d0b72f 100644 --- a/src/classes/InventoryManager.ts +++ b/src/classes/InventoryManager.ts @@ -92,14 +92,15 @@ export default class InventoryManager { const avaiableCurrencies = inventory.getCurrencies([]); - const availableKeys = avaiableCurrencies['5021;6'].length * keyPrice.toValue(); + const availableKeysValue = avaiableCurrencies['5021;6'].length * keyPrice.toValue(); const availableMetalsValue = avaiableCurrencies['5002;6'].length * 9 + avaiableCurrencies['5001;6'].length * 3 + avaiableCurrencies['5000;6'].length; return ( - (buyingPrice.keys > 0 ? availableKeys >= buyingKeysValue : true) && availableMetalsValue >= buyingMetalValue + (buyingPrice.keys > 0 ? availableKeysValue >= buyingKeysValue : true) && + availableMetalsValue >= buyingMetalValue ); } diff --git a/src/classes/Listings.ts b/src/classes/Listings.ts index d94011573..1336113de 100644 --- a/src/classes/Listings.ts +++ b/src/classes/Listings.ts @@ -5,6 +5,7 @@ import request from 'request-retry-dayjs'; import async from 'async'; import dayjs from 'dayjs'; import Currencies from 'tf2-currencies'; +import sleepasync from 'sleep-async'; import Bot from './Bot'; import { Entry } from './Pricelist'; import { BPTFGetUserInfo, UserSteamID } from './MyHandler/interfaces'; @@ -182,7 +183,10 @@ export default class Listings { const amountCanBuy = this.bot.inventoryManager.amountCanTrade(sku, true, generics); const amountCanSell = this.bot.inventoryManager.amountCanTrade(sku, false, generics); - const inventory = this.bot.inventoryManager.getInventory; + const invManager = this.bot.inventoryManager; + const inventory = invManager.getInventory; + + const isFilterCantAfford = this.bot.options.pricelist.filterCantAfford.enable; // false by default this.bot.listingManager.findListings(sku).forEach(listing => { if (listing.intent === 1 && hasSellListing) { @@ -203,6 +207,15 @@ export default class Listings { } else if ((listing.intent === 0 && amountCanBuy <= 0) || (listing.intent === 1 && amountCanSell <= 0)) { // We are not buying / selling more, remove the listing listing.remove(); + } else if ( + match !== null && + listing.intent === 0 && + !invManager.isCanAffordToBuy(match.buy, invManager.getInventory) && + isFilterCantAfford + ) { + // Listing for buying exist but we can't afford to buy, remove. + log.debug(`Intent buy, removed because can't afford: ${match.sku}`); + listing.remove(); } else { if (listing.intent === 0 && /;[p][0-9]+/.test(sku)) { // do nothing @@ -241,7 +254,11 @@ export default class Listings { // TODO: Check if we are already making a listing for same type of item + intent - if (!hasBuyListing && amountCanBuy > 0 && !/;[p][0-9]+/.test(sku)) { + const canAffordToBuy = isFilterCantAfford + ? invManager.isCanAffordToBuy(matchNew.buy, invManager.getInventory) + : true; + + if (!hasBuyListing && amountCanBuy > 0 && canAffordToBuy && !/;[p][0-9]+/.test(sku)) { // We have no buy order and we can buy more items, create buy listing this.bot.listingManager.createListing({ time: matchNew.time || dayjs().unix(), @@ -297,6 +314,30 @@ export default class Listings { const keyPrice = this.bot.pricelist.getKeyPrice; const pricelist = this.bot.pricelist.getPrices + .filter(entry => { + if (!this.bot.options.pricelist.filterCantAfford.enable) { + // if this option is set to false, then always return true + return true; + } + + // Filter pricelist to only items we can sell and we can afford to buy + + const amountCanBuy = this.bot.inventoryManager.amountCanTrade(entry.sku, true); + const amountCanSell = this.bot.inventoryManager.amountCanTrade(entry.sku, false); + + if ( + (amountCanBuy > 0 && + inventoryManager.isCanAffordToBuy(entry.buy, inventoryManager.getInventory)) || + amountCanSell > 0 + ) { + // if can amountCanBuy is more than 0 and isCanAffordToBuy is true OR amountCanSell is more than 0 + // return this entry + return true; + } + + // Else ignore + return false; + }) .sort((a, b) => { return ( currentPure.keys - @@ -328,32 +369,31 @@ export default class Listings { }); } - recursiveCheckPricelist(pricelist: Entry[], withDelay = false): Promise { + recursiveCheckPricelist(pricelist: Entry[], withDelay = false, time?: number): Promise { return new Promise(resolve => { let index = 0; - const iteration = (): void => { + const iteration = async (): Promise => { if (pricelist.length <= index || this.cancelCheckingListings) { this.cancelCheckingListings = false; return resolve(); } if (withDelay) { - setTimeout(() => { - this.checkBySKU(pricelist[index].sku, pricelist[index]); - index++; - iteration(); - }, 200); + this.checkBySKU(pricelist[index].sku, pricelist[index]); + index++; + await sleepasync().Promise.sleep(time ? time : 200); + void iteration(); } else { setImmediate(() => { this.checkBySKU(pricelist[index].sku, pricelist[index]); index++; - iteration(); + void iteration(); }); } }; - iteration(); + void iteration(); }); } diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index 124282721..a7a373a4d 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -189,7 +189,7 @@ export default class MyHandler extends Handler { private poller: NodeJS.Timeout; - private refreshInterval: NodeJS.Timeout; + private refreshTimeout: NodeJS.Timeout; private sendStatsInterval: NodeJS.Timeout; @@ -273,7 +273,7 @@ export default class MyHandler extends Handler { this.sendStats(); // Check for missing listings every 30 minutes, initiate setInterval 5 minutes after start - this.refreshInterval = setTimeout(() => { + this.refreshTimeout = setTimeout(() => { this.enableAutoRefreshListings(); }, 5 * 60 * 1000); } @@ -283,8 +283,8 @@ export default class MyHandler extends Handler { clearInterval(this.poller); } - if (this.refreshInterval) { - clearInterval(this.refreshInterval); + if (this.refreshTimeout) { + clearInterval(this.refreshTimeout); } if (this.sendStatsInterval) { @@ -424,92 +424,126 @@ export default class MyHandler extends Handler { return; } - this.autoRefreshListingsInterval = setInterval(() => { - log.debug('Running automatic check for missing listings...'); + let pricelistLength = 0; - const listingsSKUs: string[] = []; - this.bot.listingManager.getListings(async err => { - if (err) { - setTimeout(() => { - this.enableAutoRefreshListings(); - }, 30 * 60 * 1000); - clearInterval(this.autoRefreshListingsInterval); - return; - } + this.autoRefreshListingsInterval = setInterval( + () => { + pricelistLength = 0; + log.debug('Running automatic check for missing listings...'); - this.bot.listingManager.listings.forEach(listing => { - let listingSKU = listing.getSKU(); - if (listing.intent === 1) { - if (this.bot.options.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) { - listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); - } + const listingsSKUs: string[] = []; + this.bot.listingManager.getListings(async err => { + if (err) { + setTimeout(() => { + this.enableAutoRefreshListings(); + }, 30 * 60 * 1000); + clearInterval(this.autoRefreshListingsInterval); + return; + } - if (this.bot.options.normalize.festivized.our && listingSKU.includes(';festive')) { - listingSKU = listingSKU.replace(';festive', ''); - } + const inventory = this.bot.inventoryManager; + const isFilterCantAfford = this.bot.options.pricelist.filterCantAfford.enable; - if (this.bot.options.normalize.strangeAsSecondQuality.our && listingSKU.includes(';strange')) { - listingSKU = listingSKU.replace(';strange', ''); - } - } else { - if (/;[p][0-9]+/.test(listingSKU)) { - listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); + this.bot.listingManager.listings.forEach(listing => { + let listingSKU = listing.getSKU(); + if (listing.intent === 1) { + if (this.bot.options.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) { + listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); + } + + if (this.bot.options.normalize.festivized.our && listingSKU.includes(';festive')) { + listingSKU = listingSKU.replace(';festive', ''); + } + + if ( + this.bot.options.normalize.strangeAsSecondQuality.our && + listingSKU.includes(';strange') + ) { + listingSKU = listingSKU.replace(';strange', ''); + } + } else { + if (/;[p][0-9]+/.test(listingSKU)) { + listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); + } } - } - listingsSKUs.push(listingSKU); - }); + const match = this.bot.pricelist.getPrice(listingSKU); - // Remove duplicate elements - const newlistingsSKUs: string[] = []; - listingsSKUs.forEach(sku => { - if (!newlistingsSKUs.includes(sku)) { - newlistingsSKUs.push(sku); - } - }); + if (isFilterCantAfford && listing.intent === 0 && match !== null) { + const canAffordToBuy = inventory.isCanAffordToBuy(match.buy, inventory.getInventory); + if (!canAffordToBuy) { + // Listing for buying exist but we can't afford to buy, remove. + log.debug(`Intent buy, removed because can't afford: ${match.sku}`); + listing.remove(); + } + } - const inventory = this.bot.inventoryManager; - const pricelist = this.bot.pricelist.getPrices.filter(entry => { - // First find out if lising for this item from bptf already exist. - const isExist = newlistingsSKUs.find(sku => entry.sku === sku); + listingsSKUs.push(listingSKU); + }); - if (!isExist) { - // undefined - listing does not exist but item is in the pricelist + // Remove duplicate elements + const newlistingsSKUs: string[] = []; + listingsSKUs.forEach(sku => { + if (!newlistingsSKUs.includes(sku)) { + newlistingsSKUs.push(sku); + } + }); - // Get amountCanBuy and amountCanSell (already cover intent and so on) - const amountCanBuy = inventory.amountCanTrade(entry.sku, true); - const amountCanSell = inventory.amountCanTrade(entry.sku, false); + const pricelist = this.bot.pricelist.getPrices.filter(entry => { + // First find out if lising for this item from bptf already exist. + const isExist = newlistingsSKUs.find(sku => entry.sku === sku); + + if (!isExist) { + // undefined - listing does not exist but item is in the pricelist + + // Get amountCanBuy and amountCanSell (already cover intent and so on) + const amountCanBuy = inventory.amountCanTrade(entry.sku, true); + const amountCanSell = inventory.amountCanTrade(entry.sku, false); + + if ( + (amountCanBuy > 0 && inventory.isCanAffordToBuy(entry.buy, inventory.getInventory)) || + amountCanSell > 0 + ) { + // if can amountCanBuy is more than 0 and isCanAffordToBuy is true OR amountCanSell is more than 0 + // return this entry + log.debug( + `Missing${isFilterCantAfford ? '/Re-adding can afford' : ' listings'}: ${entry.sku}` + ); + return true; + } - if ( - (amountCanBuy > 0 && inventory.isCanAffordToBuy(entry.buy, inventory.getInventory)) || - amountCanSell > 0 - ) { - // if can amountCanBuy is more than 0 and isCanAffordToBuy is true OR amountCanSell is more than 0 - // return this entry - return true; + // Else ignore + return false; } - // Else ignore + // Else if listing already exist on backpack.tf, ignore return false; + }); + + if (pricelist.length > 0) { + log.debug( + 'Checking listings for ' + + pluralize('item', pricelist.length, true) + + ` [${pricelist.map(entry => entry.sku).join(', ')}]...` + ); + + await this.bot.listings.recursiveCheckPricelist( + pricelist, + true, + pricelist.length > 1000 ? 1000 : 200 + ); + + log.debug('āœ… Done checking ' + pluralize('item', pricelist.length, true)); + } else { + log.debug('āŒ Nothing to refresh.'); } - // Else if listing already exist on backpack.tf, ignore - return false; + pricelistLength = pricelist.length; }); - - if (pricelist.length > 0) { - log.debug( - 'Checking listings for ' + - pluralize('item', pricelist.length, true) + - ` [${pricelist.map(entry => entry.sku).join(', ')}] ...` - ); - await this.bot.listings.recursiveCheckPricelist(pricelist, true); - log.debug('āœ… Done checking ' + pluralize('item', pricelist.length, true)); - } else { - log.debug('āŒ Nothing to refresh.'); - } - }); - }, 30 * 60 * 1000); + }, + // set check every 60 minutes if pricelist to check was more than 1000 items + pricelistLength > 1000 ? 60 * 60 * 1000 : 30 * 60 * 1000 + ); } disableAutoRefreshListings(): void { diff --git a/src/classes/Options.ts b/src/classes/Options.ts index a2db02021..c7da27d84 100644 --- a/src/classes/Options.ts +++ b/src/classes/Options.ts @@ -65,6 +65,9 @@ export const DEFAULTS = { }, pricelist: { + filterCantAfford: { + enable: false + }, autoRemoveIntentSell: { enable: false }, @@ -1023,6 +1026,7 @@ interface HighValueAlert { // ------------ Pricelist ------------ interface Pricelist { + filterCantAfford?: OnlyEnable; autoRemoveIntentSell?: OnlyEnable; autoAddInvalidItems?: OnlyEnable; autoAddPaintedItems?: OnlyEnable; diff --git a/src/schemas/options-json/options.ts b/src/schemas/options-json/options.ts index 2c4057c58..7448e3fa8 100644 --- a/src/schemas/options-json/options.ts +++ b/src/schemas/options-json/options.ts @@ -483,6 +483,9 @@ export const optionsSchema: jsonschema.Schema = { pricelist: { type: 'object', properties: { + filterCantAfford: { + $ref: '#/definitions/only-enable' + }, autoRemoveIntentSell: { $ref: '#/definitions/only-enable' }, @@ -504,7 +507,13 @@ export const optionsSchema: jsonschema.Schema = { additionalProperties: false } }, - required: ['autoRemoveIntentSell', 'autoAddInvalidItems', 'autoAddPaintedItems', 'priceAge'], + required: [ + 'filterCantAfford', + 'autoRemoveIntentSell', + 'autoAddInvalidItems', + 'autoAddPaintedItems', + 'priceAge' + ], additionalProperties: false }, bypass: { From 20236bdb981eb813c4fecb060477fafac60ce90a Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 10:11:03 +0800 Subject: [PATCH 17/43] =?UTF-8?q?=F0=9F=9A=AB=20prevent=20double=20executi?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Commands/functions/manager.ts | 3 +++ src/classes/MyHandler/MyHandler.ts | 26 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/classes/Commands/functions/manager.ts b/src/classes/Commands/functions/manager.ts index 5edc9b895..1c32bd574 100644 --- a/src/classes/Commands/functions/manager.ts +++ b/src/classes/Commands/functions/manager.ts @@ -668,11 +668,14 @@ export function refreshListingsCommand(steamID: SteamID, bot: Bot): void { 'Refreshing listings for ' + pluralize('item', pricelist.length, true) + '...' ); + bot.handler.isRecentlyExecuteRefreshlistCommand = true; + bot.handler.setRefreshlistExecutedDelay = (pricelistLength > 1000 ? 60 : 30) * 60 * 1000; pricelistLength = pricelist.length; executed = true; executeTimeout = setTimeout(() => { lastExecutedTime = null; executed = false; + bot.handler.isRecentlyExecuteRefreshlistCommand = false; clearTimeout(executeTimeout); }, (pricelistLength > 1000 ? 60 : 30) * 60 * 1000); diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index a7a373a4d..3a8b9de90 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -197,6 +197,18 @@ export default class MyHandler extends Handler { private autoRefreshListingsInterval: NodeJS.Timeout; + private alreadyExecutedRefreshlist = false; + + set isRecentlyExecuteRefreshlistCommand(setExecuted: boolean) { + this.alreadyExecutedRefreshlist = setExecuted; + } + + private executedDelayTime = 30 * 60 * 1000; + + set setRefreshlistExecutedDelay(delay: number) { + this.executedDelayTime = delay; + } + constructor(bot: Bot, private priceSource: Pricer) { super(bot); @@ -428,6 +440,20 @@ export default class MyHandler extends Handler { this.autoRefreshListingsInterval = setInterval( () => { + if (this.alreadyExecutedRefreshlist) { + log.debug( + 'āŒ Just recently executed refreshlist command, will not run automatic check for missing listings.' + ); + setTimeout(() => { + this.enableAutoRefreshListings(); + }, this.executedDelayTime); + + // reset to default + this.setRefreshlistExecutedDelay = 30 * 60 * 1000; + clearInterval(this.autoRefreshListingsInterval); + return; + } + pricelistLength = 0; log.debug('Running automatic check for missing listings...'); From 83d170a868c0874e11d57850de0f1548e2c92480 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 12:43:50 +0800 Subject: [PATCH 18/43] =?UTF-8?q?=F0=9F=8E=A8=20replace=20keys=20&=20pure?= =?UTF-8?q?=20metals=20with=20emoji=20(discord)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/tools/summarizeOffer.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/lib/tools/summarizeOffer.ts b/src/lib/tools/summarizeOffer.ts index 16a5cb400..015621ac8 100644 --- a/src/lib/tools/summarizeOffer.ts +++ b/src/lib/tools/summarizeOffer.ts @@ -150,7 +150,17 @@ function getSummary( if (withLink) { summary.push( - `[${name}](https://www.prices.tf/items/${sku})${amount > 1 ? ` x${amount}` : ''} (${ + `[${ + sku === '5021;6' + ? '<:tf2key:742725387968184371>' + : sku === '5002;6' + ? '<:tf2refined:735533220942053396>' + : sku === '5001;6' + ? '<:tf2reclaimed:809644301633323048>' + : sku === '5000;6' + ? '<:tf2scrap:809644301067091968>' + : name + }](https://www.prices.tf/items/${sku})${amount > 1 ? ` x${amount}` : ''} (${ type === 'summary-accepted' && oldStock !== null ? `${oldStock} ā†’ ` : '' }${currentStock}${maxStock ? `/${maxStock.max}` : ''})` ); @@ -168,7 +178,17 @@ function getSummary( } else { if (withLink) { summary.push( - '[' + name + '](https://www.prices.tf/items/' + sku + ')' + (amount > 1 ? ` x${amount}` : '') + `[${ + sku === '5021;6' + ? '<:tf2key:742725387968184371>' + : sku === '5002;6' + ? '<:tf2refined:735533220942053396>' + : sku === '5001;6' + ? '<:tf2reclaimed:809644301633323048>' + : sku === '5000;6' + ? '<:tf2scrap:809644301067091968>' + : name + }](https://www.prices.tf/items/${sku})${amount > 1 ? ` x${amount}` : ''}` ); } else { summary.push(name + (amount > 1 ? ` x${amount}` : '')); From 99f9a626c6b0c33df66fb5dfd59bc5f2690c3c68 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 13:01:09 +0800 Subject: [PATCH 19/43] =?UTF-8?q?=F0=9F=8E=A8=E2=9C=85=20`showPureInEmoji`?= =?UTF-8?q?=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Options.ts | 2 ++ src/lib/tools/summarizeOffer.ts | 36 ++++++++++++++++------------- src/schemas/options-json/options.ts | 5 +++- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/classes/Options.ts b/src/classes/Options.ts index a2db02021..26c649cca 100644 --- a/src/classes/Options.ts +++ b/src/classes/Options.ts @@ -98,6 +98,7 @@ export const DEFAULTS = { showStockChanges: false, showTimeTakenInMS: false, showItemPrices: true, + showPureInEmoji: true, customText: { summary: { steamChat: 'Summary', @@ -1052,6 +1053,7 @@ interface TradeSummary { showStockChanges?: boolean; showTimeTakenInMS?: boolean; showItemPrices?: boolean; + showPureInEmoji?: boolean; customText?: TradeSummaryCustomText; } diff --git a/src/lib/tools/summarizeOffer.ts b/src/lib/tools/summarizeOffer.ts index 015621ac8..284096ec3 100644 --- a/src/lib/tools/summarizeOffer.ts +++ b/src/lib/tools/summarizeOffer.ts @@ -151,14 +151,16 @@ function getSummary( if (withLink) { summary.push( `[${ - sku === '5021;6' - ? '<:tf2key:742725387968184371>' - : sku === '5002;6' - ? '<:tf2refined:735533220942053396>' - : sku === '5001;6' - ? '<:tf2reclaimed:809644301633323048>' - : sku === '5000;6' - ? '<:tf2scrap:809644301067091968>' + bot.options.tradeSummary.showPureInEmoji + ? sku === '5021;6' + ? '<:tf2key:742725387968184371>' + : sku === '5002;6' + ? '<:tf2refined:735533220942053396>' + : sku === '5001;6' + ? '<:tf2reclaimed:809644301633323048>' + : sku === '5000;6' + ? '<:tf2scrap:809644301067091968>' + : name : name }](https://www.prices.tf/items/${sku})${amount > 1 ? ` x${amount}` : ''} (${ type === 'summary-accepted' && oldStock !== null ? `${oldStock} ā†’ ` : '' @@ -179,14 +181,16 @@ function getSummary( if (withLink) { summary.push( `[${ - sku === '5021;6' - ? '<:tf2key:742725387968184371>' - : sku === '5002;6' - ? '<:tf2refined:735533220942053396>' - : sku === '5001;6' - ? '<:tf2reclaimed:809644301633323048>' - : sku === '5000;6' - ? '<:tf2scrap:809644301067091968>' + bot.options.tradeSummary.showPureInEmoji + ? sku === '5021;6' + ? '<:tf2key:742725387968184371>' + : sku === '5002;6' + ? '<:tf2refined:735533220942053396>' + : sku === '5001;6' + ? '<:tf2reclaimed:809644301633323048>' + : sku === '5000;6' + ? '<:tf2scrap:809644301067091968>' + : name : name }](https://www.prices.tf/items/${sku})${amount > 1 ? ` x${amount}` : ''}` ); diff --git a/src/schemas/options-json/options.ts b/src/schemas/options-json/options.ts index 2c4057c58..16be960d0 100644 --- a/src/schemas/options-json/options.ts +++ b/src/schemas/options-json/options.ts @@ -538,6 +538,9 @@ export const optionsSchema: jsonschema.Schema = { showItemPrices: { type: 'boolean' }, + showPureInEmoji: { + type: 'boolean' + }, customText: { type: 'object', properties: { @@ -603,7 +606,7 @@ export const optionsSchema: jsonschema.Schema = { additionalProperties: false } }, - required: ['showStockChanges', 'showTimeTakenInMS', 'showItemPrices', 'customText'], + required: ['showStockChanges', 'showTimeTakenInMS', 'showItemPrices', 'showPureInEmoji', 'customText'], additionalProperties: false }, From a4b43549bee49f0d756774c124629c4edfd5d558 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 15:35:10 +0800 Subject: [PATCH 20/43] =?UTF-8?q?=F0=9F=94=84=20change=20default=20to=20`f?= =?UTF-8?q?alse`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Options.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/Options.ts b/src/classes/Options.ts index 26c649cca..31d57eb8f 100644 --- a/src/classes/Options.ts +++ b/src/classes/Options.ts @@ -98,7 +98,7 @@ export const DEFAULTS = { showStockChanges: false, showTimeTakenInMS: false, showItemPrices: true, - showPureInEmoji: true, + showPureInEmoji: false, customText: { summary: { steamChat: 'Summary', From 7fb88c169646d5f8aa004afd46fd14c6cc700399 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 15:54:16 +0800 Subject: [PATCH 21/43] =?UTF-8?q?=F0=9F=94=94=20mention=20on=20error=20to?= =?UTF-8?q?=20accept=20mobile=20confirmation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/DiscordWebhook/sendAlert.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/DiscordWebhook/sendAlert.ts b/src/lib/DiscordWebhook/sendAlert.ts index 695d6d5d8..853e2ca69 100644 --- a/src/lib/DiscordWebhook/sendAlert.ts +++ b/src/lib/DiscordWebhook/sendAlert.ts @@ -212,7 +212,8 @@ export default function sendAlert( 'escrow-check-failed-not-restart-bptf-down', 'queue-problem-not-restart-bptf-down', 'autoAddPaintedItemsFailed', - 'failed-accept' + 'failed-accept', + 'error-accept' ].includes(type) && optDW.sendAlert.isMention ? `<@!${optDW.ownerID}>` : '') + (content ? ` - ${content}` : ''), From d9bb69e32bfe7264a4690c5d059b303f1814a77b Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 16:07:36 +0800 Subject: [PATCH 22/43] =?UTF-8?q?=F0=9F=94=84=20update=20dev=20dependencie?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 62 +++++++++++++++++++++++------------------------ package.json | 6 ++--- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index bd9dd0ecf..65d723084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,13 +97,13 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.13.tgz", - "integrity": "sha512-Vs/e9wv7rakKYeywsmEBSRC9KtmE7Px+YBlESekLeJOF0zbGUicGfXSNi3o+tfXSNS48U/7K9mIOOCR79Cl3+Q==", + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.16.tgz", + "integrity": "sha512-KbSEj8l9zYkMVHpQqM3wJNxS1d9h3U9vm/uE5tpjMbaj3lTp+0noe3KPsV5dSD9jxKnf9jO9Ip9FX5PKNZCKow==", "dev": true, "requires": { "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.12.13", + "@babel/helper-member-expression-to-functions": "^7.12.16", "@babel/helper-optimise-call-expression": "^7.12.13", "@babel/helper-replace-supers": "^7.12.13", "@babel/helper-split-export-declaration": "^7.12.13" @@ -119,9 +119,9 @@ } }, "@babel/generator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.13.tgz", - "integrity": "sha512-9qQ8Fgo8HaSvHEt6A5+BATP7XktD/AdAnObUeTRz5/e2y3kbrxZgz32qUJJsdmwUvBJzF4AeV21nGTNwv05Mpw==", + "version": "7.12.15", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", + "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", "dev": true, "requires": { "@babel/types": "^7.12.13", @@ -150,9 +150,9 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", - "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.16.tgz", + "integrity": "sha512-zYoZC1uvebBFmj1wFAlXwt35JLEgecefATtKp20xalwEK8vHAixLBXTGxNrVGEmTT+gzOThUgr8UEdgtalc1BQ==", "dev": true, "requires": { "@babel/types": "^7.12.13" @@ -206,9 +206,9 @@ } }, "@babel/parser": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.13.tgz", - "integrity": "sha512-z7n7ybOUzaRc3wwqLpAX8UFIXsrVXUJhtNGBwAnLz6d1KUapqyq7ad2La8gZ6CXhHmGAIL32cop8Tst4/PNWLw==", + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz", + "integrity": "sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw==", "dev": true }, "@babel/template": { @@ -373,9 +373,9 @@ "dev": true }, "@babel/helper-validator-option": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", - "integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==", + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.16.tgz", + "integrity": "sha512-uCgsDBPUQDvzr11ePPo4TVEocxj8RXjUVSC/Y8N1YpVAI/XDdUwGJu78xmlGhTxj2ntaWM7n9LQdRtyhOzT2YQ==", "dev": true }, "@babel/helpers": { @@ -532,12 +532,12 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.13.tgz", - "integrity": "sha512-z1VWskPJxK9tfxoYvePWvzSJC+4pxXr8ArmRm5ofqgi+mwpKg6lvtomkIngBYMJVnKhsFYVysCQLDn//v2RHcg==", + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.16.tgz", + "integrity": "sha512-88hep+B6dtDOiEqtRzwHp2TYO+CN8nbAV3eh5OpBGPsedug9J6y1JwLKzXRIGGQZDC8NlpxpQMIIxcfIW96Wgw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.13", + "@babel/helper-create-class-features-plugin": "^7.12.16", "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-typescript": "^7.12.13" }, @@ -551,14 +551,14 @@ } }, "@babel/preset-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.13.tgz", - "integrity": "sha512-gYry7CeXwD2wtw5qHzrtzKaShEhOfTmKb4i0ZxeYBcBosN5VuAudsNbjX7Oj5EAfQ3K4s4HsVMQRRcqGsPvs2A==", + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.16.tgz", + "integrity": "sha512-IrYNrpDSuQfNHeqh7gsJsO35xTGyAyGkI1VxOpBEADFtxCqZ77a1RHbJqM3YJhroj7qMkNMkNtcw0lqeZUrzow==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-validator-option": "^7.12.11", - "@babel/plugin-transform-typescript": "^7.12.13" + "@babel/helper-validator-option": "^7.12.16", + "@babel/plugin-transform-typescript": "^7.12.16" }, "dependencies": { "@babel/helper-plugin-utils": { @@ -1782,9 +1782,9 @@ "dev": true }, "@types/cheerio": { - "version": "0.22.23", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.23.tgz", - "integrity": "sha512-QfHLujVMlGqcS/ePSf3Oe5hK3H8wi/yN2JYuxSB1U10VvW1fO3K8C+mURQesFYS1Hn7lspOsTT75SKq/XtydQg==", + "version": "0.22.24", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.24.tgz", + "integrity": "sha512-iKXt/cwltGvN06Dd6zwQG1U35edPwId9lmcSeYfcxSNvvNg4vysnFB+iBQNjj06tSVV7MBj0GWMQ7dwb4Z+p8Q==", "dev": true, "requires": { "@types/node": "*" @@ -1797,9 +1797,9 @@ "dev": true }, "@types/graceful-fs": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", - "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", "dev": true, "requires": { "@types/node": "*" diff --git a/package.json b/package.json index 9c8da6ed6..9e27ec575 100644 --- a/package.json +++ b/package.json @@ -68,12 +68,12 @@ "xmlhttprequest-ts": "^1.0.1" }, "devDependencies": { - "@babel/preset-typescript": "^7.12.13", + "@babel/preset-typescript": "^7.12.16", "@types/async": "^3.2.5", "@types/bluebird-global": "^3.5.12", - "@types/cheerio": "^0.22.23", + "@types/cheerio": "^0.22.24", "@types/death": "^1.1.1", - "@types/graceful-fs": "^4.1.4", + "@types/graceful-fs": "^4.1.5", "@types/jest": "^26.0.20", "@types/pluralize": "0.0.29", "@types/request": "^2.48.5", From 24f2d855e15cd6a7bb2ed1d8513c229be2c7509b Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 18:04:55 +0800 Subject: [PATCH 23/43] =?UTF-8?q?=E2=AD=95=20ignore=20trades=20from=20admi?= =?UTF-8?q?ns=20(#299)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/tools/profit.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/tools/profit.ts b/src/lib/tools/profit.ts index d276e08ab..eaa80bbb1 100644 --- a/src/lib/tools/profit.ts +++ b/src/lib/tools/profit.ts @@ -55,6 +55,11 @@ export default function profit( continue; } + if (trades[i].action?.reason === 'ADMIN' || bot.isAdmin(trades[i].partner)) { + // trades was from ADMIN, ignore + continue; + } + let isGift = false; if (!Object.prototype.hasOwnProperty.call(trades[i], 'dict')) { From adbd04b487f9a0e7cbf538d972dc5b3caddc2bcb Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Fri, 12 Feb 2021 21:49:11 +0800 Subject: [PATCH 24/43] =?UTF-8?q?=F0=9F=94=A8=20refactor:=20Commands=20sub?= =?UTF-8?q?-classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Commands/Commands.ts | 146 +- src/classes/Commands/functions/export.ts | 12 - src/classes/Commands/functions/help.ts | 132 -- src/classes/Commands/functions/manager.ts | 640 -------- src/classes/Commands/functions/message.ts | 139 -- src/classes/Commands/functions/misc.ts | 242 --- src/classes/Commands/functions/options.ts | 269 ---- .../Commands/functions/pricelistManager.ts | 1271 ---------------- src/classes/Commands/functions/request.ts | 251 ---- src/classes/Commands/functions/review.ts | 335 ----- src/classes/Commands/functions/status.ts | 117 -- src/classes/Commands/sub-classes/Help.ts | 140 ++ src/classes/Commands/sub-classes/Manager.ts | 666 ++++++++ src/classes/Commands/sub-classes/Message.ts | 163 ++ src/classes/Commands/sub-classes/Misc.ts | 254 ++++ src/classes/Commands/sub-classes/Options.ts | 280 ++++ .../Commands/sub-classes/PricelistManager.ts | 1335 +++++++++++++++++ src/classes/Commands/sub-classes/Request.ts | 262 ++++ src/classes/Commands/sub-classes/Review.ts | 341 +++++ src/classes/Commands/sub-classes/Status.ts | 130 ++ src/classes/Commands/sub-classes/export.ts | 21 + src/classes/Listings.ts | 3 +- src/classes/MyHandler/MyHandler.ts | 6 +- 23 files changed, 3681 insertions(+), 3474 deletions(-) delete mode 100644 src/classes/Commands/functions/export.ts delete mode 100644 src/classes/Commands/functions/help.ts delete mode 100644 src/classes/Commands/functions/manager.ts delete mode 100644 src/classes/Commands/functions/message.ts delete mode 100644 src/classes/Commands/functions/misc.ts delete mode 100644 src/classes/Commands/functions/options.ts delete mode 100644 src/classes/Commands/functions/pricelistManager.ts delete mode 100644 src/classes/Commands/functions/request.ts delete mode 100644 src/classes/Commands/functions/review.ts delete mode 100644 src/classes/Commands/functions/status.ts create mode 100644 src/classes/Commands/sub-classes/Help.ts create mode 100644 src/classes/Commands/sub-classes/Manager.ts create mode 100644 src/classes/Commands/sub-classes/Message.ts create mode 100644 src/classes/Commands/sub-classes/Misc.ts create mode 100644 src/classes/Commands/sub-classes/Options.ts create mode 100644 src/classes/Commands/sub-classes/PricelistManager.ts create mode 100644 src/classes/Commands/sub-classes/Request.ts create mode 100644 src/classes/Commands/sub-classes/Review.ts create mode 100644 src/classes/Commands/sub-classes/Status.ts create mode 100644 src/classes/Commands/sub-classes/export.ts diff --git a/src/classes/Commands/Commands.ts b/src/classes/Commands/Commands.ts index ee00c5328..dc8b0ef01 100644 --- a/src/classes/Commands/Commands.ts +++ b/src/classes/Commands/Commands.ts @@ -4,7 +4,8 @@ import pluralize from 'pluralize'; import Currencies from 'tf2-currencies'; import dayjs from 'dayjs'; -import * as c from './functions/export'; +import * as c from './sub-classes/export'; +import { removeLinkProtocol, getItemFromParams, getItemAndAmount } from './functions/utils'; import Bot from '../Bot'; import CommandParser from '../CommandParser'; @@ -15,9 +16,8 @@ import UserCart from '../Carts/UserCart'; import DonateCart from '../Carts/DonateCart'; import PremiumCart from '../Carts/PremiumCart'; import CartQueue from '../Carts/CartQueue'; - +import Pricer from '../Pricer'; import { fixItem } from '../../lib/items'; -import Pricer, { GetPriceFn, GetSalesFn, RequestCheckFn } from '../Pricer'; type Instant = 'buy' | 'b' | 'sell' | 's'; type CraftUncraft = 'craftweapon' | 'uncraftweapon'; @@ -31,16 +31,34 @@ type ForceAction = 'faccept' | 'fdecline'; export default class Commands { private isDonating = false; - private getSales: GetSalesFn; + private help: c.HelpCommands; + + private manager: c.ManagerCommands; + + private message: c.MessageCommand; + + private misc: c.MiscCommands; + + private opt: c.OptionsCommand; + + private pManager: c.PricelistManager; - private requestCheck: RequestCheckFn; + private request: c.RequestCommands; - private getPrice: GetPriceFn; + private review: c.ReviewCommands; - constructor(private readonly bot: Bot, private priceSource: Pricer) { - this.getSales = this.priceSource.getSales.bind(this.priceSource); - this.requestCheck = this.priceSource.requestCheck.bind(this.priceSource); - this.getPrice = this.priceSource.requestCheck.bind(this.priceSource); + private status: c.StatusCommands; + + constructor(private readonly bot: Bot, private readonly pricer: Pricer) { + this.help = new c.HelpCommands(bot); + this.manager = new c.ManagerCommands(bot); + this.message = new c.MessageCommand(bot); + this.misc = new c.MiscCommands(bot); + this.opt = new c.OptionsCommand(bot); + this.pManager = new c.PricelistManager(bot); + this.request = new c.RequestCommands(bot, pricer); + this.review = new c.ReviewCommands(bot); + this.status = new c.StatusCommands(bot); } private get cartQueue(): CartQueue { @@ -54,6 +72,14 @@ export default class Commands { }; } + useStatsCommand(steamID: SteamID): void { + this.status.statsCommand(steamID); + } + + useUpdateOptionsCommand(steamID: SteamID | null, message: string): void { + this.opt.updateOptionsCommand(steamID, message); + } + processMessage(steamID: SteamID, message: string): void { const command = CommandParser.getCommand(message.toLowerCase()); const isAdmin = this.bot.isAdmin(steamID); @@ -120,9 +146,9 @@ export default class Commands { }; if (command === 'help') { - c.help.helpCommand(steamID, this.bot); + this.help.helpCommand(steamID); } else if (command === 'how2trade') { - c.help.howToTradeCommand(steamID, this.bot); + this.help.howToTradeCommand(steamID); } else if (['price', 'pc'].includes(command)) { this.priceCommand(steamID, message); } else if (['buy', 'b', 'sell', 's'].includes(command)) { @@ -142,83 +168,83 @@ export default class Commands { } else if (command === 'queue') { this.queueCommand(steamID); } else if (['time', 'uptime', 'pure', 'rate', 'owner', 'discord', 'stock'].includes(command)) { - c.misc.miscCommand(steamID, this.bot, command as Misc); + this.misc.miscCommand(steamID, command as Misc); } else if (command === 'paints' && isAdmin) { - c.misc.paintsCommand(steamID, this.bot); + this.misc.paintsCommand(steamID); } else if (command === 'more') { - c.help.moreCommand(steamID, this.bot); + this.help.moreCommand(steamID); } else if (command === 'autokeys') { - c.manager.autokeysCommand(steamID, this.bot); + this.manager.autokeysCommand(steamID); } else if (command === 'message') { - c.messageCommand(steamID, message, this.bot); + this.message.message(steamID, message); } else if (['craftweapon', 'uncraftweapon'].includes(command)) { - c.misc.weaponCommand(steamID, this.bot, command as CraftUncraft); + this.misc.weaponCommand(steamID, command as CraftUncraft); } else if (command === 'sales' && isAdmin) { - void c.request.getSalesCommand(steamID, message, this.bot, this.getSales); + void this.request.getSalesCommand(steamID, message); } else if (['deposit', 'd'].includes(command) && isAdmin) { this.depositCommand(steamID, message); } else if (['withdraw', 'w'].includes(command) && isAdmin) { this.withdrawCommand(steamID, message); } else if (command === 'add' && isAdmin) { - c.pricelist.addCommand(steamID, message, this.bot); + this.pManager.addCommand(steamID, message); } else if (command === 'update' && isAdmin) { - void c.pricelist.updateCommand(steamID, message, this.bot); + void this.pManager.updateCommand(steamID, message); } else if (command === 'remove' && isAdmin) { - void c.pricelist.removeCommand(steamID, message, this.bot); + void this.pManager.removeCommand(steamID, message); } else if (command === 'get' && isAdmin) { - c.pricelist.getCommand(steamID, message, this.bot); + this.pManager.getCommand(steamID, message); } else if (command === 'autoadd' && isAdmin) { - void c.pricelist.autoAddCommand(steamID, message, this.bot); + void this.pManager.autoAddCommand(steamID, message); } else if (command === 'stopautoadd' && isAdmin) { - c.pricelist.stopAutoAddCommand(); + this.pManager.stopAutoAddCommand(); } else if (command === 'shuffle' && isAdmin) { - void c.pricelist.shuffleCommand(steamID, this.bot); + void this.pManager.shuffleCommand(steamID); } else if (['expand', 'delete', 'use'].includes(command) && isAdmin) { - c.manager.TF2GCCommand(steamID, message, this.bot, command as TF2GC); + this.manager.TF2GCCommand(steamID, message, command as TF2GC); } else if (['name', 'avatar'].includes(command) && isAdmin) { - c.manager.nameAvatarCommand(steamID, message, this.bot, command as NameAvatar); + this.manager.nameAvatarCommand(steamID, message, command as NameAvatar); } else if (['block', 'unblock'].includes(command) && isAdmin) { - c.manager.blockUnblockCommand(steamID, message, this.bot, command as BlockUnblock); + this.manager.blockUnblockCommand(steamID, message, command as BlockUnblock); } else if (command === 'clearfriends' && isAdmin) { - void c.manager.clearFriendsCommand(steamID, this.bot); + void this.manager.clearFriendsCommand(steamID); } else if (command === 'stop' && isAdmin) { - c.manager.stopCommand(steamID, this.bot); + this.manager.stopCommand(steamID); } else if (command === 'restart' && isAdmin) { - c.manager.restartCommand(steamID, this.bot); + this.manager.restartCommand(steamID); } else if (command === 'updaterepo' && isAdmin) { - c.manager.updaterepoCommand(steamID, this.bot, message); + this.manager.updaterepoCommand(steamID, message); } else if (command === 'refreshautokeys' && isAdmin) { - c.manager.refreshAutokeysCommand(steamID, this.bot); + this.manager.refreshAutokeysCommand(steamID); } else if (command === 'refreshlist' && isAdmin) { - c.manager.refreshListingsCommand(steamID, this.bot); + this.manager.refreshListingsCommand(steamID); } else if (command === 'stats' && isAdmin) { - c.botStatus.statsCommand(steamID, this.bot); + this.status.statsCommand(steamID); } else if (command === 'statsdw' && isAdmin) { - c.botStatus.statsDWCommand(steamID, this.bot); + this.status.statsDWCommand(steamID); } else if (command === 'inventory' && isAdmin) { - c.botStatus.inventoryCommand(steamID, this.bot); + this.status.inventoryCommand(steamID); } else if (command === 'version' && isAdmin) { - c.botStatus.versionCommand(steamID, this.bot); + this.status.versionCommand(steamID); } else if (command === 'trades' && isAdmin) { - c.review.tradesCommand(steamID, this.bot); + this.review.tradesCommand(steamID); } else if (command === 'trade' && isAdmin) { - c.review.tradeCommand(steamID, message, this.bot); + this.review.tradeCommand(steamID, message); } else if (['accepttrade', 'accept', 'declinetrade', 'decline'].includes(command) && isAdmin) { - void c.review.actionOnTradeCommand(steamID, message, this.bot, command as ActionOnTrade); + void this.review.actionOnTradeCommand(steamID, message, command as ActionOnTrade); } else if (['faccept', 'fdecline'].includes(command) && isAdmin) { - void c.review.forceAction(steamID, message, this.bot, command as ForceAction); + void this.review.forceAction(steamID, message, command as ForceAction); } else if (command === 'pricecheck' && isAdmin) { - c.request.pricecheckCommand(steamID, message, this.bot, this.requestCheck); + this.request.pricecheckCommand(steamID, message); } else if (command === 'pricecheckall' && isAdmin) { - void c.request.pricecheckAllCommand(steamID, this.bot, this.requestCheck); + void this.request.pricecheckAllCommand(steamID); } else if (command === 'check' && isAdmin) { - void c.request.checkCommand(steamID, message, this.bot, this.getPrice); + void this.request.checkCommand(steamID, message); } else if (command === 'find' && isAdmin) { - void c.pricelist.findCommand(steamID, message, this.bot); + void this.pManager.findCommand(steamID, message); } else if (command === 'options' && isAdmin) { - void c.options.optionsCommand(steamID, this.bot, message); + void this.opt.optionsCommand(steamID, message); } else if (command === 'config' && isAdmin) { - c.options.updateOptionsCommand(steamID, message, this.bot); + this.opt.updateOptionsCommand(steamID, message); } else if (command === 'donatebptf' && isAdmin) { this.donateBPTFCommand(steamID, message); } else if (command === 'donatenow' && isAdmin) { @@ -251,7 +277,7 @@ export default class Commands { } } - const info = c.utils.getItemAndAmount(steamID, CommandParser.removeCommand(message), this.bot); + const info = getItemAndAmount(steamID, CommandParser.removeCommand(message), this.bot); if (info === null) { return; } @@ -338,7 +364,7 @@ export default class Commands { } } - const info = c.utils.getItemAndAmount( + const info = getItemAndAmount( steamID, CommandParser.removeCommand(message), this.bot, @@ -383,7 +409,7 @@ export default class Commands { } } - const info = c.utils.getItemAndAmount(steamID, CommandParser.removeCommand(message), this.bot, 'buycart'); + const info = getItemAndAmount(steamID, CommandParser.removeCommand(message), this.bot, 'buycart'); if (info === null) { return; @@ -459,7 +485,7 @@ export default class Commands { } } - const info = c.utils.getItemAndAmount(steamID, CommandParser.removeCommand(message), this.bot, 'sellcart'); + const info = getItemAndAmount(steamID, CommandParser.removeCommand(message), this.bot, 'sellcart'); if (info === null) { return; } @@ -727,9 +753,9 @@ export default class Commands { ); } - const params = CommandParser.parseParams(CommandParser.removeCommand(c.utils.removeLinkProtocol(message))); + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); if (params.sku === undefined) { - const item = c.utils.getItemFromParams(steamID, params, this.bot); + const item = getItemFromParams(steamID, params, this.bot); if (item === null) { return this.bot.sendMessage(steamID, `āŒ Item not found.`); } @@ -776,9 +802,9 @@ export default class Commands { ); } - const params = CommandParser.parseParams(CommandParser.removeCommand(c.utils.removeLinkProtocol(message))); + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); if (params.sku === undefined) { - const item = c.utils.getItemFromParams(steamID, params, this.bot); + const item = getItemFromParams(steamID, params, this.bot); if (item === null) { return this.bot.sendMessage(steamID, `āŒ Item not found.`); } @@ -852,9 +878,9 @@ export default class Commands { ); } - const params = CommandParser.parseParams(CommandParser.removeCommand(c.utils.removeLinkProtocol(message))); + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); if (params.sku === undefined) { - const item = c.utils.getItemFromParams(steamID, params, this.bot); + const item = getItemFromParams(steamID, params, this.bot); if (item === null) { return this.bot.sendMessage(steamID, `āŒ Item not found.`); } @@ -977,7 +1003,7 @@ export default class Commands { ); } - const params = CommandParser.parseParams(CommandParser.removeCommand(c.utils.removeLinkProtocol(message))); + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); if ( params.months === undefined || typeof params.months !== 'number' || diff --git a/src/classes/Commands/functions/export.ts b/src/classes/Commands/functions/export.ts deleted file mode 100644 index 4932889aa..000000000 --- a/src/classes/Commands/functions/export.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as botStatus from './status'; -import * as help from './help'; -import messageCommand from './message'; -import * as misc from './misc'; -import * as pricelist from './pricelistManager'; -import * as review from './review'; -import * as utils from './utils'; -import * as options from './options'; -import * as manager from './manager'; -import * as request from './request'; - -export { botStatus, help, messageCommand, misc, pricelist, review, utils, options, manager, request }; diff --git a/src/classes/Commands/functions/help.ts b/src/classes/Commands/functions/help.ts deleted file mode 100644 index f36594749..000000000 --- a/src/classes/Commands/functions/help.ts +++ /dev/null @@ -1,132 +0,0 @@ -import SteamID from 'steamid'; -import Bot from '../../Bot'; - -export function helpCommand(steamID: SteamID, bot: Bot): void { - const isAdmin = bot.isAdmin(steamID); - - bot.sendMessage( - steamID, - `šŸ“Œ Note šŸ“Œ${ - isAdmin - ? '\nā€¢ a = Directly add "a"' + - '\nā€¢ [a] = Optionally add "a"' + - '\nā€¢ (a|b) = Directly input "a" OR "b"' + - '\nā€¢ = Replace "a" with relevant content' + - '\n\nDo not include characters <>, ( | ) nor [ ] when typing it. For more info, please refer to the wiki: https://github.com/idinium96/tf2autobot/wiki/What-is-the-pricelist%3F#table-of-contents' - : `\nDo not include characters <> nor [ ] - <> means required and [] means optional.` - }\n\nšŸ“œ Here's a list of my commands:\n- ${ - isAdmin - ? [ - '!deposit (sku|name|defindex)=&amount= - Deposit items', - '!withdraw (sku|name|defindex)=&amount= - Withdraw items\n\nāœØ=== Pricelist manager ===āœØ', - '!add (sku|name|defindex)=&[Listing-parameters] - Add a pricelist entry āž•', - '!autoadd [Listing-parameters] - Perform automatic adding items to the pricelist based on items that are currently available in your bot inventory (about 2 seconds every item) šŸ¤–', - '!stopautoadd - Stop automatic add items operation šŸ›‘', - '!update (sku|name|defindex|item)=&[Listing-parameters] - Update a pricelist entry', - '!remove (sku|name|defindex|item)= - Remove a pricelist entry āž–', - '!shuffle - Shuffle pricelist entries.', - '!get (sku|name|defindex|item)= - Get raw information about a pricelist entry\n\nāœØ=== Bot manager ===āœØ', - "!expand craftable=(true|false) - Use Backpack Expanders to increase the bot's inventory limit", - '!use (sku|assetid)= - Use an item (such as Gift-Stuffed Stocking 2020 - sku: 5923;6;untradable)', - "!delete (sku|assetid)= - Delete an item from the bot's inventory (SKU input only) šŸš®", - '!message - Send a message to a specific user šŸ’¬', - '!block - Block a specific user', - '!unblock - Unblock a specific user', - '!clearfriends - Clear friendlist (will keep admins and friendsToKeep) šŸ‘‹', - '!stop - Stop the bot šŸ”“', - '!restart - Restart the bot šŸ”„', - '!updaterepo - Update your bot to the latest version (only if cloned and running with PM2)', - "!refreshautokeys - Refresh the bot's autokeys settings.", - '!refreshlist - Refresh sell listings šŸ”„', - "!name - Change the bot's name", - "!avatar - Change the bot's avatar\n\nāœØ=== Bot status ===āœØ", - '!stats - Get statistics for accepted trades šŸ“Š', - '!statsdw - Send statistics to Discord Webhook šŸ“Š', - "!inventory - Get the bot's current inventory spaces šŸŽ’", - '!version - Get the TF2Autobot version that the bot is running\n\nāœØ=== Manual review ===āœØ', - '!trades - Get a list of trade offers pending for manual review šŸ”', - '!trade - Get information about a trade', - '!accept [Your Message] - Manually accept an active offer āœ…šŸ”', - '!decline [Your Message] - Manually decline an active offer āŒšŸ”', - '!faccept [Your Message] - Force accept any failed to accept offer āœ…šŸ”‚', - '!fdecline [Your Message] - Force decline any failed to decline offer āŒšŸ”‚\n\nāœØ=== Request ===āœØ', - '!check (sku|name|defindex)= - Request the current price for an item from Prices.TF', - '!pricecheck (sku|name|defindex|item)= - Request an item to be price checked by Prices.TF', - "!pricecheckall - Request all items in the bot's pricelist to be price checked by Prices.TF\n\nāœØ=== Misc ===āœØ", - "!autokeys - Get info on the bot's current autokeys settings šŸ”‘", - "!time - Show the owner's current time šŸ•„", - '!uptime - Show the bot uptime šŸ”Œ', - "!pure - Get the bot's current pure stock šŸ’°", - "!rate - Get the bot's current key rates šŸ”‘", - '!stock - Get a list of items that the bot owns', - "!craftweapon - Get a list of the bot's craftable weapon stock šŸ”«", - "!uncraftweapon - Get a list of the bot's uncraftable weapon stock šŸ”«", - '!paints - Get a list of paints partial sku šŸŽØ', - '!sales (sku|name|defindex)= - Get the sales history for an item šŸ”', - '!find - Get the list of filtered items detail based on the parameters šŸ”', - '!options [OptionsKey] - Get options.json content (current bot option settings) šŸ”§', - '!config =[&OtherOptions] - Update the current options (example: !config game.customName=Selling Tools!) šŸ”§', - '!donatebptf (sku|name|defindex)=&amount= - Donate to backpack.tf (https://backpack.tf/donate) šŸ’°', - '!premium months= - Purchase backpack.tf premium using keys (https://backpack.tf/premium/subscribe) šŸ‘‘' - ].join('\n- ') - : [ - '!help - Get a list of commands', - '!how2trade - Guide on how to trade with the bot', - '!price [amount] - Get the price and stock of an item šŸ’²šŸ“¦\n\nāœØ=== Instant item trade ===āœØ', - '!buy [amount] - Instantly buy an item šŸ’²', - '!sell [amount] - Instantly sell an item šŸ’²\n\nāœØ=== Multiple items trade ===āœØ', - '!buycart [amount] - Add an item you want to buy to your cart šŸ›’', - '!sellcart [amount] - Add an item you want to sell to your cart šŸ›’', - '!cart - View your cart šŸ›’', - '!clearcart - Clear your cart āŽšŸ›’', - '!checkout - Have the bot send an offer with the items in your cart āœ…šŸ›’\n\nāœØ=== Trade actions ===āœØ', - '!cancel - Cancel the trade offer āŒ', - '!queue - Check your position in the queue\n\nāœØ=== Contact Owner ===āœØ', - "!owner - Get the owner's Steam profile and Backpack.tf links", - '!message - Send a message to the owner of the bot šŸ’¬', - "!discord - Get a link to join TF2Autobot and/or the owner's discord server\n\nāœØ=== Other Commands ===āœØ", - '!more - Show the advanced commands list' - ].join('\n- ') - }` - ); -} - -export function moreCommand(steamID: SteamID, bot: Bot): void { - const opt = bot.options.commands.more; - - if (!opt.enable) { - if (!bot.isAdmin(steamID)) { - const custom = opt.customReply.disabled; - return bot.sendMessage(steamID, custom ? custom : 'āŒ This command is disabled by the owner.'); - } - } - - bot.sendMessage( - steamID, - `Advanced commands list:\n- ${[ - "!autokeys - Get info on the bot's current autokeys settings šŸ”‘", - "!time - Show the owner's current time šŸ•„", - '!uptime - Show the bot uptime šŸ”Œ', - "!pure - Get the bot's current pure stock šŸ’°", - "!rate - Get the bot's current key rates šŸ”‘", - '!stock - Get a list of items that the bot owns', - "!craftweapon - Get a list of the bot's craftable weapon stock šŸ”«", - "!uncraftweapon - Get a list of the bot's uncraftable weapon stock šŸ”«" - ].join('\n- ')}` - ); -} - -export function howToTradeCommand(steamID: SteamID, bot: Bot): void { - const custom = bot.options.commands.how2trade.customReply.reply; - - bot.sendMessage( - steamID, - custom - ? custom - : '/quote You can either send me an offer yourself, or use one of my commands to request a trade. ' + - 'Say you want to buy a Team Captain, just type "!buy Team Captain", if want to buy more, ' + - 'just add the [amount] - "!buy 2 Team Captain". Type "!help" for all the commands.' + - '\nYou can also buy or sell multiple items by using the "!buycart [amount] " or ' + - '"!sellcart [amount] " commands.' - ); -} diff --git a/src/classes/Commands/functions/manager.ts b/src/classes/Commands/functions/manager.ts deleted file mode 100644 index bafd1d855..000000000 --- a/src/classes/Commands/functions/manager.ts +++ /dev/null @@ -1,640 +0,0 @@ -import SteamID from 'steamid'; -import SKU from 'tf2-sku-2'; -import pluralize from 'pluralize'; -import Currencies from 'tf2-currencies'; -import validUrl from 'valid-url'; -import child from 'child_process'; -import fs from 'graceful-fs'; -import sleepasync from 'sleep-async'; -import path from 'path'; -import { EPersonaState } from 'steam-user'; -import { utils } from './export'; -import Bot from '../../Bot'; -import CommandParser from '../../CommandParser'; -import log from '../../../lib/logger'; -import { pure } from '../../../lib/tools/export'; - -// Bot manager commands - -type TF2GC = 'expand' | 'use' | 'delete'; - -export function TF2GCCommand(steamID: SteamID, message: string, bot: Bot, command: TF2GC): void { - const params = CommandParser.parseParams(CommandParser.removeCommand(message)); - - if (command === 'expand') { - // Expand command - if (typeof params.craftable !== 'boolean') { - return bot.sendMessage(steamID, 'āš ļø Missing `craftable=true|false`'); - } - - const item = SKU.fromString('5050;6'); - if (params.craftable === false) { - item.craftable = false; - } - - const assetids = bot.inventoryManager.getInventory.findBySKU(SKU.fromObject(item), false); - if (assetids.length === 0) { - // No backpack expanders - return bot.sendMessage( - steamID, - `āŒ I couldn't find any ${!item.craftable ? 'Non-Craftable' : ''} Backpack Expander` - ); - } - - bot.tf2gc.useItem(assetids[0], err => { - if (err) { - log.warn('Error trying to expand inventory: ', err); - return bot.sendMessage(steamID, `āŒ Failed to expand inventory: ${err.message}`); - } - - bot.sendMessage(steamID, `āœ… Used ${!item.craftable ? 'Non-Craftable' : ''} Backpack Expander!`); - }); - } else { - // For use and delete commands - if (params.sku !== undefined && !utils.testSKU(params.sku as string)) { - return bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); - } - - if (params.assetid !== undefined && params.sku === undefined) { - const targetedAssetId = params.assetid as string; - const sku = bot.inventoryManager.getInventory.findByAssetid(targetedAssetId); - - if (params.i_am_sure !== 'yes_i_am') { - return bot.sendMessage( - steamID, - `āš ļø Are you sure that you want to ${command} ${ - sku === null - ? `the item with asset ID ${targetedAssetId}` - : `${bot.schema.getName(SKU.fromString(sku), false)}` - }?` + - `\n- This process is irreversible and will ${command} the item from your bot's backpack!` + - `\n- If you are sure, try again with i_am_sure=yes_i_am as a parameter` - ); - } - - return bot.tf2gc[command === 'use' ? 'useItem' : 'deleteItem'](targetedAssetId, err => { - const theItem = - sku === null - ? targetedAssetId - : `${bot.schema.getName(SKU.fromString(sku), false)} (${targetedAssetId})`; - - if (err) { - log.warn(`Error trying to ${command} ${theItem}: `, err); - return bot.sendMessage(steamID, `āŒ Failed to ${command} ${theItem}: ${err.message}`); - } - - bot.sendMessage(steamID, `āœ… ${command === 'use' ? 'Used' : 'Deleted'} ${theItem}!`); - }); - } - - if (params.name !== undefined || params.item !== undefined) { - return bot.sendMessage( - steamID, - command === 'use' - ? 'āš ļø Please only use sku property.' + - '\n\nBelow are some common items to use:\n ā€¢ ' + - [ - 'Gift-Stuffed Stocking 2013: 5718;6;untradable', - 'Gift-Stuffed Stocking 2017: 5886;6;untradable', - 'Gift-Stuffed Stocking 2018: 5900;6;untradable', - 'Gift-Stuffed Stocking 2019: 5910;6;untradable', - 'Gift-Stuffed Stocking 2020: 5923;6;untradable' - ].join('\nā€¢ ') - : 'āš ļø Please only use sku property.' + - '\n\nBelow are some common items to delete:\n ā€¢ ' + - [ - 'Smissmas Sweater: 16391;15;untradable;w1;pk391', - 'Soul Gargoyle: 5826;6;uncraftable;untradable', - 'Noise Maker - TF Birthday: 536;6;untradable', - 'Bronze Dueling Badge: 242;6;untradable', - 'Silver Dueling Badge: 243;6;untradable', - 'Gold Dueling Badge: 244;6;untradable', - 'Platinum Dueling Badge: 245;6;untradable', - 'Mercenary: 166;6;untradable', - 'Soldier of Fortune: 165;6;untradable', - 'Grizzled Veteran: 164;6;untradable', - 'Primeval Warrior: 170;6;untradable', - 'Professor Speks: 343;6;untradable', - 'Mann Co. Cap: 261;6;untradable', - 'Mann Co. Online Cap: 994;6;untradable', - 'Proof of Purchase: 471;6;untradable', - 'Mildly Disturbing Halloween Mask: 115;6;untradable', - 'Seal Mask: 582;6;untradable', - 'Pyrovision Goggles: 743;6;untradable', - 'Giftapult: 5083;6;untradable', - 'Spirit Of Giving: 655;11;untradable', - 'Party Hat: 537;6;untradable', - 'Name Tag: 5020;6;untradable', - 'Description Tag: 5044;6;untradable', - 'Ghastly Gibus: 584;6;untradable', - 'Ghastlier Gibus: 279;6;untradable', - 'Power Up Canteen: 489;6;untradable', - 'Bombinomicon: 583;6;untradable', - 'Skull Island Topper: 941;6;untradable', - 'Spellbook Page: 8935;6;untradable', - 'Gun Mettle Campaign Coin: 5809;6;untradable', - 'MONOCULUS!: 581;6;untradable' - ].join('\nā€¢ ') - ); - } - - if (params.sku === undefined) { - return bot.sendMessage(steamID, `āš ļø Missing sku property. Example: "!${command} sku=5923;6;untradable"`); - } - - const targetedSKU = params.sku as string; - const [uncraft, untrade] = [targetedSKU.includes(';uncraftable'), targetedSKU.includes(';untradable')]; - - const item = SKU.fromString(targetedSKU.replace(';uncraftable', '').replace(';untradable', '')); - - if (uncraft) { - item.craftable = !uncraft; - } - if (untrade) { - item.tradable = !untrade; - } - - const assetids = bot.inventoryManager.getInventory.findBySKU(SKU.fromObject(item), false); - const name = bot.schema.getName(item, false); - - if (assetids.length === 0) { - // Item not found - return bot.sendMessage(steamID, `āŒ I couldn't find any ${pluralize(name, 0)}`); - } - - let assetid: string; - if (params.assetid !== undefined) { - const targetedAssetId = params.assetid as string; - - if (assetids.includes(targetedAssetId)) { - assetid = targetedAssetId; - } else { - return bot.sendMessage( - steamID, - `āŒ Looks like an assetid ${targetedAssetId} did not match any assetids associated with ${name}` + - ` in my inventory. Try using the sku to use a random assetid.` - ); - } - } else { - assetid = assetids[0]; - } - - if (params.i_am_sure !== 'yes_i_am') { - return bot.sendMessage( - steamID, - `/pre āš ļø Are you sure that you want to ${command} ${name}?` + - `\n- This process is irreversible and will ${command} the item from your bot's backpack!` + - `\n- If you are sure, try again with i_am_sure=yes_i_am as a parameter` - ); - } - - bot.tf2gc[command === 'use' ? 'useItem' : 'deleteItem'](assetid, err => { - if (err) { - log.warn(`Error trying to ${command} ${name}: `, err); - return bot.sendMessage(steamID, `āŒ Failed to ${command} ${name} (${assetid}): ${err.message}`); - } - - bot.sendMessage(steamID, `āœ… ${command === 'use' ? 'Used' : 'Deleted'} ${name} (${assetid})!`); - }); - } -} - -type NameAvatar = 'name' | 'avatar'; - -export function nameAvatarCommand(steamID: SteamID, message: string, bot: Bot, command: NameAvatar): void { - const example = - 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/f5/f57685d33224e32436f366d1acb4a1769bdfa60f_full.jpg'; - const input = CommandParser.removeCommand(message); - - if (!input || input === `!${command}`) { - return bot.sendMessage( - steamID, - `āŒ You forgot to add ${command === 'name' ? 'a name' : 'an image url'}. Example: "!${ - command === 'name' ? 'name IdiNium' : `avatar ${example}` - } "` - ); - } - - if (command === 'name') { - bot.community.editProfile( - { - name: input - }, - err => { - if (err) { - log.warn('Error while changing name: ', err); - return bot.sendMessage(steamID, `āŒ Error while changing name: ${err.message}`); - } - - bot.sendMessage(steamID, 'āœ… Successfully changed name.'); - } - ); - } else { - if (!validUrl.isUri(input)) { - return bot.sendMessage(steamID, `āŒ Your url is not valid. Example: "!avatar ${example}"`); - } - - bot.community.uploadAvatar(input, err => { - if (err) { - log.warn('Error while uploading new avatar: ', err); - return bot.sendMessage(steamID, `āŒ Error while uploading a new avatar: ${err.message}`); - } - - bot.sendMessage(steamID, 'āœ… Successfully uploaded a new avatar.'); - }); - } -} - -type BlockUnblock = 'block' | 'unblock'; - -export function blockUnblockCommand(steamID: SteamID, message: string, bot: Bot, command: BlockUnblock): void { - const steamid = CommandParser.removeCommand(message); - - if (!steamid || steamid === `!${command}`) { - return bot.sendMessage( - steamID, - `āŒ You forgot to add their SteamID64. Example: "!${command} 76561198798404909"` - ); - } - - const targetSteamID64 = new SteamID(steamid); - if (!targetSteamID64.isValid()) { - return bot.sendMessage(steamID, `āŒ SteamID is not valid. Example: "!${command} 76561198798404909"`); - } - - bot.client[command === 'block' ? 'blockUser' : 'unblockUser'](targetSteamID64, err => { - if (err) { - log.warn(`Failed to ${command} user ${targetSteamID64.getSteamID64()}: `, err); - return bot.sendMessage( - steamID, - `āŒ Failed to ${command} user ${targetSteamID64.getSteamID64()}: ${err.message}` - ); - } - bot.sendMessage( - steamID, - `āœ… Successfully ${command === 'block' ? 'blocked' : 'unblocked'} user ${targetSteamID64.getSteamID64()}` - ); - }); -} - -export async function clearFriendsCommand(steamID: SteamID, bot: Bot): Promise { - const friendsToRemove = bot.friends.getFriends.filter(steamid => !bot.handler.friendsToKeep.includes(steamid)); - - for (const steamid of friendsToRemove) { - bot.sendMessage( - steamid, - bot.options.customMessage.clearFriends - ? bot.options.customMessage.clearFriends - : `/quote Hey ${ - bot.friends.getFriend(steamid).player_name - }! My owner has performed friend list clearance. Please feel free to add me again if you want to trade at a later time!` - ); - - bot.client.removeFriend(steamid); - - // Prevent Steam from detecting the bot as spamming - await sleepasync().Promise.sleep(2 * 1000); - } - - bot.sendMessage(steamID, `āœ… Friendlist clearance success! Removed ${friendsToRemove.length} friends.`); -} - -export function stopCommand(steamID: SteamID, bot: Bot): void { - bot.sendMessage(steamID, 'āŒ› Stopping...'); - - bot.botManager.stopProcess().catch(err => { - log.warn('Error occurred while trying to stop: ', err); - bot.sendMessage(steamID, `āŒ An error occurred while trying to stop: ${(err as Error).message}`); - }); -} - -export function restartCommand(steamID: SteamID, bot: Bot): void { - bot.sendMessage(steamID, 'āŒ› Restarting...'); - - bot.botManager - .restartProcess() - .then(restarting => { - if (!restarting) { - bot.sendMessage( - steamID, - 'āŒ You are not running the bot with PM2! Get a VPS and run ' + - 'your bot with PM2: https://github.com/idinium96/tf2autobot/wiki/Getting-a-VPS' - ); - } - }) - .catch(err => { - log.warn('Error occurred while trying to restart: ', err); - bot.sendMessage(steamID, `āŒ An error occurred while trying to restart: ${(err as Error).message}`); - }); -} - -export function updaterepoCommand(steamID: SteamID, bot: Bot, message: string): void { - if (!fs.existsSync(path.resolve(__dirname, '..', '..', '..', '..', '.git'))) { - return bot.sendMessage(steamID, 'āŒ You did not clone the bot from Github.'); - } - - if (process.env.pm_id === undefined) { - return bot.sendMessage( - steamID, - `āŒ You're not running the bot with PM2!` + - `\n\nNavigate to your bot folder and run ` + - `[git reset HEAD --hard && git checkout master && git pull && npm install && npm run build] ` + - `and then restart your bot.` - ); - } - - const params = CommandParser.parseParams(CommandParser.removeCommand(message)); - if (params.i_am_sure !== 'yes_i_am') { - bot.sendMessage( - steamID, - `Currently running TF2Autobot@v${process.env.BOT_VERSION}. Checking for a new version...` - ); - - bot.checkForUpdates - .then(({ hasNewVersion, latestVersion }) => { - if (!hasNewVersion) { - return bot.sendMessage(steamID, 'You are running the latest version of TF2Autobot!'); - } else if (bot.lastNotifiedVersion === latestVersion) { - return bot.sendMessage( - steamID, - `āš ļø Update available! Current: v${process.env.BOT_VERSION}, Latest: v${latestVersion}.` + - '\nSend "!updaterepo i_am_sure=yes_i_am" to update your repo now!' + - `\n\nRelease note: https://github.com/idinium96/tf2autobot/releases` - ); - } - }) - .catch(err => bot.sendMessage(steamID, `āŒ Failed to check for updates: ${JSON.stringify(err)}`)); - } else { - bot.sendMessage(steamID, 'āŒ› Updating...'); - // Make the bot snooze on Steam, that way people will know it is not running - bot.client.setPersona(EPersonaState.Snooze); - - // Set isUpdating status, so any command will not be processed - bot.handler.isUpdatingStatus = true; - - // Stop polling offers - bot.manager.pollInterval = -1; - - // Callback hell šŸ˜ˆ - - // git reset HEAD --hard - child.exec('git reset HEAD --hard', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { - // ignore err - - // git checkout master - child.exec('git checkout master', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { - // ignore err - - bot.sendMessage(steamID, 'āŒ› Pulling changes...'); - - // git pull - child.exec('git pull', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { - // ignore err - - void promiseDelay(3 * 1000); - - bot.sendMessage(steamID, 'āŒ› Installing packages...'); - - // npm install - child.exec('npm install', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { - // ignore err - - // 10 seconds delay, because idk why this always cause some problem - void promiseDelay(10 * 1000); - - bot.sendMessage(steamID, 'āŒ› Compiling TypeScript codes into JavaScript...'); - - // tsc -p . - child.exec('npm run build', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { - // ignore err - - // 5 seconds delay? - void promiseDelay(5 * 1000); - - bot.sendMessage(steamID, 'āŒ› Restarting...'); - - child.exec( - 'pm2 restart ecosystem.json', - { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, - () => { - // ignore err - } - ); - }); - }); - }); - }); - }); - } -} - -function promiseDelay(ms: number): Promise { - return new Promise(resolve => setTimeout(() => resolve(), ms)); -} - -export function autokeysCommand(steamID: SteamID, bot: Bot): void { - const opt = bot.options.commands.autokeys; - if (!opt.enable) { - if (!bot.isAdmin(steamID)) { - const custom = opt.customReply.disabled; - return bot.sendMessage(steamID, custom ? custom : 'āŒ This command is disabled by the owner.'); - } - } - - bot.sendMessage(steamID, '/pre ' + generateAutokeysReply(steamID, bot)); -} - -function generateAutokeysReply(steamID: SteamID, bot: Bot): string { - const pureNow = pure.currPure(bot); - const currKey = pureNow.key; - const currRef = pureNow.refTotalInScrap; - - const keyPrices = bot.pricelist.getKeyPrices; - - const autokeys = bot.handler.autokeys; - const userPure = autokeys.userPure; - const status = autokeys.getOverallStatus; - - const keyBlMin = ` X`; - const keyAbMax = ` X`; - const keyAtBet = ` X`; - const keyAtMin = ` X`; - const keyAtMax = ` X`; - const keysLine = `Keys ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā–¶`; - const refBlMin = ` X`; - const refAbMax = ` X`; - const refAtBet = ` X`; - const refAtMin = ` X`; - const refAtMax = ` X`; - const refsLine = `Refs ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā–¶`; - const xAxisRef = ` min max`; - const keysPosition = - currKey < userPure.minKeys - ? keyBlMin - : currKey > userPure.maxKeys - ? keyAbMax - : currKey > userPure.minKeys && currKey < userPure.maxKeys - ? keyAtBet - : currKey === userPure.minKeys - ? keyAtMin - : currKey === userPure.maxKeys - ? keyAtMax - : ''; - const refsPosition = - currRef < userPure.minRefs - ? refBlMin - : currRef > userPure.maxRefs - ? refAbMax - : currRef > userPure.minRefs && currRef < userPure.maxRefs - ? refAtBet - : currRef === userPure.minRefs - ? refAtMin - : currRef === userPure.maxRefs - ? refAtMax - : ''; - const summary = `\nā€¢ ${userPure.minKeys} ā‰¤ ${pluralize('key', currKey)}(${currKey}) ā‰¤ ${ - userPure.maxKeys - }\nā€¢ ${Currencies.toRefined(userPure.minRefs)} < ${pluralize( - 'ref', - Currencies.toRefined(currRef) - )}(${Currencies.toRefined(currRef)}) < ${Currencies.toRefined(userPure.maxRefs)}`; - - let reply = - (bot.isAdmin(steamID) ? 'Your ' : 'My ') + - `current Autokeys settings:\n${summary}\n\nDiagram:\n${keysPosition}\n${keysLine}\n${refsPosition}\n${refsLine}\n${xAxisRef}\n`; - reply += `\n Key prices: ${keyPrices.buy.toString()}/${keyPrices.sell.toString()} (${ - keyPrices.src === 'manual' ? 'manual' : 'prices.tf' - })`; - - const scrapAdjustmentEnabled = autokeys.isEnableScrapAdjustment; - const scrapAdjustmentValue = autokeys.scrapAdjustmentValue; - const keyBankingEnabled = autokeys.isKeyBankingEnabled; - - reply += `\nScrap Adjustment: ${scrapAdjustmentEnabled ? 'Enabled āœ…' : 'Disabled āŒ'}`; - reply += `\n Auto-banking: ${keyBankingEnabled ? 'Enabled āœ…' : 'Disabled āŒ'}`; - reply += `\n Autokeys status: ${ - autokeys.getActiveStatus - ? status.isBankingKeys - ? 'Banking' + (scrapAdjustmentEnabled ? ' (default price)' : '') - : status.isBuyingKeys - ? 'Buying for ' + - Currencies.toRefined( - keyPrices.buy.toValue() + (scrapAdjustmentEnabled ? scrapAdjustmentValue : 0) - ).toString() + - ' ref' + - (scrapAdjustmentEnabled ? ` (+${scrapAdjustmentValue} scrap)` : '') - : 'Selling for ' + - Currencies.toRefined( - keyPrices.sell.toValue() - (scrapAdjustmentEnabled ? scrapAdjustmentValue : 0) - ).toString() + - ' ref' + - (scrapAdjustmentEnabled ? ` (-${scrapAdjustmentValue} scrap)` : '') - : 'Not active' - }`; - /* - * X - * Keys ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā–¶ - * X - * Refs ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā–¶ - * min max - */ - - return reply; -} - -export function refreshAutokeysCommand(steamID: SteamID, bot: Bot): void { - if (bot.handler.autokeys.isEnabled === false) { - return bot.sendMessage(steamID, `This feature is disabled.`); - } - - bot.handler.autokeys.refresh(); - bot.sendMessage(steamID, 'āœ… Successfully refreshed Autokeys.'); -} - -export function refreshListingsCommand(steamID: SteamID, bot: Bot): void { - const listingsSKUs: string[] = []; - bot.listingManager.getListings(async err => { - if (err) { - return bot.sendMessage( - steamID, - 'āŒ Unable to refresh listings, please try again later: ' + JSON.stringify(err) - ); - } - bot.listingManager.listings.forEach(listing => { - let listingSKU = listing.getSKU(); - if (listing.intent === 1) { - if (bot.options.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) { - listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); - } - - if (bot.options.normalize.festivized.our && listingSKU.includes(';festive')) { - listingSKU = listingSKU.replace(';festive', ''); - } - - if (bot.options.normalize.strangeAsSecondQuality.our && listingSKU.includes(';strange')) { - listingSKU = listingSKU.replace(';strange', ''); - } - } else { - if (/;[p][0-9]+/.test(listingSKU)) { - listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); - } - } - - listingsSKUs.push(listingSKU); - }); - - // Remove duplicate elements - const newlistingsSKUs: string[] = []; - listingsSKUs.forEach(sku => { - if (!newlistingsSKUs.includes(sku)) { - newlistingsSKUs.push(sku); - } - }); - - const inventory = bot.inventoryManager; - const pricelist = bot.pricelist.getPrices.filter(entry => { - // First find out if lising for this item from bptf already exist. - const isExist = newlistingsSKUs.find(sku => entry.sku === sku); - - if (!isExist) { - // undefined - listing does not exist but item is in the pricelist - - // Get amountCanBuy and amountCanSell (already cover intent and so on) - const amountCanBuy = inventory.amountCanTrade(entry.sku, true); - const amountCanSell = inventory.amountCanTrade(entry.sku, false); - - if ( - (amountCanBuy > 0 && inventory.isCanAffordToBuy(entry.buy, inventory.getInventory)) || - amountCanSell > 0 - ) { - // if can amountCanBuy is more than 0 and isCanAffordToBuy is true OR amountCanSell is more than 0 - // return this entry - return true; - } - - // Else ignore - return false; - } - - // Else if listing already exist on backpack.tf, ignore - return false; - }); - - if (pricelist.length > 0) { - log.debug( - 'Checking listings for ' + - pluralize('item', pricelist.length, true) + - ` [${pricelist.map(entry => entry.sku).join(', ')}] ...` - ); - - bot.sendMessage(steamID, 'Refreshing listings for ' + pluralize('item', pricelist.length, true) + '...'); - - await bot.listings.recursiveCheckPricelist(pricelist, true); - - log.debug('Done checking ' + pluralize('item', pricelist.length, true)); - bot.sendMessage(steamID, 'āœ… Done refreshing ' + pluralize('item', pricelist.length, true)); - } else { - bot.sendMessage(steamID, 'āŒ Nothing to refresh.'); - } - }); -} diff --git a/src/classes/Commands/functions/message.ts b/src/classes/Commands/functions/message.ts deleted file mode 100644 index f17c6c88a..000000000 --- a/src/classes/Commands/functions/message.ts +++ /dev/null @@ -1,139 +0,0 @@ -import SteamID from 'steamid'; -import Bot from '../../Bot'; -import CommandParser from '../../CommandParser'; -import { generateLinks, timeNow } from '../../../lib/tools/export'; -import { sendPartnerMessage, sendAdminMessage } from '../../../lib/DiscordWebhook/export'; - -export default function message(steamID: SteamID, message: string, bot: Bot): void { - const isAdmin = bot.isAdmin(steamID); - const custom = bot.options.commands.message.customReply; - if (!bot.options.commands.enable) { - if (isAdmin) { - bot.sendMessage( - steamID, - 'āŒ The message command is disabled. Enable it by sending `!config commands.message.enable=true`.' - ); - } else { - bot.sendMessage(steamID, custom.disabled ? custom.disabled : 'āŒ The owner has disabled messages.'); - } - return; - } - - const senderDetails = bot.friends.getFriend(steamID); - const optDW = bot.options.discordWebhook.messages; - - if (isAdmin) { - const steamIdAndMessage = CommandParser.removeCommand(message); - const parts = steamIdAndMessage.split(' '); - let recipientSteamID: SteamID; - - try { - recipientSteamID = new SteamID(parts[0]); - } catch (err) { - return bot.sendMessage( - steamID, - 'āŒ Your syntax is wrong or the SteamID is incorrectly formatted. Here\'s an example: "!message 76561198120070906 Hi"' + - "\n\nHow to get the targeted user's SteamID?" + - '\n1. Go to his/her profile page.' + - '\n2. Go to https://steamrep.com/' + - '\n3. View this gif: https://user-images.githubusercontent.com/47635037/96715154-be80b580-13d5-11eb-9bd5-39613f600f6d.gif' - ); - } - - const steamIDString = recipientSteamID.getSteamID64(); - - if (!recipientSteamID.isValid()) { - return bot.sendMessage( - steamID, - `āŒ "${steamIDString}" is not a valid SteamID.` + - "\n\nHow to get the targeted user's SteamID?" + - '\n1. Go to his/her profile page.' + - '\n2. Go to https://steamrep.com/' + - '\n3. View this gif: https://user-images.githubusercontent.com/47635037/96715154-be80b580-13d5-11eb-9bd5-39613f600f6d.gif' - ); - } else if (!bot.friends.isFriend(recipientSteamID)) { - return bot.sendMessage(steamID, `āŒ I am not friends with the user.`); - } - - const recipientDetails = bot.friends.getFriend(recipientSteamID); - const reply = steamIdAndMessage.substr(steamIDString.length); - - // Send message to recipient - bot.sendMessage( - recipientSteamID, - custom.fromOwner - ? custom.fromOwner.replace(/%reply%/g, reply) - : `/quote šŸ’¬ Message from the owner: ${reply}` + - '\n\nā” Hint: You can use the !message command to respond to the owner of this bot.' + - '\nExample: !message Hi Thanks!' - ); - - // Send a notification to the admin with message contents & details - if (optDW.enable && optDW.url !== '') { - sendAdminMessage( - recipientSteamID.toString(), - reply, - recipientDetails, - generateLinks(steamID.toString()), - timeNow(bot.options).time, - bot - ); - } else { - const customInitializer = bot.options.steamChat.customInitializer.message.toOtherAdmins; - bot.messageAdmins( - `${ - customInitializer ? customInitializer : '/quote' - } šŸ’¬ Message sent to #${recipientSteamID.toString()} (${recipientDetails.player_name}): "${reply}". `, - [] - ); - } - - bot.sendMessage(steamID, custom.success ? custom.success : 'āœ… Your message has been sent.'); - - // Send message to all other admins that an admin replied - return bot.messageAdmins( - `${ - senderDetails ? `${senderDetails.player_name} (${steamID.toString()})` : steamID.toString() - } sent a message to ${ - recipientDetails - ? recipientDetails.player_name + ` (${recipientSteamID.toString()})` - : recipientSteamID.toString() - } with "${reply}".`, - [steamID] - ); - } else { - const admins = bot.getAdmins; - if (!admins || admins.length === 0) { - // Just default to same message as if it was disabled - return bot.sendMessage(steamID, custom.disabled ? custom.disabled : 'āŒ The owner has disabled messages.'); - } - - const msg = message.substr(message.toLowerCase().indexOf('message') + 8); - if (!msg) { - return bot.sendMessage( - steamID, - custom.wrongSyntax - ? custom.wrongSyntax - : 'āŒ Please include a message. Here\'s an example: "!message Hi"' - ); - } - - const links = generateLinks(steamID.toString()); - if (optDW.enable && optDW.url !== '') { - sendPartnerMessage(steamID.toString(), msg, senderDetails, links, timeNow(bot.options).time, bot); - } else { - const customInitializer = bot.options.steamChat.customInitializer.message.onReceive; - bot.messageAdmins( - `${ - customInitializer ? customInitializer : '/quote' - } šŸ’¬ You've got a message from #${steamID.toString()} (${senderDetails.player_name}):` + - `"${msg}". ` + - `\nSteam: ${links.steam}` + - `\nBackpack.tf: ${links.bptf}` + - `\nSteamREP: ${links.steamrep}`, - [] - ); - } - bot.sendMessage(steamID, custom.success ? custom.success : 'āœ… Your message has been sent.'); - } -} diff --git a/src/classes/Commands/functions/misc.ts b/src/classes/Commands/functions/misc.ts deleted file mode 100644 index d77b6098d..000000000 --- a/src/classes/Commands/functions/misc.ts +++ /dev/null @@ -1,242 +0,0 @@ -import SteamID from 'steamid'; -import SKU from 'tf2-sku-2'; -import pluralize from 'pluralize'; -import Bot from '../../Bot'; -import { Discord, Stock } from '../../Options'; -import { pure, timeNow, uptime } from '../../../lib/tools/export'; - -type Misc = 'time' | 'uptime' | 'pure' | 'rate' | 'owner' | 'discord' | 'stock'; - -export function miscCommand(steamID: SteamID, bot: Bot, command: Misc): void { - const opt = bot.options.commands[command]; - if (!opt.enable) { - if (!bot.isAdmin(steamID)) { - const custom = opt.customReply.disabled; - return bot.sendMessage(steamID, custom ? custom : 'āŒ This command is disabled by the owner.'); - } - } - - const custom = opt.customReply.reply; - if (command === 'time') { - const timeWithEmojis = timeNow(bot.options); - bot.sendMessage( - steamID, - custom - ? custom - .replace(/%emoji%/g, timeWithEmojis.emoji) - .replace(/%time%/g, timeWithEmojis.time) - .replace(/%note%/g, timeWithEmojis.note) - : `It is currently the following time in my owner's timezone: ${timeWithEmojis.emoji} ${ - timeWithEmojis.time + (timeWithEmojis.note !== '' ? `.\n\n${timeWithEmojis.note}.` : '.') - }` - ); - } else if (command === 'uptime') { - const botUptime = uptime(); - bot.sendMessage(steamID, custom ? custom.replace(/%uptime%/g, botUptime) : botUptime); - } else if (command === 'pure') { - const pureStock = pure.stock(bot); - bot.sendMessage( - steamID, - custom - ? custom.replace(/%pure%/g, pureStock.join(' and ')) - : `šŸ’° I have ${pureStock.join(' and ')} in my inventory.` - ); - } else if (command === 'rate') { - const key = bot.pricelist.getKeyPrices; - const keyRate = key.sell.toString(); - const source = key.src === 'manual' ? 'manual' : 'https://api.prices.tf/items/5021;6?src=bptf'; - - bot.sendMessage( - steamID, - custom - ? custom - .replace(/%keyRate%/g, keyRate) - .replace(/%keyPrices%/g, `${key.buy.metal} / ${key.sell.toString()}`) - .replace(/%source%/g, source) - : 'I value šŸ”‘ Mann Co. Supply Crate Keys at ' + - keyRate + - '. This means that one key is the same as ' + - keyRate + - ', and ' + - keyRate + - ' is the same as one key.' + - `\n\nKey rate source: ${source}` - ); - } else if (command === 'owner') { - const firstAdmin = bot.getAdmins[0]; - const steamURL = `https://steamcommunity.com/profiles/${firstAdmin.toString()}`; - const bptfURL = `https://backpack.tf/profiles/${firstAdmin.toString()}`; - - bot.sendMessage( - steamID, - custom - ? custom - .replace(/%steamurl%/g, steamURL) - .replace(/%bptfurl%/g, bptfURL) - .replace(/%steamid%/, firstAdmin.toString()) - : `ā€¢ Steam: ${steamURL}\nā€¢ Backpack.tf: ${bptfURL}` - ); - } else if (command === 'discord') { - const inviteURL = (opt as Discord).inviteURL; - let reply: string; - if (custom) { - reply = - 'TF2Autobot Discord Server: https://discord.gg/D2GNnp7tv8\n\n' + - custom.replace(/%discordurl%/g, inviteURL); - } else { - if (inviteURL) { - reply = `TF2Autobot Discord Server: https://discord.gg/D2GNnp7tv8\nOwner's Discord Server: ${inviteURL}`; - // - } else reply = 'TF2Autobot Discord Server: https://discord.gg/D2GNnp7tv8'; - } - bot.sendMessage(steamID, reply); - } else { - const inventory = bot.inventoryManager.getInventory; - const dict = inventory.getItems; - const items: { amount: number; name: string }[] = []; - - for (const sku in dict) { - if (!Object.prototype.hasOwnProperty.call(dict, sku)) { - continue; - } - - if (['5021;6', '5002;6', '5001;6', '5000;6'].includes(sku)) { - continue; - } - - items.push({ - name: bot.schema.getName(SKU.fromString(sku), false), - amount: dict[sku].length - }); - } - - items.sort((a, b) => { - if (a.amount === b.amount) { - if (a.name < b.name) { - return -1; - } else if (a.name > b.name) { - return 1; - } else { - return 0; - } - } - return b.amount - a.amount; - }); - - const pure = [ - { - name: 'Mann Co. Supply Crate Key', - amount: inventory.getAmount('5021;6') - }, - { - name: 'Refined Metal', - amount: inventory.getAmount('5002;6') - }, - { - name: 'Reclaimed Metal', - amount: inventory.getAmount('5001;6') - }, - { - name: 'Scrap Metal', - amount: inventory.getAmount('5000;6') - } - ]; - - const parsed = pure.concat(items); - - const stock: string[] = []; - let left = 0; - - const max = (opt as Stock).maximumItems; - - for (let i = 0; i < parsed.length; i++) { - if (stock.length > max) { - left += parsed[i].amount; - } else { - stock.push(`${parsed[i].name}: ${parsed[i].amount}`); - } - } - - const custom = opt.customReply.reply; - let reply = custom - ? custom.replace(/%stocklist%/g, stock.join(', \n')) - : `/pre šŸ“œ Here's a list of all the items that I have in my inventory:\n${stock.join(', \n')}`; - - if (left > 0) { - reply += `,\nand ${left} other ${pluralize('item', left)}`; - } - - bot.sendMessage(steamID, reply); - } -} - -type CraftUncraft = 'craftweapon' | 'uncraftweapon'; - -export function weaponCommand(steamID: SteamID, bot: Bot, type: CraftUncraft): void { - const opt = bot.options.commands[type]; - if (!opt.enable) { - if (!bot.isAdmin(steamID)) { - const custom = opt.customReply.disabled; - return bot.sendMessage(steamID, custom ? custom : 'āŒ This command is disabled by the owner.'); - } - } - - const weaponStock = getWeaponsStock(bot, type === 'craftweapon' ? bot.craftWeapons : bot.uncraftWeapons); - - let reply: string; - if (weaponStock.length > 0) { - const custom = opt.customReply.have; - reply = custom - ? custom.replace(/%list%/g, weaponStock.join(', \n')) - : `šŸ“ƒ Here's a list of all ${ - type === 'craftweapon' ? 'craft' : 'uncraft' - } weapons stock in my inventory:\n\n` + weaponStock.join(', \n'); - } else { - const custom = opt.customReply.dontHave; - reply = custom - ? custom - : `āŒ I don't have any ${type === 'craftweapon' ? 'craftable' : 'uncraftable'} weapons in my inventory.`; - } - - bot.sendMessage(steamID, reply); -} - -function getWeaponsStock(bot: Bot, type: string[]) { - const items: { amount: number; name: string }[] = []; - - type.forEach(sku => { - const amount = bot.inventoryManager.getInventory.getAmount(sku); - if (amount > 0) { - items.push({ - name: bot.schema.getName(SKU.fromString(sku), false), - amount: amount - }); - } - }); - - items.sort((a, b) => { - if (a.amount === b.amount) { - if (a.name < b.name) { - return -1; - } else if (a.name > b.name) { - return 1; - } else { - return 0; - } - } - return b.amount - a.amount; - }); - - const stock: string[] = []; - - if (items.length > 0) { - for (let i = 0; i < items.length; i++) { - stock.push(`${items[i].name}: ${items[i].amount}`); - } - } - return stock; -} - -export function paintsCommand(steamID: SteamID, bot: Bot): void { - bot.sendMessage(steamID, '/code ' + JSON.stringify(bot.paints, null, 4)); -} diff --git a/src/classes/Commands/functions/options.ts b/src/classes/Commands/functions/options.ts deleted file mode 100644 index 50a3d08f4..000000000 --- a/src/classes/Commands/functions/options.ts +++ /dev/null @@ -1,269 +0,0 @@ -import SteamID from 'steamid'; -import { promises as fsp } from 'fs'; -import sleepasync from 'sleep-async'; -import Bot from '../../Bot'; -import CommandParser from '../../CommandParser'; -import { getOptionsPath, JsonOptions, removeCliOptions } from '../../Options'; -import validator from '../../../lib/validator'; -import log from '../../../lib/logger'; -import { deepMerge } from '../../../lib/tools/deep-merge'; - -export async function optionsCommand(steamID: SteamID, bot: Bot, message: string): Promise { - const liveOptions = deepMerge({}, bot.options) as JsonOptions; - // remove any CLI stuff - removeCliOptions(liveOptions); - - const key = CommandParser.removeCommand(message); - - if (!key || key === '!options') { - bot.sendMessage( - steamID, - `/code ${JSON.stringify( - { - miscSettings: liveOptions.miscSettings, - sendAlert: liveOptions.sendAlert, - pricelist: liveOptions.pricelist, - bypass: liveOptions.bypass, - tradeSummary: liveOptions.tradeSummary - }, - null, - 4 - )}` - ); - - await sleepasync().Promise.sleep(1 * 1000); - - bot.sendMessage( - steamID, - `/code ${JSON.stringify( - { - steamChat: liveOptions.steamChat, - highValue: liveOptions.highValue, - normalize: liveOptions.normalize, - details: liveOptions.details, - statistics: liveOptions.statistics, - autokeys: liveOptions.autokeys, - crafting: liveOptions.crafting - }, - null, - 4 - )}` - ); - - await sleepasync().Promise.sleep(1 * 1000); - - bot.sendMessage( - steamID, - `/code ${JSON.stringify( - { - offerReceived: liveOptions.offerReceived, - manualReview: liveOptions.manualReview - }, - null, - 4 - )}` - ); - - await sleepasync().Promise.sleep(1 * 1000); - - bot.sendMessage( - steamID, - `/code ${JSON.stringify( - { - discordWebhook: liveOptions.discordWebhook, - customMessage: liveOptions.customMessage - }, - null, - 4 - )}` - ); - - await sleepasync().Promise.sleep(1 * 1000); - - bot.sendMessage(steamID, `/code ${JSON.stringify({ commands: liveOptions.commands }, null, 4)}`); - - await sleepasync().Promise.sleep(1 * 1000); - - bot.sendMessage(steamID, `/code ${JSON.stringify({ detailsExtra: liveOptions.detailsExtra }, null, 4)}`); - - bot.sendMessage( - steamID, - `\n\nYou can also get only the part the you want by sending "!options [OptionsParentKey]"` - ); - } else { - const optionsKeys = Object.keys(liveOptions); - - if (!optionsKeys.includes(key)) { - return bot.sendMessage( - steamID, - `āŒ "${key}" parent key does not exist in options.` + - `\n\nValid parent keys:\nā€¢ ` + - [ - 'miscSettings', - 'sendAlert', - 'pricelist', - 'bypass', - 'tradeSummary', - 'steamChat', - 'highValue', - 'normalize', - 'details', - 'statistics', - 'autokeys', - 'crafting', - 'offerReceived', - 'manualReview', - 'discordWebhook', - 'customMessage', - 'commands', - 'detailsExtra' - ].join('\nā€¢ ') - ); - } - - const show = {}; - - show[key] = liveOptions[key as OptionsKeys]; - - bot.sendMessage(steamID, `/code ${JSON.stringify(show, null, 4)}`); - } -} - -type OptionsKeys = - | 'miscSettings' - | 'sendAlert' - | 'pricelist' - | 'bypass' - | 'tradeSummary' - | 'steamChat' - | 'highValue' - | 'normalize' - | 'details' - | 'statistics' - | 'autokeys' - | 'crafting' - | 'offerReceived' - | 'manualReview' - | 'discordWebhook' - | 'customMessage' - | 'commands' - | 'detailsExtra'; - -export function updateOptionsCommand(steamID: SteamID, message: string, bot: Bot): void { - const opt = bot.options; - const params = CommandParser.parseParams(CommandParser.removeCommand(message)) as unknown; - - const optionsPath = getOptionsPath(opt.steamAccountName); - const saveOptions = deepMerge({}, opt) as JsonOptions; - removeCliOptions(saveOptions); - - if (Object.keys(params).length === 0) { - const msg = 'āš ļø Missing properties to update.'; - if (steamID) { - bot.sendMessage(steamID, msg); - } else { - log.warn(msg); - } - - return; - } - - const knownParams = params as JsonOptions; - - if (knownParams.discordWebhook?.ownerID !== undefined) { - // Stringify numbers - knownParams.discordWebhook.ownerID = String(knownParams.discordWebhook.ownerID); - } - - if (knownParams.discordWebhook?.embedColor !== undefined) { - // Stringify numbers - knownParams.discordWebhook.embedColor = String(knownParams.discordWebhook.embedColor); - } - - const result: JsonOptions = deepMerge(saveOptions, knownParams); - - const errors = validator(result, 'options'); - if (errors !== null) { - const msg = 'āŒ Error updating options: ' + errors.join(', '); - if (steamID) { - bot.sendMessage(steamID, msg); - } else { - log.error(msg); - } - - return; - } - - fsp.writeFile(optionsPath, JSON.stringify(saveOptions, null, 4), { encoding: 'utf8' }) - .then(() => { - deepMerge(opt, saveOptions); - const msg = 'āœ… Updated options!'; - - if (knownParams.miscSettings?.game?.playOnlyTF2 === true) { - bot.client.gamesPlayed([]); - bot.client.gamesPlayed(440); - } - - if (typeof knownParams.miscSettings?.game?.customName === 'string') { - bot.client.gamesPlayed([]); - bot.client.gamesPlayed( - ( - knownParams.miscSettings?.game?.playOnlyTF2 !== undefined - ? knownParams.miscSettings.game.playOnlyTF2 - : opt.miscSettings.game.playOnlyTF2 - ) - ? 440 - : [knownParams.miscSettings.game.customName, 440] - ); - } - - if (knownParams.miscSettings?.autobump?.enable === true) { - bot.listings.setupAutorelist(); - bot.handler.disableAutoRefreshListings(); - } else if (knownParams.miscSettings?.autobump?.enable === false) { - bot.listings.disableAutorelistOption(); - bot.handler.enableAutoRefreshListings(); - } - - if (knownParams.statistics?.sendStats?.enable === true) { - bot.handler.sendStats(); - } else if (knownParams.statistics?.sendStats?.enable === false) { - bot.handler.disableSendStats(); - } - - if (knownParams.statistics?.sendStats?.time !== undefined) { - bot.handler.sendStats(); - } - - if (typeof knownParams.highValue !== undefined) { - void bot.inventoryManager.getInventory.fetch(); - } - - if (typeof knownParams.normalize === 'object') { - void bot.inventoryManager.getInventory.fetch(); - } - - if (typeof knownParams.autokeys === 'object') { - if (knownParams.autokeys.enable !== undefined && !knownParams.autokeys.enable) { - bot.handler.autokeys.disable(bot.pricelist.getKeyPrices); - } - bot.handler.autokeys.check(); - } - - if (steamID) { - return bot.sendMessage(steamID, msg); - } else { - return log.info(msg); - } - }) - .catch(err => { - const msg = `āŒ Error saving options file to disk: ${JSON.stringify(err)}`; - if (steamID) { - bot.sendMessage(steamID, msg); - } else { - log.error(msg); - } - - return; - }); -} diff --git a/src/classes/Commands/functions/pricelistManager.ts b/src/classes/Commands/functions/pricelistManager.ts deleted file mode 100644 index 08a47b2b3..000000000 --- a/src/classes/Commands/functions/pricelistManager.ts +++ /dev/null @@ -1,1271 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ - -import SteamID from 'steamid'; -import SKU from 'tf2-sku-2'; -import Currencies from 'tf2-currencies'; -import pluralize from 'pluralize'; -import dayjs from 'dayjs'; -import sleepasync from 'sleep-async'; -import { removeLinkProtocol, testSKU, getItemFromParams } from './utils'; -import Bot from '../../Bot'; -import CommandParser from '../../CommandParser'; -import { Entry, EntryData, PricelistChangedSource } from '../../Pricelist'; -import validator from '../../../lib/validator'; -import log from '../../../lib/logger'; - -// Pricelist manager - -export function addCommand(steamID: SteamID, message: string, bot: Bot): void { - const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); - - if (params.enabled === undefined) { - params.enabled = true; - } - - if (params.max === undefined) { - params.max = 1; - } - - if (params.min === undefined) { - params.min = 0; - } - - if (params.intent === undefined) { - params.intent = 2; - } else if (typeof params.intent === 'string') { - const intent = ['buy', 'sell', 'bank'].indexOf(params.intent.toLowerCase()); - - if (intent !== -1) { - params.intent = intent; - } - } - - if (typeof params.buy === 'object') { - params.buy.keys = params.buy.keys || 0; - params.buy.metal = params.buy.metal || 0; - - if (params.autoprice === undefined) { - params.autoprice = false; - } - } - if (typeof params.sell === 'object') { - params.sell.keys = params.sell.keys || 0; - params.sell.metal = params.sell.metal || 0; - - if (params.autoprice === undefined) { - params.autoprice = false; - } - } - - const isPremium = bot.handler.getBotInfo.premium; - if (params.promoted !== undefined) { - if (!isPremium) { - return bot.sendMessage( - steamID, - `āŒ This account is not Backpack.tf Premium. You can't use "promoted" parameter.` - ); - } - - if (typeof params.promoted === 'boolean') { - if (params.promoted === true) { - params.promoted = 1; - } else { - params.promoted = 0; - } - } else { - if (typeof params.promoted !== 'number') { - return bot.sendMessage(steamID, 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)'); - } else if (params.promoted < 0 || params.promoted > 1) { - return bot.sendMessage(steamID, 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)'); - } - } - } else { - params['promoted'] = 0; - } - - if (typeof params.note === 'object') { - params.note.buy = params.note.buy || null; - params.note.sell = params.note.sell || null; - } - - if (params.note === undefined) { - // If note parameter is not defined, set both note.buy and note.sell to null. - params['note'] = { buy: null, sell: null }; - } - - if (params.group && typeof params.group !== 'string') { - // if group parameter is defined, convert anything to string - params.group = (params.group as string).toString(); - } - - if (typeof params.group === undefined) { - // If group parameter is not defined, set it to null. - params['group'] = 'all'; - } - - if (params.autoprice === undefined) { - params.autoprice = true; - } - - if (params.sku !== undefined && !testSKU(params.sku as string)) { - return bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); - } - - if (params.sku === undefined) { - const item = getItemFromParams(steamID, params, bot); - - if (item === null) { - return bot.sendMessage(steamID, `āŒ Item not found.`); - } - - params.sku = SKU.fromObject(item); - } - - bot.pricelist - .addPrice(params as EntryData, true, PricelistChangedSource.Command) - .then(entry => { - bot.sendMessage(steamID, `āœ… Added "${entry.name}"` + generateAddedReply(bot, isPremium, entry)); - }) - .catch(err => { - bot.sendMessage(steamID, `āŒ Failed to add the item to the pricelist: ${(err as Error).message}`); - }); -} - -let stopAutoAdd = false; - -export function stopAutoAddCommand(): void { - stopAutoAdd = true; -} - -export async function autoAddCommand(steamID: SteamID, message: string, bot: Bot): Promise { - const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); - - if (params.sku !== undefined || params.name !== undefined || params.defindex !== undefined) { - return bot.sendMessage( - steamID, - `āŒ Please only define item listing settings parameters, more info:` + - ` https://github.com/TF2Autobot/tf2autobot/wiki/What-is-the-pricelist%3F#i2---item-listing-settings-parameters.` - ); - } - - if (!params) { - bot.sendMessage(steamID, `ā³ Adding all items with default settings...`); - } - - if (params.enabled === undefined) { - params.enabled = true; - } - - if (params.max === undefined) { - params.max = 1; - } - - if (params.min === undefined) { - params.min = 0; - } - - if (params.intent === undefined) { - params.intent = 2; - } else if (typeof params.intent === 'string') { - const intent = ['buy', 'sell', 'bank'].indexOf(params.intent.toLowerCase()); - - if (intent !== -1) { - params.intent = intent; - } - } - - if (typeof params.buy === 'object') { - params.buy.keys = params.buy.keys || 0; - params.buy.metal = params.buy.metal || 0; - - if (params.autoprice === undefined) { - params.autoprice = false; - } - } - if (typeof params.sell === 'object') { - params.sell.keys = params.sell.keys || 0; - params.sell.metal = params.sell.metal || 0; - - if (params.autoprice === undefined) { - params.autoprice = false; - } - } - - if ( - (params.sell !== undefined && params.buy === undefined) || - (params.sell === undefined && params.buy !== undefined) - ) { - return bot.sendMessage(steamID, `āŒ Please set both buy and sell prices.`); - } - - const isPremium = bot.handler.getBotInfo.premium; - - if (params.promoted !== undefined) { - if (!isPremium) { - return bot.sendMessage( - steamID, - `āŒ This account is not Backpack.tf Premium. You can't use "promoted" paramter.` - ); - } - - if (typeof params.promoted === 'boolean') { - if (params.promoted === true) { - params.promoted = 1; - } else { - params.promoted = 0; - } - } else { - if (typeof params.promoted !== 'number') { - return bot.sendMessage(steamID, 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)'); - } - - if (params.promoted < 0 || params.promoted > 1) { - return bot.sendMessage(steamID, 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)'); - } - } - } else { - params['promoted'] = 0; - } - - if (typeof params.note === 'object') { - params.note.buy = params.note.buy || null; - params.note.sell = params.note.sell || null; - } - - if (params.note === undefined) { - // If note parameter is not defined, set both note.buy and note.sell to null. - params['note'] = { buy: null, sell: null }; - } - - if (params.group && typeof params.group !== 'string') { - // if group parameter is defined, convert anything to string - params.group = (params.group as string).toString(); - } - - if (typeof params.group === undefined) { - // If group parameter is not defined, set it to null. - params['group'] = 'all'; - } - - if (params.autoprice === undefined) { - params.autoprice = true; - } - - const pricelist = bot.pricelist.getPrices; - const dict = bot.inventoryManager.getInventory.getItems; - - const pureAndWeapons = ['5021;6', '5000;6', '5001;6', '5002;6'].concat(bot.craftWeapons.concat(bot.uncraftWeapons)); - - for (const sku in dict) { - if (!Object.prototype.hasOwnProperty.call(dict, sku)) { - continue; - } - - if (pureAndWeapons.some(pureOrWeaponsSKU => pureOrWeaponsSKU === sku)) { - delete dict[sku]; - } - } - - const total = Object.keys(dict).length; - - const totalTime = total * (params.autoprice ? 2 : 1) * 1000; - const aSecond = 1 * 1000; - const aMin = 1 * 60 * 1000; - const anHour = 1 * 60 * 60 * 1000; - - bot.sendMessage( - steamID, - `ā³ Running automatic add items... Total items to add: ${total}` + - `\n${params.autoprice ? 2 : 1} seconds in between items, so it will be about ${ - totalTime < aMin - ? `${Math.round(totalTime / aSecond)} seconds` - : totalTime < anHour - ? `${Math.round(totalTime / aMin)} minutes` - : `${Math.round(totalTime / anHour)} hours` - } to complete. Send "!stopautoadd" to abort.` - ); - - let added = 0; - let skipped = 0; - let failed = 0; - - for (const sku in dict) { - if (stopAutoAdd) { - bot.sendMessage(steamID, '----------\nšŸ›‘ Stopped auto-add items'); - stopAutoAdd = false; - break; - } - - if (!Object.prototype.hasOwnProperty.call(dict, sku)) { - continue; - } - - if (pricelist.some(entry => entry.sku === sku)) { - skipped++; - bot.sendMessage( - steamID, - `----------\nāš ļø ${bot.schema.getName(SKU.fromString(sku))} (${sku}) already in pricelist, skipping...` + - `\nšŸ“œ Status: ${added} added, ${skipped} skipped, ${failed} failed / ${total} total, ${ - total - added - skipped - failed - } remaining` - ); - // Prevent spamming detection and cause the bot to stop sending messages - await sleepasync().Promise.sleep(1 * 1000); - continue; - } - - if (params.autoprice === true) { - await sleepasync().Promise.sleep(2 * 1000); - } else { - await sleepasync().Promise.sleep(1 * 1000); - } - - params.sku = sku; - - bot.pricelist - .addPrice(params as EntryData, true, PricelistChangedSource.Command) - .then(entry => { - added++; - bot.sendMessage( - steamID, - `----------\nāœ… Added "${entry.name}"` + - generateAddedReply(bot, isPremium, entry) + - `\n\nšŸ“œ Status: ${added} added, ${skipped} skipped, ${failed} failed / ${total} total, ${ - total - added - skipped - failed - } remaining` - ); - }) - .catch(err => { - failed++; - bot.sendMessage( - steamID, - `----------\nāŒ Failed to add the item to the pricelist: ${(err as Error).message}` + - `\n\nšŸ“œ Status: ${added} added, ${skipped} skipped, ${failed} failed / ${total} total, ${ - total - added - skipped - failed - } remaining` - ); - }); - } - - bot.sendMessage( - steamID, - `----------\nāœ… Done, summary: ${added} added, ${skipped} skipped, ${failed} failed / ${total} total` - ); - - stopAutoAdd = false; -} - -function generateAddedReply(bot: Bot, isPremium: boolean, entry: Entry): string { - const amount = bot.inventoryManager.getInventory.getAmount(entry.sku); - return ( - `\nšŸ’² Buy: ${entry.buy.toString()} | Sell: ${entry.sell.toString()}` + - `\nšŸ›’ Intent: ${entry.intent === 2 ? 'bank' : entry.intent === 1 ? 'sell' : 'buy'}` + - `\nšŸ“¦ Stock: ${amount} | Min: ${entry.min} | Max: ${entry.max}` + - `\nšŸ“‹ Enabled: ${entry.enabled ? 'āœ…' : 'āŒ'}` + - `\nšŸ”„ Autoprice: ${entry.autoprice ? 'āœ…' : 'āŒ'}` + - (isPremium ? `\nšŸ“¢ Promoted: ${entry.promoted === 1 ? 'āœ…' : 'āŒ'}` : '') + - `\nšŸ”° Group: ${entry.group}` + - `${entry.note.buy !== null ? `\nšŸ“„ Custom buying note: ${entry.note.buy}` : ''}` + - `${entry.note.sell !== null ? `\nšŸ“¤ Custom selling note: ${entry.note.sell}` : ''}` - ); -} - -export async function updateCommand(steamID: SteamID, message: string, bot: Bot): Promise { - const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); - - if (typeof params.intent === 'string') { - const intent = ['buy', 'sell', 'bank'].indexOf(params.intent.toLowerCase()); - - if (intent !== -1) { - params.intent = intent; - } - } - - if (params.all === true) { - // TODO: Must have at least one other param - const pricelist = bot.pricelist.getPrices; - const keyPrice = bot.pricelist.getKeyPrice; - - let targetedPricelist: Entry[]; - let unTargetedPricelist: Entry[]; - let newPricelist: Entry[]; - - if (params.promoted) { - return bot.sendMessage(steamID, `āŒ Parameter "promoted" can't be used with "!update all=true".`); - } - - if (params.withgroup && params.withoutgroup) { - return bot.sendMessage( - steamID, - `āŒ Don't be dumb. Please choose only "withgroup" OR "withoutgroup", not both. Thanks.` - ); - } - - if (params.withgroup) { - targetedPricelist = pricelist.filter(entry => - entry.group ? [(params.withgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) : false - ); - unTargetedPricelist = pricelist.filter(entry => - entry.group ? ![(params.withgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) : true - ); - - if (targetedPricelist.length === 0) { - return bot.sendMessage( - steamID, - `āŒ There is no entry with "${params.withgroup as string}" group found in your pricelist.` - ); - } - - newPricelist = targetedPricelist; - - if (typeof params.buy === 'object' || typeof params.sell === 'object') { - if ( - new Currencies(params.buy).toValue(keyPrice.metal) >= - new Currencies(params.sell).toValue(keyPrice.metal) - ) { - return bot.sendMessage(steamID, `āŒ Buying price can't be higher than selling price.`); - } else if ( - (params.buy !== null && params.sell === undefined) || - (params.buy === undefined && params.sell !== null) - ) { - return bot.sendMessage(steamID, `āŒ You must include both buying and selling prices.`); - } - } - } else if (params.withoutgroup) { - // reverse of withgroup - targetedPricelist = pricelist.filter(entry => - entry.group - ? ![(params.withoutgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) - : true - ); - unTargetedPricelist = pricelist.filter(entry => - entry.group - ? [(params.withoutgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) - : false - ); - - if (targetedPricelist.length === 0) { - return bot.sendMessage( - steamID, - `āŒ There is no entry other than "${params.withoutgroup as string}" group found in your pricelist.` - ); - } - - newPricelist = targetedPricelist; - - if (typeof params.buy === 'object' || typeof params.sell === 'object') { - if ( - new Currencies(params.buy).toValue(keyPrice.metal) >= - new Currencies(params.sell).toValue(keyPrice.metal) - ) { - return bot.sendMessage(steamID, `āŒ Buying price can't be higher than selling price.`); - } else if ( - (params.buy !== null && params.sell === undefined) || - (params.buy === undefined && params.sell !== null) - ) { - return bot.sendMessage(steamID, `āŒ You must include both buying and selling prices.`); - } - } - } else { - newPricelist = pricelist; - - if (bot.options.autokeys.enable) { - // Autokeys is a feature, so when updating multiple entry with - // "!update all=true", key entry will be removed from newPricelist. - // https://github.com/idinium96/tf2autobot/issues/131 - const keyEntry = bot.pricelist.getPrice('5021;6'); - if (keyEntry !== null) { - const index = bot.pricelist.getIndex('5021;6'); - newPricelist.splice(index, 1); - } - } - } - - if (newPricelist.length === 0) { - return bot.sendMessage(steamID, 'Your pricelist is empty.'); - } - - if (!params.withgroup && !params.withoutgroup) { - if (typeof params.note === 'object') { - return bot.sendMessage( - steamID, - `āŒ Please specify "withgroup" or "withoutgroup" to change note.` + - `\nExample: "!update all=true&withgroup=¬e.buy="` - ); - } - - if (typeof params.buy === 'object' || typeof params.sell === 'object') { - return bot.sendMessage( - steamID, - `āŒ Please specify "withgroup" or "withoutgroup" to change buying/selling price.\nExample:\n` + - `"!update all=true&withgroup=&(buy.keys|buy.metal)=&(sell.keys|sell.metal)="` - ); - } - } - - newPricelist.forEach((entry, i) => { - if (params.intent || params.intent === 0) { - entry.intent = params.intent as 0 | 1 | 2; - } - - if (params.min === 0 || typeof params.min === 'number') { - entry.min = params.min as number; - } - - if (params.max === 0 || typeof params.max === 'number') { - entry.max = params.max as number; - } - - if (typeof params.enabled === 'boolean') { - entry.enabled = params.enabled; - } - - if (params.group) { - entry.group = (params.group as string).toString(); - } - - if (params.removenote && typeof params.removenote === 'boolean' && params.removenote === true) { - // Sending "!update all=true&removenote=true" will set both - // note.buy and note.sell for entire/withgroup entries to null. - if (entry.note) { - entry.note.buy = null; - entry.note.sell = null; - } - } - - if (typeof params.autoprice === 'boolean') { - if (params.autoprice === false) { - entry.time = null; - } - entry.autoprice = params.autoprice; - } - - if (params.withgroup || params.withoutgroup) { - if (typeof params.note === 'object') { - // can change note if have withgroup/withoutgroup parameter - entry.note.buy = params.note.buy || entry.note.buy; - entry.note.sell = params.note.sell || entry.note.sell; - } - - if (typeof params.buy === 'object' && params.buy !== null) { - entry.buy.keys = params.buy.keys || 0; - entry.buy.metal = params.buy.metal || 0; - - if (params.autoprice === undefined) { - entry.autoprice = false; - } - } - - if (typeof params.sell === 'object' && params.sell !== null) { - entry.sell.keys = params.sell.keys || 0; - entry.sell.metal = params.sell.metal || 0; - - if (params.autoprice === undefined) { - entry.autoprice = false; - } - } - } - - if (i === 0) { - const errors = validator( - { - sku: entry.sku, - enabled: entry.enabled, - intent: entry.intent, - max: entry.max, - min: entry.min, - autoprice: entry.autoprice, - buy: entry.buy.toJSON(), - sell: entry.sell.toJSON(), - promoted: entry.promoted, - group: entry.group, - note: entry.note, - time: entry.time - }, - 'pricelist' - ); - - if (errors !== null) { - throw new Error(errors.join(', ')); - } - } - }); - - if (params.removenote) { - delete params.removenote; - } - - if (params.withgroup) { - newPricelist = unTargetedPricelist.concat(newPricelist); - delete params.withgroup; - } - - if (params.withoutgroup) { - newPricelist = unTargetedPricelist.concat(newPricelist); - delete params.withoutgroup; - } - - // FIXME: Make it so that it is not needed to remove all listings - - if (params.autoprice !== true) { - await bot.handler.onPricelist(newPricelist); - bot.sendMessage(steamID, 'āœ… Updated pricelist!'); - - return await bot.listings.redoListings(); - } - - bot.sendMessage(steamID, 'āŒ› Updating prices...'); - - return bot.pricelist - .setupPricelist() - .then(async () => { - bot.sendMessage(steamID, 'āœ… Updated pricelist!'); - await bot.listings.redoListings(); - }) - .catch(err => { - log.warn('Failed to update prices: ', err); - return bot.sendMessage(steamID, `āŒ Failed to update prices: ${(err as Error).message}`); - }); - } - - if (params.sku !== undefined && !testSKU(params.sku as string)) { - return bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); - } - - if (typeof params.buy === 'object' && params.buy !== null) { - params.buy.keys = params.buy.keys || 0; - params.buy.metal = params.buy.metal || 0; - - if (params.autoprice === undefined) { - params.autoprice = false; - } - } - - if (typeof params.sell === 'object' && params.sell !== null) { - params.sell.keys = params.sell.keys || 0; - params.sell.metal = params.sell.metal || 0; - - if (params.autoprice === undefined) { - params.autoprice = false; - } - } - - if (params.resetgroup) { - // if resetgroup (when sending "!update item=&resetgroup=true") is defined, - // first check if it's a booelan type - if (typeof params.resetgroup === 'boolean') { - // if it's boolean, then check if it's true - if (params.resetgroup === true) { - // if it's true, then set group key to null. - params.group = 'all'; - } - } else { - // else if it's not a boolean type, then send message. - return bot.sendMessage(steamID, 'āŒ "resetgroup" must be either "true" or "false".'); - } - // delete resetgroup key from params so it will not trying to be added into pricelist (validator error) - delete params.resetgroup; - } - - const isPremium = bot.handler.getBotInfo.premium; - if (params.promoted !== undefined) { - if (!isPremium) { - return bot.sendMessage( - steamID, - `āŒ This account is not Backpack.tf Premium. You can't use "promoted" paramter.` - ); - } - - if (typeof params.promoted === 'boolean') { - if (params.promoted === true) { - params.promoted = 1; - } else { - params.promoted = 0; - } - } else { - if (typeof params.promoted !== 'number') { - return bot.sendMessage(steamID, 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)'); - } - - if (params.promoted < 0 || params.promoted > 1) { - return bot.sendMessage(steamID, 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)'); - } - } - } - - if (params.item !== undefined) { - // Remove by full name - let match = bot.pricelist.searchByName(params.item as string, false); - if (match === null) { - return bot.sendMessage( - steamID, - `āŒ I could not find any items in my pricelist that contain "${params.item as string}"` - ); - } else if (Array.isArray(match)) { - const matchCount = match.length; - if (match.length > 20) { - match = match.splice(0, 20); - } - - let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( - ',\n' - )}`; - - if (matchCount > match.length) { - const other = matchCount - match.length; - reply += `,\nand ${other} other ${pluralize('item', other)}.`; - } - - return bot.sendMessage(steamID, reply); - } - - delete params.item; - params.sku = match.sku; - } else if (params.sku === undefined) { - const item = getItemFromParams(steamID, params, bot); - - if (item === null) { - return bot.sendMessage(steamID, `āŒ Item not found.`); - } - - params.sku = SKU.fromObject(item); - } - - if (!bot.pricelist.hasPrice(params.sku as string)) { - return bot.sendMessage(steamID, 'āŒ Item is not in the pricelist.'); - } - - const itemEntry = bot.pricelist.getPrice(params.sku as string, false); - - if (typeof params.note === 'object') { - params.note.buy = params.note.buy || itemEntry.note.buy; - params.note.sell = params.note.sell || itemEntry.note.sell; - } - - if (params.removenote) { - // removenote to set both note.buy and note.sell to null. - if (typeof params.removenote === 'boolean') { - if (params.removenote === true) { - params.note = { buy: null, sell: null }; - } - } else { - return bot.sendMessage(steamID, 'āŒ "removenote" must be either "true" or "false".'); - } - - delete params.removenote; - } - - if (params.removebuynote && params.removesellnote) { - return bot.sendMessage( - steamID, - 'āŒ Please only use either "removebuynote" or "removesellnote", not both, or if you wish to remove both buy and sell note, please use "removenote".' - ); - } - - if (params.removebuynote) { - if (typeof params.removebuynote === 'boolean') { - if (params.removebuynote === true) { - params.note = { buy: null, sell: itemEntry.note.sell }; - } - } else { - return bot.sendMessage(steamID, 'āŒ "removebuynote" must be either "true" or "false".'); - } - - delete params.removebuynote; - } - - if (params.removesellnote) { - if (typeof params.removesellnote === 'boolean') { - if (params.removesellnote === true) { - params.note = { buy: itemEntry.note.buy, sell: null }; - } - } else { - return bot.sendMessage(steamID, 'āŒ "removesellnote" must be either "true" or "false".'); - } - - delete params.removesellnote; - } - - if (params.group && typeof params.group !== 'string') { - // if group parameter is defined, convert anything to string - params.group = (params.group as string).toString(); - } - - const entryData = bot.pricelist.getPrice(params.sku as string, false).getJSON(); - delete entryData.time; - delete params.sku; - - if (Object.keys(params).length === 0) { - return bot.sendMessage(steamID, 'āš ļø Missing properties to update.'); - } - - // Update entry - for (const property in params) { - if (!Object.prototype.hasOwnProperty.call(params, property)) { - continue; - } - - entryData[property] = params[property]; - } - - const generateUpdateReply = (bot: Bot, isPremium: boolean, oldEntry: Entry, newEntry: Entry) => { - const keyPrice = bot.pricelist.getKeyPrice; - const amount = bot.inventoryManager.getInventory.getAmount(oldEntry.sku); - return ( - `\nšŸ’² Buy: ${ - oldEntry.buy.toValue(keyPrice.metal) !== newEntry.buy.toValue(keyPrice.metal) - ? `${oldEntry.buy.toString()} ā†’ ${newEntry.buy.toString()}` - : newEntry.buy.toString() - } | Sell: ${ - oldEntry.sell.toValue(keyPrice.metal) !== newEntry.sell.toValue(keyPrice.metal) - ? `${oldEntry.sell.toString()} ā†’ ${newEntry.sell.toString()}` - : newEntry.sell.toString() - }` + - `\nšŸ“¦ Stock: ${amount}` + - ` | Min: ${oldEntry.min !== newEntry.min ? `${oldEntry.min} ā†’ ${newEntry.min}` : newEntry.min} | Max: ${ - oldEntry.max !== newEntry.max ? `${oldEntry.max} ā†’ ${newEntry.max}` : newEntry.max - }` + - `\nšŸ›’ Intent: ${ - oldEntry.intent !== newEntry.intent - ? `${oldEntry.intent === 2 ? 'bank' : oldEntry.intent === 1 ? 'sell' : 'buy'} ā†’ ${ - newEntry.intent === 2 ? 'bank' : newEntry.intent === 1 ? 'sell' : 'buy' - }` - : `${newEntry.intent === 2 ? 'bank' : newEntry.intent === 1 ? 'sell' : 'buy'}` - }` + - `\nšŸ“‹ Enabled: ${ - oldEntry.enabled !== newEntry.enabled - ? `${oldEntry.enabled ? 'āœ…' : 'āŒ'} ā†’ ${newEntry.enabled ? 'āœ…' : 'āŒ'}` - : `${newEntry.enabled ? 'āœ…' : 'āŒ'}` - }` + - `\nšŸ”„ Autoprice: ${ - oldEntry.autoprice !== newEntry.autoprice - ? `${oldEntry.autoprice ? 'āœ…' : 'āŒ'} ā†’ ${newEntry.autoprice ? 'āœ…' : 'āŒ'}` - : `${newEntry.autoprice ? 'āœ…' : 'āŒ'}` - }` + - (isPremium - ? `\nšŸ“¢ Promoted: ${ - oldEntry.promoted !== newEntry.promoted - ? `${oldEntry.promoted === 1 ? 'āœ…' : 'āŒ'} ā†’ ${newEntry.promoted === 1 ? 'āœ…' : 'āŒ'}` - : `${newEntry.promoted === 1 ? 'āœ…' : 'āŒ'}` - }` - : '') + - `\nšŸ”° Group: ${ - oldEntry.group !== newEntry.group ? `${oldEntry.group} ā†’ ${newEntry.group}` : newEntry.group - }` + - `${newEntry.note.buy !== null ? `\nšŸ“„ Custom buying note: ${newEntry.note.buy}` : ''}` + - `${newEntry.note.sell !== null ? `\nšŸ“¤ Custom selling note: ${newEntry.note.sell}` : ''}` - ); - }; - - bot.pricelist - .updatePrice(entryData, true, PricelistChangedSource.Command) - .then(entry => { - bot.sendMessage( - steamID, - `āœ… Updated "${entry.name}"` + generateUpdateReply(bot, isPremium, itemEntry, entry) - ); - }) - .catch((err: ErrorRequest) => { - bot.sendMessage( - steamID, - 'āŒ Failed to update pricelist entry: ' + - (err.body && err.body.message ? err.body.message : err.message) - ); - }); -} - -let executed = false; -let lastExecutedTime: number | null = null; -let executeTimeout: NodeJS.Timeout; - -export async function shuffleCommand(steamID: SteamID, bot: Bot): Promise { - const newExecutedTime = dayjs().valueOf(); - const timeDiff = newExecutedTime - lastExecutedTime; - - const pricelist = bot.pricelist.getPrices; - - if (pricelist.length === 0) { - return bot.sendMessage(steamID, 'āŒ Pricelist is empty!'); - } - - if (executed === true) { - return bot.sendMessage( - steamID, - `āš ļø You need to wait ${Math.trunc( - (30 * 60 * 1000 - timeDiff) / (1000 * 60) - )} minutes before you can shuffle pricelist/shuffle again.` - ); - } else { - clearTimeout(executeTimeout); - lastExecutedTime = dayjs().valueOf(); - - const shufflePricelist = (arr: Entry[]) => { - return arr.sort(() => Math.random() - 0.5); - }; - - await bot.handler.onPricelist(shufflePricelist(pricelist)); - bot.sendMessage(steamID, 'āœ… Pricelist shuffled!'); - await bot.listings.redoListings(); - - executed = true; - executeTimeout = setTimeout(() => { - lastExecutedTime = null; - executed = false; - clearTimeout(executeTimeout); - }, 30 * 60 * 1000); - } -} - -export async function removeCommand(steamID: SteamID, message: string, bot: Bot): Promise { - const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); - - if (params.all === true) { - // Remove entire pricelist - const pricelistLength = bot.pricelist.getLength; - if (pricelistLength === 0) { - return bot.sendMessage(steamID, 'āŒ Your pricelist is already empty!'); - } - - const pricelist = bot.pricelist.getPrices; - let newPricelist: Entry[] = []; - let newPricelistCount: Entry[] = []; - - if (params.withgroup && params.withoutgroup) { - return bot.sendMessage( - steamID, - `āŒ Don't be dumb. Please choose only "withgroup" OR "withoutgroup", not both. Thanks.` - ); - } - - if (params.withgroup) { - // first filter out pricelist with ONLY "withgroup" value. - newPricelistCount = pricelist.filter(entry => - entry.group ? [(params.withgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) : false - ); - - if (newPricelistCount.length === 0) { - return bot.sendMessage( - steamID, - `āŒ There is no entry with "${params.withgroup as string}" group found in your pricelist.` - ); - } - - // then filter out pricelist with NOT "withgroup" value. - newPricelist = pricelist.filter(entry => - entry.group ? ![(params.withgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) : true - ); - } else if (params.withoutgroup) { - // reverse of withgroup - newPricelistCount = pricelist.filter(entry => - entry.group - ? ![(params.withoutgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) - : true - ); - - if (newPricelistCount.length === 0) { - return bot.sendMessage( - steamID, - `āŒ There is no entry other than "${params.withoutgroup as string}" group found in your pricelist.` - ); - } - - newPricelist = pricelist.filter(entry => - entry.group - ? [(params.withoutgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) - : false - ); - } else { - newPricelistCount = pricelist; - } - - if (params.i_am_sure !== 'yes_i_am') { - return bot.sendMessage( - steamID, - '/pre āš ļø Are you sure that you want to remove ' + - pluralize('item', newPricelistCount.length, true) + - '? Try again with i_am_sure=yes_i_am' - ); - } - - if (params.withgroup || params.withoutgroup) { - try { - await bot.pricelist.removeByGroup(newPricelist); - bot.sendMessage(steamID, `āœ… Removed ${newPricelistCount.length} items from pricelist.`); - return await bot.listings.redoListings(); - } catch (err) { - return bot.sendMessage(steamID, `āŒ Failed to clear pricelist: ${(err as Error).message}`); - } - } else { - try { - await bot.pricelist.removeAll(); - return bot.sendMessage(steamID, 'āœ… Cleared pricelist!'); - } catch (err) { - return bot.sendMessage(steamID, `āŒ Failed to clear pricelist: ${(err as Error).message}`); - } - } - } - - if (params.sku !== undefined && !testSKU(params.sku as string)) { - return bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); - } - - if (params.item !== undefined) { - // Remove by full name - let match = bot.pricelist.searchByName(params.item as string, false); - if (match === null) { - return bot.sendMessage( - steamID, - `āŒ I could not find any items in my pricelist that contain "${params.item as string}"` - ); - } else if (Array.isArray(match)) { - const matchCount = match.length; - if (match.length > 20) match = match.splice(0, 20); - - let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( - ',\n' - )}`; - - if (matchCount > match.length) { - const other = matchCount - match.length; - reply += `,\nand ${other} other ${pluralize('item', other)}.`; - } - - return bot.sendMessage(steamID, reply); - } - - delete params.item; - params.sku = match.sku; - } else if (params.sku === undefined) { - const item = getItemFromParams(steamID, params, bot); - - if (item === null) { - return bot.sendMessage(steamID, `āŒ Item not found.`); - } - - params.sku = SKU.fromObject(item); - } - - bot.pricelist - .removePrice(params.sku as string, true) - .then(entry => bot.sendMessage(steamID, `āœ… Removed "${entry.name}".`)) - .catch(err => bot.sendMessage(steamID, `āŒ Failed to remove pricelist entry: ${(err as Error).message}`)); -} - -export function getCommand(steamID: SteamID, message: string, bot: Bot): void { - const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); - if (params.sku !== undefined && !testSKU(params.sku as string)) { - return bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); - } - - if (params.item !== undefined) { - // Remove by full name - let match = bot.pricelist.searchByName(params.item as string, false); - - if (match === null) { - return bot.sendMessage( - steamID, - `āŒ I could not find any items in my pricelist that contain "${params.item as string}"` - ); - } else if (Array.isArray(match)) { - const matchCount = match.length; - if (match.length > 20) { - match = match.splice(0, 20); - } - - let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( - ',\n' - )}`; - - if (matchCount > match.length) { - const other = matchCount - match.length; - reply += `,\nand ${other} other ${pluralize('item', other)}.`; - } - - return bot.sendMessage(steamID, reply); - } - - delete params.item; - params.sku = match.sku; - } else if (params.sku === undefined) { - const item = getItemFromParams(steamID, params, bot); - - if (item === null) { - return bot.sendMessage(steamID, `āŒ Item not found.`); - } - - params.sku = SKU.fromObject(item); - } - - if (params.sku === undefined) { - return bot.sendMessage(steamID, 'āŒ Missing item'); - } - - const match = bot.pricelist.getPrice(params.sku as string); - if (match === null) { - bot.sendMessage(steamID, `āŒ Could not find item "${params.sku as string}" in the pricelist`); - } else { - bot.sendMessage(steamID, `/code ${generateOutput(match)}`); - } -} - -export async function findCommand(steamID: SteamID, message: string, bot: Bot): Promise { - const params = CommandParser.parseParams(CommandParser.removeCommand(message)); - if ( - !( - params.enabled !== undefined || - params.max !== undefined || - params.min !== undefined || - params.intent !== undefined || - params.autoprice !== undefined || - params.group !== undefined || - params.promoted !== undefined - ) - ) { - return bot.sendMessage( - steamID, - 'āš ļø Only parameters available for !find command: enabled, max, min, intent, promoted, autoprice or group\nExample: !find intent=bank&max=2' - ); - } - - let filter = bot.pricelist.getPrices; - if (params.enabled !== undefined) { - if (typeof params.enabled !== 'boolean') { - return bot.sendMessage(steamID, 'āš ļø enabled parameter must be "true" or "false"'); - } - filter = filter.filter(entry => entry.enabled === params.enabled); - } - - if (params.max !== undefined) { - if (typeof params.max !== 'number') { - return bot.sendMessage(steamID, 'āš ļø max parameter must be an integer'); - } - filter = filter.filter(entry => entry.max === params.max); - } - - if (params.min !== undefined) { - if (typeof params.min !== 'number') { - return bot.sendMessage(steamID, 'āš ļø min parameter must be an integer'); - } - filter = filter.filter(entry => entry.min === params.min); - } - - if (params.promoted !== undefined) { - if (typeof params.promoted === 'boolean') { - if (params.promoted === true) { - filter = filter.filter(entry => entry.promoted === 1); - } else { - filter = filter.filter(entry => entry.promoted === 0); - } - } else { - if (typeof params.promoted !== 'number') { - return bot.sendMessage(steamID, 'āš ļø promoted parameter must be either 0 (false) or 1 (true)'); - } - - if (params.promoted < 0 || params.promoted > 1) { - return bot.sendMessage(steamID, 'āš ļø "promoted" parameter must be either 0 (false) or 1 (true)'); - } - filter = filter.filter(entry => entry.promoted === params.promoted); - } - } - - if (params.intent !== undefined) { - if (typeof params.intent === 'string') { - const intent = ['buy', 'sell', 'bank'].indexOf(params.intent.toLowerCase()); - if (intent !== -1) { - params.intent = intent; - } - } - - if (typeof params.intent !== 'number' || params.intent < 0) { - return bot.sendMessage( - steamID, - 'āš ļø intent parameter must be "buy", "sell", or "bank" OR an integer of "0", "1" or "2" respectively' - ); - } - filter = filter.filter(entry => entry.intent === params.intent); - } - - if (params.autoprice !== undefined) { - if (typeof params.autoprice !== 'boolean') { - return bot.sendMessage(steamID, 'āš ļø autoprice parameter must be "true" or "false"'); - } - filter = filter.filter(entry => entry.autoprice === params.autoprice); - } - - if (params.group !== undefined) { - if (typeof params.group !== 'string') { - return bot.sendMessage(steamID, 'āš ļø group parameter must be a string'); - } - filter = filter.filter(entry => entry.group === params.group); - } - - const parametersUsed = { - enabled: params.enabled !== undefined ? `enabled=${(params.enabled as boolean).toString()}` : '', - autoprice: params.autoprice !== undefined ? `autoprice=${(params.autoprice as boolean).toString()}` : '', - max: params.max !== undefined ? `max=${params.max as number}` : '', - min: params.min !== undefined ? `min=${params.min as number}` : '', - intent: - params.intent !== undefined - ? `intent=${ - (params.intent as number) === 0 ? 'buy' : (params.intent as number) === 1 ? 'sell' : 'bank' - }` - : '', - promoted: - params.promoted !== undefined ? `promoted=${(params.promoted as number) === 1 ? 'true' : 'false'}` : '', - group: params.group !== undefined ? `group=${params.group as string}` : '' - }; - - const parameters = Object.values(parametersUsed); - const display = parameters.filter(param => param !== ''); - - const length = filter.length; - if (length === 0) { - bot.sendMessage(steamID, `No items found with ${display.join('&')}.`); - } else if (length > 20) { - bot.sendMessage( - steamID, - `Found ${pluralize('item', length, true)} with ${display.join('&')}, showing only a max of 100 items` - ); - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 0, 20)}`); - if (length <= 40) { - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 20, length > 40 ? 40 : length)}`); - } else if (length <= 60) { - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 20, 40)}`); - await sleepasync().Promise.sleep(1 * 1000); - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 40, length > 60 ? 60 : length)}`); - } else if (length <= 80) { - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 20, 40)}`); - await sleepasync().Promise.sleep(1 * 1000); - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 40, 60)}`); - await sleepasync().Promise.sleep(1 * 1000); - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 60, length > 80 ? 80 : length)}`); - } else if (length > 80) { - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 20, 40)}`); - await sleepasync().Promise.sleep(1 * 1000); - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 40, 60)}`); - await sleepasync().Promise.sleep(1 * 1000); - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 60, 80)}`); - await sleepasync().Promise.sleep(1 * 1000); - bot.sendMessage(steamID, `/code ${generateOutput(filter, true, 80, length > 100 ? 100 : length)}`); - } - } else { - bot.sendMessage(steamID, `Found ${pluralize('item', filter.length, true)} with ${display.join('&')}`); - bot.sendMessage(steamID, `/code ${generateOutput(filter)}`); - } -} - -function generateOutput(filtered: Entry[] | Entry, isSlice = false, start?: number, end?: number): string { - return isSlice - ? JSON.stringify((filtered as Entry[]).slice(start, end), null, 4) - : JSON.stringify(filtered, null, 4); -} - -interface ErrorRequest { - body?: ErrorBody; - message?: string; -} - -interface ErrorBody { - message: string; -} diff --git a/src/classes/Commands/functions/request.ts b/src/classes/Commands/functions/request.ts deleted file mode 100644 index b6545db81..000000000 --- a/src/classes/Commands/functions/request.ts +++ /dev/null @@ -1,251 +0,0 @@ -import SteamID from 'steamid'; -import SKU from 'tf2-sku-2'; -import pluralize from 'pluralize'; -import dayjs from 'dayjs'; -import sleepasync from 'sleep-async'; -import Currencies from 'tf2-currencies'; -import { removeLinkProtocol, getItemFromParams, testSKU } from './utils'; -import Bot from '../../Bot'; -import CommandParser from '../../CommandParser'; -import log from '../../../lib/logger'; -import { fixItem } from '../../../lib/items'; -import { GetPriceFn, GetSalesFn, RequestCheckFn, RequestCheckResponse } from '../../Pricer'; - -export async function getSalesCommand( - steamID: SteamID, - message: string, - bot: Bot, - getSales: GetSalesFn -): Promise { - const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); - if (params.sku === undefined) { - const item = getItemFromParams(steamID, params, bot); - - if (item === null) { - return bot.sendMessage(steamID, `āŒ Item not found.`); - } - - params.sku = SKU.fromObject(item); - } else { - params.sku = SKU.fromObject(fixItem(SKU.fromString(params.sku), bot.schema)); - } - - const name = bot.schema.getName(SKU.fromString(params.sku)); - try { - const salesData = await getSales(params.sku, 'bptf'); - if (!salesData) { - return bot.sendMessage( - steamID, - `āŒ No recorded snapshots found for ${name === null ? (params.sku as string) : name}.` - ); - } - - if (salesData.sales.length === 0) { - return bot.sendMessage( - steamID, - `āŒ No recorded snapshots found for ${name === null ? (params.sku as string) : name}.` - ); - } - - const sales: Sales[] = []; - salesData.sales.forEach(sale => - sales.push({ - seller: 'https://backpack.tf/profiles/' + sale.steamid, - itemHistory: 'https://backpack.tf/item/' + sale.id.replace('440_', ''), - keys: sale.currencies.keys, - metal: sale.currencies.metal, - date: sale.time - }) - ); - sales.sort((a, b) => b.date - a.date); - - let left = 0; - const SalesList: string[] = []; - for (let i = 0; i < sales.length; i++) { - if (SalesList.length > 40) { - left += 1; - } else { - SalesList.push( - `Listed #${i + 1}-----\nā€¢ Date: ${dayjs.unix(sales[i].date).utc().toString()}\nā€¢ Item: ${ - sales[i].itemHistory - }\nā€¢ Seller: ${sales[i].seller}\nā€¢ Was selling for: ${ - sales[i].keys > 0 ? `${sales[i].keys} keys,` : '' - } ${sales[i].metal} ref` - ); - } - } - - let reply = `šŸ”Ž Recorded removed sell listings from backpack.tf\n\nItem name: ${ - salesData.name - }\n\n-----${SalesList.join('\n\n-----')}`; - if (left > 0) { - reply += `,\n\nand ${left} other ${pluralize('sale', left)}`; - } - - bot.sendMessage(steamID, reply); - } catch (err) { - return bot.sendMessage( - steamID, - `āŒ Error getting sell snapshots for ${name === null ? (params.sku as string) : name}: ${ - (err as ErrorRequest).body && (err as ErrorRequest).body.message - ? (err as ErrorRequest).body.message - : (err as ErrorRequest).message - }` - ); - } -} - -// Request commands - -export function pricecheckCommand(steamID: SteamID, message: string, bot: Bot, requestCheck: RequestCheckFn): void { - const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); - if (params.sku !== undefined && !testSKU(params.sku as string)) { - return bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); - } - - if (params.sku === undefined) { - const item = getItemFromParams(steamID, params, bot); - if (item === null) { - return bot.sendMessage(steamID, `āŒ Item not found.`); - } - - params.sku = SKU.fromObject(item); - } else { - params.sku = SKU.fromObject(fixItem(SKU.fromString(params.sku), bot.schema)); - } - - const name = bot.schema.getName(SKU.fromString(params.sku), false); - void requestCheck(params.sku, 'bptf').asCallback((err: ErrorRequest, body: RequestCheckResponse) => { - if (err) { - return bot.sendMessage( - steamID, - `āŒ Error while requesting price check: ${ - err.body && err.body.message ? err.body.message : err.message - }` - ); - } - - if (!body) { - bot.sendMessage(steamID, 'āŒ Error while requesting price check (returned null/undefined)'); - } else { - bot.sendMessage( - steamID, - `āŒ› Price check requested for ${ - body.name.includes('War Paint') || - body.name.includes('Mann Co. Supply Crate Series #') || - body.name.includes('Salvaged Mann Co. Supply Crate #') - ? name - : body.name - }, the item will be checked.` - ); - } - }); -} - -export async function pricecheckAllCommand(steamID: SteamID, bot: Bot, requestCheck: RequestCheckFn): Promise { - const pricelist = bot.pricelist.getPrices; - - const total = pricelist.length; - const totalTime = total * 2 * 1000; - const aSecond = 1 * 1000; - const aMin = 1 * 60 * 1000; - const anHour = 1 * 60 * 60 * 1000; - bot.sendMessage( - steamID, - `āŒ› Price check requested for ${total} items. It will be completed in approximately ${ - totalTime < aMin - ? `${Math.round(totalTime / aSecond)} seconds.` - : totalTime < anHour - ? `${Math.round(totalTime / aMin)} minutes.` - : `${Math.round(totalTime / anHour)} hours.` - } (about 2 seconds for each item).` - ); - - const skus = pricelist.map(entry => entry.sku); - let submitted = 0; - let success = 0; - let failed = 0; - for (const sku of skus) { - await sleepasync().Promise.sleep(2 * 1000); - void requestCheck(sku, 'bptf').asCallback(err => { - if (err) { - submitted++; - failed++; - log.warn(`pricecheck failed for ${sku}: ${JSON.stringify(err)}`); - log.debug( - `pricecheck for ${sku} failed, status: ${submitted}/${total}, ${success} success, ${failed} failed.` - ); - } else { - submitted++; - success++; - log.debug( - `pricecheck for ${sku} success, status: ${submitted}/${total}, ${success} success, ${failed} failed.` - ); - } - if (submitted === total) { - bot.sendMessage( - steamID, - `āœ… Successfully completed pricecheck for all ${total} ${pluralize('item', total)}!` - ); - } - }); - } -} - -export async function checkCommand(steamID: SteamID, message: string, bot: Bot, getPrice: GetPriceFn): Promise { - const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); - if (params.sku !== undefined && !testSKU(params.sku as string)) { - return bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); - } - - if (params.sku === undefined) { - const item = getItemFromParams(steamID, params, bot); - if (item === null) { - return bot.sendMessage(steamID, `āŒ Item not found.`); - } - - params.sku = SKU.fromObject(item); - } else { - params.sku = SKU.fromObject(fixItem(SKU.fromString(params.sku), bot.schema)); - } - - const name = bot.schema.getName(SKU.fromString(params.sku)); - try { - const price = await getPrice(params.sku, 'bptf'); - const currBuy = new Currencies(price.buy); - const currSell = new Currencies(price.sell); - - bot.sendMessage( - steamID, - `šŸ”Ž ${name}:\nā€¢ Buy : ${currBuy.toString()}\nā€¢ Sell : ${currSell.toString()}\n\nPrices.TF: https://prices.tf/items/${ - params.sku as string - }` - ); - } catch (err) { - return bot.sendMessage( - steamID, - `Error getting price for ${name === null ? (params.sku as string) : name}: ${ - (err as ErrorRequest).body && (err as ErrorRequest).body.message - ? (err as ErrorRequest).body.message - : (err as ErrorRequest).message - }` - ); - } -} - -interface Sales { - seller: string; - itemHistory: string; - keys: number; - metal: number; - date: number; -} - -interface ErrorRequest { - body?: ErrorBody; - message?: string; -} - -interface ErrorBody { - message: string; -} diff --git a/src/classes/Commands/functions/review.ts b/src/classes/Commands/functions/review.ts deleted file mode 100644 index ca02bef1a..000000000 --- a/src/classes/Commands/functions/review.ts +++ /dev/null @@ -1,335 +0,0 @@ -import SteamID from 'steamid'; -import pluralize from 'pluralize'; -import TradeOfferManager, { Action, OfferData, OurTheirItemsDict } from 'steam-tradeoffer-manager'; -import Currencies from 'tf2-currencies'; -import { UnknownDictionaryKnownValues } from '../../../types/common'; -import SKU from 'tf2-sku-2'; -import SchemaManager from 'tf2-schema-2'; -import Bot from '../../Bot'; -import CommandParser from '../../CommandParser'; -import { generateLinks } from '../../../lib/tools/export'; - -// Manual review commands - -export function tradesCommand(steamID: SteamID, bot: Bot): void { - // Go through polldata and find active offers - const pollData = bot.manager.pollData; - - const offersForReview: UnknownDictionaryKnownValues[] = []; - const activeOffersNotForReview: UnknownDictionaryKnownValues[] = []; - - for (const id in pollData.received) { - if (!Object.prototype.hasOwnProperty.call(pollData.received, id)) { - continue; - } - - if (pollData.received[id] !== TradeOfferManager.ETradeOfferState['Active']) { - continue; - } - - const data = pollData?.offerData[id] || null; - - if (data === null) { - continue; - } else if (data?.action?.action !== 'skip') { - activeOffersNotForReview.push({ id: id, data: data }); - continue; - } - - offersForReview.push({ id: id, data: data }); - } - - if (offersForReview.length === 0 && activeOffersNotForReview.length === 0) { - return bot.sendMessage(steamID, 'āŒ There are no active offers/ pending review.'); - } - - bot.sendMessage( - steamID, - (offersForReview.length > 0 ? generateTradesReply(offersForReview.sort((a, b) => a.id - b.id)) : '') + - (offersForReview.length > 0 ? '\n\n-----------------\n\n' : '') + - (activeOffersNotForReview.length > 0 - ? generateActiveOfferReply(activeOffersNotForReview.sort((a, b) => a.id - b.id)) - : '') - ); -} - -function generateTradesReply(offers: UnknownDictionaryKnownValues[]): string { - let reply = `There ${pluralize('is', offers.length, true)} active ${pluralize( - 'offer', - offers.length - )} that you can review:\n`; - for (let i = 0; i < offers.length; i++) { - reply += - `\n- Offer #${offers[i].id as string} from ${(offers[i].data as OfferData).partner} (reason: ${(offers[i] - .data as OfferData).action.meta.uniqueReasons.join(', ')})` + - `\nāš ļø Send "!trade ${offers[i].id as string}" for more details.\n`; - } - - return reply; -} - -function generateActiveOfferReply(offers: UnknownDictionaryKnownValues[]): string { - let reply = `There ${pluralize('is', offers.length, true)} ${pluralize( - 'offer', - offers.length - )} that currently still active:\n`; - for (let i = 0; i < offers.length; i++) { - reply += - `\n- Offer #${offers[i].id as string} from ${(offers[i].data as OfferData).partner}` + - `\nāš ļø Send "!trade ${offers[i].id as string}" for more details or "!faccept ${ - offers[i].id as string - }" to force accept the trade.\n`; - } - - return reply; -} - -export function tradeCommand(steamID: SteamID, message: string, bot: Bot): void { - const offerId = CommandParser.removeCommand(message).trim(); - - if (offerId === '') { - return bot.sendMessage(steamID, 'āš ļø Missing offer id. Example: "!trade 3957959294"'); - } - - const state = bot.manager.pollData.received[offerId]; - - if (state === undefined) { - return bot.sendMessage(steamID, 'Offer does not exist. āŒ'); - } - - if (state !== TradeOfferManager.ETradeOfferState['Active']) { - // TODO: Add what the offer is now, accepted / declined and why - return bot.sendMessage(steamID, 'Offer is not active. āŒ'); - } - - const offerData = bot.manager.pollData.offerData[offerId]; - - // Log offer details - - let reply = - offerData?.action?.action === 'skip' - ? `āš ļø Offer #${offerId} from ${offerData.partner} is pending for review` + - `\nReason: ${offerData.action.meta.uniqueReasons.join(', ')}).\n\nSummary:\n\n` - : `āš ļø Offer #${offerId} from ${offerData.partner} is still active.\n\nSummary:\n\n`; - - const keyPrice = bot.pricelist.getKeyPrice; - const value = offerData.value; - const items = offerData.dict || { our: null, their: null }; - const summarizeItems = (dict: OurTheirItemsDict, schema: SchemaManager.Schema) => { - if (dict === null) { - return 'unknown items'; - } - - const summary: string[] = []; - - for (const sku in dict) { - if (!Object.prototype.hasOwnProperty.call(dict, sku)) { - continue; - } - - summary.push(schema.getName(SKU.fromString(sku), false) + (dict[sku] > 1 ? ` x${dict[sku]}` : '')); // dict[sku] = amount - } - - if (summary.length === 0) { - return 'nothing'; - } - - return summary.join(', '); - }; - - if (!value) { - reply += - 'Asked: ' + summarizeItems(items.our, bot.schema) + '\nOffered: ' + summarizeItems(items.their, bot.schema); - } else { - const valueDiff = - new Currencies(value.their).toValue(keyPrice.metal) - new Currencies(value.our).toValue(keyPrice.metal); - const valueDiffRef = Currencies.toRefined(Currencies.toScrap(Math.abs(valueDiff * (1 / 9)))).toString(); - reply += - 'Asked: ' + - new Currencies(value.our).toString() + - ' (' + - summarizeItems(items.our, bot.schema) + - ')\nOffered: ' + - new Currencies(value.their).toString() + - ' (' + - summarizeItems(items.their, bot.schema) + - (valueDiff > 0 - ? `)\nšŸ“ˆ Profit from overpay: ${valueDiffRef} ref` - : valueDiff < 0 - ? `)\nšŸ“‰ Loss from underpay: ${valueDiffRef} ref` - : ')'); - } - - const links = generateLinks(offerData.partner.toString()); - reply += - `\n\nSteam: ${links.steam}\nBackpack.tf: ${links.bptf}\nSteamREP: ${links.steamrep}` + - (offerData?.action?.action === 'skip' - ? `\n\nāš ļø Send "!accept ${offerId}" to accept or "!decline ${offerId}" to decline this offer.` - : `\n\nāš ļø Send "!faccept ${offerId}" to force accept the trade now!`); - - bot.sendMessage(steamID, reply); -} - -type ActionOnTrade = 'accept' | 'accepttrade' | 'decline' | 'declinetrade'; - -export async function actionOnTradeCommand( - steamID: SteamID, - message: string, - bot: Bot, - command: ActionOnTrade -): Promise { - const offerIdAndMessage = CommandParser.removeCommand(message); - const offerIdRegex = /\d+/.exec(offerIdAndMessage); - - const isAccepting = ['accept', 'accepttrade'].includes(command); - - if (isNaN(+offerIdRegex) || !offerIdRegex) { - return bot.sendMessage( - steamID, - `āš ļø Missing offer id. Example: "!${isAccepting ? 'accept' : 'decline'} 3957959294"` - ); - } - - const offerId = offerIdRegex[0]; - const state = bot.manager.pollData.received[offerId]; - if (state === undefined) { - return bot.sendMessage(steamID, 'Offer does not exist. āŒ'); - } - - if (state !== TradeOfferManager.ETradeOfferState['Active']) { - // TODO: Add what the offer is now, accepted / declined and why - return bot.sendMessage(steamID, 'Offer is not active. āŒ'); - } - - const offerData = bot.manager.pollData.offerData[offerId]; - if (offerData?.action?.action !== 'skip') { - return bot.sendMessage(steamID, "Offer can't be reviewed. āŒ"); - } - - try { - const offer = await bot.trades.getOffer(offerId); - bot.sendMessage(steamID, `${isAccepting ? 'Accepting' : 'Declining'} offer...`); - - const partnerId = new SteamID(bot.manager.pollData.offerData[offerId].partner); - const reply = offerIdAndMessage.substr(offerId.length); - const adminDetails = bot.friends.getFriend(steamID); - - try { - await bot.trades.applyActionToOffer( - isAccepting ? 'accept' : 'decline', - 'MANUAL', - isAccepting ? (offer.data('action') as Action).meta : {}, - offer - ); - - if (isAccepting) { - const isManyItems = offer.itemsToGive.length + offer.itemsToReceive.length > 50; - - if (isManyItems) { - bot.sendMessage( - offer.partner, - bot.options.customMessage.accepted.manual.largeOffer - ? bot.options.customMessage.accepted.manual.largeOffer - : '.\nMy owner has manually accepted your offer. The trade may take a while to finalize due to it being a large offer.' + - ' If the trade does not finalize after 5-10 minutes has passed, please send your offer again, or add me and use ' + - 'the !sell/!sellcart or !buy/!buycart command.' - ); - } else { - bot.sendMessage( - offer.partner, - bot.options.customMessage.accepted.manual.smallOffer - ? bot.options.customMessage.accepted.manual.smallOffer - : '.\nMy owner has manually accepted your offer. The trade should be finalized shortly.' + - ' If the trade does not finalize after 1-2 minutes has passed, please send your offer again, or add me and use ' + - 'the !sell/!sellcart or !buy/!buycart command.' - ); - } - } - - // Send message to recipient if includes some messages - if (reply) { - bot.sendMessage( - partnerId, - `/quote šŸ’¬ Message from ${adminDetails ? adminDetails.player_name : 'admin'}: ${reply}` - ); - } - } catch (err) { - return bot.sendMessage( - steamID, - `āŒ Ohh nooooes! Something went wrong while trying to ${ - isAccepting ? 'accept' : 'decline' - } the offer: ${(err as Error).message}` - ); - } - } catch (err) { - return bot.sendMessage( - steamID, - `āŒ Ohh nooooes! Something went wrong while trying to ${isAccepting ? 'accept' : 'decline'} the offer: ${ - (err as Error).message - }` - ); - } -} - -type ForceAction = 'faccept' | 'fdecline'; - -export async function forceAction(steamID: SteamID, message: string, bot: Bot, command: ForceAction): Promise { - const offerIdAndMessage = CommandParser.removeCommand(message); - const offerIdRegex = /\d+/.exec(offerIdAndMessage); - - const isForceAccepting = command === 'faccept'; - - if (isNaN(+offerIdRegex) || !offerIdRegex) { - return bot.sendMessage( - steamID, - `āš ļø Missing offer id. Example: "!${isForceAccepting ? 'faccept' : 'fdecline'} 3957959294"` - ); - } - - const offerId = offerIdRegex[0]; - - const state = bot.manager.pollData.received[offerId]; - if (state === undefined) { - return bot.sendMessage(steamID, 'Offer does not exist. āŒ'); - } - - try { - const offer = await bot.trades.getOffer(offerId); - bot.sendMessage(steamID, `Force ${isForceAccepting ? 'accepting' : 'declining'} offer...`); - - const partnerId = new SteamID(bot.manager.pollData.offerData[offerId].partner); - const reply = offerIdAndMessage.substr(offerId.length); - const adminDetails = bot.friends.getFriend(steamID); - - try { - await bot.trades.applyActionToOffer( - isForceAccepting ? 'accept' : 'decline', - 'MANUAL-FORCE', - isForceAccepting ? (offer.data('action') as Action).meta : {}, - offer - ); - - // Send message to recipient if includes some messages - if (reply) { - bot.sendMessage( - partnerId, - `/quote šŸ’¬ Message from ${adminDetails ? adminDetails.player_name : 'admin'}: ${reply}` - ); - } - } catch (err) { - return bot.sendMessage( - steamID, - `āŒ Ohh nooooes! Something went wrong while trying to force ${ - isForceAccepting ? 'accept' : 'decline' - } the offer: ${(err as Error).message}` - ); - } - } catch (err) { - return bot.sendMessage( - steamID, - `āŒ Ohh nooooes! Something went wrong while trying to force ${ - isForceAccepting ? 'accept' : 'decline' - } the offer: ${(err as Error).message}` - ); - } -} diff --git a/src/classes/Commands/functions/status.ts b/src/classes/Commands/functions/status.ts deleted file mode 100644 index c9985d211..000000000 --- a/src/classes/Commands/functions/status.ts +++ /dev/null @@ -1,117 +0,0 @@ -import SteamID from 'steamid'; -import pluralize from 'pluralize'; -import Currencies from 'tf2-currencies'; -import Bot from '../../Bot'; -import { stats, profit } from '../../../lib/tools/export'; -import { sendStats } from '../../../lib/DiscordWebhook/export'; - -// Bot status - -export function statsCommand(steamID: SteamID, bot: Bot): void { - const tradesFromEnv = bot.options.statistics.lastTotalTrades; - const trades = stats(bot); - const profits = profit(bot, Math.floor((Date.now() - 86400000) / 1000)); //since -24h - - const keyPrices = bot.pricelist.getKeyPrices; - - const timedProfitmadeFull = Currencies.toCurrencies(profits.profitTimed, keyPrices.sell.metal).toString(); - const timedProfitmadeInRef = timedProfitmadeFull.includes('key') - ? ` (${Currencies.toRefined(profits.profitTimed)} ref)` - : ''; - - const profitmadeFull = Currencies.toCurrencies(profits.tradeProfit, keyPrices.sell.metal).toString(); - const profitmadeInRef = profitmadeFull.includes('key') ? ` (${Currencies.toRefined(profits.tradeProfit)} ref)` : ''; - - const profitOverpayFull = Currencies.toCurrencies(profits.overpriceProfit, keyPrices.sell.metal).toString(); - const profitOverpayInRef = profitOverpayFull.includes('key') - ? ` (${Currencies.toRefined(profits.overpriceProfit)} ref)` - : ''; - - bot.sendMessage( - steamID, - `All trades (accepted) are recorded from ${pluralize('day', trades.totalDays, true)}` + - ' ago šŸ“Š\n Total accepted trades: ' + - (tradesFromEnv !== 0 - ? String(tradesFromEnv + trades.totalAcceptedTrades) - : String(trades.totalAcceptedTrades)) + - `\n\n--- Last 24 hours ---` + - `\nā€¢ Processed: ${trades.hours24.processed}` + - `\nā€¢ Accepted: ${trades.hours24.accepted.offer + trades.hours24.accepted.sent}` + - `\n---ā€¢ Received offer: ${trades.hours24.accepted.offer}` + - `\n---ā€¢ Sent offer: ${trades.hours24.accepted.sent}` + - `\nā€¢ Declined: ${trades.hours24.decline.offer + trades.hours24.decline.sent}` + - `\n---ā€¢ Received offer: ${trades.hours24.decline.offer}` + - `\n---ā€¢ Sent offer: ${trades.hours24.decline.sent}` + - `\nā€¢ Skipped: ${trades.hours24.skipped}` + - `\nā€¢ Traded away: ${trades.hours24.invalid}` + - `\nā€¢ Canceled: ${trades.hours24.canceled.total}` + - `\n---ā€¢ by user: ${trades.hours24.canceled.byUser}` + - `\n---ā€¢ confirmation failed: ${trades.hours24.canceled.failedConfirmation}` + - `\n---ā€¢ unknown reason: ${trades.hours24.canceled.unknown}` + - `\n\n--- Since beginning of today ---` + - `\nā€¢ Processed: ${trades.today.processed}` + - `\nā€¢ Accepted: ${trades.today.accepted.offer + trades.today.accepted.sent}` + - `\n---ā€¢ Received offer: ${trades.today.accepted.offer}` + - `\n---ā€¢ Sent offer: ${trades.today.accepted.sent}` + - `\nā€¢ Declined: ${trades.today.decline.offer + trades.today.decline.sent}` + - `\n---ā€¢ Received offer: ${trades.today.decline.offer}` + - `\n---ā€¢ Sent offer: ${trades.today.decline.sent}` + - `\nā€¢ Skipped: ${trades.today.skipped}` + - `\nā€¢ Traded away: ${trades.today.invalid}` + - `\nā€¢ Canceled: ${trades.today.canceled.total}` + - `\n---ā€¢ by user: ${trades.today.canceled.byUser}` + - `\n---ā€¢ confirmation failed: ${trades.today.canceled.failedConfirmation}` + - `\n---ā€¢ unknown reason: ${trades.today.canceled.unknown}` + - `\n\n Profit (last 24h): ${timedProfitmadeFull + timedProfitmadeInRef}` + - `\nProfit made: ${profitmadeFull + profitmadeInRef} ${ - profits.since !== 0 ? ` (since ${pluralize('day', profits.since, true)} ago)` : '' - }` + - `\nProfit from overpay: ${profitOverpayFull + profitOverpayInRef}` + - `\nKey rate: ${keyPrices.buy.metal}/${keyPrices.sell.metal} ref` - ); -} - -export function statsDWCommand(steamID: SteamID, bot: Bot): void { - const opt = bot.options.discordWebhook.sendStats; - - if (!opt.enable) { - return bot.sendMessage(steamID, 'āŒ Sending stats to Discord Webhook is disabled.'); - } - - if (opt.url === '') { - return bot.sendMessage(steamID, 'āŒ Your discordWebhook.sendStats.url is empty.'); - } - - sendStats(bot, true, steamID); -} - -export function inventoryCommand(steamID: SteamID, bot: Bot): void { - bot.sendMessage( - steamID, - `šŸŽ’ My current items in my inventory: ${ - String(bot.inventoryManager.getInventory.getTotalItems) + '/' + String(bot.tf2.backpackSlots) - }` - ); -} - -export function versionCommand(steamID: SteamID, bot: Bot): void { - bot.sendMessage(steamID, `Currently running TF2Autobot@v${process.env.BOT_VERSION}. Checking for a new version...`); - - bot.checkForUpdates - .then(({ hasNewVersion, latestVersion }) => { - if (!hasNewVersion) { - bot.sendMessage(steamID, 'You are running the latest version of TF2Autobot!'); - } else if (bot.lastNotifiedVersion === latestVersion) { - bot.sendMessage( - steamID, - `āš ļø Update available! Current: v${process.env.BOT_VERSION}, Latest: v${latestVersion}.\n\n` + - `Release note: https://github.com/idinium96/tf2autobot/releases` + - `\n\nRun "!updaterepo" if you're running your bot with PM2 to update now!"` + - '\n\nContact IdiNium if you have any other problem. Thank you.' - ); - } - }) - .catch(err => { - bot.sendMessage(steamID, `āŒ Failed to check for updates: ${JSON.stringify(err)}`); - }); -} diff --git a/src/classes/Commands/sub-classes/Help.ts b/src/classes/Commands/sub-classes/Help.ts new file mode 100644 index 000000000..158a5d8cc --- /dev/null +++ b/src/classes/Commands/sub-classes/Help.ts @@ -0,0 +1,140 @@ +import SteamID from 'steamid'; +import Bot from '../../Bot'; + +export default class HelpCommands { + private readonly bot: Bot; + + constructor(bot: Bot) { + this.bot = bot; + } + + helpCommand(steamID: SteamID): void { + const isAdmin = this.bot.isAdmin(steamID); + + this.bot.sendMessage( + steamID, + `šŸ“Œ Note šŸ“Œ${ + isAdmin + ? '\nā€¢ a = Directly add "a"' + + '\nā€¢ [a] = Optionally add "a"' + + '\nā€¢ (a|b) = Directly input "a" OR "b"' + + '\nā€¢ = Replace "a" with relevant content' + + '\n\nDo not include characters <>, ( | ) nor [ ] when typing it. For more info, please refer to the wiki: https://github.com/idinium96/tf2autobot/wiki/What-is-the-pricelist%3F#table-of-contents' + : `\nDo not include characters <> nor [ ] - <> means required and [] means optional.` + }\n\nšŸ“œ Here's a list of my commands:\n- ${ + isAdmin + ? [ + '!deposit (sku|name|defindex)=&amount= - Deposit items', + '!withdraw (sku|name|defindex)=&amount= - Withdraw items\n\nāœØ=== Pricelist manager ===āœØ', + '!add (sku|name|defindex)=&[Listing-parameters] - Add a pricelist entry āž•', + '!autoadd [Listing-parameters] - Perform automatic adding items to the pricelist based on items that are currently available in your bot inventory (about 2 seconds every item) šŸ¤–', + '!stopautoadd - Stop automatic add items operation šŸ›‘', + '!update (sku|name|defindex|item)=&[Listing-parameters] - Update a pricelist entry', + '!remove (sku|name|defindex|item)= - Remove a pricelist entry āž–', + '!shuffle - Shuffle pricelist entries.', + '!get (sku|name|defindex|item)= - Get raw information about a pricelist entry\n\nāœØ=== Bot manager ===āœØ', + "!expand craftable=(true|false) - Use Backpack Expanders to increase the bot's inventory limit", + '!use (sku|assetid)= - Use an item (such as Gift-Stuffed Stocking 2020 - sku: 5923;6;untradable)', + "!delete (sku|assetid)= - Delete an item from the bot's inventory (SKU input only) šŸš®", + '!message - Send a message to a specific user šŸ’¬', + '!block - Block a specific user', + '!unblock - Unblock a specific user', + '!clearfriends - Clear friendlist (will keep admins and friendsToKeep) šŸ‘‹', + '!stop - Stop the bot šŸ”“', + '!restart - Restart the bot šŸ”„', + '!updaterepo - Update your bot to the latest version (only if cloned and running with PM2)', + "!refreshautokeys - Refresh the bot's autokeys settings.", + '!refreshlist - Refresh sell listings šŸ”„', + "!name - Change the bot's name", + "!avatar - Change the bot's avatar\n\nāœØ=== Bot status ===āœØ", + '!stats - Get statistics for accepted trades šŸ“Š', + '!statsdw - Send statistics to Discord Webhook šŸ“Š', + "!inventory - Get the bot's current inventory spaces šŸŽ’", + '!version - Get the TF2Autobot version that the bot is running\n\nāœØ=== Manual review ===āœØ', + '!trades - Get a list of trade offers pending for manual review šŸ”', + '!trade - Get information about a trade', + '!accept [Your Message] - Manually accept an active offer āœ…šŸ”', + '!decline [Your Message] - Manually decline an active offer āŒšŸ”', + '!faccept [Your Message] - Force accept any failed to accept offer āœ…šŸ”‚', + '!fdecline [Your Message] - Force decline any failed to decline offer āŒšŸ”‚\n\nāœØ=== Request ===āœØ', + '!check (sku|name|defindex)= - Request the current price for an item from Prices.TF', + '!pricecheck (sku|name|defindex|item)= - Request an item to be price checked by Prices.TF', + "!pricecheckall - Request all items in the bot's pricelist to be price checked by Prices.TF\n\nāœØ=== Misc ===āœØ", + "!autokeys - Get info on the bot's current autokeys settings šŸ”‘", + "!time - Show the owner's current time šŸ•„", + '!uptime - Show the bot uptime šŸ”Œ', + "!pure - Get the bot's current pure stock šŸ’°", + "!rate - Get the bot's current key rates šŸ”‘", + '!stock - Get a list of items that the bot owns', + "!craftweapon - Get a list of the bot's craftable weapon stock šŸ”«", + "!uncraftweapon - Get a list of the bot's uncraftable weapon stock šŸ”«", + '!paints - Get a list of paints partial sku šŸŽØ', + '!sales (sku|name|defindex)= - Get the sales history for an item šŸ”', + '!find - Get the list of filtered items detail based on the parameters šŸ”', + '!options [OptionsKey] - Get options.json content (current bot option settings) šŸ”§', + '!config =[&OtherOptions] - Update the current options (example: !config game.customName=Selling Tools!) šŸ”§', + '!donatebptf (sku|name|defindex)=&amount= - Donate to backpack.tf (https://backpack.tf/donate) šŸ’°', + '!premium months= - Purchase backpack.tf premium using keys (https://backpack.tf/premium/subscribe) šŸ‘‘' + ].join('\n- ') + : [ + '!help - Get a list of commands', + '!how2trade - Guide on how to trade with the bot', + '!price [amount] - Get the price and stock of an item šŸ’²šŸ“¦\n\nāœØ=== Instant item trade ===āœØ', + '!buy [amount] - Instantly buy an item šŸ’²', + '!sell [amount] - Instantly sell an item šŸ’²\n\nāœØ=== Multiple items trade ===āœØ', + '!buycart [amount] - Add an item you want to buy to your cart šŸ›’', + '!sellcart [amount] - Add an item you want to sell to your cart šŸ›’', + '!cart - View your cart šŸ›’', + '!clearcart - Clear your cart āŽšŸ›’', + '!checkout - Have the bot send an offer with the items in your cart āœ…šŸ›’\n\nāœØ=== Trade actions ===āœØ', + '!cancel - Cancel the trade offer āŒ', + '!queue - Check your position in the queue\n\nāœØ=== Contact Owner ===āœØ', + "!owner - Get the owner's Steam profile and Backpack.tf links", + '!message - Send a message to the owner of the bot šŸ’¬', + "!discord - Get a link to join TF2Autobot and/or the owner's discord server\n\nāœØ=== Other Commands ===āœØ", + '!more - Show the advanced commands list' + ].join('\n- ') + }` + ); + } + + moreCommand(steamID: SteamID): void { + const opt = this.bot.options.commands.more; + + if (!opt.enable) { + if (!this.bot.isAdmin(steamID)) { + const custom = opt.customReply.disabled; + return this.bot.sendMessage(steamID, custom ? custom : 'āŒ This command is disabled by the owner.'); + } + } + + this.bot.sendMessage( + steamID, + `Advanced commands list:\n- ${[ + "!autokeys - Get info on the bot's current autokeys settings šŸ”‘", + "!time - Show the owner's current time šŸ•„", + '!uptime - Show the bot uptime šŸ”Œ', + "!pure - Get the bot's current pure stock šŸ’°", + "!rate - Get the bot's current key rates šŸ”‘", + '!stock - Get a list of items that the bot owns', + "!craftweapon - Get a list of the bot's craftable weapon stock šŸ”«", + "!uncraftweapon - Get a list of the bot's uncraftable weapon stock šŸ”«" + ].join('\n- ')}` + ); + } + + howToTradeCommand(steamID: SteamID): void { + const custom = this.bot.options.commands.how2trade.customReply.reply; + + this.bot.sendMessage( + steamID, + custom + ? custom + : '/quote You can either send me an offer yourself, or use one of my commands to request a trade. ' + + 'Say you want to buy a Team Captain, just type "!buy Team Captain", if want to buy more, ' + + 'just add the [amount] - "!buy 2 Team Captain". Type "!help" for all the commands.' + + '\nYou can also buy or sell multiple items by using the "!buycart [amount] " or ' + + '"!sellcart [amount] " commands.' + ); + } +} diff --git a/src/classes/Commands/sub-classes/Manager.ts b/src/classes/Commands/sub-classes/Manager.ts new file mode 100644 index 000000000..e7eccf437 --- /dev/null +++ b/src/classes/Commands/sub-classes/Manager.ts @@ -0,0 +1,666 @@ +import SteamID from 'steamid'; +import SKU from 'tf2-sku-2'; +import pluralize from 'pluralize'; +import Currencies from 'tf2-currencies'; +import validUrl from 'valid-url'; +import child from 'child_process'; +import fs from 'graceful-fs'; +import sleepasync from 'sleep-async'; +import path from 'path'; +import { EPersonaState } from 'steam-user'; +import { testSKU } from '../functions/utils'; +import Bot from '../../Bot'; +import CommandParser from '../../CommandParser'; +import log from '../../../lib/logger'; +import { pure } from '../../../lib/tools/export'; + +// Bot manager commands + +type TF2GC = 'expand' | 'use' | 'delete'; +type NameAvatar = 'name' | 'avatar'; +type BlockUnblock = 'block' | 'unblock'; + +export default class ManagerCommands { + private readonly bot: Bot; + + constructor(bot: Bot) { + this.bot = bot; + } + + TF2GCCommand(steamID: SteamID, message: string, command: TF2GC): void { + const params = CommandParser.parseParams(CommandParser.removeCommand(message)); + + if (command === 'expand') { + // Expand command + if (typeof params.craftable !== 'boolean') { + return this.bot.sendMessage(steamID, 'āš ļø Missing `craftable=true|false`'); + } + + const item = SKU.fromString('5050;6'); + if (params.craftable === false) { + item.craftable = false; + } + + const assetids = this.bot.inventoryManager.getInventory.findBySKU(SKU.fromObject(item), false); + if (assetids.length === 0) { + // No backpack expanders + return this.bot.sendMessage( + steamID, + `āŒ I couldn't find any ${!item.craftable ? 'Non-Craftable' : ''} Backpack Expander` + ); + } + + this.bot.tf2gc.useItem(assetids[0], err => { + if (err) { + log.warn('Error trying to expand inventory: ', err); + return this.bot.sendMessage(steamID, `āŒ Failed to expand inventory: ${err.message}`); + } + + this.bot.sendMessage(steamID, `āœ… Used ${!item.craftable ? 'Non-Craftable' : ''} Backpack Expander!`); + }); + } else { + // For use and delete commands + if (params.sku !== undefined && !testSKU(params.sku as string)) { + return this.bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); + } + + if (params.assetid !== undefined && params.sku === undefined) { + const targetedAssetId = params.assetid as string; + const sku = this.bot.inventoryManager.getInventory.findByAssetid(targetedAssetId); + + if (params.i_am_sure !== 'yes_i_am') { + return this.bot.sendMessage( + steamID, + `āš ļø Are you sure that you want to ${command} ${ + sku === null + ? `the item with asset ID ${targetedAssetId}` + : `${this.bot.schema.getName(SKU.fromString(sku), false)}` + }?` + + `\n- This process is irreversible and will ${command} the item from your bot's backpack!` + + `\n- If you are sure, try again with i_am_sure=yes_i_am as a parameter` + ); + } + + return this.bot.tf2gc[command === 'use' ? 'useItem' : 'deleteItem'](targetedAssetId, err => { + const theItem = + sku === null + ? targetedAssetId + : `${this.bot.schema.getName(SKU.fromString(sku), false)} (${targetedAssetId})`; + + if (err) { + log.warn(`Error trying to ${command} ${theItem}: `, err); + return this.bot.sendMessage(steamID, `āŒ Failed to ${command} ${theItem}: ${err.message}`); + } + + this.bot.sendMessage(steamID, `āœ… ${command === 'use' ? 'Used' : 'Deleted'} ${theItem}!`); + }); + } + + if (params.name !== undefined || params.item !== undefined) { + return this.bot.sendMessage( + steamID, + command === 'use' + ? 'āš ļø Please only use sku property.' + + '\n\nBelow are some common items to use:\n ā€¢ ' + + [ + 'Gift-Stuffed Stocking 2013: 5718;6;untradable', + 'Gift-Stuffed Stocking 2017: 5886;6;untradable', + 'Gift-Stuffed Stocking 2018: 5900;6;untradable', + 'Gift-Stuffed Stocking 2019: 5910;6;untradable', + 'Gift-Stuffed Stocking 2020: 5923;6;untradable' + ].join('\nā€¢ ') + : 'āš ļø Please only use sku property.' + + '\n\nBelow are some common items to delete:\n ā€¢ ' + + [ + 'Smissmas Sweater: 16391;15;untradable;w1;pk391', + 'Soul Gargoyle: 5826;6;uncraftable;untradable', + 'Noise Maker - TF Birthday: 536;6;untradable', + 'Bronze Dueling Badge: 242;6;untradable', + 'Silver Dueling Badge: 243;6;untradable', + 'Gold Dueling Badge: 244;6;untradable', + 'Platinum Dueling Badge: 245;6;untradable', + 'Mercenary: 166;6;untradable', + 'Soldier of Fortune: 165;6;untradable', + 'Grizzled Veteran: 164;6;untradable', + 'Primeval Warrior: 170;6;untradable', + 'Professor Speks: 343;6;untradable', + 'Mann Co. Cap: 261;6;untradable', + 'Mann Co. Online Cap: 994;6;untradable', + 'Proof of Purchase: 471;6;untradable', + 'Mildly Disturbing Halloween Mask: 115;6;untradable', + 'Seal Mask: 582;6;untradable', + 'Pyrovision Goggles: 743;6;untradable', + 'Giftapult: 5083;6;untradable', + 'Spirit Of Giving: 655;11;untradable', + 'Party Hat: 537;6;untradable', + 'Name Tag: 5020;6;untradable', + 'Description Tag: 5044;6;untradable', + 'Ghastly Gibus: 584;6;untradable', + 'Ghastlier Gibus: 279;6;untradable', + 'Power Up Canteen: 489;6;untradable', + 'Bombinomicon: 583;6;untradable', + 'Skull Island Topper: 941;6;untradable', + 'Spellbook Page: 8935;6;untradable', + 'Gun Mettle Campaign Coin: 5809;6;untradable', + 'MONOCULUS!: 581;6;untradable' + ].join('\nā€¢ ') + ); + } + + if (params.sku === undefined) { + return this.bot.sendMessage( + steamID, + `āš ļø Missing sku property. Example: "!${command} sku=5923;6;untradable"` + ); + } + + const targetedSKU = params.sku as string; + const [uncraft, untrade] = [targetedSKU.includes(';uncraftable'), targetedSKU.includes(';untradable')]; + + const item = SKU.fromString(targetedSKU.replace(';uncraftable', '').replace(';untradable', '')); + + if (uncraft) { + item.craftable = !uncraft; + } + if (untrade) { + item.tradable = !untrade; + } + + const assetids = this.bot.inventoryManager.getInventory.findBySKU(SKU.fromObject(item), false); + const name = this.bot.schema.getName(item, false); + + if (assetids.length === 0) { + // Item not found + return this.bot.sendMessage(steamID, `āŒ I couldn't find any ${pluralize(name, 0)}`); + } + + let assetid: string; + if (params.assetid !== undefined) { + const targetedAssetId = params.assetid as string; + + if (assetids.includes(targetedAssetId)) { + assetid = targetedAssetId; + } else { + return this.bot.sendMessage( + steamID, + `āŒ Looks like an assetid ${targetedAssetId} did not match any assetids associated with ${name}` + + ` in my inventory. Try using the sku to use a random assetid.` + ); + } + } else { + assetid = assetids[0]; + } + + if (params.i_am_sure !== 'yes_i_am') { + return this.bot.sendMessage( + steamID, + `/pre āš ļø Are you sure that you want to ${command} ${name}?` + + `\n- This process is irreversible and will ${command} the item from your bot's backpack!` + + `\n- If you are sure, try again with i_am_sure=yes_i_am as a parameter` + ); + } + + this.bot.tf2gc[command === 'use' ? 'useItem' : 'deleteItem'](assetid, err => { + if (err) { + log.warn(`Error trying to ${command} ${name}: `, err); + return this.bot.sendMessage( + steamID, + `āŒ Failed to ${command} ${name} (${assetid}): ${err.message}` + ); + } + + this.bot.sendMessage(steamID, `āœ… ${command === 'use' ? 'Used' : 'Deleted'} ${name} (${assetid})!`); + }); + } + } + + nameAvatarCommand(steamID: SteamID, message: string, command: NameAvatar): void { + const example = + 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/f5/f57685d33224e32436f366d1acb4a1769bdfa60f_full.jpg'; + const input = CommandParser.removeCommand(message); + + if (!input || input === `!${command}`) { + return this.bot.sendMessage( + steamID, + `āŒ You forgot to add ${command === 'name' ? 'a name' : 'an image url'}. Example: "!${ + command === 'name' ? 'name IdiNium' : `avatar ${example}` + } "` + ); + } + + if (command === 'name') { + this.bot.community.editProfile( + { + name: input + }, + err => { + if (err) { + log.warn('Error while changing name: ', err); + return this.bot.sendMessage(steamID, `āŒ Error while changing name: ${err.message}`); + } + + this.bot.sendMessage(steamID, 'āœ… Successfully changed name.'); + } + ); + } else { + if (!validUrl.isUri(input)) { + return this.bot.sendMessage(steamID, `āŒ Your url is not valid. Example: "!avatar ${example}"`); + } + + this.bot.community.uploadAvatar(input, err => { + if (err) { + log.warn('Error while uploading new avatar: ', err); + return this.bot.sendMessage(steamID, `āŒ Error while uploading a new avatar: ${err.message}`); + } + + this.bot.sendMessage(steamID, 'āœ… Successfully uploaded a new avatar.'); + }); + } + } + + blockUnblockCommand(steamID: SteamID, message: string, command: BlockUnblock): void { + const steamid = CommandParser.removeCommand(message); + + if (!steamid || steamid === `!${command}`) { + return this.bot.sendMessage( + steamID, + `āŒ You forgot to add their SteamID64. Example: "!${command} 76561198798404909"` + ); + } + + const targetSteamID64 = new SteamID(steamid); + if (!targetSteamID64.isValid()) { + return this.bot.sendMessage(steamID, `āŒ SteamID is not valid. Example: "!${command} 76561198798404909"`); + } + + this.bot.client[command === 'block' ? 'blockUser' : 'unblockUser'](targetSteamID64, err => { + if (err) { + log.warn(`Failed to ${command} user ${targetSteamID64.getSteamID64()}: `, err); + return this.bot.sendMessage( + steamID, + `āŒ Failed to ${command} user ${targetSteamID64.getSteamID64()}: ${err.message}` + ); + } + this.bot.sendMessage( + steamID, + `āœ… Successfully ${ + command === 'block' ? 'blocked' : 'unblocked' + } user ${targetSteamID64.getSteamID64()}` + ); + }); + } + + async clearFriendsCommand(steamID: SteamID): Promise { + const friendsToRemove = this.bot.friends.getFriends.filter( + steamid => !this.bot.handler.friendsToKeep.includes(steamid) + ); + + for (const steamid of friendsToRemove) { + this.bot.sendMessage( + steamid, + this.bot.options.customMessage.clearFriends + ? this.bot.options.customMessage.clearFriends + : `/quote Hey ${ + this.bot.friends.getFriend(steamid).player_name + }! My owner has performed friend list clearance. Please feel free to add me again if you want to trade at a later time!` + ); + + this.bot.client.removeFriend(steamid); + + // Prevent Steam from detecting the bot as spamming + await sleepasync().Promise.sleep(2 * 1000); + } + + this.bot.sendMessage(steamID, `āœ… Friendlist clearance success! Removed ${friendsToRemove.length} friends.`); + } + + stopCommand(steamID: SteamID): void { + this.bot.sendMessage(steamID, 'āŒ› Stopping...'); + + this.bot.botManager.stopProcess().catch(err => { + log.warn('Error occurred while trying to stop: ', err); + this.bot.sendMessage(steamID, `āŒ An error occurred while trying to stop: ${(err as Error).message}`); + }); + } + + restartCommand(steamID: SteamID): void { + this.bot.sendMessage(steamID, 'āŒ› Restarting...'); + + this.bot.botManager + .restartProcess() + .then(restarting => { + if (!restarting) { + this.bot.sendMessage( + steamID, + 'āŒ You are not running the bot with PM2! Get a VPS and run ' + + 'your bot with PM2: https://github.com/idinium96/tf2autobot/wiki/Getting-a-VPS' + ); + } + }) + .catch(err => { + log.warn('Error occurred while trying to restart: ', err); + this.bot.sendMessage( + steamID, + `āŒ An error occurred while trying to restart: ${(err as Error).message}` + ); + }); + } + + updaterepoCommand(steamID: SteamID, message: string): void { + if (!fs.existsSync(path.resolve(__dirname, '..', '..', '..', '..', '.git'))) { + return this.bot.sendMessage(steamID, 'āŒ You did not clone the bot from Github.'); + } + + if (process.env.pm_id === undefined) { + return this.bot.sendMessage( + steamID, + `āŒ You're not running the bot with PM2!` + + `\n\nNavigate to your bot folder and run ` + + `[git reset HEAD --hard && git checkout master && git pull && npm install && npm run build] ` + + `and then restart your bot.` + ); + } + + const params = CommandParser.parseParams(CommandParser.removeCommand(message)); + if (params.i_am_sure !== 'yes_i_am') { + this.bot.sendMessage( + steamID, + `Currently running TF2Autobot@v${process.env.BOT_VERSION}. Checking for a new version...` + ); + + this.bot.checkForUpdates + .then(({ hasNewVersion, latestVersion }) => { + if (!hasNewVersion) { + return this.bot.sendMessage(steamID, 'You are running the latest version of TF2Autobot!'); + } else if (this.bot.lastNotifiedVersion === latestVersion) { + return this.bot.sendMessage( + steamID, + `āš ļø Update available! Current: v${process.env.BOT_VERSION}, Latest: v${latestVersion}.` + + '\nSend "!updaterepo i_am_sure=yes_i_am" to update your repo now!' + + `\n\nRelease note: https://github.com/idinium96/tf2autobot/releases` + ); + } + }) + .catch(err => this.bot.sendMessage(steamID, `āŒ Failed to check for updates: ${JSON.stringify(err)}`)); + } else { + this.bot.sendMessage(steamID, 'āŒ› Updating...'); + // Make the bot snooze on Steam, that way people will know it is not running + this.bot.client.setPersona(EPersonaState.Snooze); + + // Set isUpdating status, so any command will not be processed + this.bot.handler.isUpdatingStatus = true; + + // Stop polling offers + this.bot.manager.pollInterval = -1; + + // Callback hell šŸ˜ˆ + + // git reset HEAD --hard + child.exec('git reset HEAD --hard', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { + // ignore err + + // git checkout master + child.exec('git checkout master', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { + // ignore err + + this.bot.sendMessage(steamID, 'āŒ› Pulling changes...'); + + // git pull + child.exec('git pull', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { + // ignore err + + void promiseDelay(3 * 1000); + + this.bot.sendMessage(steamID, 'āŒ› Installing packages...'); + + // npm install + child.exec('npm install', { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, () => { + // ignore err + + // 10 seconds delay, because idk why this always cause some problem + void promiseDelay(10 * 1000); + + this.bot.sendMessage(steamID, 'āŒ› Compiling TypeScript codes into JavaScript...'); + + // tsc -p . + child.exec( + 'npm run build', + { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, + () => { + // ignore err + + // 5 seconds delay? + void promiseDelay(5 * 1000); + + this.bot.sendMessage(steamID, 'āŒ› Restarting...'); + + child.exec( + 'pm2 restart ecosystem.json', + { cwd: path.resolve(__dirname, '..', '..', '..', '..') }, + () => { + // ignore err + } + ); + } + ); + }); + }); + }); + }); + } + } + + autokeysCommand(steamID: SteamID): void { + const opt = this.bot.options.commands.autokeys; + if (!opt.enable) { + if (!this.bot.isAdmin(steamID)) { + const custom = opt.customReply.disabled; + return this.bot.sendMessage(steamID, custom ? custom : 'āŒ This command is disabled by the owner.'); + } + } + + this.bot.sendMessage(steamID, '/pre ' + this.generateAutokeysReply(steamID, this.bot)); + } + + refreshAutokeysCommand(steamID: SteamID): void { + if (this.bot.handler.autokeys.isEnabled === false) { + return this.bot.sendMessage(steamID, `This feature is disabled.`); + } + + this.bot.handler.autokeys.refresh(); + this.bot.sendMessage(steamID, 'āœ… Successfully refreshed Autokeys.'); + } + + refreshListingsCommand(steamID: SteamID): void { + const listingsSKUs: string[] = []; + this.bot.listingManager.getListings(async err => { + if (err) { + return this.bot.sendMessage( + steamID, + 'āŒ Unable to refresh listings, please try again later: ' + JSON.stringify(err) + ); + } + this.bot.listingManager.listings.forEach(listing => { + let listingSKU = listing.getSKU(); + if (listing.intent === 1) { + if (this.bot.options.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) { + listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); + } + + if (this.bot.options.normalize.festivized.our && listingSKU.includes(';festive')) { + listingSKU = listingSKU.replace(';festive', ''); + } + + if (this.bot.options.normalize.strangeAsSecondQuality.our && listingSKU.includes(';strange')) { + listingSKU = listingSKU.replace(';strange', ''); + } + } else { + if (/;[p][0-9]+/.test(listingSKU)) { + listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); + } + } + + listingsSKUs.push(listingSKU); + }); + + // Remove duplicate elements + const newlistingsSKUs: string[] = []; + listingsSKUs.forEach(sku => { + if (!newlistingsSKUs.includes(sku)) { + newlistingsSKUs.push(sku); + } + }); + + const inventory = this.bot.inventoryManager; + const pricelist = this.bot.pricelist.getPrices.filter(entry => { + // First find out if lising for this item from bptf already exist. + const isExist = newlistingsSKUs.find(sku => entry.sku === sku); + + if (!isExist) { + // undefined - listing does not exist but item is in the pricelist + + // Get amountCanBuy and amountCanSell (already cover intent and so on) + const amountCanBuy = inventory.amountCanTrade(entry.sku, true); + const amountCanSell = inventory.amountCanTrade(entry.sku, false); + + if ( + (amountCanBuy > 0 && inventory.isCanAffordToBuy(entry.buy, inventory.getInventory)) || + amountCanSell > 0 + ) { + // if can amountCanBuy is more than 0 and isCanAffordToBuy is true OR amountCanSell is more than 0 + // return this entry + return true; + } + + // Else ignore + return false; + } + + // Else if listing already exist on backpack.tf, ignore + return false; + }); + + if (pricelist.length > 0) { + log.debug( + 'Checking listings for ' + + pluralize('item', pricelist.length, true) + + ` [${pricelist.map(entry => entry.sku).join(', ')}] ...` + ); + + this.bot.sendMessage( + steamID, + 'Refreshing listings for ' + pluralize('item', pricelist.length, true) + '...' + ); + + await this.bot.listings.recursiveCheckPricelist(pricelist, true); + + log.debug('Done checking ' + pluralize('item', pricelist.length, true)); + this.bot.sendMessage(steamID, 'āœ… Done refreshing ' + pluralize('item', pricelist.length, true)); + } else { + this.bot.sendMessage(steamID, 'āŒ Nothing to refresh.'); + } + }); + } + + private generateAutokeysReply(steamID: SteamID, bot: Bot): string { + const pureNow = pure.currPure(bot); + const currKey = pureNow.key; + const currRef = pureNow.refTotalInScrap; + + const keyPrices = bot.pricelist.getKeyPrices; + + const autokeys = bot.handler.autokeys; + const userPure = autokeys.userPure; + const status = autokeys.getOverallStatus; + + const keyBlMin = ` X`; + const keyAbMax = ` X`; + const keyAtBet = ` X`; + const keyAtMin = ` X`; + const keyAtMax = ` X`; + const keysLine = `Keys ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā–¶`; + const refBlMin = ` X`; + const refAbMax = ` X`; + const refAtBet = ` X`; + const refAtMin = ` X`; + const refAtMax = ` X`; + const refsLine = `Refs ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā–¶`; + const xAxisRef = ` min max`; + const keysPosition = + currKey < userPure.minKeys + ? keyBlMin + : currKey > userPure.maxKeys + ? keyAbMax + : currKey > userPure.minKeys && currKey < userPure.maxKeys + ? keyAtBet + : currKey === userPure.minKeys + ? keyAtMin + : currKey === userPure.maxKeys + ? keyAtMax + : ''; + const refsPosition = + currRef < userPure.minRefs + ? refBlMin + : currRef > userPure.maxRefs + ? refAbMax + : currRef > userPure.minRefs && currRef < userPure.maxRefs + ? refAtBet + : currRef === userPure.minRefs + ? refAtMin + : currRef === userPure.maxRefs + ? refAtMax + : ''; + const summary = `\nā€¢ ${userPure.minKeys} ā‰¤ ${pluralize('key', currKey)}(${currKey}) ā‰¤ ${ + userPure.maxKeys + }\nā€¢ ${Currencies.toRefined(userPure.minRefs)} < ${pluralize( + 'ref', + Currencies.toRefined(currRef) + )}(${Currencies.toRefined(currRef)}) < ${Currencies.toRefined(userPure.maxRefs)}`; + + let reply = + (bot.isAdmin(steamID) ? 'Your ' : 'My ') + + `current Autokeys settings:\n${summary}\n\nDiagram:\n${keysPosition}\n${keysLine}\n${refsPosition}\n${refsLine}\n${xAxisRef}\n`; + reply += `\n Key prices: ${keyPrices.buy.toString()}/${keyPrices.sell.toString()} (${ + keyPrices.src === 'manual' ? 'manual' : 'prices.tf' + })`; + + const scrapAdjustmentEnabled = autokeys.isEnableScrapAdjustment; + const scrapAdjustmentValue = autokeys.scrapAdjustmentValue; + const keyBankingEnabled = autokeys.isKeyBankingEnabled; + + reply += `\nScrap Adjustment: ${scrapAdjustmentEnabled ? 'Enabled āœ…' : 'Disabled āŒ'}`; + reply += `\n Auto-banking: ${keyBankingEnabled ? 'Enabled āœ…' : 'Disabled āŒ'}`; + reply += `\n Autokeys status: ${ + autokeys.getActiveStatus + ? status.isBankingKeys + ? 'Banking' + (scrapAdjustmentEnabled ? ' (default price)' : '') + : status.isBuyingKeys + ? 'Buying for ' + + Currencies.toRefined( + keyPrices.buy.toValue() + (scrapAdjustmentEnabled ? scrapAdjustmentValue : 0) + ).toString() + + ' ref' + + (scrapAdjustmentEnabled ? ` (+${scrapAdjustmentValue} scrap)` : '') + : 'Selling for ' + + Currencies.toRefined( + keyPrices.sell.toValue() - (scrapAdjustmentEnabled ? scrapAdjustmentValue : 0) + ).toString() + + ' ref' + + (scrapAdjustmentEnabled ? ` (-${scrapAdjustmentValue} scrap)` : '') + : 'Not active' + }`; + /* + * X + * Keys ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā–¶ + * X + * Refs ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”|ā€”ā€”ā€”ā€”ā–¶ + * min max + */ + + return reply; + } +} + +function promiseDelay(ms: number): Promise { + return new Promise(resolve => setTimeout(() => resolve(), ms)); +} diff --git a/src/classes/Commands/sub-classes/Message.ts b/src/classes/Commands/sub-classes/Message.ts new file mode 100644 index 000000000..38d111844 --- /dev/null +++ b/src/classes/Commands/sub-classes/Message.ts @@ -0,0 +1,163 @@ +import SteamID from 'steamid'; +import Bot from '../../Bot'; +import CommandParser from '../../CommandParser'; +import { generateLinks, timeNow } from '../../../lib/tools/export'; +import { sendPartnerMessage, sendAdminMessage } from '../../../lib/DiscordWebhook/export'; + +export default class MessageCommand { + private readonly bot: Bot; + + constructor(bot: Bot) { + this.bot = bot; + } + + message(steamID: SteamID, message: string): void { + const isAdmin = this.bot.isAdmin(steamID); + const custom = this.bot.options.commands.message.customReply; + if (!this.bot.options.commands.enable) { + if (isAdmin) { + this.bot.sendMessage( + steamID, + 'āŒ The message command is disabled. Enable it by sending `!config commands.message.enable=true`.' + ); + } else { + this.bot.sendMessage( + steamID, + custom.disabled ? custom.disabled : 'āŒ The owner has disabled messages.' + ); + } + return; + } + + const senderDetails = this.bot.friends.getFriend(steamID); + const optDW = this.bot.options.discordWebhook.messages; + + if (isAdmin) { + const steamIdAndMessage = CommandParser.removeCommand(message); + const parts = steamIdAndMessage.split(' '); + let recipientSteamID: SteamID; + + try { + recipientSteamID = new SteamID(parts[0]); + } catch (err) { + return this.bot.sendMessage( + steamID, + 'āŒ Your syntax is wrong or the SteamID is incorrectly formatted. Here\'s an example: "!message 76561198120070906 Hi"' + + "\n\nHow to get the targeted user's SteamID?" + + '\n1. Go to his/her profile page.' + + '\n2. Go to https://steamrep.com/' + + '\n3. View this gif: https://user-images.githubusercontent.com/47635037/96715154-be80b580-13d5-11eb-9bd5-39613f600f6d.gif' + ); + } + + const steamIDString = recipientSteamID.getSteamID64(); + + if (!recipientSteamID.isValid()) { + return this.bot.sendMessage( + steamID, + `āŒ "${steamIDString}" is not a valid SteamID.` + + "\n\nHow to get the targeted user's SteamID?" + + '\n1. Go to his/her profile page.' + + '\n2. Go to https://steamrep.com/' + + '\n3. View this gif: https://user-images.githubusercontent.com/47635037/96715154-be80b580-13d5-11eb-9bd5-39613f600f6d.gif' + ); + } else if (!this.bot.friends.isFriend(recipientSteamID)) { + return this.bot.sendMessage(steamID, `āŒ I am not friends with the user.`); + } + + const recipientDetails = this.bot.friends.getFriend(recipientSteamID); + const reply = steamIdAndMessage.substr(steamIDString.length); + + // Send message to recipient + this.bot.sendMessage( + recipientSteamID, + custom.fromOwner + ? custom.fromOwner.replace(/%reply%/g, reply) + : `/quote šŸ’¬ Message from the owner: ${reply}` + + '\n\nā” Hint: You can use the !message command to respond to the owner of this bot.' + + '\nExample: !message Hi Thanks!' + ); + + // Send a notification to the admin with message contents & details + if (optDW.enable && optDW.url !== '') { + sendAdminMessage( + recipientSteamID.toString(), + reply, + recipientDetails, + generateLinks(steamID.toString()), + timeNow(this.bot.options).time, + this.bot + ); + } else { + const customInitializer = this.bot.options.steamChat.customInitializer.message.toOtherAdmins; + this.bot.messageAdmins( + `${ + customInitializer ? customInitializer : '/quote' + } šŸ’¬ Message sent to #${recipientSteamID.toString()} (${ + recipientDetails.player_name + }): "${reply}". `, + [] + ); + } + + this.bot.sendMessage(steamID, custom.success ? custom.success : 'āœ… Your message has been sent.'); + + // Send message to all other admins that an admin replied + return this.bot.messageAdmins( + `${ + senderDetails ? `${senderDetails.player_name} (${steamID.toString()})` : steamID.toString() + } sent a message to ${ + recipientDetails + ? recipientDetails.player_name + ` (${recipientSteamID.toString()})` + : recipientSteamID.toString() + } with "${reply}".`, + [steamID] + ); + } else { + const admins = this.bot.getAdmins; + if (!admins || admins.length === 0) { + // Just default to same message as if it was disabled + return this.bot.sendMessage( + steamID, + custom.disabled ? custom.disabled : 'āŒ The owner has disabled messages.' + ); + } + + const msg = message.substr(message.toLowerCase().indexOf('message') + 8); + if (!msg) { + return this.bot.sendMessage( + steamID, + custom.wrongSyntax + ? custom.wrongSyntax + : 'āŒ Please include a message. Here\'s an example: "!message Hi"' + ); + } + + const links = generateLinks(steamID.toString()); + if (optDW.enable && optDW.url !== '') { + sendPartnerMessage( + steamID.toString(), + msg, + senderDetails, + links, + timeNow(this.bot.options).time, + this.bot + ); + } else { + const customInitializer = this.bot.options.steamChat.customInitializer.message.onReceive; + this.bot.messageAdmins( + `${ + customInitializer ? customInitializer : '/quote' + } šŸ’¬ You've got a message from #${steamID.toString()} (${senderDetails.player_name}):` + + `"${msg}". ` + + `\nSteam: ${links.steam}` + + `\nBackpack.tf: ${links.bptf}` + + `\nSteamREP: ${links.steamrep}`, + [] + ); + } + + this.bot.sendMessage(steamID, custom.success ? custom.success : 'āœ… Your message has been sent.'); + } + } +} diff --git a/src/classes/Commands/sub-classes/Misc.ts b/src/classes/Commands/sub-classes/Misc.ts new file mode 100644 index 000000000..e294b4fbb --- /dev/null +++ b/src/classes/Commands/sub-classes/Misc.ts @@ -0,0 +1,254 @@ +import SteamID from 'steamid'; +import SKU from 'tf2-sku-2'; +import pluralize from 'pluralize'; +import Bot from '../../Bot'; +import { Discord, Stock } from '../../Options'; +import { pure, timeNow, uptime } from '../../../lib/tools/export'; + +type Misc = 'time' | 'uptime' | 'pure' | 'rate' | 'owner' | 'discord' | 'stock'; +type CraftUncraft = 'craftweapon' | 'uncraftweapon'; + +export default class MiscCommands { + private readonly bot: Bot; + + constructor(bot: Bot) { + this.bot = bot; + } + + miscCommand(steamID: SteamID, command: Misc): void { + const opt = this.bot.options.commands[command]; + if (!opt.enable) { + if (!this.bot.isAdmin(steamID)) { + const custom = opt.customReply.disabled; + return this.bot.sendMessage(steamID, custom ? custom : 'āŒ This command is disabled by the owner.'); + } + } + + const custom = opt.customReply.reply; + if (command === 'time') { + const timeWithEmojis = timeNow(this.bot.options); + this.bot.sendMessage( + steamID, + custom + ? custom + .replace(/%emoji%/g, timeWithEmojis.emoji) + .replace(/%time%/g, timeWithEmojis.time) + .replace(/%note%/g, timeWithEmojis.note) + : `It is currently the following time in my owner's timezone: ${timeWithEmojis.emoji} ${ + timeWithEmojis.time + (timeWithEmojis.note !== '' ? `.\n\n${timeWithEmojis.note}.` : '.') + }` + ); + } else if (command === 'uptime') { + const botUptime = uptime(); + this.bot.sendMessage(steamID, custom ? custom.replace(/%uptime%/g, botUptime) : botUptime); + } else if (command === 'pure') { + const pureStock = pure.stock(this.bot); + this.bot.sendMessage( + steamID, + custom + ? custom.replace(/%pure%/g, pureStock.join(' and ')) + : `šŸ’° I have ${pureStock.join(' and ')} in my inventory.` + ); + } else if (command === 'rate') { + const key = this.bot.pricelist.getKeyPrices; + const keyRate = key.sell.toString(); + const source = key.src === 'manual' ? 'manual' : 'https://api.prices.tf/items/5021;6?src=bptf'; + + this.bot.sendMessage( + steamID, + custom + ? custom + .replace(/%keyRate%/g, keyRate) + .replace(/%keyPrices%/g, `${key.buy.metal} / ${key.sell.toString()}`) + .replace(/%source%/g, source) + : 'I value šŸ”‘ Mann Co. Supply Crate Keys at ' + + keyRate + + '. This means that one key is the same as ' + + keyRate + + ', and ' + + keyRate + + ' is the same as one key.' + + `\n\nKey rate source: ${source}` + ); + } else if (command === 'owner') { + const firstAdmin = this.bot.getAdmins[0]; + const steamURL = `https://steamcommunity.com/profiles/${firstAdmin.toString()}`; + const bptfURL = `https://backpack.tf/profiles/${firstAdmin.toString()}`; + + this.bot.sendMessage( + steamID, + custom + ? custom + .replace(/%steamurl%/g, steamURL) + .replace(/%bptfurl%/g, bptfURL) + .replace(/%steamid%/, firstAdmin.toString()) + : `ā€¢ Steam: ${steamURL}\nā€¢ Backpack.tf: ${bptfURL}` + ); + } else if (command === 'discord') { + const inviteURL = (opt as Discord).inviteURL; + let reply: string; + if (custom) { + reply = + 'TF2Autobot Discord Server: https://discord.gg/D2GNnp7tv8\n\n' + + custom.replace(/%discordurl%/g, inviteURL); + } else { + if (inviteURL) { + reply = `TF2Autobot Discord Server: https://discord.gg/D2GNnp7tv8\nOwner's Discord Server: ${inviteURL}`; + // + } else reply = 'TF2Autobot Discord Server: https://discord.gg/D2GNnp7tv8'; + } + this.bot.sendMessage(steamID, reply); + } else { + const inventory = this.bot.inventoryManager.getInventory; + const dict = inventory.getItems; + const items: { amount: number; name: string }[] = []; + + for (const sku in dict) { + if (!Object.prototype.hasOwnProperty.call(dict, sku)) { + continue; + } + + if (['5021;6', '5002;6', '5001;6', '5000;6'].includes(sku)) { + continue; + } + + items.push({ + name: this.bot.schema.getName(SKU.fromString(sku), false), + amount: dict[sku].length + }); + } + + items.sort((a, b) => { + if (a.amount === b.amount) { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } else { + return 0; + } + } + return b.amount - a.amount; + }); + + const pure = [ + { + name: 'Mann Co. Supply Crate Key', + amount: inventory.getAmount('5021;6') + }, + { + name: 'Refined Metal', + amount: inventory.getAmount('5002;6') + }, + { + name: 'Reclaimed Metal', + amount: inventory.getAmount('5001;6') + }, + { + name: 'Scrap Metal', + amount: inventory.getAmount('5000;6') + } + ]; + + const parsed = pure.concat(items); + + const stock: string[] = []; + let left = 0; + + const max = (opt as Stock).maximumItems; + + for (let i = 0; i < parsed.length; i++) { + if (stock.length > max) { + left += parsed[i].amount; + } else { + stock.push(`${parsed[i].name}: ${parsed[i].amount}`); + } + } + + const custom = opt.customReply.reply; + let reply = custom + ? custom.replace(/%stocklist%/g, stock.join(', \n')) + : `/pre šŸ“œ Here's a list of all the items that I have in my inventory:\n${stock.join(', \n')}`; + + if (left > 0) { + reply += `,\nand ${left} other ${pluralize('item', left)}`; + } + + this.bot.sendMessage(steamID, reply); + } + } + + weaponCommand(steamID: SteamID, type: CraftUncraft): void { + const opt = this.bot.options.commands[type]; + if (!opt.enable) { + if (!this.bot.isAdmin(steamID)) { + const custom = opt.customReply.disabled; + return this.bot.sendMessage(steamID, custom ? custom : 'āŒ This command is disabled by the owner.'); + } + } + + const weaponStock = this.getWeaponsStock( + this.bot, + type === 'craftweapon' ? this.bot.craftWeapons : this.bot.uncraftWeapons + ); + + let reply: string; + if (weaponStock.length > 0) { + const custom = opt.customReply.have; + reply = custom + ? custom.replace(/%list%/g, weaponStock.join(', \n')) + : `šŸ“ƒ Here's a list of all ${ + type === 'craftweapon' ? 'craft' : 'uncraft' + } weapons stock in my inventory:\n\n` + weaponStock.join(', \n'); + } else { + const custom = opt.customReply.dontHave; + reply = custom + ? custom + : `āŒ I don't have any ${ + type === 'craftweapon' ? 'craftable' : 'uncraftable' + } weapons in my inventory.`; + } + + this.bot.sendMessage(steamID, reply); + } + + paintsCommand(steamID: SteamID): void { + this.bot.sendMessage(steamID, '/code ' + JSON.stringify(this.bot.paints, null, 4)); + } + + private getWeaponsStock(bot: Bot, type: string[]): string[] { + const items: { amount: number; name: string }[] = []; + + type.forEach(sku => { + const amount = bot.inventoryManager.getInventory.getAmount(sku); + if (amount > 0) { + items.push({ + name: bot.schema.getName(SKU.fromString(sku), false), + amount: amount + }); + } + }); + + items.sort((a, b) => { + if (a.amount === b.amount) { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } else { + return 0; + } + } + return b.amount - a.amount; + }); + + const stock: string[] = []; + + if (items.length > 0) { + for (let i = 0; i < items.length; i++) { + stock.push(`${items[i].name}: ${items[i].amount}`); + } + } + return stock; + } +} diff --git a/src/classes/Commands/sub-classes/Options.ts b/src/classes/Commands/sub-classes/Options.ts new file mode 100644 index 000000000..657e90d5b --- /dev/null +++ b/src/classes/Commands/sub-classes/Options.ts @@ -0,0 +1,280 @@ +import SteamID from 'steamid'; +import { promises as fsp } from 'fs'; +import sleepasync from 'sleep-async'; +import Bot from '../../Bot'; +import CommandParser from '../../CommandParser'; +import { getOptionsPath, JsonOptions, removeCliOptions } from '../../Options'; +import validator from '../../../lib/validator'; +import log from '../../../lib/logger'; +import { deepMerge } from '../../../lib/tools/deep-merge'; + +export type OptionsKeys = + | 'miscSettings' + | 'sendAlert' + | 'pricelist' + | 'bypass' + | 'tradeSummary' + | 'steamChat' + | 'highValue' + | 'normalize' + | 'details' + | 'statistics' + | 'autokeys' + | 'crafting' + | 'offerReceived' + | 'manualReview' + | 'discordWebhook' + | 'customMessage' + | 'commands' + | 'detailsExtra'; + +export default class OptionsCommands { + private readonly bot: Bot; + + constructor(bot: Bot) { + this.bot = bot; + } + + async optionsCommand(steamID: SteamID, message: string): Promise { + const liveOptions = deepMerge({}, this.bot.options) as JsonOptions; + // remove any CLI stuff + removeCliOptions(liveOptions); + + const key = CommandParser.removeCommand(message); + + if (!key || key === '!options') { + this.bot.sendMessage( + steamID, + `/code ${JSON.stringify( + { + miscSettings: liveOptions.miscSettings, + sendAlert: liveOptions.sendAlert, + pricelist: liveOptions.pricelist, + bypass: liveOptions.bypass, + tradeSummary: liveOptions.tradeSummary + }, + null, + 4 + )}` + ); + + await sleepasync().Promise.sleep(1 * 1000); + + this.bot.sendMessage( + steamID, + `/code ${JSON.stringify( + { + steamChat: liveOptions.steamChat, + highValue: liveOptions.highValue, + normalize: liveOptions.normalize, + details: liveOptions.details, + statistics: liveOptions.statistics, + autokeys: liveOptions.autokeys, + crafting: liveOptions.crafting + }, + null, + 4 + )}` + ); + + await sleepasync().Promise.sleep(1 * 1000); + + this.bot.sendMessage( + steamID, + `/code ${JSON.stringify( + { + offerReceived: liveOptions.offerReceived, + manualReview: liveOptions.manualReview + }, + null, + 4 + )}` + ); + + await sleepasync().Promise.sleep(1 * 1000); + + this.bot.sendMessage( + steamID, + `/code ${JSON.stringify( + { + discordWebhook: liveOptions.discordWebhook, + customMessage: liveOptions.customMessage + }, + null, + 4 + )}` + ); + + await sleepasync().Promise.sleep(1 * 1000); + + this.bot.sendMessage(steamID, `/code ${JSON.stringify({ commands: liveOptions.commands }, null, 4)}`); + + await sleepasync().Promise.sleep(1 * 1000); + + this.bot.sendMessage( + steamID, + `/code ${JSON.stringify({ detailsExtra: liveOptions.detailsExtra }, null, 4)}` + ); + + this.bot.sendMessage( + steamID, + `\n\nYou can also get only the part the you want by sending "!options [OptionsParentKey]"` + ); + } else { + const optionsKeys = Object.keys(liveOptions); + + if (!optionsKeys.includes(key)) { + return this.bot.sendMessage( + steamID, + `āŒ "${key}" parent key does not exist in options.` + + `\n\nValid parent keys:\nā€¢ ` + + [ + 'miscSettings', + 'sendAlert', + 'pricelist', + 'bypass', + 'tradeSummary', + 'steamChat', + 'highValue', + 'normalize', + 'details', + 'statistics', + 'autokeys', + 'crafting', + 'offerReceived', + 'manualReview', + 'discordWebhook', + 'customMessage', + 'commands', + 'detailsExtra' + ].join('\nā€¢ ') + ); + } + + const show = {}; + + show[key] = liveOptions[key as OptionsKeys]; + + this.bot.sendMessage(steamID, `/code ${JSON.stringify(show, null, 4)}`); + } + } + + updateOptionsCommand(steamID: SteamID, message: string): void { + const opt = this.bot.options; + const params = CommandParser.parseParams(CommandParser.removeCommand(message)) as unknown; + + const optionsPath = getOptionsPath(opt.steamAccountName); + const saveOptions = deepMerge({}, opt) as JsonOptions; + removeCliOptions(saveOptions); + + if (Object.keys(params).length === 0) { + const msg = 'āš ļø Missing properties to update.'; + if (steamID) { + this.bot.sendMessage(steamID, msg); + } else { + log.warn(msg); + } + + return; + } + + const knownParams = params as JsonOptions; + + if (knownParams.discordWebhook?.ownerID !== undefined) { + // Stringify numbers + knownParams.discordWebhook.ownerID = String(knownParams.discordWebhook.ownerID); + } + + if (knownParams.discordWebhook?.embedColor !== undefined) { + // Stringify numbers + knownParams.discordWebhook.embedColor = String(knownParams.discordWebhook.embedColor); + } + + const result: JsonOptions = deepMerge(saveOptions, knownParams); + + const errors = validator(result, 'options'); + if (errors !== null) { + const msg = 'āŒ Error updating options: ' + errors.join(', '); + if (steamID) { + this.bot.sendMessage(steamID, msg); + } else { + log.error(msg); + } + + return; + } + + fsp.writeFile(optionsPath, JSON.stringify(saveOptions, null, 4), { encoding: 'utf8' }) + .then(() => { + deepMerge(opt, saveOptions); + const msg = 'āœ… Updated options!'; + + if (knownParams.miscSettings?.game?.playOnlyTF2 === true) { + this.bot.client.gamesPlayed([]); + this.bot.client.gamesPlayed(440); + } + + if (typeof knownParams.miscSettings?.game?.customName === 'string') { + this.bot.client.gamesPlayed([]); + this.bot.client.gamesPlayed( + ( + knownParams.miscSettings?.game?.playOnlyTF2 !== undefined + ? knownParams.miscSettings.game.playOnlyTF2 + : opt.miscSettings.game.playOnlyTF2 + ) + ? 440 + : [knownParams.miscSettings.game.customName, 440] + ); + } + + if (knownParams.miscSettings?.autobump?.enable === true) { + this.bot.listings.setupAutorelist(); + this.bot.handler.disableAutoRefreshListings(); + } else if (knownParams.miscSettings?.autobump?.enable === false) { + this.bot.listings.disableAutorelistOption(); + this.bot.handler.enableAutoRefreshListings(); + } + + if (knownParams.statistics?.sendStats?.enable === true) { + this.bot.handler.sendStats(); + } else if (knownParams.statistics?.sendStats?.enable === false) { + this.bot.handler.disableSendStats(); + } + + if (knownParams.statistics?.sendStats?.time !== undefined) { + this.bot.handler.sendStats(); + } + + if (typeof knownParams.highValue !== undefined) { + void this.bot.inventoryManager.getInventory.fetch(); + } + + if (typeof knownParams.normalize === 'object') { + void this.bot.inventoryManager.getInventory.fetch(); + } + + if (typeof knownParams.autokeys === 'object') { + if (knownParams.autokeys.enable !== undefined && !knownParams.autokeys.enable) { + this.bot.handler.autokeys.disable(this.bot.pricelist.getKeyPrices); + } + this.bot.handler.autokeys.check(); + } + + if (steamID) { + return this.bot.sendMessage(steamID, msg); + } else { + return log.info(msg); + } + }) + .catch(err => { + const msg = `āŒ Error saving options file to disk: ${JSON.stringify(err)}`; + if (steamID) { + this.bot.sendMessage(steamID, msg); + } else { + log.error(msg); + } + + return; + }); + } +} diff --git a/src/classes/Commands/sub-classes/PricelistManager.ts b/src/classes/Commands/sub-classes/PricelistManager.ts new file mode 100644 index 000000000..668ea1a5c --- /dev/null +++ b/src/classes/Commands/sub-classes/PricelistManager.ts @@ -0,0 +1,1335 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +import SteamID from 'steamid'; +import SKU from 'tf2-sku-2'; +import Currencies from 'tf2-currencies'; +import pluralize from 'pluralize'; +import dayjs from 'dayjs'; +import sleepasync from 'sleep-async'; +import { removeLinkProtocol, testSKU, getItemFromParams } from '../functions/utils'; +import Bot from '../../Bot'; +import CommandParser from '../../CommandParser'; +import { Entry, EntryData, PricelistChangedSource } from '../../Pricelist'; +import validator from '../../../lib/validator'; +import log from '../../../lib/logger'; + +// Pricelist manager + +export default class PricelistManagerCommands { + private readonly bot: Bot; + + private stopAutoAdd = false; + + private executed = false; + + private lastExecutedTime: number | null = null; + + private executeTimeout: NodeJS.Timeout; + + stopAutoAddCommand(): void { + this.stopAutoAdd = true; + } + + constructor(bot: Bot) { + this.bot = bot; + } + + addCommand(steamID: SteamID, message: string): void { + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); + + if (params.enabled === undefined) { + params.enabled = true; + } + + if (params.max === undefined) { + params.max = 1; + } + + if (params.min === undefined) { + params.min = 0; + } + + if (params.intent === undefined) { + params.intent = 2; + } else if (typeof params.intent === 'string') { + const intent = ['buy', 'sell', 'bank'].indexOf(params.intent.toLowerCase()); + + if (intent !== -1) { + params.intent = intent; + } + } + + if (typeof params.buy === 'object') { + params.buy.keys = params.buy.keys || 0; + params.buy.metal = params.buy.metal || 0; + + if (params.autoprice === undefined) { + params.autoprice = false; + } + } + if (typeof params.sell === 'object') { + params.sell.keys = params.sell.keys || 0; + params.sell.metal = params.sell.metal || 0; + + if (params.autoprice === undefined) { + params.autoprice = false; + } + } + + const isPremium = this.bot.handler.getBotInfo.premium; + if (params.promoted !== undefined) { + if (!isPremium) { + return this.bot.sendMessage( + steamID, + `āŒ This account is not Backpack.tf Premium. You can't use "promoted" parameter.` + ); + } + + if (typeof params.promoted === 'boolean') { + if (params.promoted === true) { + params.promoted = 1; + } else { + params.promoted = 0; + } + } else { + if (typeof params.promoted !== 'number') { + return this.bot.sendMessage( + steamID, + 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)' + ); + } else if (params.promoted < 0 || params.promoted > 1) { + return this.bot.sendMessage( + steamID, + 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)' + ); + } + } + } else { + params['promoted'] = 0; + } + + if (typeof params.note === 'object') { + params.note.buy = params.note.buy || null; + params.note.sell = params.note.sell || null; + } + + if (params.note === undefined) { + // If note parameter is not defined, set both note.buy and note.sell to null. + params['note'] = { buy: null, sell: null }; + } + + if (params.group && typeof params.group !== 'string') { + // if group parameter is defined, convert anything to string + params.group = (params.group as string).toString(); + } + + if (typeof params.group === undefined) { + // If group parameter is not defined, set it to null. + params['group'] = 'all'; + } + + if (params.autoprice === undefined) { + params.autoprice = true; + } + + if (params.sku !== undefined && !testSKU(params.sku as string)) { + return this.bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); + } + + if (params.sku === undefined) { + const item = getItemFromParams(steamID, params, this.bot); + + if (item === null) { + return this.bot.sendMessage(steamID, `āŒ Item not found.`); + } + + params.sku = SKU.fromObject(item); + } + + this.bot.pricelist + .addPrice(params as EntryData, true, PricelistChangedSource.Command) + .then(entry => { + this.bot.sendMessage( + steamID, + `āœ… Added "${entry.name}"` + this.generateAddedReply(this.bot, isPremium, entry) + ); + }) + .catch(err => { + this.bot.sendMessage(steamID, `āŒ Failed to add the item to the pricelist: ${(err as Error).message}`); + }); + } + + private generateAddedReply(bot: Bot, isPremium: boolean, entry: Entry): string { + const amount = bot.inventoryManager.getInventory.getAmount(entry.sku); + return ( + `\nšŸ’² Buy: ${entry.buy.toString()} | Sell: ${entry.sell.toString()}` + + `\nšŸ›’ Intent: ${entry.intent === 2 ? 'bank' : entry.intent === 1 ? 'sell' : 'buy'}` + + `\nšŸ“¦ Stock: ${amount} | Min: ${entry.min} | Max: ${entry.max}` + + `\nšŸ“‹ Enabled: ${entry.enabled ? 'āœ…' : 'āŒ'}` + + `\nšŸ”„ Autoprice: ${entry.autoprice ? 'āœ…' : 'āŒ'}` + + (isPremium ? `\nšŸ“¢ Promoted: ${entry.promoted === 1 ? 'āœ…' : 'āŒ'}` : '') + + `\nšŸ”° Group: ${entry.group}` + + `${entry.note.buy !== null ? `\nšŸ“„ Custom buying note: ${entry.note.buy}` : ''}` + + `${entry.note.sell !== null ? `\nšŸ“¤ Custom selling note: ${entry.note.sell}` : ''}` + ); + } + + async autoAddCommand(steamID: SteamID, message: string): Promise { + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); + + if (params.sku !== undefined || params.name !== undefined || params.defindex !== undefined) { + return this.bot.sendMessage( + steamID, + `āŒ Please only define item listing settings parameters, more info:` + + ` https://github.com/TF2Autobot/tf2autobot/wiki/What-is-the-pricelist%3F#i2---item-listing-settings-parameters.` + ); + } + + if (!params) { + this.bot.sendMessage(steamID, `ā³ Adding all items with default settings...`); + } + + if (params.enabled === undefined) { + params.enabled = true; + } + + if (params.max === undefined) { + params.max = 1; + } + + if (params.min === undefined) { + params.min = 0; + } + + if (params.intent === undefined) { + params.intent = 2; + } else if (typeof params.intent === 'string') { + const intent = ['buy', 'sell', 'bank'].indexOf(params.intent.toLowerCase()); + + if (intent !== -1) { + params.intent = intent; + } + } + + if (typeof params.buy === 'object') { + params.buy.keys = params.buy.keys || 0; + params.buy.metal = params.buy.metal || 0; + + if (params.autoprice === undefined) { + params.autoprice = false; + } + } + if (typeof params.sell === 'object') { + params.sell.keys = params.sell.keys || 0; + params.sell.metal = params.sell.metal || 0; + + if (params.autoprice === undefined) { + params.autoprice = false; + } + } + + if ( + (params.sell !== undefined && params.buy === undefined) || + (params.sell === undefined && params.buy !== undefined) + ) { + return this.bot.sendMessage(steamID, `āŒ Please set both buy and sell prices.`); + } + + const isPremium = this.bot.handler.getBotInfo.premium; + + if (params.promoted !== undefined) { + if (!isPremium) { + return this.bot.sendMessage( + steamID, + `āŒ This account is not Backpack.tf Premium. You can't use "promoted" paramter.` + ); + } + + if (typeof params.promoted === 'boolean') { + if (params.promoted === true) { + params.promoted = 1; + } else { + params.promoted = 0; + } + } else { + if (typeof params.promoted !== 'number') { + return this.bot.sendMessage( + steamID, + 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)' + ); + } + + if (params.promoted < 0 || params.promoted > 1) { + return this.bot.sendMessage( + steamID, + 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)' + ); + } + } + } else { + params['promoted'] = 0; + } + + if (typeof params.note === 'object') { + params.note.buy = params.note.buy || null; + params.note.sell = params.note.sell || null; + } + + if (params.note === undefined) { + // If note parameter is not defined, set both note.buy and note.sell to null. + params['note'] = { buy: null, sell: null }; + } + + if (params.group && typeof params.group !== 'string') { + // if group parameter is defined, convert anything to string + params.group = (params.group as string).toString(); + } + + if (typeof params.group === undefined) { + // If group parameter is not defined, set it to null. + params['group'] = 'all'; + } + + if (params.autoprice === undefined) { + params.autoprice = true; + } + + const pricelist = this.bot.pricelist.getPrices; + const dict = this.bot.inventoryManager.getInventory.getItems; + + const pureAndWeapons = ['5021;6', '5000;6', '5001;6', '5002;6'].concat( + this.bot.craftWeapons.concat(this.bot.uncraftWeapons) + ); + + for (const sku in dict) { + if (!Object.prototype.hasOwnProperty.call(dict, sku)) { + continue; + } + + if (pureAndWeapons.some(pureOrWeaponsSKU => pureOrWeaponsSKU === sku)) { + delete dict[sku]; + } + } + + const total = Object.keys(dict).length; + + const totalTime = total * (params.autoprice ? 2 : 1) * 1000; + const aSecond = 1 * 1000; + const aMin = 1 * 60 * 1000; + const anHour = 1 * 60 * 60 * 1000; + + this.bot.sendMessage( + steamID, + `ā³ Running automatic add items... Total items to add: ${total}` + + `\n${params.autoprice ? 2 : 1} seconds in between items, so it will be about ${ + totalTime < aMin + ? `${Math.round(totalTime / aSecond)} seconds` + : totalTime < anHour + ? `${Math.round(totalTime / aMin)} minutes` + : `${Math.round(totalTime / anHour)} hours` + } to complete. Send "!stopautoadd" to abort.` + ); + + let added = 0; + let skipped = 0; + let failed = 0; + + for (const sku in dict) { + if (this.stopAutoAdd) { + this.bot.sendMessage(steamID, '----------\nšŸ›‘ Stopped auto-add items'); + this.stopAutoAdd = false; + break; + } + + if (!Object.prototype.hasOwnProperty.call(dict, sku)) { + continue; + } + + if (pricelist.some(entry => entry.sku === sku)) { + skipped++; + this.bot.sendMessage( + steamID, + `----------\nāš ļø ${this.bot.schema.getName( + SKU.fromString(sku) + )} (${sku}) already in pricelist, skipping...` + + `\nšŸ“œ Status: ${added} added, ${skipped} skipped, ${failed} failed / ${total} total, ${ + total - added - skipped - failed + } remaining` + ); + // Prevent spamming detection and cause the bot to stop sending messages + await sleepasync().Promise.sleep(1 * 1000); + continue; + } + + if (params.autoprice === true) { + await sleepasync().Promise.sleep(2 * 1000); + } else { + await sleepasync().Promise.sleep(1 * 1000); + } + + params.sku = sku; + + this.bot.pricelist + .addPrice(params as EntryData, true, PricelistChangedSource.Command) + .then(entry => { + added++; + this.bot.sendMessage( + steamID, + `----------\nāœ… Added "${entry.name}"` + + this.generateAddedReply(this.bot, isPremium, entry) + + `\n\nšŸ“œ Status: ${added} added, ${skipped} skipped, ${failed} failed / ${total} total, ${ + total - added - skipped - failed + } remaining` + ); + }) + .catch(err => { + failed++; + this.bot.sendMessage( + steamID, + `----------\nāŒ Failed to add the item to the pricelist: ${(err as Error).message}` + + `\n\nšŸ“œ Status: ${added} added, ${skipped} skipped, ${failed} failed / ${total} total, ${ + total - added - skipped - failed + } remaining` + ); + }); + } + + this.bot.sendMessage( + steamID, + `----------\nāœ… Done, summary: ${added} added, ${skipped} skipped, ${failed} failed / ${total} total` + ); + + this.stopAutoAdd = false; + } + + async updateCommand(steamID: SteamID, message: string): Promise { + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); + + if (typeof params.intent === 'string') { + const intent = ['buy', 'sell', 'bank'].indexOf(params.intent.toLowerCase()); + + if (intent !== -1) { + params.intent = intent; + } + } + + if (params.all === true) { + // TODO: Must have at least one other param + const pricelist = this.bot.pricelist.getPrices; + const keyPrice = this.bot.pricelist.getKeyPrice; + + let targetedPricelist: Entry[]; + let unTargetedPricelist: Entry[]; + let newPricelist: Entry[]; + + if (params.promoted) { + return this.bot.sendMessage(steamID, `āŒ Parameter "promoted" can't be used with "!update all=true".`); + } + + if (params.withgroup && params.withoutgroup) { + return this.bot.sendMessage( + steamID, + `āŒ Don't be dumb. Please choose only "withgroup" OR "withoutgroup", not both. Thanks.` + ); + } + + if (params.withgroup) { + targetedPricelist = pricelist.filter(entry => + entry.group + ? [(params.withgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) + : false + ); + unTargetedPricelist = pricelist.filter(entry => + entry.group + ? ![(params.withgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) + : true + ); + + if (targetedPricelist.length === 0) { + return this.bot.sendMessage( + steamID, + `āŒ There is no entry with "${params.withgroup as string}" group found in your pricelist.` + ); + } + + newPricelist = targetedPricelist; + + if (typeof params.buy === 'object' || typeof params.sell === 'object') { + if ( + new Currencies(params.buy).toValue(keyPrice.metal) >= + new Currencies(params.sell).toValue(keyPrice.metal) + ) { + return this.bot.sendMessage(steamID, `āŒ Buying price can't be higher than selling price.`); + } else if ( + (params.buy !== null && params.sell === undefined) || + (params.buy === undefined && params.sell !== null) + ) { + return this.bot.sendMessage(steamID, `āŒ You must include both buying and selling prices.`); + } + } + } else if (params.withoutgroup) { + // reverse of withgroup + targetedPricelist = pricelist.filter(entry => + entry.group + ? ![(params.withoutgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) + : true + ); + unTargetedPricelist = pricelist.filter(entry => + entry.group + ? [(params.withoutgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) + : false + ); + + if (targetedPricelist.length === 0) { + return this.bot.sendMessage( + steamID, + `āŒ There is no entry other than "${ + params.withoutgroup as string + }" group found in your pricelist.` + ); + } + + newPricelist = targetedPricelist; + + if (typeof params.buy === 'object' || typeof params.sell === 'object') { + if ( + new Currencies(params.buy).toValue(keyPrice.metal) >= + new Currencies(params.sell).toValue(keyPrice.metal) + ) { + return this.bot.sendMessage(steamID, `āŒ Buying price can't be higher than selling price.`); + } else if ( + (params.buy !== null && params.sell === undefined) || + (params.buy === undefined && params.sell !== null) + ) { + return this.bot.sendMessage(steamID, `āŒ You must include both buying and selling prices.`); + } + } + } else { + newPricelist = pricelist; + + if (this.bot.options.autokeys.enable) { + // Autokeys is a feature, so when updating multiple entry with + // "!update all=true", key entry will be removed from newPricelist. + // https://github.com/idinium96/tf2autobot/issues/131 + const keyEntry = this.bot.pricelist.getPrice('5021;6'); + if (keyEntry !== null) { + const index = this.bot.pricelist.getIndex('5021;6'); + newPricelist.splice(index, 1); + } + } + } + + if (newPricelist.length === 0) { + return this.bot.sendMessage(steamID, 'Your pricelist is empty.'); + } + + if (!params.withgroup && !params.withoutgroup) { + if (typeof params.note === 'object') { + return this.bot.sendMessage( + steamID, + `āŒ Please specify "withgroup" or "withoutgroup" to change note.` + + `\nExample: "!update all=true&withgroup=¬e.buy="` + ); + } + + if (typeof params.buy === 'object' || typeof params.sell === 'object') { + return this.bot.sendMessage( + steamID, + `āŒ Please specify "withgroup" or "withoutgroup" to change buying/selling price.\nExample:\n` + + `"!update all=true&withgroup=&(buy.keys|buy.metal)=&(sell.keys|sell.metal)="` + ); + } + } + + newPricelist.forEach((entry, i) => { + if (params.intent || params.intent === 0) { + entry.intent = params.intent as 0 | 1 | 2; + } + + if (params.min === 0 || typeof params.min === 'number') { + entry.min = params.min as number; + } + + if (params.max === 0 || typeof params.max === 'number') { + entry.max = params.max as number; + } + + if (typeof params.enabled === 'boolean') { + entry.enabled = params.enabled; + } + + if (params.group) { + entry.group = (params.group as string).toString(); + } + + if (params.removenote && typeof params.removenote === 'boolean' && params.removenote === true) { + // Sending "!update all=true&removenote=true" will set both + // note.buy and note.sell for entire/withgroup entries to null. + if (entry.note) { + entry.note.buy = null; + entry.note.sell = null; + } + } + + if (typeof params.autoprice === 'boolean') { + if (params.autoprice === false) { + entry.time = null; + } + entry.autoprice = params.autoprice; + } + + if (params.withgroup || params.withoutgroup) { + if (typeof params.note === 'object') { + // can change note if have withgroup/withoutgroup parameter + entry.note.buy = params.note.buy || entry.note.buy; + entry.note.sell = params.note.sell || entry.note.sell; + } + + if (typeof params.buy === 'object' && params.buy !== null) { + entry.buy.keys = params.buy.keys || 0; + entry.buy.metal = params.buy.metal || 0; + + if (params.autoprice === undefined) { + entry.autoprice = false; + } + } + + if (typeof params.sell === 'object' && params.sell !== null) { + entry.sell.keys = params.sell.keys || 0; + entry.sell.metal = params.sell.metal || 0; + + if (params.autoprice === undefined) { + entry.autoprice = false; + } + } + } + + if (i === 0) { + const errors = validator( + { + sku: entry.sku, + enabled: entry.enabled, + intent: entry.intent, + max: entry.max, + min: entry.min, + autoprice: entry.autoprice, + buy: entry.buy.toJSON(), + sell: entry.sell.toJSON(), + promoted: entry.promoted, + group: entry.group, + note: entry.note, + time: entry.time + }, + 'pricelist' + ); + + if (errors !== null) { + throw new Error(errors.join(', ')); + } + } + }); + + if (params.removenote) { + delete params.removenote; + } + + if (params.withgroup) { + newPricelist = unTargetedPricelist.concat(newPricelist); + delete params.withgroup; + } + + if (params.withoutgroup) { + newPricelist = unTargetedPricelist.concat(newPricelist); + delete params.withoutgroup; + } + + // FIXME: Make it so that it is not needed to remove all listings + + if (params.autoprice !== true) { + await this.bot.handler.onPricelist(newPricelist); + this.bot.sendMessage(steamID, 'āœ… Updated pricelist!'); + + return await this.bot.listings.redoListings(); + } + + this.bot.sendMessage(steamID, 'āŒ› Updating prices...'); + + return this.bot.pricelist + .setupPricelist() + .then(async () => { + this.bot.sendMessage(steamID, 'āœ… Updated pricelist!'); + await this.bot.listings.redoListings(); + }) + .catch(err => { + log.warn('Failed to update prices: ', err); + return this.bot.sendMessage(steamID, `āŒ Failed to update prices: ${(err as Error).message}`); + }); + } + + if (params.sku !== undefined && !testSKU(params.sku as string)) { + return this.bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); + } + + if (typeof params.buy === 'object' && params.buy !== null) { + params.buy.keys = params.buy.keys || 0; + params.buy.metal = params.buy.metal || 0; + + if (params.autoprice === undefined) { + params.autoprice = false; + } + } + + if (typeof params.sell === 'object' && params.sell !== null) { + params.sell.keys = params.sell.keys || 0; + params.sell.metal = params.sell.metal || 0; + + if (params.autoprice === undefined) { + params.autoprice = false; + } + } + + if (params.resetgroup) { + // if resetgroup (when sending "!update item=&resetgroup=true") is defined, + // first check if it's a booelan type + if (typeof params.resetgroup === 'boolean') { + // if it's boolean, then check if it's true + if (params.resetgroup === true) { + // if it's true, then set group key to null. + params.group = 'all'; + } + } else { + // else if it's not a boolean type, then send message. + return this.bot.sendMessage(steamID, 'āŒ "resetgroup" must be either "true" or "false".'); + } + // delete resetgroup key from params so it will not trying to be added into pricelist (validator error) + delete params.resetgroup; + } + + const isPremium = this.bot.handler.getBotInfo.premium; + if (params.promoted !== undefined) { + if (!isPremium) { + return this.bot.sendMessage( + steamID, + `āŒ This account is not Backpack.tf Premium. You can't use "promoted" paramter.` + ); + } + + if (typeof params.promoted === 'boolean') { + if (params.promoted === true) { + params.promoted = 1; + } else { + params.promoted = 0; + } + } else { + if (typeof params.promoted !== 'number') { + return this.bot.sendMessage( + steamID, + 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)' + ); + } + + if (params.promoted < 0 || params.promoted > 1) { + return this.bot.sendMessage( + steamID, + 'āŒ "promoted" parameter must be either 0 (false) or 1 (true)' + ); + } + } + } + + if (params.item !== undefined) { + // Remove by full name + let match = this.bot.pricelist.searchByName(params.item as string, false); + if (match === null) { + return this.bot.sendMessage( + steamID, + `āŒ I could not find any items in my pricelist that contain "${params.item as string}"` + ); + } else if (Array.isArray(match)) { + const matchCount = match.length; + if (match.length > 20) { + match = match.splice(0, 20); + } + + let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( + ',\n' + )}`; + + if (matchCount > match.length) { + const other = matchCount - match.length; + reply += `,\nand ${other} other ${pluralize('item', other)}.`; + } + + return this.bot.sendMessage(steamID, reply); + } + + delete params.item; + params.sku = match.sku; + } else if (params.sku === undefined) { + const item = getItemFromParams(steamID, params, this.bot); + + if (item === null) { + return this.bot.sendMessage(steamID, `āŒ Item not found.`); + } + + params.sku = SKU.fromObject(item); + } + + if (!this.bot.pricelist.hasPrice(params.sku as string)) { + return this.bot.sendMessage(steamID, 'āŒ Item is not in the pricelist.'); + } + + const itemEntry = this.bot.pricelist.getPrice(params.sku as string, false); + + if (typeof params.note === 'object') { + params.note.buy = params.note.buy || itemEntry.note.buy; + params.note.sell = params.note.sell || itemEntry.note.sell; + } + + if (params.removenote) { + // removenote to set both note.buy and note.sell to null. + if (typeof params.removenote === 'boolean') { + if (params.removenote === true) { + params.note = { buy: null, sell: null }; + } + } else { + return this.bot.sendMessage(steamID, 'āŒ "removenote" must be either "true" or "false".'); + } + + delete params.removenote; + } + + if (params.removebuynote && params.removesellnote) { + return this.bot.sendMessage( + steamID, + 'āŒ Please only use either "removebuynote" or "removesellnote", not both, or if you wish to remove both buy and sell note, please use "removenote".' + ); + } + + if (params.removebuynote) { + if (typeof params.removebuynote === 'boolean') { + if (params.removebuynote === true) { + params.note = { buy: null, sell: itemEntry.note.sell }; + } + } else { + return this.bot.sendMessage(steamID, 'āŒ "removebuynote" must be either "true" or "false".'); + } + + delete params.removebuynote; + } + + if (params.removesellnote) { + if (typeof params.removesellnote === 'boolean') { + if (params.removesellnote === true) { + params.note = { buy: itemEntry.note.buy, sell: null }; + } + } else { + return this.bot.sendMessage(steamID, 'āŒ "removesellnote" must be either "true" or "false".'); + } + + delete params.removesellnote; + } + + if (params.group && typeof params.group !== 'string') { + // if group parameter is defined, convert anything to string + params.group = (params.group as string).toString(); + } + + const entryData = this.bot.pricelist.getPrice(params.sku as string, false).getJSON(); + delete entryData.time; + delete params.sku; + + if (Object.keys(params).length === 0) { + return this.bot.sendMessage(steamID, 'āš ļø Missing properties to update.'); + } + + // Update entry + for (const property in params) { + if (!Object.prototype.hasOwnProperty.call(params, property)) { + continue; + } + + entryData[property] = params[property]; + } + + this.bot.pricelist + .updatePrice(entryData, true, PricelistChangedSource.Command) + .then(entry => { + this.bot.sendMessage( + steamID, + `āœ… Updated "${entry.name}"` + this.generateUpdateReply(this.bot, isPremium, itemEntry, entry) + ); + }) + .catch((err: ErrorRequest) => { + this.bot.sendMessage( + steamID, + 'āŒ Failed to update pricelist entry: ' + + (err.body && err.body.message ? err.body.message : err.message) + ); + }); + } + + private generateUpdateReply(bot: Bot, isPremium: boolean, oldEntry: Entry, newEntry: Entry): string { + const keyPrice = bot.pricelist.getKeyPrice; + const amount = bot.inventoryManager.getInventory.getAmount(oldEntry.sku); + return ( + `\nšŸ’² Buy: ${ + oldEntry.buy.toValue(keyPrice.metal) !== newEntry.buy.toValue(keyPrice.metal) + ? `${oldEntry.buy.toString()} ā†’ ${newEntry.buy.toString()}` + : newEntry.buy.toString() + } | Sell: ${ + oldEntry.sell.toValue(keyPrice.metal) !== newEntry.sell.toValue(keyPrice.metal) + ? `${oldEntry.sell.toString()} ā†’ ${newEntry.sell.toString()}` + : newEntry.sell.toString() + }` + + `\nšŸ“¦ Stock: ${amount}` + + ` | Min: ${oldEntry.min !== newEntry.min ? `${oldEntry.min} ā†’ ${newEntry.min}` : newEntry.min} | Max: ${ + oldEntry.max !== newEntry.max ? `${oldEntry.max} ā†’ ${newEntry.max}` : newEntry.max + }` + + `\nšŸ›’ Intent: ${ + oldEntry.intent !== newEntry.intent + ? `${oldEntry.intent === 2 ? 'bank' : oldEntry.intent === 1 ? 'sell' : 'buy'} ā†’ ${ + newEntry.intent === 2 ? 'bank' : newEntry.intent === 1 ? 'sell' : 'buy' + }` + : `${newEntry.intent === 2 ? 'bank' : newEntry.intent === 1 ? 'sell' : 'buy'}` + }` + + `\nšŸ“‹ Enabled: ${ + oldEntry.enabled !== newEntry.enabled + ? `${oldEntry.enabled ? 'āœ…' : 'āŒ'} ā†’ ${newEntry.enabled ? 'āœ…' : 'āŒ'}` + : `${newEntry.enabled ? 'āœ…' : 'āŒ'}` + }` + + `\nšŸ”„ Autoprice: ${ + oldEntry.autoprice !== newEntry.autoprice + ? `${oldEntry.autoprice ? 'āœ…' : 'āŒ'} ā†’ ${newEntry.autoprice ? 'āœ…' : 'āŒ'}` + : `${newEntry.autoprice ? 'āœ…' : 'āŒ'}` + }` + + (isPremium + ? `\nšŸ“¢ Promoted: ${ + oldEntry.promoted !== newEntry.promoted + ? `${oldEntry.promoted === 1 ? 'āœ…' : 'āŒ'} ā†’ ${newEntry.promoted === 1 ? 'āœ…' : 'āŒ'}` + : `${newEntry.promoted === 1 ? 'āœ…' : 'āŒ'}` + }` + : '') + + `\nšŸ”° Group: ${ + oldEntry.group !== newEntry.group ? `${oldEntry.group} ā†’ ${newEntry.group}` : newEntry.group + }` + + `${newEntry.note.buy !== null ? `\nšŸ“„ Custom buying note: ${newEntry.note.buy}` : ''}` + + `${newEntry.note.sell !== null ? `\nšŸ“¤ Custom selling note: ${newEntry.note.sell}` : ''}` + ); + } + + async shuffleCommand(steamID: SteamID): Promise { + const newExecutedTime = dayjs().valueOf(); + const timeDiff = newExecutedTime - this.lastExecutedTime; + + const pricelist = this.bot.pricelist.getPrices; + + if (pricelist.length === 0) { + return this.bot.sendMessage(steamID, 'āŒ Pricelist is empty!'); + } + + if (this.executed === true) { + return this.bot.sendMessage( + steamID, + `āš ļø You need to wait ${Math.trunc( + (30 * 60 * 1000 - timeDiff) / (1000 * 60) + )} minutes before you can shuffle pricelist/shuffle again.` + ); + } else { + clearTimeout(this.executeTimeout); + this.lastExecutedTime = dayjs().valueOf(); + + const shufflePricelist = (arr: Entry[]) => { + return arr.sort(() => Math.random() - 0.5); + }; + + await this.bot.handler.onPricelist(shufflePricelist(pricelist)); + this.bot.sendMessage(steamID, 'āœ… Pricelist shuffled!'); + await this.bot.listings.redoListings(); + + this.executed = true; + this.executeTimeout = setTimeout(() => { + this.lastExecutedTime = null; + this.executed = false; + clearTimeout(this.executeTimeout); + }, 30 * 60 * 1000); + } + } + + async removeCommand(steamID: SteamID, message: string): Promise { + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); + + if (params.all === true) { + // Remove entire pricelist + const pricelistLength = this.bot.pricelist.getLength; + if (pricelistLength === 0) { + return this.bot.sendMessage(steamID, 'āŒ Your pricelist is already empty!'); + } + + const pricelist = this.bot.pricelist.getPrices; + let newPricelist: Entry[] = []; + let newPricelistCount: Entry[] = []; + + if (params.withgroup && params.withoutgroup) { + return this.bot.sendMessage( + steamID, + `āŒ Don't be dumb. Please choose only "withgroup" OR "withoutgroup", not both. Thanks.` + ); + } + + if (params.withgroup) { + // first filter out pricelist with ONLY "withgroup" value. + newPricelistCount = pricelist.filter(entry => + entry.group + ? [(params.withgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) + : false + ); + + if (newPricelistCount.length === 0) { + return this.bot.sendMessage( + steamID, + `āŒ There is no entry with "${params.withgroup as string}" group found in your pricelist.` + ); + } + + // then filter out pricelist with NOT "withgroup" value. + newPricelist = pricelist.filter(entry => + entry.group + ? ![(params.withgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) + : true + ); + } else if (params.withoutgroup) { + // reverse of withgroup + newPricelistCount = pricelist.filter(entry => + entry.group + ? ![(params.withoutgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) + : true + ); + + if (newPricelistCount.length === 0) { + return this.bot.sendMessage( + steamID, + `āŒ There is no entry other than "${ + params.withoutgroup as string + }" group found in your pricelist.` + ); + } + + newPricelist = pricelist.filter(entry => + entry.group + ? [(params.withoutgroup as string).toLowerCase()].includes(entry.group.toLowerCase()) + : false + ); + } else { + newPricelistCount = pricelist; + } + + if (params.i_am_sure !== 'yes_i_am') { + return this.bot.sendMessage( + steamID, + '/pre āš ļø Are you sure that you want to remove ' + + pluralize('item', newPricelistCount.length, true) + + '? Try again with i_am_sure=yes_i_am' + ); + } + + if (params.withgroup || params.withoutgroup) { + try { + await this.bot.pricelist.removeByGroup(newPricelist); + this.bot.sendMessage(steamID, `āœ… Removed ${newPricelistCount.length} items from pricelist.`); + return await this.bot.listings.redoListings(); + } catch (err) { + return this.bot.sendMessage(steamID, `āŒ Failed to clear pricelist: ${(err as Error).message}`); + } + } else { + try { + await this.bot.pricelist.removeAll(); + return this.bot.sendMessage(steamID, 'āœ… Cleared pricelist!'); + } catch (err) { + return this.bot.sendMessage(steamID, `āŒ Failed to clear pricelist: ${(err as Error).message}`); + } + } + } + + if (params.sku !== undefined && !testSKU(params.sku as string)) { + return this.bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); + } + + if (params.item !== undefined) { + // Remove by full name + let match = this.bot.pricelist.searchByName(params.item as string, false); + if (match === null) { + return this.bot.sendMessage( + steamID, + `āŒ I could not find any items in my pricelist that contain "${params.item as string}"` + ); + } else if (Array.isArray(match)) { + const matchCount = match.length; + if (match.length > 20) match = match.splice(0, 20); + + let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( + ',\n' + )}`; + + if (matchCount > match.length) { + const other = matchCount - match.length; + reply += `,\nand ${other} other ${pluralize('item', other)}.`; + } + + return this.bot.sendMessage(steamID, reply); + } + + delete params.item; + params.sku = match.sku; + } else if (params.sku === undefined) { + const item = getItemFromParams(steamID, params, this.bot); + + if (item === null) { + return this.bot.sendMessage(steamID, `āŒ Item not found.`); + } + + params.sku = SKU.fromObject(item); + } + + this.bot.pricelist + .removePrice(params.sku as string, true) + .then(entry => this.bot.sendMessage(steamID, `āœ… Removed "${entry.name}".`)) + .catch(err => + this.bot.sendMessage(steamID, `āŒ Failed to remove pricelist entry: ${(err as Error).message}`) + ); + } + + getCommand(steamID: SteamID, message: string): void { + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); + if (params.sku !== undefined && !testSKU(params.sku as string)) { + return this.bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); + } + + if (params.item !== undefined) { + // Remove by full name + let match = this.bot.pricelist.searchByName(params.item as string, false); + + if (match === null) { + return this.bot.sendMessage( + steamID, + `āŒ I could not find any items in my pricelist that contain "${params.item as string}"` + ); + } else if (Array.isArray(match)) { + const matchCount = match.length; + if (match.length > 20) { + match = match.splice(0, 20); + } + + let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( + ',\n' + )}`; + + if (matchCount > match.length) { + const other = matchCount - match.length; + reply += `,\nand ${other} other ${pluralize('item', other)}.`; + } + + return this.bot.sendMessage(steamID, reply); + } + + delete params.item; + params.sku = match.sku; + } else if (params.sku === undefined) { + const item = getItemFromParams(steamID, params, this.bot); + + if (item === null) { + return this.bot.sendMessage(steamID, `āŒ Item not found.`); + } + + params.sku = SKU.fromObject(item); + } + + if (params.sku === undefined) { + return this.bot.sendMessage(steamID, 'āŒ Missing item'); + } + + const match = this.bot.pricelist.getPrice(params.sku as string); + if (match === null) { + this.bot.sendMessage(steamID, `āŒ Could not find item "${params.sku as string}" in the pricelist`); + } else { + this.bot.sendMessage(steamID, `/code ${this.generateOutput(match)}`); + } + } + + async findCommand(steamID: SteamID, message: string): Promise { + const params = CommandParser.parseParams(CommandParser.removeCommand(message)); + if ( + !( + params.enabled !== undefined || + params.max !== undefined || + params.min !== undefined || + params.intent !== undefined || + params.autoprice !== undefined || + params.group !== undefined || + params.promoted !== undefined + ) + ) { + return this.bot.sendMessage( + steamID, + 'āš ļø Only parameters available for !find command: enabled, max, min, intent, promoted, autoprice or group\nExample: !find intent=bank&max=2' + ); + } + + let filter = this.bot.pricelist.getPrices; + if (params.enabled !== undefined) { + if (typeof params.enabled !== 'boolean') { + return this.bot.sendMessage(steamID, 'āš ļø enabled parameter must be "true" or "false"'); + } + filter = filter.filter(entry => entry.enabled === params.enabled); + } + + if (params.max !== undefined) { + if (typeof params.max !== 'number') { + return this.bot.sendMessage(steamID, 'āš ļø max parameter must be an integer'); + } + filter = filter.filter(entry => entry.max === params.max); + } + + if (params.min !== undefined) { + if (typeof params.min !== 'number') { + return this.bot.sendMessage(steamID, 'āš ļø min parameter must be an integer'); + } + filter = filter.filter(entry => entry.min === params.min); + } + + if (params.promoted !== undefined) { + if (typeof params.promoted === 'boolean') { + if (params.promoted === true) { + filter = filter.filter(entry => entry.promoted === 1); + } else { + filter = filter.filter(entry => entry.promoted === 0); + } + } else { + if (typeof params.promoted !== 'number') { + return this.bot.sendMessage(steamID, 'āš ļø promoted parameter must be either 0 (false) or 1 (true)'); + } + + if (params.promoted < 0 || params.promoted > 1) { + return this.bot.sendMessage( + steamID, + 'āš ļø "promoted" parameter must be either 0 (false) or 1 (true)' + ); + } + filter = filter.filter(entry => entry.promoted === params.promoted); + } + } + + if (params.intent !== undefined) { + if (typeof params.intent === 'string') { + const intent = ['buy', 'sell', 'bank'].indexOf(params.intent.toLowerCase()); + if (intent !== -1) { + params.intent = intent; + } + } + + if (typeof params.intent !== 'number' || params.intent < 0) { + return this.bot.sendMessage( + steamID, + 'āš ļø intent parameter must be "buy", "sell", or "bank" OR an integer of "0", "1" or "2" respectively' + ); + } + filter = filter.filter(entry => entry.intent === params.intent); + } + + if (params.autoprice !== undefined) { + if (typeof params.autoprice !== 'boolean') { + return this.bot.sendMessage(steamID, 'āš ļø autoprice parameter must be "true" or "false"'); + } + filter = filter.filter(entry => entry.autoprice === params.autoprice); + } + + if (params.group !== undefined) { + if (typeof params.group !== 'string') { + return this.bot.sendMessage(steamID, 'āš ļø group parameter must be a string'); + } + filter = filter.filter(entry => entry.group === params.group); + } + + const parametersUsed = { + enabled: params.enabled !== undefined ? `enabled=${(params.enabled as boolean).toString()}` : '', + autoprice: params.autoprice !== undefined ? `autoprice=${(params.autoprice as boolean).toString()}` : '', + max: params.max !== undefined ? `max=${params.max as number}` : '', + min: params.min !== undefined ? `min=${params.min as number}` : '', + intent: + params.intent !== undefined + ? `intent=${ + (params.intent as number) === 0 ? 'buy' : (params.intent as number) === 1 ? 'sell' : 'bank' + }` + : '', + promoted: + params.promoted !== undefined ? `promoted=${(params.promoted as number) === 1 ? 'true' : 'false'}` : '', + group: params.group !== undefined ? `group=${params.group as string}` : '' + }; + + const parameters = Object.values(parametersUsed); + const display = parameters.filter(param => param !== ''); + + const length = filter.length; + if (length === 0) { + this.bot.sendMessage(steamID, `No items found with ${display.join('&')}.`); + } else if (length > 20) { + this.bot.sendMessage( + steamID, + `Found ${pluralize('item', length, true)} with ${display.join('&')}, showing only a max of 100 items` + ); + this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 0, 20)}`); + if (length <= 40) { + this.bot.sendMessage( + steamID, + `/code ${this.generateOutput(filter, true, 20, length > 40 ? 40 : length)}` + ); + } else if (length <= 60) { + this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 20, 40)}`); + await sleepasync().Promise.sleep(1 * 1000); + this.bot.sendMessage( + steamID, + `/code ${this.generateOutput(filter, true, 40, length > 60 ? 60 : length)}` + ); + } else if (length <= 80) { + this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 20, 40)}`); + await sleepasync().Promise.sleep(1 * 1000); + this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 40, 60)}`); + await sleepasync().Promise.sleep(1 * 1000); + this.bot.sendMessage( + steamID, + `/code ${this.generateOutput(filter, true, 60, length > 80 ? 80 : length)}` + ); + } else if (length > 80) { + this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 20, 40)}`); + await sleepasync().Promise.sleep(1 * 1000); + this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 40, 60)}`); + await sleepasync().Promise.sleep(1 * 1000); + this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 60, 80)}`); + await sleepasync().Promise.sleep(1 * 1000); + this.bot.sendMessage( + steamID, + `/code ${this.generateOutput(filter, true, 80, length > 100 ? 100 : length)}` + ); + } + } else { + this.bot.sendMessage(steamID, `Found ${pluralize('item', filter.length, true)} with ${display.join('&')}`); + this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter)}`); + } + } + + private generateOutput(filtered: Entry[] | Entry, isSlice = false, start?: number, end?: number): string { + return isSlice + ? JSON.stringify((filtered as Entry[]).slice(start, end), null, 4) + : JSON.stringify(filtered, null, 4); + } +} + +interface ErrorRequest { + body?: ErrorBody; + message?: string; +} + +interface ErrorBody { + message: string; +} diff --git a/src/classes/Commands/sub-classes/Request.ts b/src/classes/Commands/sub-classes/Request.ts new file mode 100644 index 000000000..cacf1858c --- /dev/null +++ b/src/classes/Commands/sub-classes/Request.ts @@ -0,0 +1,262 @@ +import SteamID from 'steamid'; +import SKU from 'tf2-sku-2'; +import pluralize from 'pluralize'; +import dayjs from 'dayjs'; +import sleepasync from 'sleep-async'; +import Currencies from 'tf2-currencies'; +import { removeLinkProtocol, getItemFromParams, testSKU } from '../functions/utils'; +import Bot from '../../Bot'; +import CommandParser from '../../CommandParser'; +import log from '../../../lib/logger'; +import { fixItem } from '../../../lib/items'; +import Pricer, { GetPriceFn, GetSalesFn, RequestCheckFn, RequestCheckResponse } from '../../Pricer'; + +export default class RequestCommands { + private readonly bot: Bot; + + private getSales: GetSalesFn; + + private requestCheck: RequestCheckFn; + + private getPrice: GetPriceFn; + + constructor(bot: Bot, private priceSource: Pricer) { + this.bot = bot; + + this.getSales = this.priceSource.getSales.bind(this.priceSource); + this.requestCheck = this.priceSource.requestCheck.bind(this.priceSource); + this.getPrice = this.priceSource.requestCheck.bind(this.priceSource); + } + + async getSalesCommand(steamID: SteamID, message: string): Promise { + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); + if (params.sku === undefined) { + const item = getItemFromParams(steamID, params, this.bot); + + if (item === null) { + return this.bot.sendMessage(steamID, `āŒ Item not found.`); + } + + params.sku = SKU.fromObject(item); + } else { + params.sku = SKU.fromObject(fixItem(SKU.fromString(params.sku), this.bot.schema)); + } + + const name = this.bot.schema.getName(SKU.fromString(params.sku)); + try { + const salesData = await this.getSales(params.sku, 'bptf'); + if (!salesData) { + return this.bot.sendMessage( + steamID, + `āŒ No recorded snapshots found for ${name === null ? (params.sku as string) : name}.` + ); + } + + if (salesData.sales.length === 0) { + return this.bot.sendMessage( + steamID, + `āŒ No recorded snapshots found for ${name === null ? (params.sku as string) : name}.` + ); + } + + const sales: Sales[] = []; + salesData.sales.forEach(sale => + sales.push({ + seller: 'https://backpack.tf/profiles/' + sale.steamid, + itemHistory: 'https://backpack.tf/item/' + sale.id.replace('440_', ''), + keys: sale.currencies.keys, + metal: sale.currencies.metal, + date: sale.time + }) + ); + sales.sort((a, b) => b.date - a.date); + + let left = 0; + const SalesList: string[] = []; + for (let i = 0; i < sales.length; i++) { + if (SalesList.length > 40) { + left += 1; + } else { + SalesList.push( + `Listed #${i + 1}-----\nā€¢ Date: ${dayjs.unix(sales[i].date).utc().toString()}\nā€¢ Item: ${ + sales[i].itemHistory + }\nā€¢ Seller: ${sales[i].seller}\nā€¢ Was selling for: ${ + sales[i].keys > 0 ? `${sales[i].keys} keys,` : '' + } ${sales[i].metal} ref` + ); + } + } + + let reply = `šŸ”Ž Recorded removed sell listings from backpack.tf\n\nItem name: ${ + salesData.name + }\n\n-----${SalesList.join('\n\n-----')}`; + if (left > 0) { + reply += `,\n\nand ${left} other ${pluralize('sale', left)}`; + } + + this.bot.sendMessage(steamID, reply); + } catch (err) { + return this.bot.sendMessage( + steamID, + `āŒ Error getting sell snapshots for ${name === null ? (params.sku as string) : name}: ${ + (err as ErrorRequest).body && (err as ErrorRequest).body.message + ? (err as ErrorRequest).body.message + : (err as ErrorRequest).message + }` + ); + } + } + + pricecheckCommand(steamID: SteamID, message: string): void { + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); + if (params.sku !== undefined && !testSKU(params.sku as string)) { + return this.bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); + } + + if (params.sku === undefined) { + const item = getItemFromParams(steamID, params, this.bot); + if (item === null) { + return this.bot.sendMessage(steamID, `āŒ Item not found.`); + } + + params.sku = SKU.fromObject(item); + } else { + params.sku = SKU.fromObject(fixItem(SKU.fromString(params.sku), this.bot.schema)); + } + + const name = this.bot.schema.getName(SKU.fromString(params.sku), false); + void this.requestCheck(params.sku, 'bptf').asCallback((err: ErrorRequest, body: RequestCheckResponse) => { + if (err) { + return this.bot.sendMessage( + steamID, + `āŒ Error while requesting price check: ${ + err.body && err.body.message ? err.body.message : err.message + }` + ); + } + + if (!body) { + this.bot.sendMessage(steamID, 'āŒ Error while requesting price check (returned null/undefined)'); + } else { + this.bot.sendMessage( + steamID, + `āŒ› Price check requested for ${ + body.name.includes('War Paint') || + body.name.includes('Mann Co. Supply Crate Series #') || + body.name.includes('Salvaged Mann Co. Supply Crate #') + ? name + : body.name + }, the item will be checked.` + ); + } + }); + } + + async pricecheckAllCommand(steamID: SteamID): Promise { + const pricelist = this.bot.pricelist.getPrices; + + const total = pricelist.length; + const totalTime = total * 2 * 1000; + const aSecond = 1 * 1000; + const aMin = 1 * 60 * 1000; + const anHour = 1 * 60 * 60 * 1000; + this.bot.sendMessage( + steamID, + `āŒ› Price check requested for ${total} items. It will be completed in approximately ${ + totalTime < aMin + ? `${Math.round(totalTime / aSecond)} seconds.` + : totalTime < anHour + ? `${Math.round(totalTime / aMin)} minutes.` + : `${Math.round(totalTime / anHour)} hours.` + } (about 2 seconds for each item).` + ); + + const skus = pricelist.map(entry => entry.sku); + let submitted = 0; + let success = 0; + let failed = 0; + for (const sku of skus) { + await sleepasync().Promise.sleep(2 * 1000); + void this.requestCheck(sku, 'bptf').asCallback(err => { + if (err) { + submitted++; + failed++; + log.warn(`pricecheck failed for ${sku}: ${JSON.stringify(err)}`); + log.debug( + `pricecheck for ${sku} failed, status: ${submitted}/${total}, ${success} success, ${failed} failed.` + ); + } else { + submitted++; + success++; + log.debug( + `pricecheck for ${sku} success, status: ${submitted}/${total}, ${success} success, ${failed} failed.` + ); + } + if (submitted === total) { + this.bot.sendMessage( + steamID, + `āœ… Successfully completed pricecheck for all ${total} ${pluralize('item', total)}!` + ); + } + }); + } + } + + async checkCommand(steamID: SteamID, message: string): Promise { + const params = CommandParser.parseParams(CommandParser.removeCommand(removeLinkProtocol(message))); + if (params.sku !== undefined && !testSKU(params.sku as string)) { + return this.bot.sendMessage(steamID, `āŒ "sku" should not be empty or wrong format.`); + } + + if (params.sku === undefined) { + const item = getItemFromParams(steamID, params, this.bot); + if (item === null) { + return this.bot.sendMessage(steamID, `āŒ Item not found.`); + } + + params.sku = SKU.fromObject(item); + } else { + params.sku = SKU.fromObject(fixItem(SKU.fromString(params.sku), this.bot.schema)); + } + + const name = this.bot.schema.getName(SKU.fromString(params.sku)); + try { + const price = await this.getPrice(params.sku, 'bptf'); + const currBuy = new Currencies(price.buy); + const currSell = new Currencies(price.sell); + + this.bot.sendMessage( + steamID, + `šŸ”Ž ${name}:\nā€¢ Buy : ${currBuy.toString()}\nā€¢ Sell : ${currSell.toString()}\n\nPrices.TF: https://prices.tf/items/${ + params.sku as string + }` + ); + } catch (err) { + return this.bot.sendMessage( + steamID, + `Error getting price for ${name === null ? (params.sku as string) : name}: ${ + (err as ErrorRequest).body && (err as ErrorRequest).body.message + ? (err as ErrorRequest).body.message + : (err as ErrorRequest).message + }` + ); + } + } +} + +interface Sales { + seller: string; + itemHistory: string; + keys: number; + metal: number; + date: number; +} + +interface ErrorRequest { + body?: ErrorBody; + message?: string; +} + +interface ErrorBody { + message: string; +} diff --git a/src/classes/Commands/sub-classes/Review.ts b/src/classes/Commands/sub-classes/Review.ts new file mode 100644 index 000000000..7145a290c --- /dev/null +++ b/src/classes/Commands/sub-classes/Review.ts @@ -0,0 +1,341 @@ +import SteamID from 'steamid'; +import pluralize from 'pluralize'; +import TradeOfferManager, { Action, OfferData, OurTheirItemsDict } from 'steam-tradeoffer-manager'; +import Currencies from 'tf2-currencies'; +import { UnknownDictionaryKnownValues } from '../../../types/common'; +import SKU from 'tf2-sku-2'; +import SchemaManager from 'tf2-schema-2'; +import Bot from '../../Bot'; +import CommandParser from '../../CommandParser'; +import { generateLinks } from '../../../lib/tools/export'; + +// Manual review commands + +type ActionOnTrade = 'accept' | 'accepttrade' | 'decline' | 'declinetrade'; +type ForceAction = 'faccept' | 'fdecline'; + +export default class ReviewCommands { + private readonly bot: Bot; + + constructor(bot: Bot) { + this.bot = bot; + } + + tradesCommand(steamID: SteamID): void { + // Go through polldata and find active offers + const pollData = this.bot.manager.pollData; + + const offersForReview: UnknownDictionaryKnownValues[] = []; + const activeOffersNotForReview: UnknownDictionaryKnownValues[] = []; + + for (const id in pollData.received) { + if (!Object.prototype.hasOwnProperty.call(pollData.received, id)) { + continue; + } + + if (pollData.received[id] !== TradeOfferManager.ETradeOfferState['Active']) { + continue; + } + + const data = pollData?.offerData[id] || null; + + if (data === null) { + continue; + } else if (data?.action?.action !== 'skip') { + activeOffersNotForReview.push({ id: id, data: data }); + continue; + } + + offersForReview.push({ id: id, data: data }); + } + + if (offersForReview.length === 0 && activeOffersNotForReview.length === 0) { + return this.bot.sendMessage(steamID, 'āŒ There are no active offers/ pending review.'); + } + + this.bot.sendMessage( + steamID, + (offersForReview.length > 0 ? this.generateTradesReply(offersForReview.sort((a, b) => a.id - b.id)) : '') + + (offersForReview.length > 0 ? '\n\n-----------------\n\n' : '') + + (activeOffersNotForReview.length > 0 + ? this.generateActiveOfferReply(activeOffersNotForReview.sort((a, b) => a.id - b.id)) + : '') + ); + } + + private generateTradesReply(offers: UnknownDictionaryKnownValues[]): string { + let reply = `There ${pluralize('is', offers.length, true)} active ${pluralize( + 'offer', + offers.length + )} that you can review:\n`; + for (let i = 0; i < offers.length; i++) { + reply += + `\n- Offer #${offers[i].id as string} from ${(offers[i].data as OfferData).partner} (reason: ${(offers[ + i + ].data as OfferData).action.meta.uniqueReasons.join(', ')})` + + `\nāš ļø Send "!trade ${offers[i].id as string}" for more details.\n`; + } + + return reply; + } + + private generateActiveOfferReply(offers: UnknownDictionaryKnownValues[]): string { + let reply = `There ${pluralize('is', offers.length, true)} ${pluralize( + 'offer', + offers.length + )} that currently still active:\n`; + for (let i = 0; i < offers.length; i++) { + reply += + `\n- Offer #${offers[i].id as string} from ${(offers[i].data as OfferData).partner}` + + `\nāš ļø Send "!trade ${offers[i].id as string}" for more details or "!faccept ${ + offers[i].id as string + }" to force accept the trade.\n`; + } + + return reply; + } + + tradeCommand(steamID: SteamID, message: string): void { + const offerId = CommandParser.removeCommand(message).trim(); + + if (offerId === '') { + return this.bot.sendMessage(steamID, 'āš ļø Missing offer id. Example: "!trade 3957959294"'); + } + + const state = this.bot.manager.pollData.received[offerId]; + + if (state === undefined) { + return this.bot.sendMessage(steamID, 'Offer does not exist. āŒ'); + } + + if (state !== TradeOfferManager.ETradeOfferState['Active']) { + // TODO: Add what the offer is now, accepted / declined and why + return this.bot.sendMessage(steamID, 'Offer is not active. āŒ'); + } + + const offerData = this.bot.manager.pollData.offerData[offerId]; + + // Log offer details + + let reply = + offerData?.action?.action === 'skip' + ? `āš ļø Offer #${offerId} from ${offerData.partner} is pending for review` + + `\nReason: ${offerData.action.meta.uniqueReasons.join(', ')}).\n\nSummary:\n\n` + : `āš ļø Offer #${offerId} from ${offerData.partner} is still active.\n\nSummary:\n\n`; + + const keyPrice = this.bot.pricelist.getKeyPrice; + const value = offerData.value; + const items = offerData.dict || { our: null, their: null }; + const summarizeItems = (dict: OurTheirItemsDict, schema: SchemaManager.Schema) => { + if (dict === null) { + return 'unknown items'; + } + + const summary: string[] = []; + + for (const sku in dict) { + if (!Object.prototype.hasOwnProperty.call(dict, sku)) { + continue; + } + + summary.push(schema.getName(SKU.fromString(sku), false) + (dict[sku] > 1 ? ` x${dict[sku]}` : '')); // dict[sku] = amount + } + + if (summary.length === 0) { + return 'nothing'; + } + + return summary.join(', '); + }; + + if (!value) { + reply += + 'Asked: ' + + summarizeItems(items.our, this.bot.schema) + + '\nOffered: ' + + summarizeItems(items.their, this.bot.schema); + } else { + const valueDiff = + new Currencies(value.their).toValue(keyPrice.metal) - new Currencies(value.our).toValue(keyPrice.metal); + const valueDiffRef = Currencies.toRefined(Currencies.toScrap(Math.abs(valueDiff * (1 / 9)))).toString(); + reply += + 'Asked: ' + + new Currencies(value.our).toString() + + ' (' + + summarizeItems(items.our, this.bot.schema) + + ')\nOffered: ' + + new Currencies(value.their).toString() + + ' (' + + summarizeItems(items.their, this.bot.schema) + + (valueDiff > 0 + ? `)\nšŸ“ˆ Profit from overpay: ${valueDiffRef} ref` + : valueDiff < 0 + ? `)\nšŸ“‰ Loss from underpay: ${valueDiffRef} ref` + : ')'); + } + + const links = generateLinks(offerData.partner.toString()); + reply += + `\n\nSteam: ${links.steam}\nBackpack.tf: ${links.bptf}\nSteamREP: ${links.steamrep}` + + (offerData?.action?.action === 'skip' + ? `\n\nāš ļø Send "!accept ${offerId}" to accept or "!decline ${offerId}" to decline this offer.` + : `\n\nāš ļø Send "!faccept ${offerId}" to force accept the trade now!`); + + this.bot.sendMessage(steamID, reply); + } + + async actionOnTradeCommand(steamID: SteamID, message: string, command: ActionOnTrade): Promise { + const offerIdAndMessage = CommandParser.removeCommand(message); + const offerIdRegex = /\d+/.exec(offerIdAndMessage); + + const isAccepting = ['accept', 'accepttrade'].includes(command); + + if (isNaN(+offerIdRegex) || !offerIdRegex) { + return this.bot.sendMessage( + steamID, + `āš ļø Missing offer id. Example: "!${isAccepting ? 'accept' : 'decline'} 3957959294"` + ); + } + + const offerId = offerIdRegex[0]; + const state = this.bot.manager.pollData.received[offerId]; + if (state === undefined) { + return this.bot.sendMessage(steamID, 'Offer does not exist. āŒ'); + } + + if (state !== TradeOfferManager.ETradeOfferState['Active']) { + // TODO: Add what the offer is now, accepted / declined and why + return this.bot.sendMessage(steamID, 'Offer is not active. āŒ'); + } + + const offerData = this.bot.manager.pollData.offerData[offerId]; + if (offerData?.action?.action !== 'skip') { + return this.bot.sendMessage(steamID, "Offer can't be reviewed. āŒ"); + } + + try { + const offer = await this.bot.trades.getOffer(offerId); + this.bot.sendMessage(steamID, `${isAccepting ? 'Accepting' : 'Declining'} offer...`); + + const partnerId = new SteamID(this.bot.manager.pollData.offerData[offerId].partner); + const reply = offerIdAndMessage.substr(offerId.length); + const adminDetails = this.bot.friends.getFriend(steamID); + + try { + await this.bot.trades.applyActionToOffer( + isAccepting ? 'accept' : 'decline', + 'MANUAL', + isAccepting ? (offer.data('action') as Action).meta : {}, + offer + ); + + if (isAccepting) { + const isManyItems = offer.itemsToGive.length + offer.itemsToReceive.length > 50; + + if (isManyItems) { + this.bot.sendMessage( + offer.partner, + this.bot.options.customMessage.accepted.manual.largeOffer + ? this.bot.options.customMessage.accepted.manual.largeOffer + : '.\nMy owner has manually accepted your offer. The trade may take a while to finalize due to it being a large offer.' + + ' If the trade does not finalize after 5-10 minutes has passed, please send your offer again, or add me and use ' + + 'the !sell/!sellcart or !buy/!buycart command.' + ); + } else { + this.bot.sendMessage( + offer.partner, + this.bot.options.customMessage.accepted.manual.smallOffer + ? this.bot.options.customMessage.accepted.manual.smallOffer + : '.\nMy owner has manually accepted your offer. The trade should be finalized shortly.' + + ' If the trade does not finalize after 1-2 minutes has passed, please send your offer again, or add me and use ' + + 'the !sell/!sellcart or !buy/!buycart command.' + ); + } + } + + // Send message to recipient if includes some messages + if (reply) { + this.bot.sendMessage( + partnerId, + `/quote šŸ’¬ Message from ${adminDetails ? adminDetails.player_name : 'admin'}: ${reply}` + ); + } + } catch (err) { + return this.bot.sendMessage( + steamID, + `āŒ Ohh nooooes! Something went wrong while trying to ${ + isAccepting ? 'accept' : 'decline' + } the offer: ${(err as Error).message}` + ); + } + } catch (err) { + return this.bot.sendMessage( + steamID, + `āŒ Ohh nooooes! Something went wrong while trying to ${ + isAccepting ? 'accept' : 'decline' + } the offer: ${(err as Error).message}` + ); + } + } + + async forceAction(steamID: SteamID, message: string, command: ForceAction): Promise { + const offerIdAndMessage = CommandParser.removeCommand(message); + const offerIdRegex = /\d+/.exec(offerIdAndMessage); + + const isForceAccepting = command === 'faccept'; + + if (isNaN(+offerIdRegex) || !offerIdRegex) { + return this.bot.sendMessage( + steamID, + `āš ļø Missing offer id. Example: "!${isForceAccepting ? 'faccept' : 'fdecline'} 3957959294"` + ); + } + + const offerId = offerIdRegex[0]; + + const state = this.bot.manager.pollData.received[offerId]; + if (state === undefined) { + return this.bot.sendMessage(steamID, 'Offer does not exist. āŒ'); + } + + try { + const offer = await this.bot.trades.getOffer(offerId); + this.bot.sendMessage(steamID, `Force ${isForceAccepting ? 'accepting' : 'declining'} offer...`); + + const partnerId = new SteamID(this.bot.manager.pollData.offerData[offerId].partner); + const reply = offerIdAndMessage.substr(offerId.length); + const adminDetails = this.bot.friends.getFriend(steamID); + + try { + await this.bot.trades.applyActionToOffer( + isForceAccepting ? 'accept' : 'decline', + 'MANUAL-FORCE', + isForceAccepting ? (offer.data('action') as Action).meta : {}, + offer + ); + + // Send message to recipient if includes some messages + if (reply) { + this.bot.sendMessage( + partnerId, + `/quote šŸ’¬ Message from ${adminDetails ? adminDetails.player_name : 'admin'}: ${reply}` + ); + } + } catch (err) { + return this.bot.sendMessage( + steamID, + `āŒ Ohh nooooes! Something went wrong while trying to force ${ + isForceAccepting ? 'accept' : 'decline' + } the offer: ${(err as Error).message}` + ); + } + } catch (err) { + return this.bot.sendMessage( + steamID, + `āŒ Ohh nooooes! Something went wrong while trying to force ${ + isForceAccepting ? 'accept' : 'decline' + } the offer: ${(err as Error).message}` + ); + } + } +} diff --git a/src/classes/Commands/sub-classes/Status.ts b/src/classes/Commands/sub-classes/Status.ts new file mode 100644 index 000000000..9000677ac --- /dev/null +++ b/src/classes/Commands/sub-classes/Status.ts @@ -0,0 +1,130 @@ +import SteamID from 'steamid'; +import pluralize from 'pluralize'; +import Currencies from 'tf2-currencies'; +import Bot from '../../Bot'; +import { stats, profit } from '../../../lib/tools/export'; +import { sendStats } from '../../../lib/DiscordWebhook/export'; + +// Bot status + +export default class StatusCommands { + private readonly bot: Bot; + + constructor(bot: Bot) { + this.bot = bot; + } + + statsCommand(steamID: SteamID): void { + const tradesFromEnv = this.bot.options.statistics.lastTotalTrades; + const trades = stats(this.bot); + const profits = profit(this.bot, Math.floor((Date.now() - 86400000) / 1000)); //since -24h + + const keyPrices = this.bot.pricelist.getKeyPrices; + + const timedProfitmadeFull = Currencies.toCurrencies(profits.profitTimed, keyPrices.sell.metal).toString(); + const timedProfitmadeInRef = timedProfitmadeFull.includes('key') + ? ` (${Currencies.toRefined(profits.profitTimed)} ref)` + : ''; + + const profitmadeFull = Currencies.toCurrencies(profits.tradeProfit, keyPrices.sell.metal).toString(); + const profitmadeInRef = profitmadeFull.includes('key') + ? ` (${Currencies.toRefined(profits.tradeProfit)} ref)` + : ''; + + const profitOverpayFull = Currencies.toCurrencies(profits.overpriceProfit, keyPrices.sell.metal).toString(); + const profitOverpayInRef = profitOverpayFull.includes('key') + ? ` (${Currencies.toRefined(profits.overpriceProfit)} ref)` + : ''; + + this.bot.sendMessage( + steamID, + `All trades (accepted) are recorded from ${pluralize('day', trades.totalDays, true)}` + + ' ago šŸ“Š\n Total accepted trades: ' + + (tradesFromEnv !== 0 + ? String(tradesFromEnv + trades.totalAcceptedTrades) + : String(trades.totalAcceptedTrades)) + + `\n\n--- Last 24 hours ---` + + `\nā€¢ Processed: ${trades.hours24.processed}` + + `\nā€¢ Accepted: ${trades.hours24.accepted.offer + trades.hours24.accepted.sent}` + + `\n---ā€¢ Received offer: ${trades.hours24.accepted.offer}` + + `\n---ā€¢ Sent offer: ${trades.hours24.accepted.sent}` + + `\nā€¢ Declined: ${trades.hours24.decline.offer + trades.hours24.decline.sent}` + + `\n---ā€¢ Received offer: ${trades.hours24.decline.offer}` + + `\n---ā€¢ Sent offer: ${trades.hours24.decline.sent}` + + `\nā€¢ Skipped: ${trades.hours24.skipped}` + + `\nā€¢ Traded away: ${trades.hours24.invalid}` + + `\nā€¢ Canceled: ${trades.hours24.canceled.total}` + + `\n---ā€¢ by user: ${trades.hours24.canceled.byUser}` + + `\n---ā€¢ confirmation failed: ${trades.hours24.canceled.failedConfirmation}` + + `\n---ā€¢ unknown reason: ${trades.hours24.canceled.unknown}` + + `\n\n--- Since beginning of today ---` + + `\nā€¢ Processed: ${trades.today.processed}` + + `\nā€¢ Accepted: ${trades.today.accepted.offer + trades.today.accepted.sent}` + + `\n---ā€¢ Received offer: ${trades.today.accepted.offer}` + + `\n---ā€¢ Sent offer: ${trades.today.accepted.sent}` + + `\nā€¢ Declined: ${trades.today.decline.offer + trades.today.decline.sent}` + + `\n---ā€¢ Received offer: ${trades.today.decline.offer}` + + `\n---ā€¢ Sent offer: ${trades.today.decline.sent}` + + `\nā€¢ Skipped: ${trades.today.skipped}` + + `\nā€¢ Traded away: ${trades.today.invalid}` + + `\nā€¢ Canceled: ${trades.today.canceled.total}` + + `\n---ā€¢ by user: ${trades.today.canceled.byUser}` + + `\n---ā€¢ confirmation failed: ${trades.today.canceled.failedConfirmation}` + + `\n---ā€¢ unknown reason: ${trades.today.canceled.unknown}` + + `\n\n Profit (last 24h): ${timedProfitmadeFull + timedProfitmadeInRef}` + + `\nProfit made: ${profitmadeFull + profitmadeInRef} ${ + profits.since !== 0 ? ` (since ${pluralize('day', profits.since, true)} ago)` : '' + }` + + `\nProfit from overpay: ${profitOverpayFull + profitOverpayInRef}` + + `\nKey rate: ${keyPrices.buy.metal}/${keyPrices.sell.metal} ref` + ); + } + + statsDWCommand(steamID: SteamID): void { + const opt = this.bot.options.discordWebhook.sendStats; + + if (!opt.enable) { + return this.bot.sendMessage(steamID, 'āŒ Sending stats to Discord Webhook is disabled.'); + } + + if (opt.url === '') { + return this.bot.sendMessage(steamID, 'āŒ Your discordWebhook.sendStats.url is empty.'); + } + + sendStats(this.bot, true, steamID); + } + + inventoryCommand(steamID: SteamID): void { + this.bot.sendMessage( + steamID, + `šŸŽ’ My current items in my inventory: ${ + String(this.bot.inventoryManager.getInventory.getTotalItems) + '/' + String(this.bot.tf2.backpackSlots) + }` + ); + } + + versionCommand(steamID: SteamID): void { + this.bot.sendMessage( + steamID, + `Currently running TF2Autobot@v${process.env.BOT_VERSION}. Checking for a new version...` + ); + + this.bot.checkForUpdates + .then(({ hasNewVersion, latestVersion }) => { + if (!hasNewVersion) { + this.bot.sendMessage(steamID, 'You are running the latest version of TF2Autobot!'); + } else if (this.bot.lastNotifiedVersion === latestVersion) { + this.bot.sendMessage( + steamID, + `āš ļø Update available! Current: v${process.env.BOT_VERSION}, Latest: v${latestVersion}.\n\n` + + `Release note: https://github.com/idinium96/tf2autobot/releases` + + `\n\nRun "!updaterepo" if you're running your bot with PM2 to update now!"` + + '\n\nContact IdiNium if you have any other problem. Thank you.' + ); + } + }) + .catch(err => { + this.bot.sendMessage(steamID, `āŒ Failed to check for updates: ${JSON.stringify(err)}`); + }); + } +} diff --git a/src/classes/Commands/sub-classes/export.ts b/src/classes/Commands/sub-classes/export.ts new file mode 100644 index 000000000..efd4f0256 --- /dev/null +++ b/src/classes/Commands/sub-classes/export.ts @@ -0,0 +1,21 @@ +import StatusCommands from './Status'; +import HelpCommands from './Help'; +import MessageCommand from './Message'; +import MiscCommands from './Misc'; +import PricelistManager from './PricelistManager'; +import ReviewCommands from './Review'; +import OptionsCommand from './Options'; +import ManagerCommands from './Manager'; +import RequestCommands from './Request'; + +export { + StatusCommands, + HelpCommands, + MessageCommand, + MiscCommands, + PricelistManager, + ReviewCommands, + OptionsCommand, + ManagerCommands, + RequestCommands +}; diff --git a/src/classes/Listings.ts b/src/classes/Listings.ts index d94011573..22e0ed85a 100644 --- a/src/classes/Listings.ts +++ b/src/classes/Listings.ts @@ -11,7 +11,6 @@ import { BPTFGetUserInfo, UserSteamID } from './MyHandler/interfaces'; import log from '../lib/logger'; import { exponentialBackoff } from '../lib/helpers'; import { noiseMakers, spellsData, killstreakersData, sheensData } from '../lib/data'; -import { updateOptionsCommand } from './Commands/functions/options'; import { DictItem } from './Inventory'; import { PaintedNames } from './Options'; import { Paints, StrangeParts } from 'tf2-schema-2'; @@ -134,7 +133,7 @@ export default class Listings { this.enableAutoRelist(); } else if (this.isAutoRelistEnabled && info.premium === 1) { log.warn('Disabling autobump! - Your account is premium, no need to forcefully bump listings'); - updateOptionsCommand(null, '!config miscSettings.autobump.enable=false', this.bot); + this.bot.handler.commands.useUpdateOptionsCommand(null, '!config miscSettings.autobump.enable=false'); } }); } diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index 124282721..a7d8a037f 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -37,8 +37,6 @@ import Inventory, { Dict } from '../Inventory'; import TF2Inventory from '../TF2Inventory'; import Autokeys from '../Autokeys/Autokeys'; -import { statsCommand } from '../Commands/functions/status'; - import { Paths } from '../../resources/paths'; import log from '../../lib/logger'; import * as files from '../../lib/files'; @@ -52,7 +50,7 @@ import genPaths from '../../resources/paths'; import Pricer, { RequestCheckFn } from '../Pricer'; export default class MyHandler extends Handler { - private readonly commands: Commands; + readonly commands: Commands; readonly autokeys: Autokeys; @@ -543,7 +541,7 @@ export default class MyHandler extends Handler { sendStats(this.bot); } else { this.bot.getAdmins.forEach(admin => { - statsCommand(admin, this.bot); + this.commands.useStatsCommand(admin); }); } } From 726ddae29c0f0a6a91bb724f0ecc5dd242dd4c6c Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 09:36:25 +0800 Subject: [PATCH 25/43] =?UTF-8?q?=E2=9C=A8=20add=20missing=20`isFull`=20at?= =?UTF-8?q?tribute=20(for=20duel/noise)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Carts/UserCart.ts | 2 ++ src/classes/MyHandler/MyHandler.ts | 2 ++ src/lib/tools/getHighValue.ts | 4 +++- src/types/modules/steam-tradeoffer-manager/index.d.ts | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/classes/Carts/UserCart.ts b/src/classes/Carts/UserCart.ts index 22ff94a89..64aab332b 100644 --- a/src/classes/Carts/UserCart.ts +++ b/src/classes/Carts/UserCart.ts @@ -798,6 +798,8 @@ export default class UserCart extends Cart { } } }); + } else if (item.isFullUses !== undefined) { + getHighValue[whichIs].items[sku] = { isFull: item.isFullUses }; } }); } diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index 03c87e177..969d9bcc8 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -727,6 +727,8 @@ export default class MyHandler extends Handler { } } }); + } else if (item.isFullUses !== undefined) { + getHighValue[which].items[sku] = { isFull: item.isFullUses }; } }); } diff --git a/src/lib/tools/getHighValue.ts b/src/lib/tools/getHighValue.ts index f417b7730..71e34a546 100644 --- a/src/lib/tools/getHighValue.ts +++ b/src/lib/tools/getHighValue.ts @@ -27,7 +27,9 @@ export default function getHighValueItems( const toJoin: string[] = []; Object.keys(items[sku]).forEach(attachment => { - if (attachment === 's') { + if (attachment === 'isFull') { + toString += `\nšŸ’Æ Full uses: ${items[sku].isFull ? 'āœ…' : 'āŒ'}`; + } else if (attachment === 's') { toString += `\n${cT.spells ? cT.spells : 'šŸŽƒ Spells:'} `; items[sku].s.forEach(spellSKU => { diff --git a/src/types/modules/steam-tradeoffer-manager/index.d.ts b/src/types/modules/steam-tradeoffer-manager/index.d.ts index 71fd69aea..f59d5ffb3 100644 --- a/src/types/modules/steam-tradeoffer-manager/index.d.ts +++ b/src/types/modules/steam-tradeoffer-manager/index.d.ts @@ -244,6 +244,7 @@ declare module 'steam-tradeoffer-manager' { ks?: PartialSKUWithMention; ke?: PartialSKUWithMention; p?: PartialSKUWithMention; + isFull?: boolean; } interface Items { From 33c10c99463b58e57220000ddaa6330c620df0e0 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 09:43:03 +0800 Subject: [PATCH 26/43] =?UTF-8?q?=E2=9C=A8=F0=9F=94=A8=20add=20missing=20h?= =?UTF-8?q?ighValue,=20only=20define=20if=20true?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/MyHandler/MyHandler.ts | 85 +++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index 969d9bcc8..20e26dce2 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -1518,14 +1518,26 @@ export default class MyHandler extends Handler { const uniqueReasons = reasons.filter(reason => reasons.includes(reason)); if (!opt.offerReceived.escrowCheckFailed.ignoreFailed) { - return { - action: 'skip', - reason: 'ā¬œ_ESCROW_CHECK_FAILED', - meta: { - uniqueReasons: filterReasons(uniqueReasons), - reasons: wrongAboutOffer - } - }; + if (isContainsHighValue) { + return { + action: 'skip', + reason: 'ā¬œ_ESCROW_CHECK_FAILED', + meta: { + uniqueReasons: filterReasons(uniqueReasons), + reasons: wrongAboutOffer + } + }; + } else { + return { + action: 'skip', + reason: 'ā¬œ_ESCROW_CHECK_FAILED', + meta: { + uniqueReasons: filterReasons(uniqueReasons), + reasons: wrongAboutOffer, + highValue: highValueMeta(input) + } + }; + } } else { // Do nothing. bad. return; @@ -1560,14 +1572,26 @@ export default class MyHandler extends Handler { const uniqueReasons = reasons.filter(reason => reasons.includes(reason)); if (!opt.offerReceived.bannedCheckFailed.ignoreFailed) { - return { - action: 'skip', - reason: 'ā¬œ_BANNED_CHECK_FAILED', - meta: { - uniqueReasons: filterReasons(uniqueReasons), - reasons: wrongAboutOffer - } - }; + if (isContainsHighValue) { + return { + action: 'skip', + reason: 'ā¬œ_BANNED_CHECK_FAILED', + meta: { + uniqueReasons: filterReasons(uniqueReasons), + reasons: wrongAboutOffer, + highValue: highValueMeta(input) + } + }; + } else { + return { + action: 'skip', + reason: 'ā¬œ_BANNED_CHECK_FAILED', + meta: { + uniqueReasons: filterReasons(uniqueReasons), + reasons: wrongAboutOffer + } + }; + } } else { // Do nothing. bad. return; @@ -1759,15 +1783,26 @@ export default class MyHandler extends Handler { } else { offer.log('info', `offer needs review (${uniqueReasons.join(', ')}), skipping...`); - return { - action: 'skip', - reason: 'REVIEW', - meta: { - uniqueReasons: uniqueReasons, - reasons: wrongAboutOffer, - highValue: highValueMeta(input) - } - }; + if (isContainsHighValue) { + return { + action: 'skip', + reason: 'REVIEW', + meta: { + uniqueReasons: uniqueReasons, + reasons: wrongAboutOffer, + highValue: highValueMeta(input) + } + }; + } else { + return { + action: 'skip', + reason: 'REVIEW', + meta: { + uniqueReasons: uniqueReasons, + reasons: wrongAboutOffer + } + }; + } } } From 7a1bec66756da51de9d7ef5000d5f1af8457d9b4 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 09:49:17 +0800 Subject: [PATCH 27/43] =?UTF-8?q?=F0=9F=8E=A8=F0=9F=94=A8=20simplify?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/MyHandler/MyHandler.ts | 184 +++++++++-------------------- 1 file changed, 56 insertions(+), 128 deletions(-) diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index 20e26dce2..fbf791eea 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -770,18 +770,11 @@ export default class MyHandler extends Handler { )}` ); - if (isContainsHighValue) { - return { - action: 'accept', - reason: 'ADMIN', - meta: { highValue: highValueMeta(input) } - }; - } else { - return { - action: 'accept', - reason: 'ADMIN' - }; - } + return { + action: 'accept', + reason: 'ADMIN', + meta: isContainsHighValue ? { highValue: highValueMeta(input) } : undefined + }; } // check if the trade is valid @@ -864,18 +857,11 @@ export default class MyHandler extends Handler { 4 )}` ); - if (isContainsHighValue) { - return { - action: 'accept', - reason: 'GIFT', - meta: { highValue: highValueMeta(input) } - }; - } else { - return { - action: 'accept', - reason: 'GIFT' - }; - } + return { + action: 'accept', + reason: 'GIFT', + meta: isContainsHighValue ? { highValue: highValueMeta(input) } : undefined + }; } else if (offer.itemsToGive.length === 0 && offer.itemsToReceive.length > 0 && !isGift) { if (opt.bypass.giftWithoutMessage.allow) { offer.log( @@ -883,18 +869,11 @@ export default class MyHandler extends Handler { 'is a gift offer without any offer message, but allowed to be accepted, accepting...' ); - if (isContainsHighValue) { - return { - action: 'accept', - reason: 'GIFT', - meta: { highValue: highValueMeta(input) } - }; - } else { - return { - action: 'accept', - reason: 'GIFT' - }; - } + return { + action: 'accept', + reason: 'GIFT', + meta: isContainsHighValue ? { highValue: highValueMeta(input) } : undefined + }; } else { offer.log('info', 'is a gift offer without any offer message, declining...'); return { action: 'decline', reason: 'GIFT_NO_NOTE' }; @@ -1518,26 +1497,15 @@ export default class MyHandler extends Handler { const uniqueReasons = reasons.filter(reason => reasons.includes(reason)); if (!opt.offerReceived.escrowCheckFailed.ignoreFailed) { - if (isContainsHighValue) { - return { - action: 'skip', - reason: 'ā¬œ_ESCROW_CHECK_FAILED', - meta: { - uniqueReasons: filterReasons(uniqueReasons), - reasons: wrongAboutOffer - } - }; - } else { - return { - action: 'skip', - reason: 'ā¬œ_ESCROW_CHECK_FAILED', - meta: { - uniqueReasons: filterReasons(uniqueReasons), - reasons: wrongAboutOffer, - highValue: highValueMeta(input) - } - }; - } + return { + action: 'skip', + reason: 'ā¬œ_ESCROW_CHECK_FAILED', + meta: { + uniqueReasons: filterReasons(uniqueReasons), + reasons: wrongAboutOffer, + highValue: isContainsHighValue ? highValueMeta(input) : undefined + } + }; } else { // Do nothing. bad. return; @@ -1572,26 +1540,15 @@ export default class MyHandler extends Handler { const uniqueReasons = reasons.filter(reason => reasons.includes(reason)); if (!opt.offerReceived.bannedCheckFailed.ignoreFailed) { - if (isContainsHighValue) { - return { - action: 'skip', - reason: 'ā¬œ_BANNED_CHECK_FAILED', - meta: { - uniqueReasons: filterReasons(uniqueReasons), - reasons: wrongAboutOffer, - highValue: highValueMeta(input) - } - }; - } else { - return { - action: 'skip', - reason: 'ā¬œ_BANNED_CHECK_FAILED', - meta: { - uniqueReasons: filterReasons(uniqueReasons), - reasons: wrongAboutOffer - } - }; - } + return { + action: 'skip', + reason: 'ā¬œ_BANNED_CHECK_FAILED', + meta: { + uniqueReasons: filterReasons(uniqueReasons), + reasons: wrongAboutOffer, + highValue: isContainsHighValue ? highValueMeta(input) : undefined + } + }; } else { // Do nothing. bad. return; @@ -1703,26 +1660,15 @@ export default class MyHandler extends Handler { ); } - if (isContainsHighValue) { - return { - action: 'accept', - reason: 'VALID_WITH_OVERPAY', - meta: { - uniqueReasons: uniqueReasons, - reasons: wrongAboutOffer, - highValue: highValueMeta(input) - } - }; - } else { - return { - action: 'accept', - reason: 'VALID_WITH_OVERPAY', - meta: { - uniqueReasons: uniqueReasons, - reasons: wrongAboutOffer - } - }; - } + return { + action: 'accept', + reason: 'VALID_WITH_OVERPAY', + meta: { + uniqueReasons: uniqueReasons, + reasons: wrongAboutOffer, + highValue: isContainsHighValue ? highValueMeta(input) : undefined + } + }; } else if ( opt.offerReceived.invalidValue.autoDecline.enable && isInvalidValue && @@ -1783,26 +1729,15 @@ export default class MyHandler extends Handler { } else { offer.log('info', `offer needs review (${uniqueReasons.join(', ')}), skipping...`); - if (isContainsHighValue) { - return { - action: 'skip', - reason: 'REVIEW', - meta: { - uniqueReasons: uniqueReasons, - reasons: wrongAboutOffer, - highValue: highValueMeta(input) - } - }; - } else { - return { - action: 'skip', - reason: 'REVIEW', - meta: { - uniqueReasons: uniqueReasons, - reasons: wrongAboutOffer - } - }; - } + return { + action: 'skip', + reason: 'REVIEW', + meta: { + uniqueReasons: uniqueReasons, + reasons: wrongAboutOffer, + highValue: isContainsHighValue ? highValueMeta(input) : undefined + } + }; } } @@ -1831,18 +1766,11 @@ export default class MyHandler extends Handler { ); } - if (isContainsHighValue) { - return { - action: 'accept', - reason: 'VALID', - meta: { highValue: highValueMeta(input) } - }; - } else { - return { - action: 'accept', - reason: 'VALID' - }; - } + return { + action: 'accept', + reason: 'VALID', + meta: isContainsHighValue ? { highValue: highValueMeta(input) } : undefined + }; } onTradeOfferChanged(offer: TradeOffer, oldState: number, processTime?: number): void { From 40d0c6fa921c406c415f235c0ed42c2cdce70884 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 13:42:18 +0800 Subject: [PATCH 28/43] =?UTF-8?q?=F0=9F=94=A8=20slight=20optimization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 165 ++++++++++++------ package.json | 6 +- src/classes/Carts/AdminCart.ts | 24 +-- src/classes/Carts/Cart.ts | 16 +- src/classes/Carts/DonateCart.ts | 13 +- src/classes/Carts/PremiumCart.ts | 13 +- src/classes/Carts/UserCart.ts | 90 ++++++---- src/classes/Commands/functions/utils.ts | 46 +++-- src/classes/Commands/sub-classes/Manager.ts | 14 +- src/classes/Commands/sub-classes/Misc.ts | 9 +- .../Commands/sub-classes/PricelistManager.ts | 42 +++-- src/classes/Commands/sub-classes/Request.ts | 24 +-- src/classes/Commands/sub-classes/Review.ts | 36 ++-- src/classes/Inventory.ts | 8 +- src/classes/MyHandler/MyHandler.ts | 135 +++++++------- .../offer/accepted/updateListings.ts | 8 +- .../offer/review/reasons/disabledItems.ts | 6 +- .../MyHandler/offer/review/reasons/duped.ts | 6 +- .../offer/review/reasons/invalidItems.ts | 6 +- .../MyHandler/utils/craftClassWeapons.ts | 6 +- src/classes/Pricelist.ts | 24 ++- src/classes/TF2GC.ts | 4 +- src/classes/Trades.ts | 23 ++- src/lib/DiscordWebhook/sendOfferReview.ts | 5 +- src/lib/DiscordWebhook/sendTradeSummary.ts | 5 +- src/lib/extend/item/getSKU.ts | 42 +++-- src/lib/items.ts | 35 ++-- src/lib/tools/profit.ts | 10 +- src/lib/tools/pure.ts | 5 +- src/lib/tools/summarizeItems.ts | 72 ++++---- src/lib/tools/summarizeOffer.ts | 8 +- 31 files changed, 537 insertions(+), 369 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65d723084..4851a1bce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2870,9 +2870,9 @@ } }, "bptf-listings-2": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.10.tgz", - "integrity": "sha512-yrrksUauwcJs058V0coPte1K/HJnoKRSDglbbQGvOpsK1fRFiLJPsuSitj4gjeVB9E0DrG9/FIaBjyi/tqC22A==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/bptf-listings-2/-/bptf-listings-2-1.1.11.tgz", + "integrity": "sha512-4CyuT4r/+6E+CeIYsfkfmTVTIbb8XrrnpY8Cvyyo6OXnZV09D42al8a5YsmX3Zfe4GNUTONuWCgGPZU63WpR1A==", "requires": { "async": "^3.2.0", "events": "^3.2.0", @@ -3026,12 +3026,12 @@ } }, "call-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", - "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "requires": { "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.0" + "get-intrinsic": "^1.0.2" } }, "callback-queue": { @@ -5087,9 +5087,9 @@ "dev": true }, "get-intrinsic": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", - "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -5786,6 +5786,11 @@ "is-path-inside": "^3.0.1" } }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + }, "is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", @@ -5853,32 +5858,36 @@ } }, "is-typed-array": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", - "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.4.tgz", + "integrity": "sha512-ILaRgn4zaSrVNXNGtON6iFNotXW3hAPF3+0fB1usg2jFlWqo5fEDdmJkz0zBfoi7Dgskr8Khi2xZ8cXqZEfXNA==", "requires": { - "available-typed-arrays": "^1.0.0", - "es-abstract": "^1.17.4", + "available-typed-arrays": "^1.0.2", + "call-bind": "^1.0.0", + "es-abstract": "^1.18.0-next.1", "foreach": "^2.0.5", "has-symbols": "^1.0.1" }, "dependencies": { "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "version": "1.18.0-next.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", + "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", "requires": { + "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2", "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.1", "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", + "object-inspect": "^1.9.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.3", + "string.prototype.trimstart": "^1.0.3" } }, "es-to-primitive": { @@ -5897,22 +5906,23 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" }, "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", "requires": { + "call-bind": "^1.0.2", "has-symbols": "^1.0.1" } }, "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" }, "object.assign": { "version": "4.1.2", @@ -5924,6 +5934,24 @@ "has-symbols": "^1.0.1", "object-keys": "^1.1.1" } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } } } }, @@ -10808,9 +10836,9 @@ } }, "tf2-schema-2": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/tf2-schema-2/-/tf2-schema-2-1.2.3.tgz", - "integrity": "sha512-HA0BHnb/ZP/pZt/6thEtpOR54vWt65dpT1N7LvKm5dFqVLUSpUqXABhxyl5tcgwXgByZD8jCgMQ9OtSCL4VOUA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tf2-schema-2/-/tf2-schema-2-1.2.4.tgz", + "integrity": "sha512-1U/dGRbhLMI7DfZKcQJRVDe8xZbROQJDicR/jWvNjV4p9dXbC4DAXIiAkY5ehYBA5p8cDkjcuDUSxlE+H3mPhQ==", "requires": { "async": "^3.2.0", "events": "^3.2.0", @@ -10836,9 +10864,9 @@ } }, "tf2-sku-2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tf2-sku-2/-/tf2-sku-2-1.0.1.tgz", - "integrity": "sha512-iIFuv4XM/qtYlpWKVSidf6Roxo8Lj+5oT1reWI9DPHFinTZIyq1Axhx1NCSw9niQG/Qp3MkaQCF+XwXq+g/G8w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tf2-sku-2/-/tf2-sku-2-1.0.2.tgz", + "integrity": "sha512-dnJxAfvK1A08m+4KRx1fi3OVctqB2M0yLK/rFIWhDEFMya+bWCdSwhqiYJvukf3TX19Tg7kPnpeiwLyGM22uRQ==", "requires": { "defaults": "^1.0.3", "object-prettify": "^1.0.0" @@ -11512,12 +11540,13 @@ "dev": true }, "which-typed-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", - "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", + "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", "requires": { "available-typed-arrays": "^1.0.2", - "es-abstract": "^1.17.5", + "call-bind": "^1.0.0", + "es-abstract": "^1.18.0-next.1", "foreach": "^2.0.5", "function-bind": "^1.1.1", "has-symbols": "^1.0.1", @@ -11525,21 +11554,24 @@ }, "dependencies": { "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "version": "1.18.0-next.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", + "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", "requires": { + "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2", "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.1", "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", + "object-inspect": "^1.9.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.3", + "string.prototype.trimstart": "^1.0.3" } }, "es-to-primitive": { @@ -11558,22 +11590,23 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" }, "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", "requires": { + "call-bind": "^1.0.2", "has-symbols": "^1.0.1" } }, "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" }, "object.assign": { "version": "4.1.2", @@ -11585,6 +11618,24 @@ "has-symbols": "^1.0.1", "object-keys": "^1.1.1" } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } } } }, diff --git a/package.json b/package.json index 9e27ec575..9a2ed735f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "async": "^3.2.0", "bluebird": "^3.7.2", "bluebird-global": "^1.0.1", - "bptf-listings-2": "^1.1.10", + "bptf-listings-2": "^1.1.11", "bptf-login-2": "^1.0.0", "callback-queue": "^3.0.0", "change-case": "^4.1.2", @@ -58,8 +58,8 @@ "steamid": "^1.1.3", "tf2": "^3.0.2", "tf2-currencies": "^1.2.4", - "tf2-schema-2": "^1.2.3", - "tf2-sku-2": "^1.0.1", + "tf2-schema-2": "^1.2.4", + "tf2-sku-2": "^1.0.2", "url": "^0.11.0", "valid-url": "^1.0.9", "websocket-extensions": "^0.1.4", diff --git a/src/classes/Carts/AdminCart.ts b/src/classes/Carts/AdminCart.ts index 17f8eb198..115afcd4e 100644 --- a/src/classes/Carts/AdminCart.ts +++ b/src/classes/Carts/AdminCart.ts @@ -27,31 +27,32 @@ export default class AdminCart extends Cart { let amount = this.getOurCount(sku); const ourAssetids = ourInventory.findBySKU(sku, true); + const ourAssetidsCount = ourAssetids.length; - if (amount > ourAssetids.length) { - amount = ourAssetids.length; + if (amount > ourAssetidsCount) { + amount = ourAssetidsCount; // Remove the item from the cart this.removeOurItem(sku); - if (ourAssetids.length === 0) { + if (ourAssetidsCount === 0) { alteredMessages.push( "I don't have any " + pluralize(this.bot.schema.getName(SKU.fromString(sku), false)) ); } else { alteredMessages.push( 'I only have ' + - pluralize(this.bot.schema.getName(SKU.fromString(sku), false), ourAssetids.length, true) + pluralize(this.bot.schema.getName(SKU.fromString(sku), false), ourAssetidsCount, true) ); // Add the max amount to the offer - this.addOurItem(sku, ourAssetids.length); + this.addOurItem(sku, ourAssetidsCount); } } let missing = amount; let isSkipped = false; - for (let i = 0; i < ourAssetids.length; i++) { + for (let i = 0; i < ourAssetidsCount; i++) { if ( this.bot.options.miscSettings.skipItemsInTrade.enable && this.bot.trades.isInTrade(ourAssetids[i]) @@ -132,13 +133,14 @@ export default class AdminCart extends Cart { let amount = this.getTheirCount(sku); const theirAssetids = theirInventory.findBySKU(sku, true); + const theirAssetidsCount = theirAssetids.length; - if (amount > theirAssetids.length) { - amount = theirAssetids.length; + if (amount > theirAssetidsCount) { + amount = theirAssetidsCount; // Remove the item from the cart this.removeTheirItem(sku); - if (theirAssetids.length === 0) { + if (theirAssetidsCount === 0) { alteredMessages.push( "you don't have any " + pluralize(this.bot.schema.getName(SKU.fromString(sku), false)) ); @@ -147,7 +149,7 @@ export default class AdminCart extends Cart { 'you only have ' + pluralize( this.bot.schema.getName(SKU.fromString(sku), false), - theirAssetids.length, + theirAssetidsCount, true ) ); @@ -155,7 +157,7 @@ export default class AdminCart extends Cart { // Add the max amount to the offer substract current added amount this.addTheirItem( sku, - this.their[sku] ? theirAssetids.length - this.their[sku] : theirAssetids.length + this.their[sku] ? theirAssetidsCount - this.their[sku] : theirAssetidsCount ); } } diff --git a/src/classes/Carts/Cart.ts b/src/classes/Carts/Cart.ts index c1c69fc8a..948ea1f42 100644 --- a/src/classes/Carts/Cart.ts +++ b/src/classes/Carts/Cart.ts @@ -198,28 +198,28 @@ export default abstract class Cart { summarize(isDonating: boolean, isBuyingPremium: boolean): string { const ourSummary = this.summarizeOur(); + const ourSummaryCount = ourSummary.length; let ourSummaryString: string; - if (ourSummary.length > 1) { + if (ourSummaryCount > 1) { ourSummaryString = - ourSummary.slice(0, ourSummary.length - 1).join(', ') + ' and ' + ourSummary[ourSummary.length - 1]; - } else if (ourSummary.length === 0) { + ourSummary.slice(0, ourSummaryCount - 1).join(', ') + ' and ' + ourSummary[ourSummaryCount - 1]; + } else if (ourSummaryCount === 0) { ourSummaryString = 'nothing'; } else { ourSummaryString = ourSummary.join(', '); } const theirSummary = this.summarizeTheir(); + const theirSummaryCount = theirSummary.length; let theirSummaryString: string; - if (theirSummary.length > 1) { + if (theirSummaryCount > 1) { theirSummaryString = - theirSummary.slice(0, theirSummary.length - 1).join(', ') + - ' and ' + - theirSummary[theirSummary.length - 1]; - } else if (theirSummary.length === 0) { + theirSummary.slice(0, theirSummaryCount - 1).join(', ') + ' and ' + theirSummary[theirSummaryCount - 1]; + } else if (theirSummaryCount === 0) { theirSummaryString = 'nothing'; } else { theirSummaryString = theirSummary.join(', '); diff --git a/src/classes/Carts/DonateCart.ts b/src/classes/Carts/DonateCart.ts index 688958e8b..88150cfa0 100644 --- a/src/classes/Carts/DonateCart.ts +++ b/src/classes/Carts/DonateCart.ts @@ -31,31 +31,32 @@ export default class DonateCart extends Cart { let amount = this.getOurCount(sku); const ourAssetids = ourInventory.findBySKU(sku, true); + const ourAssetidsCount = ourAssetids.length; - if (amount > ourAssetids.length) { - amount = ourAssetids.length; + if (amount > ourAssetidsCount) { + amount = ourAssetidsCount; // Remove the item from the cart this.removeOurItem(sku); - if (ourAssetids.length === 0) { + if (ourAssetidsCount === 0) { alteredMessages.push( "I don't have any " + pluralize(this.bot.schema.getName(SKU.fromString(sku), false)) ); } else { alteredMessages.push( 'I only have ' + - pluralize(this.bot.schema.getName(SKU.fromString(sku), false), ourAssetids.length, true) + pluralize(this.bot.schema.getName(SKU.fromString(sku), false), ourAssetidsCount, true) ); // Add the max amount to the offer - this.addOurItem(sku, ourAssetids.length); + this.addOurItem(sku, ourAssetidsCount); } } let missing = amount; let isSkipped = false; - for (let i = 0; i < ourAssetids.length; i++) { + for (let i = 0; i < ourAssetidsCount; i++) { if ( this.bot.options.miscSettings.skipItemsInTrade.enable && this.bot.trades.isInTrade(ourAssetids[i]) diff --git a/src/classes/Carts/PremiumCart.ts b/src/classes/Carts/PremiumCart.ts index 0f4887278..eba269d5a 100644 --- a/src/classes/Carts/PremiumCart.ts +++ b/src/classes/Carts/PremiumCart.ts @@ -31,31 +31,32 @@ export default class PremiumCart extends Cart { let amount = this.getOurCount(sku); const ourAssetids = ourInventory.findBySKU(sku, true); + const ourAssetidsCount = ourAssetids.length; - if (amount > ourAssetids.length) { - amount = ourAssetids.length; + if (amount > ourAssetidsCount) { + amount = ourAssetidsCount; // Remove the item from the cart this.removeOurItem(sku); - if (ourAssetids.length === 0) { + if (ourAssetidsCount === 0) { alteredMessages.push( "I don't have any " + pluralize(this.bot.schema.getName(SKU.fromString(sku), false)) ); } else { alteredMessages.push( 'I only have ' + - pluralize(this.bot.schema.getName(SKU.fromString(sku), false), ourAssetids.length, true) + pluralize(this.bot.schema.getName(SKU.fromString(sku), false), ourAssetidsCount, true) ); // Add the max amount to the offer - this.addOurItem(sku, ourAssetids.length); + this.addOurItem(sku, ourAssetidsCount); } } let missing = amount; let isSkipped = false; - for (let i = 0; i < ourAssetids.length; i++) { + for (let i = 0; i < ourAssetidsCount; i++) { if ( this.bot.options.miscSettings.skipItemsInTrade.enable && this.bot.trades.isInTrade(ourAssetids[i]) diff --git a/src/classes/Carts/UserCart.ts b/src/classes/Carts/UserCart.ts index 64aab332b..41324b20e 100644 --- a/src/classes/Carts/UserCart.ts +++ b/src/classes/Carts/UserCart.ts @@ -62,7 +62,9 @@ export default class UserCart extends Cart { log.debug(`Got result from dupe checks on ${assetidsToCheck.join(', ')}`, { result: result }); - for (let i = 0; i < result.length; i++) { + const resultCount = result.length; + + for (let i = 0; i < resultCount; i++) { if (result[i] === true) { // Found duped item return Promise.reject('offer contains duped items'); @@ -175,6 +177,7 @@ export default class UserCart extends Cart { } const skus = Object.keys(currencyValues); + const skusCount = skus.length; let remaining = price.toValue(useKeys ? keyPrice.metal : undefined); @@ -194,7 +197,7 @@ export default class UserCart extends Cart { amount = buyerCurrencies[key]; } - if (index === skus.length - 1) { + if (index === skusCount - 1) { // If we are at the end of the list and have a postive remaining amount, // then we need to loop the other way and pick the value that will make the remaining 0 or negative @@ -260,24 +263,26 @@ export default class UserCart extends Cart { log.debug('Picked too much value, removing...'); // Removes unnecessary items - for (let i = 0; i < skus.length; i++) { - if (pickedCurrencies[skus[i]] === undefined) { + for (let i = 0; i < skusCount; i++) { + const sku = skus[i]; + + if (pickedCurrencies[sku] === undefined) { continue; } - let amount = Math.floor(Math.abs(remaining) / currencyValues[skus[i]]); - if (pickedCurrencies[skus[i]] < amount) { - amount = pickedCurrencies[skus[i]]; + let amount = Math.floor(Math.abs(remaining) / currencyValues[sku]); + if (pickedCurrencies[sku] < amount) { + amount = pickedCurrencies[sku]; } if (amount >= 1) { - remaining += amount * currencyValues[skus[i]]; - pickedCurrencies[skus[i]] -= amount; + remaining += amount * currencyValues[sku]; + pickedCurrencies[sku] -= amount; } if (!this.bot.handler.isWeaponsAsCurrency.enable) { log.debug('Iteration', { - sku: skus[i], + sku: sku, amount: amount, remaining: remaining, picked: pickedCurrencies @@ -397,20 +402,21 @@ export default class UserCart extends Cart { let amount = this.getOurCount(sku); const ourAssetids = ourInventory.findBySKU(sku, true); + const ourAssetidsCount = ourAssetids.length; - if (amount > ourAssetids.length) { - amount = ourAssetids.length; + if (amount > ourAssetidsCount) { + amount = ourAssetidsCount; // Remove the item from the cart this.removeOurItem(sku, Infinity); - if (ourAssetids.length === 0) { + if (ourAssetidsCount === 0) { alteredMessage = "I don't have any " + pluralize(this.bot.schema.getName(SKU.fromString(sku), false)); } else { alteredMessage = 'I only have ' + - pluralize(this.bot.schema.getName(SKU.fromString(sku), false), ourAssetids.length, true); + pluralize(this.bot.schema.getName(SKU.fromString(sku), false), ourAssetidsCount, true); // Add the max amount to the cart this.addOurItem(sku, amount); @@ -479,19 +485,20 @@ export default class UserCart extends Cart { let amount = this.getTheirCount(sku); const theirAssetids = theirInventory.findBySKU(sku, true); + const theirAssetidsCount = theirAssetids.length; - if (amount > theirAssetids.length) { + if (amount > theirAssetidsCount) { // Remove the item from the cart this.removeTheirItem(sku, Infinity); - if (theirAssetids.length === 0) { + if (theirAssetidsCount === 0) { alteredMessage = "you don't have any " + pluralize(this.bot.schema.getName(SKU.fromString(sku), false)); } else { - amount = theirAssetids.length; + amount = theirAssetidsCount; alteredMessage = 'you only have ' + - pluralize(this.bot.schema.getName(SKU.fromString(sku), false), theirAssetids.length, true); + pluralize(this.bot.schema.getName(SKU.fromString(sku), false), theirAssetidsCount, true); // Add the max amount to the cart this.addTheirItem(sku, amount); @@ -619,25 +626,28 @@ export default class UserCart extends Cart { const amount = this.our[sku]; const assetids = ourInventory.findBySKU(sku, true); + const ourAssetidsCount = assetids.length; this.ourItemsCount += amount; let missing = amount; let isSkipped = false; - for (let i = 0; i < assetids.length; i++) { - if (opt.miscSettings.skipItemsInTrade.enable && this.bot.trades.isInTrade(assetids[i])) { + for (let i = 0; i < ourAssetidsCount; i++) { + const assetid = assetids[i]; + + if (opt.miscSettings.skipItemsInTrade.enable && this.bot.trades.isInTrade(assetid)) { isSkipped = true; continue; } const isAdded = offer.addMyItem({ appid: 440, contextid: '2', - assetid: assetids[i] + assetid: assetid }); if (isAdded) { // The item was added to the offer - whichAssetIds.our.push(assetids[i]); + whichAssetIds.our.push(assetid); missing--; if (missing === 0) { // We added all the items @@ -702,20 +712,24 @@ export default class UserCart extends Cart { assetids = getAssetidsWithFullUses(inventoryDict.their[sku]); } - for (let i = 0; i < assetids.length; i++) { + const theirAssetidsCount = assetids.length; + + for (let i = 0; i < theirAssetidsCount; i++) { + const assetid = assetids[i]; + const isAdded = offer.addTheirItem({ appid: 440, contextid: '2', - assetid: assetids[i] + assetid: assetid }); if (isAdded) { missing--; - whichAssetIds.their.push(assetids[i]); + whichAssetIds.their.push(assetid); if (addToDupeCheckList) { - assetidsToCheck.push(assetids[i]); + assetidsToCheck.push(assetid); } if (missing === 0) { @@ -863,17 +877,21 @@ export default class UserCart extends Cart { if (change / value >= 1) { const whose = isBuyer ? 'their' : 'our'; - for (let i = 0; i < currencies[sku].length; i++) { + const currenciesCount = currencies[sku].length; + + for (let i = 0; i < currenciesCount; i++) { + const assetid = currencies[sku][i]; + if ( !isBuyer && opt.miscSettings.skipItemsInTrade.enable && - this.bot.trades.isInTrade(currencies[sku][i]) + this.bot.trades.isInTrade(assetid) ) { isSkipped = true; continue; } const isAdded = offer[isBuyer ? 'addTheirItem' : 'addMyItem']({ - assetid: currencies[sku][i], + assetid: assetid, appid: 440, contextid: '2', amount: 1 @@ -930,17 +948,17 @@ export default class UserCart extends Cart { let isSkipped = false; - for (let i = 0; i < buyerCurrenciesWithAssetids[sku].length; i++) { - if ( - isBuyer && - opt.miscSettings.skipItemsInTrade.enable && - this.bot.trades.isInTrade(buyerCurrenciesWithAssetids[sku][i]) - ) { + const buyerCurrenciesWithAssetidsCount = buyerCurrenciesWithAssetids[sku].length; + + for (let i = 0; i < buyerCurrenciesWithAssetidsCount; i++) { + const assetid = buyerCurrenciesWithAssetids[sku][i]; + + if (isBuyer && opt.miscSettings.skipItemsInTrade.enable && this.bot.trades.isInTrade(assetid)) { isSkipped = true; continue; } const isAdded = offer[isBuyer ? 'addMyItem' : 'addTheirItem']({ - assetid: buyerCurrenciesWithAssetids[sku][i], + assetid: assetid, appid: 440, contextid: '2', amount: 1 diff --git a/src/classes/Commands/functions/utils.ts b/src/classes/Commands/functions/utils.ts index 34fda618a..39323f597 100644 --- a/src/classes/Commands/functions/utils.ts +++ b/src/classes/Commands/functions/utils.ts @@ -162,11 +162,11 @@ export function getItemAndAmount( } else if (Array.isArray(match)) { const matchCount = match.length; - if (match.length > 20) { + if (matchCount > 20) { match = match.splice(0, 20); } - let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join(',\n')}`; + let reply = `I've found ${matchCount} items. Try with one of the items shown below:\n${match.join(',\n')}`; if (matchCount > match.length) { const other = matchCount - match.length; reply += `,\nand ${other} other ${pluralize('item', other)}.`; @@ -197,28 +197,33 @@ export function getItemFromParams( const match: SchemaManager.SchemaItem[] = []; - for (let i = 0; i < bot.schema.raw.schema.items.length; i++) { - if (bot.schema.raw.schema.items[i].item_name === params.name) { - match.push(bot.schema.raw.schema.items[i]); + const items = bot.schema.raw.schema.items; + const itemsCount = items.length; + + for (let i = 0; i < itemsCount; i++) { + if (items[i].item_name === params.name) { + match.push(items[i]); } } - if (match.length === 0) { + const matchCount = match.length; + + if (matchCount === 0) { bot.sendMessage( steamID, `āŒ Could not find an item in the schema with the name "${params.name as string}".` ); return null; - } else if (match.length !== 1) { - const matchCount = match.length; + } else if (matchCount !== 1) { const parsed = match.splice(0, 20).map(schemaItem => `${schemaItem.defindex} (${schemaItem.name})`); + const parsedCount = parsed.length; let reply = `I've found ${matchCount} items with a matching name. Please use one of the defindexes below as "defindex":\n${parsed.join( ',\n' )}`; - if (matchCount > parsed.length) { - const other = matchCount - parsed.length; + if (matchCount > parsedCount) { + const other = matchCount - parsedCount; reply += `,\nand ${other} other ${pluralize('item', other)}.`; } @@ -450,28 +455,33 @@ export function getItemFromParams( // Look for all items that have the same name const match: SchemaManager.SchemaItem[] = []; - for (let i = 0; i < bot.schema.raw.schema.items.length; i++) { - if (bot.schema.raw.schema.items[i].item_name === params.name) { - match.push(bot.schema.raw.schema.items[i]); + const items = bot.schema.raw.schema.items; + const itemsCount = bot.schema.raw.schema.items.length; + + for (let i = 0; i < itemsCount; i++) { + if (items[i].item_name === params.name) { + match.push(items[i]); } } - if (match.length === 0) { + const matchCount = match.length; + + if (matchCount === 0) { bot.sendMessage( steamID, `āŒ Could not find an item in the schema with the name "${params.name as string}".` ); return null; - } else if (match.length !== 1) { - const matchCount = match.length; + } else if (matchCount !== 1) { const parsed = match.splice(0, 20).map(schemaItem => `${schemaItem.defindex} (${schemaItem.name})`); + const parsedCount = parsed.length; let reply = `I've found ${matchCount} items with a matching name. Please use one of the defindexes below as "output":\n${parsed.join( ',\n' )}`; - if (matchCount > parsed.length) { - const other = matchCount - parsed.length; + if (matchCount > parsedCount) { + const other = matchCount - parsedCount; reply += `,\nand ${other} other ${pluralize('item', other)}.`; } diff --git a/src/classes/Commands/sub-classes/Manager.ts b/src/classes/Commands/sub-classes/Manager.ts index 748beef0b..3be82c005 100644 --- a/src/classes/Commands/sub-classes/Manager.ts +++ b/src/classes/Commands/sub-classes/Manager.ts @@ -578,24 +578,26 @@ export default class ManagerCommands { return false; }); - if (pricelist.length > 0) { + const pricelistCount = pricelist.length; + + if (pricelistCount > 0) { clearTimeout(this.executeTimeout); this.lastExecutedTime = dayjs().valueOf(); log.debug( 'Checking listings for ' + - pluralize('item', pricelist.length, true) + + pluralize('item', pricelistCount, true) + ` [${pricelist.map(entry => entry.sku).join(', ')}] ...` ); this.bot.sendMessage( steamID, - 'Refreshing listings for ' + pluralize('item', pricelist.length, true) + '...' + 'Refreshing listings for ' + pluralize('item', pricelistCount, true) + '...' ); this.bot.handler.isRecentlyExecuteRefreshlistCommand = true; this.bot.handler.setRefreshlistExecutedDelay = (this.pricelistLength > 1000 ? 60 : 30) * 60 * 1000; - this.pricelistLength = pricelist.length; + this.pricelistLength = pricelistCount; this.executed = true; this.executeTimeout = setTimeout(() => { this.lastExecutedTime = null; @@ -606,8 +608,8 @@ export default class ManagerCommands { await this.bot.listings.recursiveCheckPricelist(pricelist, true); - log.debug('Done checking ' + pluralize('item', pricelist.length, true)); - this.bot.sendMessage(steamID, 'āœ… Done refreshing ' + pluralize('item', pricelist.length, true)); + log.debug('Done checking ' + pluralize('item', pricelistCount, true)); + this.bot.sendMessage(steamID, 'āœ… Done refreshing ' + pluralize('item', pricelistCount, true)); } else { this.bot.sendMessage(steamID, 'āŒ Nothing to refresh.'); } diff --git a/src/classes/Commands/sub-classes/Misc.ts b/src/classes/Commands/sub-classes/Misc.ts index e294b4fbb..2c2b36793 100644 --- a/src/classes/Commands/sub-classes/Misc.ts +++ b/src/classes/Commands/sub-classes/Misc.ts @@ -157,7 +157,9 @@ export default class MiscCommands { const max = (opt as Stock).maximumItems; - for (let i = 0; i < parsed.length; i++) { + const parsedCount = parsed.length; + + for (let i = 0; i < parsedCount; i++) { if (stock.length > max) { left += parsed[i].amount; } else { @@ -243,9 +245,10 @@ export default class MiscCommands { }); const stock: string[] = []; + const itemsCount = items.length; - if (items.length > 0) { - for (let i = 0; i < items.length; i++) { + if (itemsCount > 0) { + for (let i = 0; i < itemsCount; i++) { stock.push(`${items[i].name}: ${items[i].amount}`); } } diff --git a/src/classes/Commands/sub-classes/PricelistManager.ts b/src/classes/Commands/sub-classes/PricelistManager.ts index 668ea1a5c..9da2ef28c 100644 --- a/src/classes/Commands/sub-classes/PricelistManager.ts +++ b/src/classes/Commands/sub-classes/PricelistManager.ts @@ -748,11 +748,11 @@ export default class PricelistManagerCommands { ); } else if (Array.isArray(match)) { const matchCount = match.length; - if (match.length > 20) { + if (matchCount > 20) { match = match.splice(0, 20); } - let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( + let reply = `I've found ${matchCount} items. Try with one of the items shown below:\n${match.join( ',\n' )}`; @@ -1066,9 +1066,11 @@ export default class PricelistManagerCommands { ); } else if (Array.isArray(match)) { const matchCount = match.length; - if (match.length > 20) match = match.splice(0, 20); + if (matchCount > 20) { + match = match.splice(0, 20); + } - let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( + let reply = `I've found ${matchCount} items. Try with one of the items shown below:\n${match.join( ',\n' )}`; @@ -1117,11 +1119,11 @@ export default class PricelistManagerCommands { ); } else if (Array.isArray(match)) { const matchCount = match.length; - if (match.length > 20) { + if (matchCount > 20) { match = match.splice(0, 20); } - let reply = `I've found ${match.length} items. Try with one of the items shown below:\n${match.join( + let reply = `I've found ${matchCount} items. Try with one of the items shown below:\n${match.join( ',\n' )}`; @@ -1270,37 +1272,39 @@ export default class PricelistManagerCommands { const parameters = Object.values(parametersUsed); const display = parameters.filter(param => param !== ''); - const length = filter.length; - if (length === 0) { + const filterCount = filter.length; + if (filterCount === 0) { this.bot.sendMessage(steamID, `No items found with ${display.join('&')}.`); - } else if (length > 20) { + } else if (filterCount > 20) { this.bot.sendMessage( steamID, - `Found ${pluralize('item', length, true)} with ${display.join('&')}, showing only a max of 100 items` + `Found ${pluralize('item', filterCount, true)} with ${display.join( + '&' + )}, showing only a max of 100 items` ); this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 0, 20)}`); - if (length <= 40) { + if (filterCount <= 40) { this.bot.sendMessage( steamID, - `/code ${this.generateOutput(filter, true, 20, length > 40 ? 40 : length)}` + `/code ${this.generateOutput(filter, true, 20, filterCount > 40 ? 40 : filterCount)}` ); - } else if (length <= 60) { + } else if (filterCount <= 60) { this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 20, 40)}`); await sleepasync().Promise.sleep(1 * 1000); this.bot.sendMessage( steamID, - `/code ${this.generateOutput(filter, true, 40, length > 60 ? 60 : length)}` + `/code ${this.generateOutput(filter, true, 40, filterCount > 60 ? 60 : filterCount)}` ); - } else if (length <= 80) { + } else if (filterCount <= 80) { this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 20, 40)}`); await sleepasync().Promise.sleep(1 * 1000); this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 40, 60)}`); await sleepasync().Promise.sleep(1 * 1000); this.bot.sendMessage( steamID, - `/code ${this.generateOutput(filter, true, 60, length > 80 ? 80 : length)}` + `/code ${this.generateOutput(filter, true, 60, filterCount > 80 ? 80 : filterCount)}` ); - } else if (length > 80) { + } else if (filterCount > 80) { this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 20, 40)}`); await sleepasync().Promise.sleep(1 * 1000); this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter, true, 40, 60)}`); @@ -1309,11 +1313,11 @@ export default class PricelistManagerCommands { await sleepasync().Promise.sleep(1 * 1000); this.bot.sendMessage( steamID, - `/code ${this.generateOutput(filter, true, 80, length > 100 ? 100 : length)}` + `/code ${this.generateOutput(filter, true, 80, filterCount > 100 ? 100 : filterCount)}` ); } } else { - this.bot.sendMessage(steamID, `Found ${pluralize('item', filter.length, true)} with ${display.join('&')}`); + this.bot.sendMessage(steamID, `Found ${pluralize('item', filterCount, true)} with ${display.join('&')}`); this.bot.sendMessage(steamID, `/code ${this.generateOutput(filter)}`); } } diff --git a/src/classes/Commands/sub-classes/Request.ts b/src/classes/Commands/sub-classes/Request.ts index cacf1858c..1239db892 100644 --- a/src/classes/Commands/sub-classes/Request.ts +++ b/src/classes/Commands/sub-classes/Request.ts @@ -72,24 +72,28 @@ export default class RequestCommands { sales.sort((a, b) => b.date - a.date); let left = 0; - const SalesList: string[] = []; - for (let i = 0; i < sales.length; i++) { - if (SalesList.length > 40) { + const salesList: string[] = []; + const salesListCount = salesList.length; + const salesCount = sales.length; + + for (let i = 0; i < salesCount; i++) { + if (salesListCount > 40) { left += 1; } else { - SalesList.push( - `Listed #${i + 1}-----\nā€¢ Date: ${dayjs.unix(sales[i].date).utc().toString()}\nā€¢ Item: ${ - sales[i].itemHistory - }\nā€¢ Seller: ${sales[i].seller}\nā€¢ Was selling for: ${ - sales[i].keys > 0 ? `${sales[i].keys} keys,` : '' - } ${sales[i].metal} ref` + const sale = sales[i]; + salesList.push( + `Listed #${i + 1}-----\nā€¢ Date: ${dayjs.unix(sale.date).utc().toString()}\nā€¢ Item: ${ + sale.itemHistory + }\nā€¢ Seller: ${sale.seller}\nā€¢ Was selling for: ${sale.keys > 0 ? `${sale.keys} keys,` : ''} ${ + sale.metal + } ref` ); } } let reply = `šŸ”Ž Recorded removed sell listings from backpack.tf\n\nItem name: ${ salesData.name - }\n\n-----${SalesList.join('\n\n-----')}`; + }\n\n-----${salesList.join('\n\n-----')}`; if (left > 0) { reply += `,\n\nand ${left} other ${pluralize('sale', left)}`; } diff --git a/src/classes/Commands/sub-classes/Review.ts b/src/classes/Commands/sub-classes/Review.ts index 7145a290c..dbb425cfc 100644 --- a/src/classes/Commands/sub-classes/Review.ts +++ b/src/classes/Commands/sub-classes/Review.ts @@ -64,31 +64,41 @@ export default class ReviewCommands { } private generateTradesReply(offers: UnknownDictionaryKnownValues[]): string { - let reply = `There ${pluralize('is', offers.length, true)} active ${pluralize( + const offersCount = offers.length; + + let reply = `There ${pluralize('is', offersCount, true)} active ${pluralize( 'offer', - offers.length + offersCount )} that you can review:\n`; - for (let i = 0; i < offers.length; i++) { + + for (let i = 0; i < offersCount; i++) { + const offer = offers[i]; + reply += - `\n- Offer #${offers[i].id as string} from ${(offers[i].data as OfferData).partner} (reason: ${(offers[ - i - ].data as OfferData).action.meta.uniqueReasons.join(', ')})` + - `\nāš ļø Send "!trade ${offers[i].id as string}" for more details.\n`; + `\n- Offer #${offer.id as string} from ${ + (offer.data as OfferData).partner + } (reason: ${(offer.data as OfferData).action.meta.uniqueReasons.join(', ')})` + + `\nāš ļø Send "!trade ${offer.id as string}" for more details.\n`; } return reply; } private generateActiveOfferReply(offers: UnknownDictionaryKnownValues[]): string { - let reply = `There ${pluralize('is', offers.length, true)} ${pluralize( + const offersCount = offers.length; + + let reply = `There ${pluralize('is', offersCount, true)} ${pluralize( 'offer', - offers.length + offersCount )} that currently still active:\n`; - for (let i = 0; i < offers.length; i++) { + + for (let i = 0; i < offersCount; i++) { + const offer = offers[i]; + reply += - `\n- Offer #${offers[i].id as string} from ${(offers[i].data as OfferData).partner}` + - `\nāš ļø Send "!trade ${offers[i].id as string}" for more details or "!faccept ${ - offers[i].id as string + `\n- Offer #${offer.id as string} from ${(offer.data as OfferData).partner}` + + `\nāš ļø Send "!trade ${offer.id as string}" for more details or "!faccept ${ + offer.id as string }" to force accept the trade.\n`; } diff --git a/src/classes/Inventory.ts b/src/classes/Inventory.ts index b875ea85e..6d1bd8986 100644 --- a/src/classes/Inventory.ts +++ b/src/classes/Inventory.ts @@ -246,7 +246,9 @@ export default class Inventory { ? Object.keys(paints).map(paint => paint.toLowerCase()) : opt.highValue.painted.map(paint => paint.toLowerCase()); - for (let i = 0; i < items.length; i++) { + const itemsCount = items.length; + + for (let i = 0; i < itemsCount; i++) { const sku = items[i].getSKU( schema, opt.normalize.festivized[which], @@ -257,6 +259,8 @@ export default class Inventory { ); const attributes = highValue(items[i], opt, paints, strangeParts); + const attributesCount = Object.keys(attributes).length; + const isUses = sku === '241;6' ? isFull(items[i], 'duel') @@ -264,7 +268,7 @@ export default class Inventory { ? isFull(items[i], 'noise') : null; - if (Object.keys(attributes).length === 0 && isUses === null) { + if (attributesCount === 0 && isUses === null) { (dict[sku] = dict[sku] || []).push({ id: items[i].id }); } else { if (isUses !== null) { diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index fbf791eea..6ef5275f8 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -48,6 +48,7 @@ import { summarize, uptime, getHighValueItems } from '../../lib/tools/export'; import genPaths from '../../resources/paths'; import Pricer, { RequestCheckFn } from '../Pricer'; +import Options from '../Options'; export default class MyHandler extends Handler { readonly commands: Commands; @@ -60,9 +61,13 @@ export default class MyHandler extends Handler { private requestCheck: RequestCheckFn; + private get opt(): Options { + return this.bot.options; + } + private get groups(): string[] { if (!this.groupsStore) { - const groups = this.bot.options.groups; + const groups = this.opt.groups; if (groups !== null && Array.isArray(groups)) { groups.forEach(groupID64 => { @@ -83,9 +88,7 @@ export default class MyHandler extends Handler { get friendsToKeep(): string[] { if (!this.friendsToKeepStore) { - const friendsToKeep = this.bot.options.keep.concat( - this.bot.getAdmins.map(steamID => steamID.getSteamID64()) - ); + const friendsToKeep = this.opt.keep.concat(this.bot.getAdmins.map(steamID => steamID.getSteamID64())); if (friendsToKeep !== null && Array.isArray(friendsToKeep)) { friendsToKeep.forEach(steamID64 => { @@ -103,56 +106,54 @@ export default class MyHandler extends Handler { } private get minimumScrap(): number { - return this.bot.options.crafting.metals.minScrap; + return this.opt.crafting.metals.minScrap; } private get minimumReclaimed(): number { - return this.bot.options.crafting.metals.minRec; + return this.opt.crafting.metals.minRec; } private get combineThreshold(): number { - return this.bot.options.crafting.metals.threshold; + return this.opt.crafting.metals.threshold; } get dupeCheckEnabled(): boolean { - return this.bot.options.offerReceived.duped.enableCheck; + return this.opt.offerReceived.duped.enableCheck; } get minimumKeysDupeCheck(): number { - return this.bot.options.offerReceived.duped.minKeys; + return this.opt.offerReceived.duped.minKeys; } private get isPriceUpdateWebhook(): boolean { - return ( - this.bot.options.discordWebhook.priceUpdate.enable && this.bot.options.discordWebhook.priceUpdate.url !== '' - ); + return this.opt.discordWebhook.priceUpdate.enable && this.opt.discordWebhook.priceUpdate.url !== ''; } get isWeaponsAsCurrency(): { enable: boolean; withUncraft: boolean } { return { - enable: this.bot.options.miscSettings.weaponsAsCurrency.enable, - withUncraft: this.bot.options.miscSettings.weaponsAsCurrency.withUncraft + enable: this.opt.miscSettings.weaponsAsCurrency.enable, + withUncraft: this.opt.miscSettings.weaponsAsCurrency.withUncraft }; } private get isAutoRelistEnabled(): boolean { - return this.bot.options.miscSettings.autobump.enable; + return this.opt.miscSettings.autobump.enable; } private get invalidValueException(): number { - return Currencies.toScrap(this.bot.options.offerReceived.invalidValue.exceptionValue.valueInRef); + return Currencies.toScrap(this.opt.offerReceived.invalidValue.exceptionValue.valueInRef); } private hasInvalidValueException = false; private get sendStatsEnabled(): boolean { - return this.bot.options.statistics.sendStats.enable; + return this.opt.statistics.sendStats.enable; } private isTradingKeys = false; get customGameName(): string { - const customGameName = this.bot.options.miscSettings.game.customName; + const customGameName = this.opt.miscSettings.game.customName; if (customGameName === '' || customGameName === 'TF2Autobot') { return `TF2Autobot v${process.env.BOT_VERSION}`; @@ -214,7 +215,7 @@ export default class MyHandler extends Handler { this.cartQueue = new CartQueue(bot); this.autokeys = new Autokeys(bot); - this.paths = genPaths(this.bot.options.steamAccountName); + this.paths = genPaths(this.opt.steamAccountName); this.requestCheck = this.priceSource.requestCheck.bind(this.priceSource); } @@ -244,7 +245,7 @@ export default class MyHandler extends Handler { )} | Startup time: ${process.uptime().toFixed(0)} s` ); - this.bot.client.gamesPlayed(this.bot.options.miscSettings.game.playOnlyTF2 ? 440 : [this.customGameName, 440]); + this.bot.client.gamesPlayed(this.opt.miscSettings.game.playOnlyTF2 ? 440 : [this.customGameName, 440]); this.bot.client.setPersona(EPersonaState.Online); this.botSteamID = this.bot.client.steamID; @@ -316,7 +317,7 @@ export default class MyHandler extends Handler { this.bot.listings.disableAutorelistOption(); return new Promise(resolve => { - if (this.bot.options.autokeys.enable && this.autokeys.getActiveStatus) { + if (this.opt.autokeys.enable && this.autokeys.getActiveStatus) { log.debug('Disabling Autokeys and disabling key entry in the pricelist...'); this.autokeys.disable(this.bot.pricelist.getKeyPrices); } @@ -339,16 +340,14 @@ export default class MyHandler extends Handler { onLoggedOn(): void { if (this.bot.isReady) { this.bot.client.setPersona(EPersonaState.Online); - this.bot.client.gamesPlayed( - this.bot.options.miscSettings.game.playOnlyTF2 ? 440 : [this.customGameName, 440] - ); + this.bot.client.gamesPlayed(this.opt.miscSettings.game.playOnlyTF2 ? 440 : [this.customGameName, 440]); } } onMessage(steamID: SteamID, message: string): void { - if (!this.bot.options.commands.enable) { + if (!this.opt.commands.enable) { if (!this.bot.isAdmin(steamID)) { - const custom = this.bot.options.commands.customDisableReply; + const custom = this.opt.commands.customDisableReply; return this.bot.sendMessage(steamID, custom ? custom : 'āŒ Command function is disabled by the owner.'); } } @@ -466,23 +465,20 @@ export default class MyHandler extends Handler { } const inventory = this.bot.inventoryManager; - const isFilterCantAfford = this.bot.options.pricelist.filterCantAfford.enable; + const isFilterCantAfford = this.opt.pricelist.filterCantAfford.enable; this.bot.listingManager.listings.forEach(listing => { let listingSKU = listing.getSKU(); if (listing.intent === 1) { - if (this.bot.options.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) { + if (this.opt.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) { listingSKU = listingSKU.replace(/;[p][0-9]+/, ''); } - if (this.bot.options.normalize.festivized.our && listingSKU.includes(';festive')) { + if (this.opt.normalize.festivized.our && listingSKU.includes(';festive')) { listingSKU = listingSKU.replace(';festive', ''); } - if ( - this.bot.options.normalize.strangeAsSecondQuality.our && - listingSKU.includes(';strange') - ) { + if (this.opt.normalize.strangeAsSecondQuality.our && listingSKU.includes(';strange')) { listingSKU = listingSKU.replace(';strange', ''); } } else { @@ -544,25 +540,27 @@ export default class MyHandler extends Handler { return false; }); - if (pricelist.length > 0) { + const pricelistCount = pricelist.length; + + if (pricelistCount > 0) { log.debug( 'Checking listings for ' + - pluralize('item', pricelist.length, true) + + pluralize('item', pricelistCount, true) + ` [${pricelist.map(entry => entry.sku).join(', ')}]...` ); await this.bot.listings.recursiveCheckPricelist( pricelist, true, - pricelist.length > 1000 ? 1000 : 200 + pricelistCount > 1000 ? 1000 : 200 ); - log.debug('āœ… Done checking ' + pluralize('item', pricelist.length, true)); + log.debug('āœ… Done checking ' + pluralize('item', pricelistCount, true)); } else { log.debug('āŒ Nothing to refresh.'); } - pricelistLength = pricelist.length; + pricelistLength = pricelistCount; }); }, // set check every 60 minutes if pricelist to check was more than 1000 items @@ -622,7 +620,7 @@ export default class MyHandler extends Handler { // If crafting class weapons still waiting, cancel it. clearTimeout(this.classWeaponsTimeout); - const opt = this.bot.options; + const opt = this.opt; const items = { our: Inventory.fromItems( @@ -777,8 +775,11 @@ export default class MyHandler extends Handler { }; } + const itemsToGiveCount = offer.itemsToGive.length; + const itemsToReceiveCount = offer.itemsToReceive.length; + // check if the trade is valid - const isCannotProceedProcessingOffer = offer.itemsToGive.length === 0 && offer.itemsToReceive.length === 0; + const isCannotProceedProcessingOffer = itemsToGiveCount === 0 && itemsToReceiveCount === 0; if (isCannotProceedProcessingOffer) { log.warn('isCannotProceedProcessingOffer', { @@ -793,9 +794,9 @@ export default class MyHandler extends Handler { ` My owner has been informed, and they might manually act on your offer later.` ); - const optDw = this.bot.options.discordWebhook; + const optDw = opt.discordWebhook; - if (this.bot.options.sendAlert.unableToProcessOffer) { + if (opt.sendAlert.unableToProcessOffer) { if (optDw.sendAlert.enable && optDw.sendAlert.url !== '') { sendAlert('failed-processing-offer', this.bot, null, null, null, [ offer.partner.getSteamID64(), @@ -848,7 +849,7 @@ export default class MyHandler extends Handler { 'cute' // right? ].some(word => offerMessage.includes(word)); - if (offer.itemsToGive.length === 0 && isGift) { + if (itemsToGiveCount === 0 && isGift) { offer.log( 'trade', `is a gift offer, accepting. Summary:\n${JSON.stringify( @@ -862,7 +863,7 @@ export default class MyHandler extends Handler { reason: 'GIFT', meta: isContainsHighValue ? { highValue: highValueMeta(input) } : undefined }; - } else if (offer.itemsToGive.length === 0 && offer.itemsToReceive.length > 0 && !isGift) { + } else if (itemsToGiveCount === 0 && itemsToReceiveCount > 0 && !isGift) { if (opt.bypass.giftWithoutMessage.allow) { offer.log( 'info', @@ -878,7 +879,7 @@ export default class MyHandler extends Handler { offer.log('info', 'is a gift offer without any offer message, declining...'); return { action: 'decline', reason: 'GIFT_NO_NOTE' }; } - } else if (offer.itemsToGive.length > 0 && offer.itemsToReceive.length === 0) { + } else if (itemsToGiveCount > 0 && itemsToReceiveCount === 0) { offer.log('info', 'is taking our items for free, declining...'); return { action: 'decline', reason: 'CRIME_ATTEMPT' }; } @@ -939,14 +940,16 @@ export default class MyHandler extends Handler { } } + const ourItemsHVCount = Object.keys(getHighValue.our.items).length; + const isInPricelist = - Object.keys(getHighValue.our.items).length > 0 // Only check if this not empty + ourItemsHVCount > 0 // Only check if this not empty ? Object.keys(getHighValue.our.items).some(sku => { return checkExist.getPrice(sku, false) !== null; // Return true if exist in pricelist, enabled or not. }) : null; - if (Object.keys(getHighValue.our.items).length > 0 && isInPricelist === false) { + if (ourItemsHVCount > 0 && isInPricelist === false) { // Decline trade that offer overpay on high valued (spelled) items that are not in our pricelist. offer.log('info', 'contains higher value item on our side that is not in our pricelist.'); @@ -1416,8 +1419,10 @@ export default class MyHandler extends Handler { return { action: 'decline', reason: 'OVERPAY' }; } - if (this.dupeCheckEnabled && assetidsToCheck.length > 0) { - offer.log('info', 'checking ' + pluralize('item', assetidsToCheck.length, true) + ' for dupes...'); + const assetidsToCheckCount = assetidsToCheck.length; + + if (this.dupeCheckEnabled && assetidsToCheckCount > 0) { + offer.log('info', 'checking ' + pluralize('item', assetidsToCheckCount, true) + ' for dupes...'); const inventory = new TF2Inventory(offer.partner, this.bot.manager); const requests = assetidsToCheck.map(assetid => { @@ -1437,7 +1442,9 @@ export default class MyHandler extends Handler { log.debug('Got result from dupe checks on ' + assetidsToCheck.join(', '), { result: result }); const declineDupes = opt.offerReceived.duped.autoDecline.enable; - for (let i = 0; i < result.length; i++) { + const resultCount = result.length; + + for (let i = 0; i < resultCount; i++) { if (result[i] === true) { // Found duped item if (declineDupes) { @@ -1640,7 +1647,7 @@ export default class MyHandler extends Handler { )}` ); - if (offer.itemsToGive.length + offer.itemsToReceive.length > 50) { + if (itemsToGiveCount + itemsToReceiveCount > 50) { this.bot.sendMessage( offer.partner, opt.customMessage.accepted.automatic.largeOffer @@ -1746,7 +1753,7 @@ export default class MyHandler extends Handler { `accepting. Summary:\n${JSON.stringify(summarize(offer, this.bot, 'summary-accepting', false), null, 4)}` ); - if (offer.itemsToGive.length + offer.itemsToReceive.length > 50) { + if (itemsToGiveCount + itemsToReceiveCount > 50) { this.bot.sendMessage( offer.partner, opt.customMessage.accepted.automatic.largeOffer @@ -1869,14 +1876,14 @@ export default class MyHandler extends Handler { } private sortInventory(): void { - if (this.bot.options.miscSettings.sortInventory.enable) { - const type = this.bot.options.miscSettings.sortInventory.type; + if (this.opt.miscSettings.sortInventory.enable) { + const type = this.opt.miscSettings.sortInventory.type; this.bot.tf2gc.sortInventory([1, 2, 3, 4, 5, 101, 102].includes(type) ? type : 3); } } private inviteToGroups(steamID: SteamID | string): void { - if (!this.bot.options.miscSettings.sendGroupInvite.enable) { + if (!this.opt.miscSettings.sendGroupInvite.enable) { return; } @@ -1913,7 +1920,7 @@ export default class MyHandler extends Handler { } private respondToFriendRequest(steamID: SteamID | string): void { - if (!this.bot.options.miscSettings.addFriends.enable) { + if (!this.opt.miscSettings.addFriends.enable) { if (!this.bot.isAdmin(steamID)) { return this.bot.client.removeFriend(steamID); } @@ -1950,8 +1957,8 @@ export default class MyHandler extends Handler { return this.bot.sendMessage( steamID, - this.bot.options.customMessage.welcome - ? this.bot.options.customMessage.welcome + this.opt.customMessage.welcome + ? this.opt.customMessage.welcome .replace(/%name%/g, '') .replace(/%admin%/g, isAdmin ? '!help' : '!how2trade') + ` - TF2Autobot v${process.env.BOT_VERSION}` @@ -1973,8 +1980,8 @@ export default class MyHandler extends Handler { this.bot.sendMessage( steamID, - this.bot.options.customMessage.welcome - ? this.bot.options.customMessage.welcome + this.opt.customMessage.welcome + ? this.opt.customMessage.welcome .replace(/%name%/g, friend.player_name) .replace(/%admin%/g, isAdmin ? '!help' : '!how2trade') + ` - TF2Autobot v${process.env.BOT_VERSION}` @@ -2021,8 +2028,8 @@ export default class MyHandler extends Handler { friendsToRemove.forEach(element => { this.bot.sendMessage( element.steamID, - this.bot.options.customMessage.clearFriends - ? this.bot.options.customMessage.clearFriends.replace( + this.opt.customMessage.clearFriends + ? this.opt.customMessage.clearFriends.replace( /%name%/g, this.bot.friends.getFriend(element.steamID).player_name ) @@ -2042,7 +2049,7 @@ export default class MyHandler extends Handler { url: 'https://backpack.tf/api/users/info/v1', method: 'GET', qs: { - key: this.bot.options.bptfAPIKey, + key: this.opt.bptfAPIKey, steamids: steamID64 }, gzip: true, @@ -2143,7 +2150,7 @@ export default class MyHandler extends Handler { onTF2QueueCompleted(): void { log.debug('Queue finished'); - this.bot.client.gamesPlayed(this.bot.options.miscSettings.game.playOnlyTF2 ? 440 : [this.customGameName, 440]); + this.bot.client.gamesPlayed(this.opt.miscSettings.game.playOnlyTF2 ? 440 : [this.customGameName, 440]); } } diff --git a/src/classes/MyHandler/offer/accepted/updateListings.ts b/src/classes/MyHandler/offer/accepted/updateListings.ts index 1ecfabbc3..3b49f8c01 100644 --- a/src/classes/MyHandler/offer/accepted/updateListings.ts +++ b/src/classes/MyHandler/offer/accepted/updateListings.ts @@ -187,7 +187,9 @@ export default function updateListings( let msg = 'I have received a high-valued items which is not in my pricelist.' + '\n\nItem information:\n\n- '; - for (let i = 0; i < highValue.theirItems.length; i++) { + const theirCount = highValue.theirItems.length; + + for (let i = 0; i < theirCount; i++) { if (highValue.theirItems[i].includes(name)) { msg += `${highValue.isDisableSKU[i]}: ` + highValue.theirItems[i]; } @@ -265,7 +267,9 @@ export default function updateListings( ` or just re-enable it with "!update sku=${sku}&enabled=true".` + '\n\nItem information:\n\n- '; - for (let i = 0; i < highValue.theirItems.length; i++) { + const theirCount = highValue.theirItems.length; + + for (let i = 0; i < theirCount; i++) { if (highValue.theirItems[i].includes(name)) msg += highValue.theirItems[i]; } diff --git a/src/classes/MyHandler/offer/review/reasons/disabledItems.ts b/src/classes/MyHandler/offer/review/reasons/disabledItems.ts index d6317e87e..d8d95ca38 100644 --- a/src/classes/MyHandler/offer/review/reasons/disabledItems.ts +++ b/src/classes/MyHandler/offer/review/reasons/disabledItems.ts @@ -21,14 +21,16 @@ export default function disabledItems(meta: Meta, bot: Bot): { note: string; nam disabledForTheir.push(bot.schema.getName(SKU.fromString(el.sku), false)); }); + const disabledForTheirCount = disabledForTheir.length; + return { note: bot.options.manualReview.disabledItems.note ? `šŸŸ§_DISABLED_ITEMS - ${bot.options.manualReview.disabledItems.note}` .replace(/%itemsName%/g, disabledForTheir.join(', ')) - .replace(/%isOrAre%/g, pluralize('is', disabledForTheir.length)) + .replace(/%isOrAre%/g, pluralize('is', disabledForTheirCount)) : `šŸŸ§_DISABLED_ITEMS - ${disabledForTheir.join(', ')} ${pluralize( 'is', - disabledForTheir.length + disabledForTheirCount )} currently disabled.`, // Default note: %itemsName% %isOrAre% currently disabled. name: disabledForOur diff --git a/src/classes/MyHandler/offer/review/reasons/duped.ts b/src/classes/MyHandler/offer/review/reasons/duped.ts index d88d1b3d0..41eaeca49 100644 --- a/src/classes/MyHandler/offer/review/reasons/duped.ts +++ b/src/classes/MyHandler/offer/review/reasons/duped.ts @@ -29,14 +29,16 @@ export default function duped(meta: Meta, bot: Bot): { note: string; name: strin ); }); + const dupedItemsNameTheirCount = dupedItemsNameTheir.length; + return { note: bot.options.manualReview.duped.note ? `šŸŸ«_DUPED_ITEMS - ${bot.options.manualReview.duped.note}` .replace(/%itemsName%/g, dupedItemsNameTheir.join(', ')) - .replace(/%isOrAre%/g, pluralize('is', dupedItemsNameTheir.length)) + .replace(/%isOrAre%/g, pluralize('is', dupedItemsNameTheirCount)) : `šŸŸ«_DUPED_ITEMS - ${dupedItemsNameTheir.join(', ')} ${pluralize( 'is', - dupedItemsNameTheir.length + dupedItemsNameTheirCount )} appeared to be duped.`, // Default note: %itemsName% is|are appeared to be duped. name: dupedItemsNameOur diff --git a/src/classes/MyHandler/offer/review/reasons/invalidItems.ts b/src/classes/MyHandler/offer/review/reasons/invalidItems.ts index 1d3f20fa6..e3784abf1 100644 --- a/src/classes/MyHandler/offer/review/reasons/invalidItems.ts +++ b/src/classes/MyHandler/offer/review/reasons/invalidItems.ts @@ -21,14 +21,16 @@ export default function invalidItems(meta: Meta, bot: Bot): { note: string; name invalidForTheir.push(bot.schema.getName(SKU.fromString(el.sku), false)); }); + const invalidForTheirCount = invalidForTheir.length; + return { note: bot.options.manualReview.invalidItems.note ? `šŸŸØ_INVALID_ITEMS - ${bot.options.manualReview.invalidItems.note}` .replace(/%itemsName%/g, invalidForTheir.join(', ')) - .replace(/%isOrAre%/g, pluralize('is', invalidForTheir.length)) + .replace(/%isOrAre%/g, pluralize('is', invalidForTheirCount)) : `šŸŸØ_INVALID_ITEMS - ${invalidForTheir.join(', ')} ${pluralize( 'is', - invalidForTheir.length + invalidForTheirCount )} not in my pricelist.`, // Default note: %itemsName% %isOrAre% not in my pricelist. name: invalidForOur diff --git a/src/classes/MyHandler/utils/craftClassWeapons.ts b/src/classes/MyHandler/utils/craftClassWeapons.ts index 0180a8e2f..5f78bdac5 100644 --- a/src/classes/MyHandler/utils/craftClassWeapons.ts +++ b/src/classes/MyHandler/utils/craftClassWeapons.ts @@ -17,12 +17,14 @@ function craftEachClassWeapons(bot: Bot, weapons: string[], currencies: { [sku: return new Promise(resolve => { let stopLoop = false; - for (let i = 0; i < weapons.length; i++) { + const weaponsCount = weapons.length; + + for (let i = 0; i < weaponsCount; i++) { // first loop // check if that weapon1 only have 1 in inventory AND it's not in pricelist const isWep1 = currencies[weapons[i]].length === 1 && bot.pricelist.getPrice(weapons[i], true) === null; - for (let j = 0; j < weapons.length; j++) { + for (let j = 0; j < weaponsCount; j++) { if (j === i) { // second loop inside first loop, but ignore same index (same weapons) continue; diff --git a/src/classes/Pricelist.ts b/src/classes/Pricelist.ts index 14d596d05..857f81f9e 100644 --- a/src/classes/Pricelist.ts +++ b/src/classes/Pricelist.ts @@ -241,8 +241,9 @@ export default class Pricelist extends EventEmitter { search = search.toLowerCase(); const match: Entry[] = []; + const pricesCount = this.prices.length; - for (let i = 0; i < this.prices.length; i++) { + for (let i = 0; i < pricesCount; i++) { const entry = this.prices[i]; if (enabledOnly && entry.enabled === false) { @@ -282,10 +283,12 @@ export default class Pricelist extends EventEmitter { } } - if (match.length === 0) { + const matchCount = match.length; + + if (matchCount === 0) { // No match return null; - } else if (match.length === 1) { + } else if (matchCount === 1) { // Found one that matched the search return match[0]; } @@ -681,7 +684,9 @@ export default class Pricelist extends EventEmitter { let pricesChanged = false; // Go through our pricelist - for (let i = 0; i < old.length; i++) { + const oldCount = old.length; + + for (let i = 0; i < oldCount; i++) { // const currPrice = old[i]; if (old[i].autoprice !== true) { continue; @@ -692,8 +697,11 @@ export default class Pricelist extends EventEmitter { const name = this.schema.getName(item, true); // Go through pricestf prices - for (let j = 0; j < groupedPrices[item.quality][item.killstreak].length; j++) { - const newestPrice = groupedPrices[item.quality][item.killstreak][j]; + const grouped = groupedPrices[item.quality][item.killstreak]; + const groupedCount = grouped.length; + + for (let j = 0; j < groupedCount; j++) { + const newestPrice = grouped[j]; if (name === newestPrice.name) { // Found matching items @@ -800,7 +808,9 @@ export default class Pricelist extends EventEmitter { static groupPrices(prices: Item[]): Group { const sorted: Group = {}; - for (let i = 0; i < prices.length; i++) { + const pricesCount = prices.length; + + for (let i = 0; i < pricesCount; i++) { if (prices[i].buy === null) { continue; } diff --git a/src/classes/TF2GC.ts b/src/classes/TF2GC.ts index 91754a8fd..ae199b670 100644 --- a/src/classes/TF2GC.ts +++ b/src/classes/TF2GC.ts @@ -466,7 +466,9 @@ export default class TF2GC { .findBySKU(String(job.defindex) + ';6', true) .filter(assetid => !this.bot.trades.isInTrade(assetid)); - return (job.type === 'smelt' && assetids.length > 0) || (job.type === 'combine' && assetids.length >= 3); + const assetidsCount = assetids.length; + + return (job.type === 'smelt' && assetidsCount > 0) || (job.type === 'combine' && assetidsCount >= 3); } else if (job.type === 'use' || job.type === 'delete') { return this.bot.inventoryManager.getInventory.findByAssetid(job.assetid) !== null; } diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index 1f0cdab7b..9f088ded4 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -63,14 +63,18 @@ export default class Trades { } // Go through all sent / received offers and mark the items as in trade - for (let i = 0; i < activeOrCreatedNeedsConfirmation.length; i++) { + const activeCount = activeOrCreatedNeedsConfirmation.length; + + for (let i = 0; i < activeCount; i++) { const id = activeOrCreatedNeedsConfirmation[i]; const offerData: UnknownDictionaryKnownValues = pollData.offerData === undefined ? {} : pollData.offerData[id] || {}; const items = (offerData.items || []) as TradeOfferManager.TradeOfferItem[]; - for (let i = 0; i < items.length; i++) { + const itemsCount = items.length; + + for (let i = 0; i < itemsCount; i++) { this.setItemInTrade = items[i].assetid; } } @@ -103,6 +107,7 @@ export default class Trades { }); const activeReceived = received.filter(offer => offer.state === TradeOfferManager.ETradeOfferState['Active']); + const activeReceivedCount = activeReceived.length; if ( filter === TradeOfferManager.EOfferFilter['ActiveOnly'] && @@ -111,19 +116,21 @@ export default class Trades { this.pollCount = 0; const activeSent = sent.filter(offer => offer.state === TradeOfferManager.ETradeOfferState['Active']); + const activeSentCount = activeSent.length; const receivedOnHold = received.filter( offer => offer.state === TradeOfferManager.ETradeOfferState['InEscrow'] ).length; + const sentOnHold = sent.filter(offer => offer.state === TradeOfferManager.ETradeOfferState['InEscrow']) .length; log.verbose( - `${activeReceived.length} incoming ${pluralize('offer', activeReceived.length)}${ - activeReceived.length > 0 ? ` [${activeReceived.map(offer => offer.id).join(', ')}]` : '' - } (${receivedOnHold} on hold), ${activeSent.length} outgoing ${pluralize( + `${activeReceivedCount} incoming ${pluralize('offer', activeReceivedCount)}${ + activeReceivedCount > 0 ? ` [${activeReceived.map(offer => offer.id).join(', ')}]` : '' + } (${receivedOnHold} on hold), ${activeSentCount} outgoing ${pluralize( 'offer', - activeSent.length + activeSentCount )} (${sentOnHold} on hold)` ); } @@ -1123,7 +1130,9 @@ export default class Trades { } const copy = b.slice(0); - for (let i = 0; i < a.length; i++) { + const aCount = a.length; + + for (let i = 0; i < aCount; i++) { // Find index of matching item const index = copy.findIndex(item => Trades.itemEquals(item, a[i])); if (index === -1) { diff --git a/src/lib/DiscordWebhook/sendOfferReview.ts b/src/lib/DiscordWebhook/sendOfferReview.ts index 4d46b236a..6198d1545 100644 --- a/src/lib/DiscordWebhook/sendOfferReview.ts +++ b/src/lib/DiscordWebhook/sendOfferReview.ts @@ -146,17 +146,18 @@ export default function sendOfferReview( webhookReview.embeds[0].fields.length = 0; const separate = itemList.split('@'); + const separateCount = separate.length; let newSentences = ''; let j = 1; separate.forEach((sentence, i) => { - if ((newSentences.length >= 800 || i === separate.length - 1) && !(j > 4)) { + if ((newSentences.length >= 800 || i === separateCount - 1) && !(j > 4)) { webhookReview.embeds[0].fields.push({ name: `__Item list ${j}__`, value: newSentences.replace(/@/g, '') }); - if (i === separate.length - 1 || j > 4) { + if (i === separateCount - 1 || j > 4) { webhookReview.embeds[0].fields.push(statusElement); } diff --git a/src/lib/DiscordWebhook/sendTradeSummary.ts b/src/lib/DiscordWebhook/sendTradeSummary.ts index 024816ac0..4e6e70391 100644 --- a/src/lib/DiscordWebhook/sendTradeSummary.ts +++ b/src/lib/DiscordWebhook/sendTradeSummary.ts @@ -166,17 +166,18 @@ export default async function sendTradeSummary( acceptedTradeSummary.embeds[0].fields.length = 0; const separate = itemList.split('@'); + const separateCount = separate.length; let newSentences = ''; let j = 1; separate.forEach((sentence, i) => { - if ((newSentences.length >= 800 || i === separate.length - 1) && !(j > 4)) { + if ((newSentences.length >= 800 || i === separateCount - 1) && !(j > 4)) { acceptedTradeSummary.embeds[0].fields.push({ name: `__Item list ${j}__`, value: newSentences.replace(/@/g, '') }); - if (i === separate.length - 1 || j > 4) { + if (i === separateCount - 1 || j > 4) { acceptedTradeSummary.embeds[0].fields.push(statusElement); } diff --git a/src/lib/extend/item/getSKU.ts b/src/lib/extend/item/getSKU.ts index 537e255b8..eeb7fd6fa 100644 --- a/src/lib/extend/item/getSKU.ts +++ b/src/lib/extend/item/getSKU.ts @@ -178,7 +178,9 @@ function getPaintKit(item: EconItem, schema: SchemaManager.Schema): number | nul let hasCaseCollection = false; let skin: string | null = null; - for (let i = 0; i < item.descriptions.length; i++) { + const descriptionsCount = item.descriptions.length; + + for (let i = 0; i < descriptionsCount; i++) { if (!hasCaseCollection && item.descriptions[i].value.endsWith('Collection')) { hasCaseCollection = true; } else if ( @@ -243,7 +245,9 @@ function getOutput( ): { target: number | null; output: number | null; outputQuality: number | null } { let index = -1; - for (let i = 0; i < item.descriptions.length; i++) { + const descriptionsCount = item.descriptions.length; + + for (let i = 0; i < descriptionsCount; i++) { if ( item.descriptions[i].value == 'You will receive all of the following outputs once all of the inputs are fulfilled.' @@ -330,6 +334,8 @@ function getTarget(item: EconItem, schema: SchemaManager.Schema): number | null throw new Error('Could not find target for item "' + item.market_hash_name + '"'); } + const itemHashNameLength = item.market_hash_name.length; + if ( [ 6527, // general @@ -364,22 +370,19 @@ function getTarget(item: EconItem, schema: SchemaManager.Schema): number | null // Killstreak Kit return schema.getItemByItemName( item.market_hash_name - .substring(10, item.market_hash_name.length - 3) + .substring(10, itemHashNameLength - 3) .replace('Killstreak', '') .trim() ).defindex; } else if (defindex === 6523) { // Specialized Killstreak Kit - return schema.getItemByItemName(item.market_hash_name.substring(22, item.market_hash_name.length - 3).trim()) - .defindex; + return schema.getItemByItemName(item.market_hash_name.substring(22, itemHashNameLength - 3).trim()).defindex; } else if (defindex === 6526) { // Professional Killstreak Kit - return schema.getItemByItemName(item.market_hash_name.substring(23, item.market_hash_name.length - 3).trim()) - .defindex; + return schema.getItemByItemName(item.market_hash_name.substring(23, itemHashNameLength - 3).trim()).defindex; } else if (defindex === 9258) { // Unusualifier - return schema.getItemByItemName(item.market_hash_name.substring(7, item.market_hash_name.length - 12).trim()) - .defindex; + return schema.getItemByItemName(item.market_hash_name.substring(7, itemHashNameLength - 12).trim()).defindex; } return null; @@ -458,14 +461,16 @@ function getCrateSeries(item: EconItem): number | null { } }; - if (defindex === 5022 && Object.keys(crates.is5022).includes(item.market_hash_name)) { - series = crates.is5022[item.market_hash_name]; - } else if (defindex === 5041 && Object.keys(crates.is5041).includes(item.market_hash_name)) { - series = crates.is5041[item.market_hash_name]; - } else if (defindex === 5045 && Object.keys(crates.is5045).includes(item.market_hash_name)) { - series = crates.is5045[item.market_hash_name]; - } else if (defindex === 5068 && Object.keys(crates.is5068).includes(item.market_hash_name)) { - series = crates.is5068[item.market_hash_name]; + const itemHashMarketName = item.market_hash_name; + + if (defindex === 5022 && Object.keys(crates.is5022).includes(itemHashMarketName)) { + series = crates.is5022[itemHashMarketName]; + } else if (defindex === 5041 && Object.keys(crates.is5041).includes(itemHashMarketName)) { + series = crates.is5041[itemHashMarketName]; + } else if (defindex === 5045 && Object.keys(crates.is5045).includes(itemHashMarketName)) { + series = crates.is5045[itemHashMarketName]; + } else if (defindex === 5068 && Object.keys(crates.is5068).includes(itemHashMarketName)) { + series = crates.is5068[itemHashMarketName]; } if (series !== null) { @@ -488,8 +493,9 @@ function getPainted( } const descriptions = item.descriptions; + const descriptionCount = descriptions.length; - for (let i = 0; i < descriptions.length; i++) { + for (let i = 0; i < descriptionCount; i++) { if (descriptions[i].value.startsWith('Paint Color: ') && descriptions[i].color === '756b5e') { const name = descriptions[i].value.replace('Paint Color: ', '').trim(); diff --git a/src/lib/items.ts b/src/lib/items.ts index 9bbb3a940..647201647 100644 --- a/src/lib/items.ts +++ b/src/lib/items.ts @@ -12,13 +12,13 @@ export function fixItem(item: Item, schema: SchemaManager.Schema): Item { return item; } + const items = schema.raw.schema.items; + const itemsCount = items.length; + if (schemaItem.name.includes(schemaItem.item_class.toUpperCase())) { - for (let i = 0; i < schema.raw.schema.items.length; i++) { - if ( - schema.raw.schema.items[i].item_class === schemaItem.item_class && - schema.raw.schema.items[i].name.startsWith('Upgradeable ') - ) { - item.defindex = schema.raw.schema.items[i].defindex; + for (let i = 0; i < itemsCount; i++) { + if (items[i].item_class === schemaItem.item_class && items[i].name.startsWith('Upgradeable ')) { + item.defindex = items[i].defindex; } } } @@ -30,23 +30,18 @@ export function fixItem(item: Item, schema: SchemaManager.Schema): Item { } const isPromo = isPromoItem(schemaItem); + if (isPromo && item.quality != 1) { - for (let i = 0; i < schema.raw.schema.items.length; i++) { - if ( - !isPromoItem(schema.raw.schema.items[i]) && - schema.raw.schema.items[i].item_name == schemaItem.item_name - ) { + for (let i = 0; i < itemsCount; i++) { + if (!isPromoItem(items[i]) && items[i].item_name == schemaItem.item_name) { // This is the non-promo version, use that defindex instead - item.defindex = schema.raw.schema.items[i].defindex; + item.defindex = items[i].defindex; } } } else if (!isPromo && item.quality == 1) { - for (let i = 0; i < schema.raw.schema.items.length; i++) { - if ( - isPromoItem(schema.raw.schema.items[i]) && - schema.raw.schema.items[i].item_name == schemaItem.item_name - ) { - item.defindex = schema.raw.schema.items[i].defindex; + for (let i = 0; i < itemsCount; i++) { + if (isPromoItem(items[i]) && items[i].item_name == schemaItem.item_name) { + item.defindex = items[i].defindex; } } } @@ -55,7 +50,9 @@ export function fixItem(item: Item, schema: SchemaManager.Schema): Item { let series: number | null = null; if (schemaItem.attributes !== undefined) { - for (let i = 0; i < schemaItem.attributes.length; i++) { + const attributesCount = schemaItem.attributes.length; + + for (let i = 0; i < attributesCount; i++) { if (schemaItem.attributes[i].name === 'set supply crate series') { series = schemaItem.attributes[i].value; } diff --git a/src/lib/tools/profit.ts b/src/lib/tools/profit.ts index eaa80bbb1..c61cb181a 100644 --- a/src/lib/tools/profit.ts +++ b/src/lib/tools/profit.ts @@ -48,7 +48,9 @@ export default function profit( const tracker = new itemTracker(); - for (let i = 0; i < trades.length; i++) { + const tradesCount = trades.length; + + for (let i = 0; i < tradesCount; i++) { // const trade = trades[i]; if (!(trades[i].handledByUs && trades[i].isAccepted)) { // trade was not accepted, go to next trade @@ -67,9 +69,11 @@ export default function profit( continue; } - if (typeof Object.keys(trades[i].dict.our).length === 'undefined') { + const ourDictCount = Object.keys(trades[i].dict.our).length; + + if (typeof ourDictCount === 'undefined') { isGift = true; // no items on our side, so it is probably gift - } else if (Object.keys(trades[i].dict.our).length > 0) { + } else if (ourDictCount > 0) { // trade is not a gift if (!Object.prototype.hasOwnProperty.call(trades[i], 'value')) { // trade is missing value object diff --git a/src/lib/tools/pure.ts b/src/lib/tools/pure.ts index 9f8c0e822..53431201e 100644 --- a/src/lib/tools/pure.ts +++ b/src/lib/tools/pure.ts @@ -29,7 +29,10 @@ export function stock(bot: Bot): string[] { }` } ]; - for (let i = 0; i < pureCombine.length; i++) { + + const pureCombineCount = pureCombine.length; + + for (let i = 0; i < pureCombineCount; i++) { if (i < 1 && totalKeys < 1) { continue; } diff --git a/src/lib/tools/summarizeItems.ts b/src/lib/tools/summarizeItems.ts index 2d565cd53..74d9ed9f2 100644 --- a/src/lib/tools/summarizeItems.ts +++ b/src/lib/tools/summarizeItems.ts @@ -21,46 +21,50 @@ export default function listItems( const itemsPrices = bot.options.tradeSummary.showItemPrices ? listPrices(offer, bot, isSteamChat) : ''; let list = itemsPrices; + const itemsPricesLength = itemsPrices.length; + const invalidCount = items.invalid.length; + const disabledCount = items.disabled.length; + const overstockedCount = items.overstock.length; + const understockedCount = items.understock.length; + const dupedCount = items.duped.length; + const dupedFailedCount = items.dupedFailed.length; + const highValueCount = items.highValue.length; + list += - items.invalid.length > 0 - ? (itemsPrices.length > 0 ? '\n\n' : '') + + invalidCount > 0 + ? (itemsPricesLength > 0 ? '\n\n' : '') + (isSteamChat ? 'šŸŸØ_INVALID_ITEMS:\n- ' + items.invalid.join(',\n- ') : 'šŸŸØ`_INVALID_ITEMS:`\n- ' + items.invalid.join(',@\n- ')) : ''; list += - items.disabled.length > 0 - ? (itemsPrices.length > 0 || items.invalid.length > 0 ? '\n\n' : '') + + disabledCount > 0 + ? (itemsPricesLength > 0 || invalidCount > 0 ? '\n\n' : '') + (isSteamChat ? 'šŸŸ§_DISABLED_ITEMS:\n- ' + items.disabled.join(',\n- ') : 'šŸŸ§`_DISABLED_ITEMS:`\n- ' + items.disabled.join(',@\n- ')) : ''; list += - items.overstock.length > 0 - ? (itemsPrices.length > 0 || items.invalid.length > 0 || items.disabled.length > 0 ? '\n\n' : '') + + overstockedCount > 0 + ? (itemsPricesLength > 0 || invalidCount > 0 || disabledCount > 0 ? '\n\n' : '') + (isSteamChat ? 'šŸŸ¦_OVERSTOCKED:\n- ' + items.overstock.join(',\n- ') : 'šŸŸ¦`_OVERSTOCKED:`\n- ' + items.overstock.join(',@\n- ')) : ''; list += - items.understock.length > 0 - ? (itemsPrices.length > 0 || - items.invalid.length > 0 || - items.disabled.length > 0 || - items.overstock.length > 0 - ? '\n\n' - : '') + + understockedCount > 0 + ? (itemsPricesLength > 0 || invalidCount > 0 || disabledCount > 0 || overstockedCount > 0 ? '\n\n' : '') + (isSteamChat ? 'šŸŸ©_UNDERSTOCKED:\n- ' + items.understock.join(',\n- ') : 'šŸŸ©`_UNDERSTOCKED:`\n- ' + items.understock.join(',@\n- ')) : ''; list += - items.duped.length > 0 - ? (itemsPrices.length > 0 || - items.invalid.length > 0 || - items.disabled.length > 0 || - items.overstock.length > 0 || - items.understock.length > 0 + dupedCount > 0 + ? (itemsPricesLength > 0 || + invalidCount > 0 || + disabledCount > 0 || + overstockedCount > 0 || + understockedCount > 0 ? '\n\n' : '') + (isSteamChat @@ -68,13 +72,13 @@ export default function listItems( : 'šŸŸ«`_DUPED_ITEMS:`\n- ' + items.duped.join(',@\n- ')) : ''; list += - items.dupedFailed.length > 0 - ? (itemsPrices.length > 0 || - items.invalid.length > 0 || - items.disabled.length > 0 || - items.overstock.length > 0 || - items.understock.length > 0 || - items.duped.length > 0 + dupedFailedCount > 0 + ? (itemsPricesLength > 0 || + invalidCount > 0 || + disabledCount > 0 || + overstockedCount > 0 || + understockedCount > 0 || + dupedCount > 0 ? '\n\n' : '') + (isSteamChat @@ -82,14 +86,14 @@ export default function listItems( : 'šŸŸŖ`_DUPE_CHECK_FAILED:`\n- ' + items.dupedFailed.join(',@\n- ')) : ''; list += - items.highValue.length > 0 - ? (itemsPrices.length > 0 || - items.invalid.length > 0 || - items.disabled.length > 0 || - items.overstock.length > 0 || - items.understock.length > 0 || - items.duped.length > 0 || - items.dupedFailed.length > 0 + highValueCount > 0 + ? (itemsPricesLength > 0 || + invalidCount > 0 || + disabledCount > 0 || + overstockedCount > 0 || + understockedCount > 0 || + dupedCount > 0 || + dupedFailedCount > 0 ? '\n\n' : '') + (isSteamChat diff --git a/src/lib/tools/summarizeOffer.ts b/src/lib/tools/summarizeOffer.ts index 284096ec3..64fb4dd49 100644 --- a/src/lib/tools/summarizeOffer.ts +++ b/src/lib/tools/summarizeOffer.ts @@ -200,14 +200,16 @@ function getSummary( } } - if (summary.length === 0) { + const summaryCount = summary.length; + + if (summaryCount === 0) { return 'nothing'; } if (withLink) { let left = 0; - if (summary.length > 15) { - left = summary.length - 15; + if (summaryCount > 15) { + left = summaryCount - 15; summary.splice(15); } From d654c4502b4b37ca3acc5cbab6398bf3411ad0e0 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 13:48:28 +0800 Subject: [PATCH 29/43] =?UTF-8?q?=F0=9F=8E=A8=20bring=20back=20square=20br?= =?UTF-8?q?acket=20if=20showChanges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/tools/summarizeOffer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/tools/summarizeOffer.ts b/src/lib/tools/summarizeOffer.ts index 64fb4dd49..046d81eaf 100644 --- a/src/lib/tools/summarizeOffer.ts +++ b/src/lib/tools/summarizeOffer.ts @@ -102,13 +102,15 @@ export default function summarize( }; } else { // If trade with trade partner + const opening = showStockChanges ? '怚' : ' ('; + const closing = showStockChanges ? '怛' : ')'; return { asked: `${new Currencies(value.our).toString()}` + - ` (${getSummary(items.our, bot, 'our', type, withLink, showStockChanges)})`, + `${opening}${getSummary(items.our, bot, 'our', type, withLink, showStockChanges)}${closing}`, offered: `${new Currencies(value.their).toString()}` + - ` (${getSummary(items.their, bot, 'their', type, withLink, showStockChanges)})` + `${opening}${getSummary(items.their, bot, 'their', type, withLink, showStockChanges)}${closing}` }; } } From 7338366e4a35f3f328ed8b4da7655c8129d9760d Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 13:52:53 +0800 Subject: [PATCH 30/43] =?UTF-8?q?=F0=9F=90=9B=20fix=20invalid=20item=20qua?= =?UTF-8?q?ntity=20-=20`!deposit`=20(#326)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Carts/AdminCart.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/classes/Carts/AdminCart.ts b/src/classes/Carts/AdminCart.ts index 115afcd4e..b2072e8a8 100644 --- a/src/classes/Carts/AdminCart.ts +++ b/src/classes/Carts/AdminCart.ts @@ -171,6 +171,11 @@ export default class AdminCart extends Cart { } } + if (this.isEmpty) { + theirInventory.clearFetch(); + return reject(alteredMessages.join(', ')); + } + offer.data('dict', { our: this.our, their: this.their }); this.offer = offer; From 56ffd2ee5526a165aba4c7b26d90d9ec3617c873 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 14:11:03 +0800 Subject: [PATCH 31/43] =?UTF-8?q?=F0=9F=94=A8=20fix=20crashed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4851a1bce..a642f2e41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10864,9 +10864,9 @@ } }, "tf2-sku-2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tf2-sku-2/-/tf2-sku-2-1.0.2.tgz", - "integrity": "sha512-dnJxAfvK1A08m+4KRx1fi3OVctqB2M0yLK/rFIWhDEFMya+bWCdSwhqiYJvukf3TX19Tg7kPnpeiwLyGM22uRQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tf2-sku-2/-/tf2-sku-2-1.0.3.tgz", + "integrity": "sha512-1uM++EN+Bu1ululVrEgjq1aHGYL/VZTBzrIin0Em0ob9kn0dWs5UNDiG3OrcyiT/0AbPARQSNdOLyMmdHlBJNQ==", "requires": { "defaults": "^1.0.3", "object-prettify": "^1.0.0" diff --git a/package.json b/package.json index 9a2ed735f..39901b75d 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "tf2": "^3.0.2", "tf2-currencies": "^1.2.4", "tf2-schema-2": "^1.2.4", - "tf2-sku-2": "^1.0.2", + "tf2-sku-2": "^1.0.3", "url": "^0.11.0", "valid-url": "^1.0.9", "websocket-extensions": "^0.1.4", From 7380101b33fdae812ca81f82d15798eec03c6220 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 14:11:49 +0800 Subject: [PATCH 32/43] =?UTF-8?q?=F0=9F=94=84=20update=20`eslint`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 91 ++++++++++++++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index a642f2e41..0c908aae3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4201,12 +4201,12 @@ } }, "eslint": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz", - "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz", + "integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", + "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -4218,7 +4218,7 @@ "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.1", - "esquery": "^1.2.0", + "esquery": "^1.4.0", "esutils": "^2.0.2", "file-entry-cache": "^6.0.0", "functional-red-black-tree": "^1.0.1", @@ -4245,13 +4245,43 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", + "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "chalk": { @@ -4262,6 +4292,26 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "color-convert": { @@ -4404,15 +4454,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4569,9 +4610,9 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -10739,9 +10780,9 @@ }, "dependencies": { "ajv": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.4.tgz", - "integrity": "sha512-xzzzaqgEQfmuhbhAoqjJ8T/1okb6gAzXn/eQRNpAN1AEUoHJTNF9xCDRTtf/s3SKldtZfa+RJeTs+BQq+eZ/sw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", + "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", diff --git a/package.json b/package.json index 39901b75d..8fff5a0c2 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@types/valid-url": "^1.0.3", "@typescript-eslint/eslint-plugin": "^4.15.0", "@typescript-eslint/parser": "^4.15.0", - "eslint": "^7.19.0", + "eslint": "^7.20.0", "eslint-plugin-jest": "^24.1.3", "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-tsdoc": "^0.2.11", From e10c009e0bacb1e06bd5ed2848f77239aaa29abd Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 14:20:47 +0800 Subject: [PATCH 33/43] =?UTF-8?q?=F0=9F=90=9B=20fix=20`!check`=20command?= =?UTF-8?q?=20always=20getting=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Commands/sub-classes/Request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/Commands/sub-classes/Request.ts b/src/classes/Commands/sub-classes/Request.ts index 1239db892..e9280b35c 100644 --- a/src/classes/Commands/sub-classes/Request.ts +++ b/src/classes/Commands/sub-classes/Request.ts @@ -25,7 +25,7 @@ export default class RequestCommands { this.getSales = this.priceSource.getSales.bind(this.priceSource); this.requestCheck = this.priceSource.requestCheck.bind(this.priceSource); - this.getPrice = this.priceSource.requestCheck.bind(this.priceSource); + this.getPrice = this.priceSource.getPrice.bind(this.priceSource); } async getSalesCommand(steamID: SteamID, message: string): Promise { From d4d8b0d52e1516d6c36e517c035a4a35bc3fb487 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 14:22:42 +0800 Subject: [PATCH 34/43] =?UTF-8?q?=F0=9F=94=A5=20remove=20unnecessary=20rep?= =?UTF-8?q?ly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Commands/Commands.ts | 6 +++--- src/classes/Commands/sub-classes/PricelistManager.ts | 8 ++++---- src/classes/Commands/sub-classes/Request.ts | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/classes/Commands/Commands.ts b/src/classes/Commands/Commands.ts index dc8b0ef01..c1bc11167 100644 --- a/src/classes/Commands/Commands.ts +++ b/src/classes/Commands/Commands.ts @@ -757,7 +757,7 @@ export default class Commands { if (params.sku === undefined) { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); @@ -806,7 +806,7 @@ export default class Commands { if (params.sku === undefined) { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); @@ -882,7 +882,7 @@ export default class Commands { if (params.sku === undefined) { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); diff --git a/src/classes/Commands/sub-classes/PricelistManager.ts b/src/classes/Commands/sub-classes/PricelistManager.ts index 9da2ef28c..d84117a2b 100644 --- a/src/classes/Commands/sub-classes/PricelistManager.ts +++ b/src/classes/Commands/sub-classes/PricelistManager.ts @@ -141,7 +141,7 @@ export default class PricelistManagerCommands { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); @@ -770,7 +770,7 @@ export default class PricelistManagerCommands { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); @@ -1088,7 +1088,7 @@ export default class PricelistManagerCommands { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); @@ -1141,7 +1141,7 @@ export default class PricelistManagerCommands { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); diff --git a/src/classes/Commands/sub-classes/Request.ts b/src/classes/Commands/sub-classes/Request.ts index e9280b35c..0e37d696a 100644 --- a/src/classes/Commands/sub-classes/Request.ts +++ b/src/classes/Commands/sub-classes/Request.ts @@ -34,7 +34,7 @@ export default class RequestCommands { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); @@ -120,7 +120,7 @@ export default class RequestCommands { if (params.sku === undefined) { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); @@ -215,7 +215,7 @@ export default class RequestCommands { if (params.sku === undefined) { const item = getItemFromParams(steamID, params, this.bot); if (item === null) { - return this.bot.sendMessage(steamID, `āŒ Item not found.`); + return; } params.sku = SKU.fromObject(item); From 0d5d20012fc5f125f9d8d4446a0beffa554947bb Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sat, 13 Feb 2021 17:30:17 +0800 Subject: [PATCH 35/43] =?UTF-8?q?=F0=9F=94=82=20nope,=20this=20does=20not?= =?UTF-8?q?=20fix=20#326?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Carts/AdminCart.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/classes/Carts/AdminCart.ts b/src/classes/Carts/AdminCart.ts index b2072e8a8..115afcd4e 100644 --- a/src/classes/Carts/AdminCart.ts +++ b/src/classes/Carts/AdminCart.ts @@ -171,11 +171,6 @@ export default class AdminCart extends Cart { } } - if (this.isEmpty) { - theirInventory.clearFetch(); - return reject(alteredMessages.join(', ')); - } - offer.data('dict', { our: this.our, their: this.their }); this.offer = offer; From 4bf87eb62b9b8cec13ea3c7e3baa7fa4cbfb76dd Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 14 Feb 2021 07:23:29 +0800 Subject: [PATCH 36/43] =?UTF-8?q?=F0=9F=A7=AA=20increase=20pollInterval=20?= =?UTF-8?q?from=201k=20ms=20to=2030k=20ms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Bot.ts | 2 +- src/classes/Trades.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index a6701a36e..ad1da0ebe 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -561,7 +561,7 @@ export default class Bot { log.debug('Setting Steam API Key to schema'); this.botManager.setAPIKeyForSchema = this.manager.apiKey; - this.manager.pollInterval = 1000; + this.manager.pollInterval = 30 * 1000; this.setReady = true; this.handler.onReady(); this.manager.doPoll(); diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index 9f088ded4..a3531b4e8 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -111,7 +111,7 @@ export default class Trades { if ( filter === TradeOfferManager.EOfferFilter['ActiveOnly'] && - (this.pollCount * this.bot.manager.pollInterval) / (2 * 60 * 1000) >= 1 + (this.pollCount * this.bot.manager.pollInterval) / (2 * 30 * 60 * 1000) >= 1 ) { this.pollCount = 0; From 47d9e4fe75c0519b2ccc323286cfbf297112b4b7 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 14 Feb 2021 07:52:05 +0800 Subject: [PATCH 37/43] =?UTF-8?q?=F0=9F=94=A8refactor:=20take=20out=20`met?= =?UTF-8?q?a`=20from=20`action`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Commands/sub-classes/Review.ts | 10 +-- .../offer/accepted/processAccepted.ts | 69 +++++++------------ .../MyHandler/offer/notify/declined.ts | 5 +- src/classes/Trades.ts | 7 +- .../steam-tradeoffer-manager/index.d.ts | 2 +- 5 files changed, 39 insertions(+), 54 deletions(-) diff --git a/src/classes/Commands/sub-classes/Review.ts b/src/classes/Commands/sub-classes/Review.ts index dbb425cfc..ae4003743 100644 --- a/src/classes/Commands/sub-classes/Review.ts +++ b/src/classes/Commands/sub-classes/Review.ts @@ -1,6 +1,6 @@ import SteamID from 'steamid'; import pluralize from 'pluralize'; -import TradeOfferManager, { Action, OfferData, OurTheirItemsDict } from 'steam-tradeoffer-manager'; +import TradeOfferManager, { Meta, OfferData, OurTheirItemsDict } from 'steam-tradeoffer-manager'; import Currencies from 'tf2-currencies'; import { UnknownDictionaryKnownValues } from '../../../types/common'; import SKU from 'tf2-sku-2'; @@ -77,7 +77,7 @@ export default class ReviewCommands { reply += `\n- Offer #${offer.id as string} from ${ (offer.data as OfferData).partner - } (reason: ${(offer.data as OfferData).action.meta.uniqueReasons.join(', ')})` + + } (reason: ${(offer.data as OfferData).meta.uniqueReasons.join(', ')})` + `\nāš ļø Send "!trade ${offer.id as string}" for more details.\n`; } @@ -130,7 +130,7 @@ export default class ReviewCommands { let reply = offerData?.action?.action === 'skip' ? `āš ļø Offer #${offerId} from ${offerData.partner} is pending for review` + - `\nReason: ${offerData.action.meta.uniqueReasons.join(', ')}).\n\nSummary:\n\n` + `\nReason: ${offerData.meta.uniqueReasons.join(', ')}).\n\nSummary:\n\n` : `āš ļø Offer #${offerId} from ${offerData.partner} is still active.\n\nSummary:\n\n`; const keyPrice = this.bot.pricelist.getKeyPrice; @@ -235,7 +235,7 @@ export default class ReviewCommands { await this.bot.trades.applyActionToOffer( isAccepting ? 'accept' : 'decline', 'MANUAL', - isAccepting ? (offer.data('action') as Action).meta : {}, + isAccepting ? (offer.data('meta') as Meta) : {}, offer ); @@ -320,7 +320,7 @@ export default class ReviewCommands { await this.bot.trades.applyActionToOffer( isForceAccepting ? 'accept' : 'decline', 'MANUAL-FORCE', - isForceAccepting ? (offer.data('action') as Action).meta : {}, + isForceAccepting ? (offer.data('meta') as Meta) : {}, offer ); diff --git a/src/classes/MyHandler/offer/accepted/processAccepted.ts b/src/classes/MyHandler/offer/accepted/processAccepted.ts index 33937b977..797874bec 100644 --- a/src/classes/MyHandler/offer/accepted/processAccepted.ts +++ b/src/classes/MyHandler/offer/accepted/processAccepted.ts @@ -25,6 +25,7 @@ export default function processAccepted( }; const offerReceived = offer.data('action') as i.Action; + const meta = offer.data('meta') as i.Meta; const offerSent = offer.data('highValue') as i.HighValueOutput; const isWebhookEnabled = opt.discordWebhook.tradeSummary.enable && opt.discordWebhook.tradeSummary.url.length > 0; @@ -33,27 +34,23 @@ export default function processAccepted( // doing this because if an offer is being made by bot (from command), then this is undefined if (['VALID_WITH_OVERPAY', 'MANUAL', 'MANUAL-FORCE', 'AUTO-RETRY'].includes(offerReceived.reason)) { // only for accepted overpay with INVALID_ITEMS/OVERSTOCKED/UNDERSTOCKED or MANUAL offer - if (offerReceived.meta?.uniqueReasons?.includes('šŸŸØ_INVALID_ITEMS')) { + if (meta?.uniqueReasons?.includes('šŸŸØ_INVALID_ITEMS')) { // doing this so it will only executed if includes šŸŸØ_INVALID_ITEMS reason. - (offerReceived.meta.reasons.filter(el => el.reason === 'šŸŸØ_INVALID_ITEMS') as i.InvalidItems[]).forEach( - el => { - accepted.invalidItems.push( - `${ - isWebhookEnabled - ? `_${bot.schema.getName(SKU.fromString(el.sku), false)}_` - : bot.schema.getName(SKU.fromString(el.sku), false) - } - ${el.price}` - ); - } - ); + (meta.reasons.filter(el => el.reason === 'šŸŸØ_INVALID_ITEMS') as i.InvalidItems[]).forEach(el => { + accepted.invalidItems.push( + `${ + isWebhookEnabled + ? `_${bot.schema.getName(SKU.fromString(el.sku), false)}_` + : bot.schema.getName(SKU.fromString(el.sku), false) + } - ${el.price}` + ); + }); } - if (offerReceived.meta?.uniqueReasons?.includes('šŸŸ§_DISABLED_ITEMS')) { + if (meta?.uniqueReasons?.includes('šŸŸ§_DISABLED_ITEMS')) { // doing this so it will only executed if includes šŸŸ§_DISABLED_ITEMS reason. - (offerReceived.meta.reasons.filter( - el => el.reason === 'šŸŸ§_DISABLED_ITEMS' - ) as i.DisabledItems[]).forEach(el => { + (meta.reasons.filter(el => el.reason === 'šŸŸ§_DISABLED_ITEMS') as i.DisabledItems[]).forEach(el => { accepted.disabledItems.push( isWebhookEnabled ? `_${bot.schema.getName(SKU.fromString(el.sku), false)}_` @@ -61,12 +58,10 @@ export default function processAccepted( ); }); } - if (offerReceived.meta?.uniqueReasons?.includes('šŸŸ¦_OVERSTOCKED')) { + if (meta?.uniqueReasons?.includes('šŸŸ¦_OVERSTOCKED')) { // doing this so it will only executed if includes šŸŸ¦_OVERSTOCKED reason. - (offerReceived.meta.reasons.filter(el => - el.reason.includes('šŸŸ¦_OVERSTOCKED') - ) as i.Overstocked[]).forEach(el => { + (meta.reasons.filter(el => el.reason.includes('šŸŸ¦_OVERSTOCKED')) as i.Overstocked[]).forEach(el => { accepted.overstocked.push( `${ isWebhookEnabled @@ -77,12 +72,10 @@ export default function processAccepted( }); } - if (offerReceived.meta?.uniqueReasons?.includes('šŸŸ©_UNDERSTOCKED')) { + if (meta?.uniqueReasons?.includes('šŸŸ©_UNDERSTOCKED')) { // doing this so it will only executed if includes šŸŸ©_UNDERSTOCKED reason. - (offerReceived.meta.reasons.filter(el => - el.reason.includes('šŸŸ©_UNDERSTOCKED') - ) as i.Understocked[]).forEach(el => { + (meta.reasons.filter(el => el.reason.includes('šŸŸ©_UNDERSTOCKED')) as i.Understocked[]).forEach(el => { accepted.understocked.push( `${ isWebhookEnabled @@ -94,15 +87,10 @@ export default function processAccepted( } } - if (offerReceived.meta?.highValue && offerReceived.meta?.highValue['has'] === undefined) { - if (Object.keys(offerReceived.meta.highValue.items.their).length > 0) { + if (meta?.highValue && meta.highValue['has'] === undefined) { + if (Object.keys(meta.highValue.items.their).length > 0) { // doing this to check if their side have any high value items, if so, push each name into accepted.highValue const. - const itemsName = t.getHighValueItems( - offerReceived.meta.highValue.items.their, - bot, - bot.paints, - bot.strangeParts - ); + const itemsName = t.getHighValueItems(meta.highValue.items.their, bot, bot.paints, bot.strangeParts); for (const name in itemsName) { if (!Object.prototype.hasOwnProperty.call(itemsName, name)) { @@ -113,8 +101,8 @@ export default function processAccepted( theirHighValuedItems.push(`${isWebhookEnabled ? `_${name}_` : name}` + itemsName[name]); } - if (offerReceived.meta.highValue.isMention.their) { - Object.keys(offerReceived.meta.highValue.items.their).forEach(sku => isDisableSKU.push(sku)); + if (meta.highValue.isMention.their) { + Object.keys(meta.highValue.items.their).forEach(sku => isDisableSKU.push(sku)); if (!bot.isAdmin(offer.partner)) { accepted.isMention = true; @@ -122,14 +110,9 @@ export default function processAccepted( } } - if (Object.keys(offerReceived.meta.highValue.items.our).length > 0) { + if (Object.keys(meta.highValue.items.our).length > 0) { // doing this to check if our side have any high value items, if so, push each name into accepted.highValue const. - const itemsName = t.getHighValueItems( - offerReceived.meta.highValue.items.our, - bot, - bot.paints, - bot.strangeParts - ); + const itemsName = t.getHighValueItems(meta.highValue.items.our, bot, bot.paints, bot.strangeParts); for (const name in itemsName) { if (!Object.prototype.hasOwnProperty.call(itemsName, name)) { @@ -139,7 +122,7 @@ export default function processAccepted( accepted.highValue.push(`${isWebhookEnabled ? `_${name}_` : name}` + itemsName[name]); } - if (offerReceived.meta.highValue.isMention.our) { + if (meta.highValue.isMention.our) { if (!bot.isAdmin(offer.partner)) { accepted.isMention = true; } @@ -251,7 +234,7 @@ export default function processAccepted( return { theirHighValuedItems, isDisableSKU, - items: offerReceived?.meta?.highValue?.items?.their || offerSent?.items?.their + items: meta?.highValue?.items?.their || offerSent?.items?.their }; } diff --git a/src/classes/MyHandler/offer/notify/declined.ts b/src/classes/MyHandler/offer/notify/declined.ts index 9837f89c1..448a20112 100644 --- a/src/classes/MyHandler/offer/notify/declined.ts +++ b/src/classes/MyHandler/offer/notify/declined.ts @@ -1,4 +1,4 @@ -import { Action, TradeOffer } from 'steam-tradeoffer-manager'; +import { Action, Meta, TradeOffer } from 'steam-tradeoffer-manager'; import { valueDiff, summarizeToChat } from '../../../../lib/tools/export'; import Bot from '../../../Bot'; @@ -6,6 +6,7 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo const opt = bot.options; const offerReason = offer.data('action') as Action; + const meta = offer.data('meta') as Meta; const keyPrices = bot.pricelist.getKeyPrices; const value = valueDiff(offer, keyPrices, isTradingKeys, opt.miscSettings.showOnlyMetal.enable); const manualReviewDisabled = !opt.manualReview.enable; @@ -63,7 +64,7 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo } else if (offerReason.reason === 'HIGH_VALUE_ITEMS_NOT_SELLING') { // const custom = opt.customMessage.decline.highValueItemsNotSelling; - const highValueName = offerReason.meta.highValueName.join(', '); + const highValueName = meta.highValueName.join(', '); reply = custom ? custom.replace(/%highValueName%/g, highValueName) : `/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ` + diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index a3531b4e8..a39f5bc7d 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -310,10 +310,11 @@ export default class Trades { offer.data('action', { action: action, - reason: reason, - meta: meta + reason: reason } as Action); + offer.data('meta', meta); + if (actionFunc === undefined) { return Promise.resolve(); } @@ -410,7 +411,7 @@ export default class Trades { await this.applyActionToOffer( isRetryAccept ? 'accept' : 'decline', 'AUTO-RETRY', - isRetryAccept ? (offer.data('action') as Action).meta : {}, + isRetryAccept ? (offer.data('meta') as Meta) : {}, offer ); diff --git a/src/types/modules/steam-tradeoffer-manager/index.d.ts b/src/types/modules/steam-tradeoffer-manager/index.d.ts index f59d5ffb3..982d9e6a4 100644 --- a/src/types/modules/steam-tradeoffer-manager/index.d.ts +++ b/src/types/modules/steam-tradeoffer-manager/index.d.ts @@ -105,6 +105,7 @@ declare module 'steam-tradeoffer-manager' { finishTimestamp?: number; // checked isAccepted?: boolean; // checked action?: Action; // checked + meta?: Meta; highValue?: HighValueOutput; // Only offer sent // checked _dupeCheck?: string[]; // Only offer sent // checked _ourItems?: OutItems[]; // checked @@ -151,7 +152,6 @@ declare module 'steam-tradeoffer-manager' { export interface Action { action: 'accept' | 'decline' | 'skip'; reason: string; - meta: Meta; } export interface Overstocked { From 60ce1b9ce9d72d81e5f3994ac2671b314c30815e Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 14 Feb 2021 08:01:08 +0800 Subject: [PATCH 38/43] =?UTF-8?q?=F0=9F=94=A5=20remove=20`notify`=20&=20`m?= =?UTF-8?q?eta`=20from=20polldata=20on=20=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/MyHandler/MyHandler.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/classes/MyHandler/MyHandler.ts b/src/classes/MyHandler/MyHandler.ts index 6ef5275f8..57d32f214 100644 --- a/src/classes/MyHandler/MyHandler.ts +++ b/src/classes/MyHandler/MyHandler.ts @@ -1811,10 +1811,13 @@ export default class MyHandler extends Handler { } else if (offer.state === TradeOfferManager.ETradeOfferState['Declined']) { declined(offer, this.bot, this.isTradingKeys); this.isTradingKeys = false; // reset + this.removePolldataKeys(offer); } else if (offer.state === TradeOfferManager.ETradeOfferState['Canceled']) { cancelled(offer, oldState, this.bot); + this.removePolldataKeys(offer); } else if (offer.state === TradeOfferManager.ETradeOfferState['InvalidItems']) { invalid(offer, this.bot); + this.removePolldataKeys(offer); } } @@ -1862,9 +1865,17 @@ export default class MyHandler extends Handler { // Invite to group this.inviteToGroups(offer.partner); + + // delete notify and meta keys from polldata after each successful trades + this.removePolldataKeys(offer); } } + private removePolldataKeys(offer: TradeOffer): void { + offer.data('notify', undefined); + offer.data('meta', undefined); + } + onOfferAction(offer: TradeOffer, action: 'accept' | 'decline' | 'skip', reason: string, meta: Meta): void { if (offer.data('notify') !== true) { return; From c222b0220f7565c00c14e2c592c86c129ea3f93c Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 14 Feb 2021 08:14:16 +0800 Subject: [PATCH 39/43] =?UTF-8?q?=E2=AC=86=20bump=20License=20year?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index b6763773a..745086185 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 IdiNium/Nicklas Marc Pedersen +Copyright (c) 2020 - 2021 IdiNium/Nicklas Marc Pedersen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 371748013bff72aac631ad177a146e0ff8459d42 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 14 Feb 2021 08:25:26 +0800 Subject: [PATCH 40/43] =?UTF-8?q?=F0=9F=8E=A8=20a=20bit=20cleaner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyHandler/offer/notify/declined.ts | 85 +++++++------------ 1 file changed, 30 insertions(+), 55 deletions(-) diff --git a/src/classes/MyHandler/offer/notify/declined.ts b/src/classes/MyHandler/offer/notify/declined.ts index 448a20112..0934a2bed 100644 --- a/src/classes/MyHandler/offer/notify/declined.ts +++ b/src/classes/MyHandler/offer/notify/declined.ts @@ -13,53 +13,43 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo offer.data('isDeclined', true); + const declined = '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined'; + let reasonForInvalidValue = false; let reply: string; if (!offerReason) { const custom = opt.customMessage.decline.general; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined.'; + reply = custom ? custom : declined + '.'; } else if (offerReason.reason === 'GIFT_NO_NOTE') { // const custom = opt.customMessage.decline.giftNoNote; reply = custom ? custom - : `/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because` + - `the offer you've sent is an empty offer on my side without any offer message. ` + + : declined + + ` because the offer you've sent is an empty offer on my side without any offer message. ` + `If you wish to give it as a gift, please include "gift" in the offer message. Thank you.`; // } else if (offerReason.reason === 'CRIME_ATTEMPT') { // const custom = opt.customMessage.decline.crimeAttempt; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - "you're taking free items. No."; + reply = custom ? custom : declined + " because you're taking free items. No."; // } else if (offerReason.reason === 'ONLY_METAL') { // const custom = opt.customMessage.decline.onlyMetal; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - 'you might forgot to add items into the trade.'; + reply = custom ? custom : declined + ' because you might forgot to add items into the trade.'; // } else if (offerReason.reason === 'DUELING_NOT_5_USES') { // const custom = opt.customMessage.decline.duelingNot5Uses; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - 'your offer contains Dueling Mini-Game(s) that does not have 5 uses.'; + : declined + ' because your offer contains Dueling Mini-Game(s) that does not have 5 uses.'; // } else if (offerReason.reason === 'NOISE_MAKER_NOT_25_USES') { // const custom = opt.customMessage.decline.noiseMakerNot25Uses; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - 'your offer contains Noise Maker(s) that does not have 25 uses.'; + reply = custom ? custom : declined + ' because your offer contains Noise Maker(s) that does not have 25 uses.'; // } else if (offerReason.reason === 'HIGH_VALUE_ITEMS_NOT_SELLING') { // @@ -67,48 +57,48 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo const highValueName = meta.highValueName.join(', '); reply = custom ? custom.replace(/%highValueName%/g, highValueName) - : `/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ` + - `you're attempting to purchase ${highValueName}, but I am not selling it right now.`; + : declined + ` because you're attempting to purchase ${highValueName}, but I am not selling it right now.`; // } else if (offerReason.reason === 'NOT_TRADING_KEYS') { // const custom = opt.customMessage.decline.notTradingKeys; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - 'I am no longer trading keys. You can confirm this by typing "!price Mann Co. Supply Crate Key" or "!autokeys".'; + : declined + + ' because I am no longer trading keys. You can confirm this by typing "!price Mann Co. Supply Crate Key" or "!autokeys".'; // } else if (offerReason.reason === 'NOT_SELLING_KEYS') { // const custom = opt.customMessage.decline.notSellingKeys; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - 'I am no longer selling keys. You can confirm this by typing "!price Mann Co. Supply Crate Key" or "!autokeys".'; + : declined + + ' because I am no longer selling keys. You can confirm this by typing "!price Mann Co. Supply Crate Key" or "!autokeys".'; // } else if (offerReason.reason === 'NOT_BUYING_KEYS') { // const custom = opt.customMessage.decline.notBuyingKeys; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - 'I am no longer buying keys. You can confirm this by typing "!price Mann Co. Supply Crate Key" or "!autokeys".'; + : declined + + ' because I am no longer buying keys. You can confirm this by typing "!price Mann Co. Supply Crate Key" or "!autokeys".'; // } else if (offerReason.reason === 'BANNED') { // const custom = opt.customMessage.decline.banned; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - "you're currently banned on backpack.tf or labeled as a scammer on steamrep.com or another community."; + : declined + + " because you're currently banned on backpack.tf or labeled as a scammer on steamrep.com or another community."; // } else if (offerReason.reason === 'ESCROW') { // const custom = opt.customMessage.decline.escrow; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - 'I do not accept escrow (trade holds). To prevent this from happening in the future, please enable Steam Guard Mobile Authenticator.' + + : declined + + ' because I do not accept escrow (trade holds). To prevent this from happening in the future, ' + + 'please enable Steam Guard Mobile Authenticator.' + '\nRead:\n' + 'ā€¢ Steam Guard Mobile Authenticator - https://support.steampowered.com/kb_article.php?ref=8625-WRAH-9030' + '\nā€¢ How to set up Steam Guard Mobile Authenticator - https://support.steampowered.com/kb_article.php?ref=4440-RTUI-9218'; @@ -116,9 +106,7 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo } else if (offerReason.reason === 'MANUAL') { // const custom = opt.customMessage.decline.manual; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined by the owner.'; + reply = custom ? custom : declined + ' by the owner.'; // } else if ( offerReason.reason === 'ONLY_INVALID_VALUE' || @@ -129,8 +117,8 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo reasonForInvalidValue = true; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - "you've sent a trade with an invalid value (your side and my side do not hold equal value)."; + : declined + + " because you've sent a trade with an invalid value (your side and my side do not hold equal value)."; // } else if ( offerReason.reason === 'ONLY_INVALID_ITEMS' || @@ -141,8 +129,7 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo reasonForInvalidValue = value.diff < 0; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - "you've sent a trade with an invalid items (not exist in my pricelist)."; + : declined + " because you've sent a trade with an invalid items (not exist in my pricelist)."; // } else if ( offerReason.reason === 'ONLY_DISABLED_ITEMS' || @@ -151,10 +138,7 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo // const custom = opt.offerReceived.disabledItems.autoDecline.declineReply; reasonForInvalidValue = value.diff < 0; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - "the item(s) you're trying to take/give is currently disabled"; + reply = custom ? custom : declined + " because the item(s) you're trying to take/give is currently disabled"; // } else if ( offerReason.reason === 'ONLY_OVERSTOCKED' || @@ -163,10 +147,7 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo // const custom = opt.offerReceived.overstocked.autoDecline.declineReply; reasonForInvalidValue = value.diff < 0; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - "you're attempting to sell item(s) that I can't buy more of."; + reply = custom ? custom : declined + " because you're attempting to sell item(s) that I can't buy more of."; // } else if ( offerReason.reason === 'ONLY_UNDERSTOCKED' || @@ -177,23 +158,17 @@ export default function declined(offer: TradeOffer, bot: Bot, isTradingKeys: boo reasonForInvalidValue = value.diff < 0; reply = custom ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - "you're attempting to purchase item(s) that I can't sell more of."; + : declined + " because you're attempting to purchase item(s) that I can't sell more of."; // } else if (offerReason.reason === 'šŸŸ«_DUPED_ITEMS') { // const custom = opt.offerReceived.duped.autoDecline.declineReply; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined because ' + - "I don't accept duped items."; + reply = custom ? custom : declined + " because I don't accept duped items."; // } else { // const custom = opt.customMessage.decline.general; - reply = custom - ? custom - : '/pre āŒ Ohh nooooes! The offer is no longer available. Reason: The offer has been declined.'; + reply = custom ? custom : declined + '.'; // } From d99de18be598c6f0fe686eee5cf38fa830ae2649 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 14 Feb 2021 08:26:49 +0800 Subject: [PATCH 41/43] =?UTF-8?q?=F0=9F=8E=A8=20update=20github=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/contributing.md | 6 +++--- package.json | 6 +++--- src/app.ts | 6 ++++-- src/classes/Commands/sub-classes/Manager.ts | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3742d1739..9cbe09859 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,7 +9,7 @@ assignees: '' **Describe the bug** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 67a209718..ad5fcc6b6 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -9,7 +9,7 @@ assignees: '' **Is your feature request related to a problem? Please describe.** diff --git a/.github/contributing.md b/.github/contributing.md index 55a2189db..0ed19b877 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -34,7 +34,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r #### How to submit a bug report -Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you have determined that it is a bug, create an issue and provide following information by filling in [the template](https://github.com/idinium96/tf2autobot/blob/master/.github/ISSUE_TEMPLATE/bug_report.md). +Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you have determined that it is a bug, create an issue and provide following information by filling in [the template](https://github.com/TF2Autobot/tf2autobot/blob/master/.github/ISSUE_TEMPLATE/bug_report.md). #### Before submitting a bug report @@ -70,7 +70,7 @@ Just like [bug reports](#reporting-bugs), feature requests are tracked as [GitHu When contributing to this repository, please first discuss the change you wish to make via issue, or any other method with the owners or contributors of this repository before making a change. -All pull requests should be made to [the development branch](https://github.com/idinium96/tf2autobot/tree/development). When a new release is made the development branch will be merged with the master branch. +All pull requests should be made to [the development branch](https://github.com/TF2Autobot/tf2autobot/tree/development). When a new release is made the development branch will be merged with the master branch. Please make sure that you follow the [style guides](#styleguides). @@ -99,7 +99,7 @@ The body of the commit message should be one or more paragraphs, explaining things in more detail. Please word-wrap to keep columns to 72 characters or less. -Fixes: https://github.com/Nicklason/tf2autobot/issues/1337 +Fixes: https://github.com/TF2Autobot/tf2autobot/issues/1337 Refs: https://eslint.org/docs/rules/space-in-parens.html ``` diff --git a/package.json b/package.json index dee59b7ca..8b5ddabe5 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,14 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/idinium96/tf2autobot.git" + "url": "git+https://github.com/TF2Autobot/tf2autobot.git" }, "author": "IdiNium/Nicklas Marc Pedersen", "license": "MIT", "bugs": { - "url": "https://github.com/idinium96/tf2autobot/issues" + "url": "https://github.com/TF2Autobot/tf2autobot/issues" }, - "homepage": "https://github.com/idinium96/tf2autobot#readme", + "homepage": "https://github.com/TF2Autobot/tf2autobot#readme", "dependencies": { "async": "^3.2.0", "bluebird": "^3.7.2", diff --git a/src/app.ts b/src/app.ts index b18cf6bc8..1e596e777 100644 --- a/src/app.ts +++ b/src/app.ts @@ -38,7 +38,8 @@ init(paths, options); if (process.env.pm_id === undefined) { log.warn( - "You are not running the bot with PM2! If the bot crashes it won't start again. Get a VPS and run your bot with PM2: https://github.com/idinium96/tf2autobot/wiki/Getting-a-VPS" + "You are not running the bot with PM2! If the bot crashes it won't start again." + + ' Get a VPS and run your bot with PM2: https://github.com/TF2Autobot/tf2autobot/wiki/Getting-a-VPS' ); } @@ -104,7 +105,8 @@ ON_DEATH({ uncaughtException: true })((signalOrErr, origin) => { if (botReady) { log.error( - 'Refer to Wiki here: https://github.com/idinium96/tf2autobot/wiki/Common-Errors OR Create an issue here: https://github.com/idinium96/tf2autobot/issues/new?assignees=&labels=bug&template=bug_report.md&title=' + 'Refer to Wiki here: https://github.com/TF2Autobot/tf2autobot/wiki/Common-Errors OR ' + + 'Create an issue here: https://github.com/idinium96/TF2Autobot/issues/new?assignees=&labels=bug&template=bug_report.md&title=' ); } } else { diff --git a/src/classes/Commands/sub-classes/Manager.ts b/src/classes/Commands/sub-classes/Manager.ts index 3be82c005..0fa972919 100644 --- a/src/classes/Commands/sub-classes/Manager.ts +++ b/src/classes/Commands/sub-classes/Manager.ts @@ -342,7 +342,7 @@ export default class ManagerCommands { this.bot.sendMessage( steamID, 'āŒ You are not running the bot with PM2! Get a VPS and run ' + - 'your bot with PM2: https://github.com/idinium96/tf2autobot/wiki/Getting-a-VPS' + 'your bot with PM2: https://github.com/TF2Autobot/tf2autobot/wiki/Getting-a-VPS' ); } }) @@ -386,7 +386,7 @@ export default class ManagerCommands { steamID, `āš ļø Update available! Current: v${process.env.BOT_VERSION}, Latest: v${latestVersion}.` + '\nSend "!updaterepo i_am_sure=yes_i_am" to update your repo now!' + - `\n\nRelease note: https://github.com/idinium96/tf2autobot/releases` + `\n\nRelease note: https://github.com/TF2Autobot/tf2autobot/releases` ); } }) From 5e58f281d6ed91668b09db41047b895bd25c407c Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 14 Feb 2021 08:36:00 +0800 Subject: [PATCH 42/43] =?UTF-8?q?=F0=9F=94=95=20ignore=20`Could=20not=20ac?= =?UTF-8?q?t=20on=20confirmation`=20err?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Trades.ts | 105 +++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/src/classes/Trades.ts b/src/classes/Trades.ts index a39f5bc7d..7399ecbf0 100644 --- a/src/classes/Trades.ts +++ b/src/classes/Trades.ts @@ -582,59 +582,70 @@ export default class Trades { this.acceptConfirmation(offer).catch(err => { log.debug(`Error while trying to accept mobile confirmation on offer #${offer.id}: `, err); - const opt = this.bot.options; - if (opt.sendAlert.failedAccept) { - const keyPrices = this.bot.pricelist.getKeyPrices; - const value = t.valueDiff(offer, keyPrices, false, opt.miscSettings.showOnlyMetal.enable); + if (!(err as CustomError).message?.includes('Could not act on confirmation')) { + // Only notify is error is not "Could not act on confirmation" - if (opt.discordWebhook.sendAlert.enable && opt.discordWebhook.sendAlert.url !== '') { - const summary = t.summarizeToChat( + const opt = this.bot.options; + if (opt.sendAlert.failedAccept) { + const keyPrices = this.bot.pricelist.getKeyPrices; + const value = t.valueDiff( offer, - this.bot, - 'summary-accepting', - true, - value, keyPrices, false, - false - ); - sendAlert( - `error-accept`, - this.bot, - `Error while trying to accept mobile confirmation on offer #${offer.id}` + - summary + - `\n\nThe offer might already get cancelled. You can check if this offer is still active by` + - ` sending "!trade ${offer.id}"`, - null, - err, - [offer.id] - ); - } else { - const summary = t.summarizeToChat( - offer, - this.bot, - 'summary-accepting', - false, - value, - keyPrices, - true, - false + opt.miscSettings.showOnlyMetal.enable ); - this.bot.messageAdmins( - `Error while trying to accept mobile confirmation on offer #${offer.id}:` + - summary + - `\n\nThe offer might already get cancelled. You can check if this offer is still active by` + - ` sending "!trade ${offer.id}` + - `\n\nError: ${ - (err as CustomError).eresult - ? `${ - TradeOfferManager.EResult[(err as CustomError).eresult] as string - } (https://steamerrors.com/${(err as CustomError).eresult})` - : (err as Error).message - }`, - [] - ); + if (opt.discordWebhook.sendAlert.enable && opt.discordWebhook.sendAlert.url !== '') { + const summary = t.summarizeToChat( + offer, + this.bot, + 'summary-accepting', + true, + value, + keyPrices, + false, + false + ); + sendAlert( + `error-accept`, + this.bot, + `Error while trying to accept mobile confirmation on offer #${offer.id}` + + summary + + `\n\nThe offer might already get cancelled. You can check if this offer is still active by` + + ` sending "!trade ${offer.id}"`, + null, + err, + [offer.id] + ); + } else { + const summary = t.summarizeToChat( + offer, + this.bot, + 'summary-accepting', + false, + value, + keyPrices, + true, + false + ); + + this.bot.messageAdmins( + `Error while trying to accept mobile confirmation on offer #${offer.id}:` + + summary + + `\n\nThe offer might already get cancelled. You can check if this offer is still active by` + + ` sending "!trade ${offer.id}` + + `\n\nError: ${ + (err as CustomError).eresult + ? `${ + TradeOfferManager.EResult[ + (err as CustomError).eresult + ] as string + } (https://steamerrors.com/${(err as CustomError).eresult})` + : (err as Error).message + }`, + [] + ); + } } } }); From 97611d6277aeaa3ed6f34211c02bedf7bc1afb81 Mon Sep 17 00:00:00 2001 From: idinium96 <47635037+idinium96@users.noreply.github.com> Date: Sun, 14 Feb 2021 09:01:13 +0800 Subject: [PATCH 43/43] =?UTF-8?q?=E2=9C=A8=20add=20`!offerinfo`=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/Commands/Commands.ts | 2 ++ src/classes/Commands/sub-classes/Help.ts | 1 + src/classes/Commands/sub-classes/Review.ts | 26 ++++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/classes/Commands/Commands.ts b/src/classes/Commands/Commands.ts index c1bc11167..6ef13dcde 100644 --- a/src/classes/Commands/Commands.ts +++ b/src/classes/Commands/Commands.ts @@ -233,6 +233,8 @@ export default class Commands { void this.review.actionOnTradeCommand(steamID, message, command as ActionOnTrade); } else if (['faccept', 'fdecline'].includes(command) && isAdmin) { void this.review.forceAction(steamID, message, command as ForceAction); + } else if (command === 'offerinfo' && isAdmin) { + this.review.offerInfo(steamID, message); } else if (command === 'pricecheck' && isAdmin) { this.request.pricecheckCommand(steamID, message); } else if (command === 'pricecheckall' && isAdmin) { diff --git a/src/classes/Commands/sub-classes/Help.ts b/src/classes/Commands/sub-classes/Help.ts index 158a5d8cc..dff6d6bf1 100644 --- a/src/classes/Commands/sub-classes/Help.ts +++ b/src/classes/Commands/sub-classes/Help.ts @@ -53,6 +53,7 @@ export default class HelpCommands { '!version - Get the TF2Autobot version that the bot is running\n\nāœØ=== Manual review ===āœØ', '!trades - Get a list of trade offers pending for manual review šŸ”', '!trade - Get information about a trade', + '!offerinfo - Get information about the offer from polldata šŸ”', '!accept [Your Message] - Manually accept an active offer āœ…šŸ”', '!decline [Your Message] - Manually decline an active offer āŒšŸ”', '!faccept [Your Message] - Force accept any failed to accept offer āœ…šŸ”‚', diff --git a/src/classes/Commands/sub-classes/Review.ts b/src/classes/Commands/sub-classes/Review.ts index ae4003743..52ac915b9 100644 --- a/src/classes/Commands/sub-classes/Review.ts +++ b/src/classes/Commands/sub-classes/Review.ts @@ -348,4 +348,30 @@ export default class ReviewCommands { ); } } + + offerInfo(steamID: SteamID, message: string): void { + const offerIdAndMessage = CommandParser.removeCommand(message); + const offerIdRegex = /\d+/.exec(offerIdAndMessage); + + if (isNaN(+offerIdRegex) || !offerIdRegex) { + return this.bot.sendMessage(steamID, `āš ļø Missing offer id. Example: "!offerinfo 3957959294"`); + } + + const offerId = offerIdRegex[0]; + + const state = this.bot.manager.pollData.received[offerId]; + if (state === undefined) { + return this.bot.sendMessage(steamID, 'Offer does not exist. āŒ'); + } + + try { + const offer = this.bot.manager.pollData.offerData[offerId]; + const show = {}; + show[offerId] = offer; + + this.bot.sendMessage(steamID, '/code ' + JSON.stringify(show, null, 4)); + } catch (err) { + return this.bot.sendMessage(steamID, `āŒ Error getting offer #${offerId} info: ${(err as Error).message}`); + } + } }