diff --git a/README.md b/README.md index df512013..f8f154de 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Open the `accounts.txt` file and provide your accounts in the `username:password If you don't want to use a shared_secret just leave it out and only provide the account in the `username:password` format. Please make sure you know about limited/unlimited accounts. Your accounts also need to have E-Mail Steam Guard active. -You can read a detailed explanation [here in the wiki](https://github.com/3urobeat/steam-comment-service-bot/wiki/Steam-limitations). +You can read a detailed explanation [here in the wiki](https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/steam_limitations.md).
Another, optional method (not recommended anymore): @@ -122,7 +122,7 @@ Head over to your Steam client, add the main bot (the first account in your acco It should respond with a list of commands available to you. To request a comment, simply type `!comment 1`! [Click to see Demo](https://youtu.be/8J78rC9Z28U?t=294) -You can see all commands and their usage [here in the wiki](https://github.com/3urobeat/steam-comment-service-bot/wiki/Commands-documentation). +You can see all commands and their usage [here in the wiki](https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/commands_doc.md). ## **Additional Information:** [![YouTube](https://img.shields.io/badge/YouTube-Tutorial%20section-red)](https://youtu.be/8J78rC9Z28U?t=339) Hey, if you like this project please consider donating a buck on my [PayPal!](https://paypal.me/3urobeat) diff --git a/advancedconfig.json b/advancedconfig.json index 2ae6a448..3ca44e4a 100644 --- a/advancedconfig.json +++ b/advancedconfig.json @@ -1,6 +1,6 @@ { "_disclaimer_": "This file includes only advanced settings! Please only use the 'config.json' for setup and ignore this file if you are new!", - "_help_": "Read the docs: https://github.com/3urobeat/steam-comment-service-bot/wiki/Advanced-Config-Documentation", + "_help_": "Read the docs: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/advancedconfig_doc.md", "disableAutoUpdate": false, "loginDelay": 2500, "loginTimeout": 60000, diff --git a/config.json b/config.json index 02907824..eef10589 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { - "_help_": "Read the docs: https://github.com/3urobeat/steam-comment-service-bot/wiki/Config-documentation", "commentdelay": 15000, + "_help_": "Read the docs: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/config_doc.md", "skipSteamGuard": false, "commentcooldown": 5, "botaccountcooldown": 10, diff --git a/customlang.json b/customlang.json index d158fbbc..a2624f23 100644 --- a/customlang.json +++ b/customlang.json @@ -1,3 +1,3 @@ { - "note": "Please read here on how to use this file: https://github.com/3urobeat/steam-comment-service-bot/wiki/How-to-add-custom-messages" + "note": "Please read here on how to use this file: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/customlang_doc.md" } \ No newline at end of file diff --git a/docs/wiki/changelogs/CHANGELOG_v2.13.md b/docs/wiki/changelogs/CHANGELOG_v2.13.md index 12795086..c5aa0b0f 100644 --- a/docs/wiki/changelogs/CHANGELOG_v2.13.md +++ b/docs/wiki/changelogs/CHANGELOG_v2.13.md @@ -10,6 +10,7 @@ - [2.13.3](#2.13.3) - [2.13.4](#2.13.4) - [2.13.5](#2.13.5) +- [2.13.6](#2.13.6)   @@ -398,4 +399,41 @@ Commit: [829c387](https://github.com/3urobeat/steam-comment-service-bot/commit/8 - Updated dependencies - Minor other changes +Note: The russian translation added by [@Blueberryy](https://github.com/Blueberryy) in #186 will be noted in Version 2.14.0 with the upcoming improved language system. + +Commit: [75779db](https://github.com/3urobeat/steam-comment-service-bot/commit/75779db) + +  + + + +## **2023-07-26, Version 2.13.6** +**Additions:** +- The !info command now logs amount of loaded plugins instead of maxComments & commentdelay settings +- Added support for reloading data from the disk using the !reload command + +**Fixes:** +- Fixed error on loading sharedfiles when breadcrumbs are incomplete +- Fixed bot waiting for user object of skipped accounts which aren't last to be populated +- Fixed soft-lock with infinite error spam caused by user object populated check if lastBot is undefined +- Fixed possibility of a duplicate handleExpiringTokens interval when DataManager's _importFromDisk() function is called multiple times + +**Changes:** +- Reworked how commands accept and use message sender IDs to greatly improve plugin support: + - Removed the steamID64 parameter and replaced it with resInfo.userID + - Commands do not (and must not) expect a userID property to be provided anymore + - Commands will now handle an unavailable default behavior when command is called from outside the Steam Chat using resInfo.fromSteamChat (e.g. !comment 5 commenting on the requester's profile) + - Added resInfo.ownerIDs to enable privilege checking when using command from outside the Steam Chat + - Added `resInfo` typedef to commandHandler +- Wiki: Added runCommand() example and added note about userID & ownerIDs parameters +- Increased next login attempt delay on login timeout to hopefully prevent further "Already logged on" errors +- Improved sharedfile type detection +- Improved !info & !help whitespace regex to only match spaces at the line start +- Attempted to align secondary values in !info command response to improve readability +- The !settings command will now show the currently loaded config instead of reading it from the disk +- Updated all command names to always have the primary one at first position +- Updated all old wiki links +- Updated dependencies +- Minor other changes + Commit: [](https://github.com/3urobeat/steam-comment-service-bot/commit/) \ No newline at end of file diff --git a/docs/wiki/creating_plugins.md b/docs/wiki/creating_plugins.md index 9b07ff4b..ae98c3fa 100644 --- a/docs/wiki/creating_plugins.md +++ b/docs/wiki/creating_plugins.md @@ -156,14 +156,36 @@ From there, every other module of the bot is accessible. Worth noting: ## **Command System** -The CommandHandler allows you to register and run commands. +The CommandHandler allows you to register new and run existing commands. Commands are the core functionalities that allow you to request comments, retrieve information and manage the bot. -Any command you register will instantly be available to all message handlers. +Any command you register will instantly be available to all message handlers (e.g. to other plugins as well). By default the bot has built-in Steam Chat message handling. You can implement your own message handler as well, allowing you to integrate all registered commands into other platforms. Take a look at the developer documentation (TODO) for more information about how to write a custom message handler. +Running existing commands is very easy. Let's take a look at how requesting 5 comments for 3urobeat would work: +```js +this.plugin.commandHandler.runCommand( + "comment", // Command Name + ["5", "3urobeat"], // Arguments Array + (x, y, msg) => { logger("info", "Comment Command said: " + msg) }, // Response Function + this, // The current context + { cmdprefix: "/", userID: "12345", ownerIDs: ["12345"] // The resInfo object +}) +``` + +This would cause the comment command to send 5 comments to the Steam Profile "3urobeat". +The comments are requested by the user "12345", who has owner privileges. +Every response from the command will be logged to the terminal output using the logger function. + +**Note:** +It is very important to provide a unique `userID` parameter in the `resInfo` object, which must not clash with steamID64s. +This ID is used to uniquely identify the requesting user, for example apply cooldown for them and check for owner privileges. +Failing to provide an ID will result in either unprotected or no access. + +This is also where the `ownerIDs` array comes into play: It allows you to overwrite the `ownerid` array set in the config to enable owner privilege checking when using commands from outside the Steam Chat. Very cool, right? +   diff --git a/docs/wiki/customlang_doc.md b/docs/wiki/customlang_doc.md index 8ed82b01..1d63ac3b 100644 --- a/docs/wiki/customlang_doc.md +++ b/docs/wiki/customlang_doc.md @@ -17,7 +17,7 @@ After you have done that add a colon behind your key and write your message in t Example of how your `customlang.json` could look like after changing the `useradded` message: ``` { - "note": "Please read here on how to use this file: https://github.com/3urobeat/steam-comment-service-bot/wiki/How-to-add-custom-messages", + "note": "Please read here on how to use this file: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/customlang_doc.md", "useradded": "You will recieve this message if you add me and it was modified using the customlang file!", } ``` diff --git a/package-lock.json b/package-lock.json index db321084..ea1dd300 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,31 +1,32 @@ { "name": "steam-comment-service-bot", - "version": "2.13.5", + "version": "2.13.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "steam-comment-service-bot", - "version": "2.13.5", + "version": "2.13.6", "license": "GPL-3.0", "dependencies": { "@seald-io/nedb": "^4.0.2", + "@types/tail": "^2.2.1", "download": "^8.0.0", "htmlparser2": "^9.0.0", "https": "^1.0.0", "output-logger": "^2.3.7", "request": "^2.88.2", - "steam-comment-bot-rest": "^1.0.4", + "steam-comment-bot-rest": "^1.1.0", "steam-comment-bot-webserver": "file:plugins/steam-comment-bot-webserver-1.0.0.tgz", - "steam-session": "^1.2.4", - "steam-user": "^4.28.9", + "steam-session": "^1.2.5", + "steam-user": "^4.29.1", "steamcommunity": "^3.46.1", "steamid": "^2.0.0", "steamid-resolver": "^1.3.3" }, "devDependencies": { - "eslint": "^8.44.0", - "eslint-plugin-jsdoc": "^46.4.3", + "eslint": "^8.45.0", + "eslint-plugin-jsdoc": "^46.4.4", "tsd-jsdoc": "^2.5.0" } }, @@ -118,9 +119,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -432,9 +433,9 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "20.4.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.1.tgz", - "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==" + "version": "20.4.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz", + "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==" }, "node_modules/@types/qs": { "version": "6.9.7", @@ -515,6 +516,11 @@ "resolved": "https://registry.npmjs.org/@types/steamid/-/steamid-2.0.1.tgz", "integrity": "sha512-B8sy9wfOOeh+yNrFFzyghCQ1n3rssYbTvZD2kXzuy3HSJEK7TVeYnrpXG/0dOKrq1VU8D7cWORgszsdqKBZnVg==" }, + "node_modules/@types/tail": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/tail/-/tail-2.2.1.tgz", + "integrity": "sha512-j75Gs5MiIpNR14wztQ4vtViUqxZi+lcgflyXC7P9iMgNnMab7XcV5p+2590IO3njsWWn5l8C+55ILk2CDDyaHg==" + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -1239,7 +1245,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1617,6 +1622,11 @@ "node": ">=10" } }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, "node_modules/duplexer3": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", @@ -1717,9 +1727,9 @@ } }, "node_modules/eslint": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", - "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", + "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -1747,7 +1757,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -1759,7 +1768,6 @@ "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -1773,9 +1781,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "46.4.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.4.3.tgz", - "integrity": "sha512-Prc7ol+vCIghPeECpwZq5+P+VZfoi87suywvbYCiCnkI1kTmVSdcOC2M8mioglWxBbd28wbb1OVjg/8OzGzatA==", + "version": "46.4.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.4.4.tgz", + "integrity": "sha512-D8TGPOkq3bnzmYmA7Q6jdsW+Slx7CunhJk1tlouVq6wJjlP1p6eigZPvxFn7aufud/D66xBsNVMhkDQEuqumMg==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.39.4", @@ -1796,9 +1804,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", + "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1824,9 +1832,9 @@ } }, "node_modules/espree": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", - "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { "acorn": "^8.9.0", @@ -1890,6 +1898,20 @@ "node": ">= 0.6" } }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -2048,9 +2070,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.6.tgz", + "integrity": "sha512-Xo1qV++h/Y3Ng8dphjahnYe+rGHaaNdsYOBWL9Y9GCPKpNKilJtilvWkLcI9f9X2DoKTLsZsGYAls5+JL5jfLA==", "funding": [ { "type": "paypal", @@ -2274,6 +2296,11 @@ "node": ">= 0.6" } }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==" + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -2848,15 +2875,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -2878,8 +2901,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isstream": { "version": "0.1.2", @@ -3033,9 +3055,9 @@ } }, "node_modules/kvparser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kvparser/-/kvparser-1.0.1.tgz", - "integrity": "sha512-Hih4Cw2gQFMAUB4gbKHTgRr+9ClDGQHbu4q05NiTmgYswt1AaOehvLe0l5AntTmXZS3j2Uw9FnEkLqIG8CuVFA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/kvparser/-/kvparser-1.0.2.tgz", + "integrity": "sha512-5P/5qpTAHjVYWqcI55B3yQwSY2FUrYYrJj5i65V1Wmg7/4W4OnBcaodaEvLyVuugeOnS+BAaKm9LbPazGJcRyA==", "engines": { "node": ">=4.0.0" } @@ -3205,13 +3227,18 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "bin": { "semver": "bin/semver" } }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==" + }, "node_modules/markdown-it": { "version": "12.3.2", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", @@ -3381,6 +3408,11 @@ "node": ">=0.4.0" } }, + "node_modules/node-cleanup": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", + "integrity": "sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==" + }, "node_modules/normalize-url": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", @@ -3589,7 +3621,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -3599,6 +3630,14 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dependencies": { + "through": "~2.3" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -3709,6 +3748,20 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -4052,7 +4105,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4064,7 +4116,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -4209,6 +4260,17 @@ "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -4308,9 +4370,9 @@ } }, "node_modules/steam-comment-bot-rest": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/steam-comment-bot-rest/-/steam-comment-bot-rest-1.0.4.tgz", - "integrity": "sha512-WkZIsRcN44E4TBgki7DSurvs3xt4N3hZuKZqookst++o8aXrzDLw7L6B8QTyXoVMNakjYQBClGZGQTvlpXSWsg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/steam-comment-bot-rest/-/steam-comment-bot-rest-1.1.0.tgz", + "integrity": "sha512-AXo6H2tyzaxEms6Aqul9Toe6CsMbFzchSGXHhb4mWTaAsmH98dP0z7ZRGnUufyZ6XcmrH8pnnuBqczfVPFC8Og==", "hasInstallScript": true, "dependencies": { "@types/axios": "^0.14.0", @@ -4323,6 +4385,8 @@ "express": "^4.18.2", "fast-xml-parser": "^4.2.2", "socket.io": "^4.6.1", + "tail": "^2.2.6", + "tsc-watch": "^6.0.4", "typescript": "^5.0.4" } }, @@ -4336,9 +4400,9 @@ } }, "node_modules/steam-session": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/steam-session/-/steam-session-1.2.4.tgz", - "integrity": "sha512-jmpGAygK6dGy/53QotsaBiw6aGCS+9KiM1XxTZ/J/7H1T4+jhszysW22dWeZ8QjEnSec7BsrDvpWEgBzTsqQkw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/steam-session/-/steam-session-1.2.5.tgz", + "integrity": "sha512-GeIvyH9jmgMBmIBXmPpdJRLyR3o4gKa98vwxzDW5A7WrDLAF60O38mQ/MV4EhaRUmZS1HMMMTo1cEk+AKEqjjg==", "dependencies": { "@doctormckay/stdlib": "^2.4.1", "debug": "^4.3.4", @@ -4363,9 +4427,9 @@ } }, "node_modules/steam-user": { - "version": "4.28.9", - "resolved": "https://registry.npmjs.org/steam-user/-/steam-user-4.28.9.tgz", - "integrity": "sha512-95M5ALWtVMPWKQKD9xW/bI9a9GVcf3bQcHsR0FEpEA/yGiQJnBSUS2TxBK0XvUTEsYA3bhNu7RAUae7v9eN35Q==", + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/steam-user/-/steam-user-4.29.1.tgz", + "integrity": "sha512-J4Z3QZRvB6ETRMe/oSiW3byLdqixUBv2UrBJ9cGeyFCuxl0z69zuarK/IbCFxDeCM2t8zLQwY9l0z94ofzF15w==", "dependencies": { "@bbob/parser": "^2.2.0", "@doctormckay/stdlib": "^1.16.0", @@ -4379,7 +4443,7 @@ "protobufjs": "^6.11.3", "socks-proxy-agent": "^7.0.0", "steam-appticket": "^1.0.1", - "steam-session": "^1.1.0", + "steam-session": "^1.2.5", "steam-totp": "^2.0.1", "steamid": "^1.1.0", "websocket13": "^3.0.1" @@ -4502,6 +4566,14 @@ "node": ">=4.0.0" } }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dependencies": { + "duplexer": "~0.1.1" + } + }, "node_modules/strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -4523,6 +4595,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4598,6 +4678,14 @@ "dev": true, "peer": true }, + "node_modules/tail": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/tail/-/tail-2.2.6.tgz", + "integrity": "sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", @@ -4683,6 +4771,26 @@ "node": ">=0.8.0" } }, + "node_modules/tsc-watch": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-6.0.4.tgz", + "integrity": "sha512-cHvbvhjO86w2aGlaHgSCeQRl+Aqw6X6XN4sQMPZKF88GoP30O+oTuh5lRIJr5pgFWrRpF1AgXnJJ2DoFEIPHyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.3.1" + }, + "bin": { + "tsc-watch": "dist/lib/tsc-watch.js" + }, + "engines": { + "node": ">=12.12.0" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/tsd-jsdoc": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tsd-jsdoc/-/tsd-jsdoc-2.5.0.tgz", @@ -4925,7 +5033,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -4937,16 +5044,15 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index addca217..e4da09af 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,20 @@ { "name": "steam-comment-service-bot", - "version": "2.13.5", + "version": "2.13.6", "description": "Request a ton of steam profile/group comments from a bot network with just one command!", "main": "start.js", "dependencies": { + "@types/tail": "^2.2.1", "@seald-io/nedb": "^4.0.2", "download": "^8.0.0", "htmlparser2": "^9.0.0", "https": "^1.0.0", "output-logger": "^2.3.7", "request": "^2.88.2", - "steam-comment-bot-rest": "^1.0.4", + "steam-comment-bot-rest": "^1.1.0", "steam-comment-bot-webserver": "file:plugins/steam-comment-bot-webserver-1.0.0.tgz", - "steam-session": "^1.2.4", - "steam-user": "^4.28.9", + "steam-session": "^1.2.5", + "steam-user": "^4.29.1", "steamcommunity": "^3.46.1", "steamid": "^2.0.0", "steamid-resolver": "^1.3.3" @@ -30,8 +31,8 @@ "homepage": "https://github.com/3urobeat", "repository": "https://github.com/3urobeat/steam-comment-service-bot", "devDependencies": { - "eslint": "^8.44.0", - "eslint-plugin-jsdoc": "^46.4.3", + "eslint": "^8.45.0", + "eslint-plugin-jsdoc": "^46.4.4", "tsd-jsdoc": "^2.5.0" }, "types": "./types/types.d.ts" diff --git a/proxies.txt b/proxies.txt index 300aa211..b43e2f52 100755 --- a/proxies.txt +++ b/proxies.txt @@ -1 +1 @@ -//Comment: This file is used to provide proxies to spread your accounts over multiple IPs. Read the instructions here: https://github.com/3urobeat/steam-comment-service-bot/wiki/Adding-proxies \ No newline at end of file +//Comment: This file is used to provide proxies to spread your accounts over multiple IPs. Read the instructions here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/adding_proxies.md \ No newline at end of file diff --git a/src/bot/bot.js b/src/bot/bot.js index dbc7aa6f..77ae7fbb 100644 --- a/src/bot/bot.js +++ b/src/bot/bot.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 04.07.2023 19:05:34 + * Last Modified: 08.07.2023 00:41:25 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -195,7 +195,7 @@ Bot.prototype.handleMissingGameLicenses = function() {}; /** * Our commandHandler respondModule implementation - Sends a message to a Steam user * @param {object} _this The Bot object context - * @param {object} resInfo Object containing information passed to command by friendMessage event + * @param {import("../commands/commandHandler.js").resInfo} resInfo Object containing information passed to command by friendMessage event * @param {string} txt The text to send * @param {boolean} retry Internal: true if this message called itself again to send failure message * @param {number} part Internal: Index of which part to send for messages larger than 750 chars diff --git a/src/bot/events/friendMessage.js b/src/bot/events/friendMessage.js index 3d50573d..569b5353 100644 --- a/src/bot/events/friendMessage.js +++ b/src/bot/events/friendMessage.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 29.06.2023 22:35:03 + * Last Modified: 10.07.2023 09:36:42 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -30,7 +30,7 @@ Bot.prototype._attachSteamFriendMessageEvent = function() { let steamID = msg.steamid_friend; let steamID64 = new SteamID(String(steamID)).getSteamID64(); - let resInfo = { steamID64: steamID64, cmdprefix: "!" }; // Object required for sendChatMessage(), our commandHandler respondModule implementation + let resInfo = { userID: steamID64, cmdprefix: "!", fromSteamChat: true }; // Object required for sendChatMessage(), our commandHandler respondModule implementation // Check if another friendMessage handler is currently active if (this.friendMessageBlock.includes(steamID64)) return logger("debug", `[${this.logPrefix}] Ignoring friendMessage event from ${steamID64} as user is on friendMessageBlock list.`); @@ -90,7 +90,7 @@ Bot.prototype._attachSteamFriendMessageEvent = function() { let cont = message.slice(1).split(" "); // Remove prefix and split let args = cont.slice(1); // Remove cmd name to only get arguments - let success = this.controller.commandHandler.runCommand(cont[0].toLowerCase(), args, steamID64, this.sendChatMessage, this, resInfo); + let success = this.controller.commandHandler.runCommand(cont[0].toLowerCase(), args, this.sendChatMessage, this, resInfo); if (!success) this.sendChatMessage(this, resInfo, this.controller.data.lang.commandnotfound.replace(/cmdprefix/g, resInfo.cmdprefix)); // Send cmd not found msg if runCommand() returned false }); diff --git a/src/bot/events/relationship.js b/src/bot/events/relationship.js index 79774e76..8f98e8aa 100644 --- a/src/bot/events/relationship.js +++ b/src/bot/events/relationship.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 29.06.2023 22:35:03 + * Last Modified: 10.07.2023 09:33:09 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -39,7 +39,7 @@ Bot.prototype._attachSteamFriendRelationshipEvent = function() { // Log message and send welcome message logger("info", `[${this.logPrefix}] Added User: ` + steamID64); - if (this.index == 0) this.sendChatMessage(this, { steamID64: steamID64 }, this.controller.data.lang.useradded.replace(/cmdprefix/g, "!")); + if (this.index == 0) this.sendChatMessage(this, { userID: steamID64 }, this.controller.data.lang.useradded.replace(/cmdprefix/g, "!")); // Add user to lastcomment database diff --git a/src/bot/events/webSession.js b/src/bot/events/webSession.js index d4cb514a..a74bd487 100644 --- a/src/bot/events/webSession.js +++ b/src/bot/events/webSession.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 29.06.2023 22:35:03 + * Last Modified: 10.07.2023 09:33:16 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -64,7 +64,7 @@ Bot.prototype._attachSteamWebSessionEvent = function() { logger("info", `[${this.logPrefix}] Added user while I was offline! User: ` + thisfriend); setTimeout(() => { - if (this.index == 0) this.sendChatMessage(this, { steamID64: String(thisfriend) }, this.controller.data.lang.useradded.replace(/cmdprefix/g, "!")); + if (this.index == 0) this.sendChatMessage(this, { userID: String(thisfriend) }, this.controller.data.lang.useradded.replace(/cmdprefix/g, "!")); else logger("debug", "Not sending useradded message because this isn't the main user..."); }, 1000 * processedFriendRequests); diff --git a/src/bot/helpers/checkMsgBlock.js b/src/bot/helpers/checkMsgBlock.js index 195900cb..eefb2eef 100644 --- a/src/bot/helpers/checkMsgBlock.js +++ b/src/bot/helpers/checkMsgBlock.js @@ -4,7 +4,7 @@ * Created Date: 20.03.2023 12:46:47 * Author: 3urobeat * - * Last Modified: 04.07.2023 17:55:45 + * Last Modified: 10.07.2023 13:05:31 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -40,7 +40,7 @@ Bot.prototype.checkMsgBlock = function(steamID64, message) { if (lastmessage[steamID64] && lastmessage[steamID64][0] + this.controller.data.advancedconfig.commandCooldown > Date.now() && lastmessage[steamID64][1] > 5) return true; // Just don't respond if (lastmessage[steamID64] && lastmessage[steamID64][0] + this.controller.data.advancedconfig.commandCooldown > Date.now() && lastmessage[steamID64][1] > 4) { // Inform the user about the cooldown - this.sendChatMessage({ steamID: steamID64, prefix: "/me" }, this.controller.data.lang.userspamblock); + this.sendChatMessage({ userID: steamID64, prefix: "/me" }, this.controller.data.lang.userspamblock); logger("info", `${steamID64} has been blocked for 90 seconds for spamming.`); lastmessage[steamID64][0] += 90000; @@ -49,12 +49,12 @@ Bot.prototype.checkMsgBlock = function(steamID64, message) { return true; } - if (!this.controller.data.cachefile.ownerid.includes(steamID64)) lastmessage[steamID64][1]++; // Push new message to array if user isn't an owner + if (!this.controller.data.cachefile.ownerid.includes(steamID64)) lastmessage[steamID64][1]++; // Push new message to array if user isn't an owner (this function is only called for Steam Chat messages, not for plugins) // Deny non-friends the use of any command if (this.user.myFriends[steamID64] != 3) { - this.sendChatMessage({ steamID: steamID64, prefix: "/me" }, this.controller.data.lang.usernotfriend); + this.sendChatMessage({ userID: steamID64, prefix: "/me" }, this.controller.data.lang.usernotfriend); return true; } diff --git a/src/bot/helpers/handleLoginTimeout.js b/src/bot/helpers/handleLoginTimeout.js index 658c47cb..626fb72b 100644 --- a/src/bot/helpers/handleLoginTimeout.js +++ b/src/bot/helpers/handleLoginTimeout.js @@ -4,7 +4,7 @@ * Created Date: 03.11.2022 12:27:46 * Author: 3urobeat * - * Last Modified: 29.06.2023 22:35:03 + * Last Modified: 24.07.2023 19:37:03 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -69,7 +69,7 @@ Bot.prototype.handleLoginTimeout = function() { this.user.logOff(); // Call logOff() just to be sure if (this.sessionHandler.session) this.sessionHandler.session.cancelLoginAttempt(); // TODO: This might cause an error as idk if we are polling. Maybe use the timeout event of steam-session - setTimeout(() => this._loginToSteam(), 2500); // Attempt another login as we still have attempts left. Delay it a bit to avoid any "Already logged on" errors + setTimeout(() => this._loginToSteam(), 5000); // Attempt another login as we still have attempts left. Delay it a bit to avoid any "Already logged on" errors, steam-user uses a 4s timeout before setting steamID = null } }, this.data.advancedconfig.loginTimeout); diff --git a/src/bot/helpers/steamChatInteraction.js b/src/bot/helpers/steamChatInteraction.js index 7b052a80..866e30de 100644 --- a/src/bot/helpers/steamChatInteraction.js +++ b/src/bot/helpers/steamChatInteraction.js @@ -4,7 +4,7 @@ * Created Date: 01.04.2023 21:09:00 * Author: 3urobeat * - * Last Modified: 04.07.2023 17:51:07 + * Last Modified: 10.07.2023 09:29:15 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -29,7 +29,7 @@ const { cutStringsIntelligently } = require("../../controller/helpers/misc.js"); /** * Our commandHandler respondModule implementation - Sends a message to a Steam user * @param {Bot} _this The Bot object context - * @param {object} resInfo Object containing information passed to command by friendMessage event. Supported by this handler: prefix, charLimit, cutChars + * @param {import("../../commands/commandHandler").resInfo} resInfo Object containing information passed to command by friendMessage event. Supported by this handler: prefix, charLimit, cutChars * @param {string} txt The text to send * @param {number} retry Internal: Counter of retries for this part if sending failed * @param {number} part Internal: Index of which part to send for messages larger than charLimit chars @@ -39,7 +39,7 @@ Bot.prototype.sendChatMessage = function(_this, resInfo, txt, retry = 0, part = if (!txt) return logger("warn", "sendChatMessage() was called without any message content! Ignoring call..."); if (typeof txt !== "string") return logger("warn", "sendChatMessage() was called with txt that isn't a string! Ignoring call..."); - let steamID64 = resInfo.steamID64; + let steamID64 = resInfo.userID; // Allow resInfo to overwrite char limit of 750 chars let limit = 750; diff --git a/src/commands/commandHandler.js b/src/commands/commandHandler.js index a7c05eea..3d4c1a42 100644 --- a/src/commands/commandHandler.js +++ b/src/commands/commandHandler.js @@ -4,7 +4,7 @@ * Created Date: 01.04.2023 21:54:21 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:52:46 + * Last Modified: 10.07.2023 21:25:57 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -157,7 +157,7 @@ CommandHandler.prototype.unregisterCommand = function(commandName) { let thisCmd = this.commands.find(e => e.names.includes(commandName)); if (!thisCmd) { - logger("warn", `CommandHandler runCommand(): Command '${commandName}' was not found!`); + logger("warn", `CommandHandler unregisterCommand(): Command '${commandName}' was not found!`); return false; } @@ -172,17 +172,29 @@ CommandHandler.prototype.unregisterCommand = function(commandName) { }; +/** + * @typedef resInfo Documentation of the default/commonly used content the resInfo object can/should contain + * @type {object} + * @property {string} [cmdprefix] Prefix your command execution handler uses. This will be used in response messages referencing commands. Default: ! + * @property {string} userID ID of the user who executed this command. Will be used for command default behavior (e.g. commenting on the requester's profile), to check for owner privileges, apply cooldowns and maybe your respondModule implementation for responding. Strongly advised to include. + * @property {Array.} [ownerIDs] Can be provided to overwrite `config.ownerid` for owner privilege checks. Useful if you are implementing a different platform and so `userID` won't be a steamID64 (e.g. discord) + * @property {number} [charLimit] Supported by the Steam Chat Message handler: Overwrites the default index from which response messages will be cut up into parts + * @property {Array.} [cutChars] Custom chars to search after for cutting string in parts to overwrite cutStringsIntelligently's default: [" ", "\n", "\r"] + * @property {boolean} [fromSteamChat] Set to true if your command handler is receiving messages from the Steam Chat and `userID` is therefore a `steamID64`. Will be used to enable command default behavior (e.g. commenting on the requester's profile) + * @property {string} [prefix] Do not provide this argument, you'll receive it from commands: Steam Chat Message prefixes like /me. Can be ignored or translated to similar prefixes your platform might support + */ + + /** * Finds a loaded command by name and runs it * @param {string} name The name of the command * @param {Array} args Array of arguments that will be passed to the command - * @param {number} steamID64 SteamID64 of the requesting user which is used to check for ownerOnly and will be passed to the command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. - * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). Please also include a "cmdprefix" key & value pair if your command handler uses a prefix other than "!". + * @param {object} context The context (`this.`) of the object calling this command. Will be passed to respondModule() as first parameter to make working in this function easier. + * @param {resInfo} resInfo Object containing additional information * @returns {boolean} `true` if command was found, `false` if not */ -CommandHandler.prototype.runCommand = function(name, args, steamID64, respondModule, context, resInfo) { +CommandHandler.prototype.runCommand = function(name, args, respondModule, context, resInfo) { // Iterate through all command objects in commands array and check if name is included in names array of each command. let thisCmd = this.commands.find(e => e.names.includes(name)); @@ -198,8 +210,17 @@ CommandHandler.prototype.runCommand = function(name, args, steamID64, respondMod thisCmd.ownersOnly = true; } - // If command is ownersOnly, check if user is included in owners array. If not, send error msg and return true to avoid caller sending a not found msg. - if (thisCmd.ownersOnly && !this.data.cachefile.ownerid.includes(steamID64)) { + // Display warning if a non Steam Chat userID was provided without a custom owner ID array. Permit usage of owner only parameters of non owner only commands. + if (resInfo.userID && !resInfo.fromSteamChat && (!resInfo.ownerIDs || resInfo.ownerIDs.length == 0)) { + logger("warn", `CommandHandler runCommand(): Command '${name}' was called with a non-SteamID without a custom ownerIDs array! *${logger.colors.fgred}This will either BYPASS or BLOCK all owner checks, leading to unprotected or no access!${logger.colors.reset}*`); + } + + // Get the correct ownerid array for this request + let owners = this.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + + // If command is ownersOnly, check if user is included in owners array. If not, send error msg and return true to avoid caller sending a not found msg + if (thisCmd.ownersOnly && !owners.includes(resInfo.userID)) { // If no userID was provided this check will also trigger respondModule(context, resInfo, this.data.lang.commandowneronly); return true; } @@ -208,7 +229,7 @@ CommandHandler.prototype.runCommand = function(name, args, steamID64, respondMod if (!resInfo || !resInfo.cmdprefix) resInfo["cmdprefix"] = "!"; // Run command if one was found - thisCmd.run(this, args, steamID64, respondModule, context, resInfo); + thisCmd.run(this, args, respondModule, context, resInfo); // Return true if command was found return true; diff --git a/src/commands/core/block.js b/src/commands/core/block.js index be315c5d..2d090cb2 100644 --- a/src/commands/core/block.js +++ b/src/commands/core/block.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:51:40 + * Last Modified: 10.07.2023 12:58:49 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -38,21 +38,24 @@ module.exports.block = { * The block command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained if (!args[0]) return respond(commandHandler.data.lang.invalidprofileid); + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + commandHandler.controller.handleSteamIdResolving(args[0], "profile", (err, res) => { if (err) return respond(commandHandler.data.lang.invalidprofileid + "\n\nError: " + err); - if (commandHandler.data.cachefile.ownerid.includes(res)) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.idisownererror); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained + if (owners.includes(res)) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.idisownererror); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained commandHandler.controller.getBots().forEach((e, i) => { e.user.blockUser(new SteamID(res), (err) => { if (err) logger("error", `[Bot ${i}] Error blocking user ${res}: ${err}`); }); @@ -83,12 +86,11 @@ module.exports.unblock = { * The unblock command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained diff --git a/src/commands/core/comment.js b/src/commands/core/comment.js index edde4058..7ba9ebb4 100644 --- a/src/commands/core/comment.js +++ b/src/commands/core/comment.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:51:55 + * Last Modified: 24.07.2023 19:41:59 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -56,24 +56,34 @@ module.exports.comment = { * The comment command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: async (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: async (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call - let requesterSteamID64 = steamID64; + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + + let requesterSteamID64 = resInfo.userID; let receiverSteamID64 = requesterSteamID64; - let ownercheck = commandHandler.data.cachefile.ownerid.includes(requesterSteamID64); + let ownercheck = owners.includes(requesterSteamID64); - /* --------- Check for disabled comment cmd or if update is queued --------- */ + /* --------- Various checks --------- */ + if (!resInfo.userID) { + respond(commandHandler.data.lang.nouserid); // Reject usage of command without an userID to avoid cooldown bypass + return logger("err", "The comment command was called without resInfo.userID! Blocking the command as I'm unable to apply cooldowns, which is required for this command!"); + } if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained if (commandHandler.controller.info.activeLogin) return respond(commandHandler.data.lang.activerelog); // Bot is waiting for relog if (commandHandler.data.config.maxComments == 0 && !ownercheck) return respond(commandHandler.data.lang.commandowneronly); // Comment command is restricted to owners only + // Check for no id param as default behavior is unavailable when calling from outside the Steam Chat + if (!resInfo.fromSteamChat && !args[1]) return respond(commandHandler.data.lang.noidparam); + /* --------- Calculate maxRequestAmount and get arguments from comment request --------- */ let { maxRequestAmount, numberOfComments, profileID, idType, quotesArr } = await getCommentArgs(commandHandler, args, requesterSteamID64, resInfo, respond); @@ -187,7 +197,7 @@ module.exports.comment = { if (idType == "profile") { commandHandler.controller.main.community.getSteamUser(new SteamID(receiverSteamID64), (err, user) => { if (err) { - logger("warn", `[Main] Failed to check if ${steamID64} is private: ${err}\n Trying to comment anyway and hoping no error occurs...`); // This can happen sometimes and most of the times commenting will still work + logger("warn", `[Main] Failed to check if ${receiverSteamID64} is private: ${err}\n Trying to comment anyway and hoping no error occurs...`); // This can happen sometimes and most of the times commenting will still work } else { logger("debug", "Successfully checked privacyState of receiving user: " + user.privacyState); @@ -216,7 +226,7 @@ module.exports.comment = { /** * Internal: Do the actual commenting, activeRequests entry with all relevant information was processed by the comment command function above. * @param {CommandHandler} commandHandler The commandHandler object - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). * @param {function(string): void} respond The shortened respondModule call * @param {Function} postComment The correct postComment function for this idType. Context from the correct bot account is being applied later. * @param {object} commentArgs All arguments this postComment function needs, without callback. It will be applied and a callback added as last param. Include a key called "quote" to dynamically replace it with a random quote. @@ -358,7 +368,7 @@ function comment(commandHandler, resInfo, respond, postComment, commentArgs, rec let failedcmdreference = ""; if (Object.keys(commandHandler.controller.activeRequests[receiverSteamID64].failed).length > 0) { - failedcmdreference = `\nTo get detailed information why which comment failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/wiki/Errors,-FAQ-&-Common-problems`; + failedcmdreference = `\nTo get detailed information why which comment failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/errors_doc.md`; } // Send finished message diff --git a/src/commands/core/favorite.js b/src/commands/core/favorite.js index b5640242..6ef4d24b 100644 --- a/src/commands/core/favorite.js +++ b/src/commands/core/favorite.js @@ -4,7 +4,7 @@ * Created Date: 02.06.2023 13:23:01 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:53:13 + * Last Modified: 24.07.2023 19:42:37 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -47,19 +47,26 @@ module.exports.favorite = { * The favorite command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: async (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: async (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call - let requesterSteamID64 = steamID64; - let ownercheck = commandHandler.data.cachefile.ownerid.includes(requesterSteamID64); + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + let requesterSteamID64 = resInfo.userID; + let ownercheck = owners.includes(requesterSteamID64); - /* --------- Check for disabled cmd or if update is queued --------- */ + + /* --------- Various checks --------- */ + if (!resInfo.userID) { + respond(commandHandler.data.lang.nouserid); // Reject usage of command without an userID to avoid cooldown bypass + return logger("err", "The favorite command was called without resInfo.userID! Blocking the command as I'm unable to apply cooldowns, which is required for this command!"); + } if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained if (commandHandler.controller.info.activeLogin) return respond(commandHandler.data.lang.activerelog); // Bot is waiting for relog if (commandHandler.data.config.maxComments == 0 && !ownercheck) return respond(commandHandler.data.lang.commandowneronly); // Command is restricted to owners only @@ -188,7 +195,7 @@ module.exports.favorite = { let failedcmdreference = ""; if (Object.keys(commandHandler.controller.activeRequests[id].failed).length > 0) { - failedcmdreference = `\nTo get detailed information why which request failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/wiki/Errors,-FAQ-&-Common-problems`; + failedcmdreference = `\nTo get detailed information why which request failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/errors_doc.md`; } // Send finished message @@ -230,19 +237,26 @@ module.exports.unfavorite = { * The unfavorite command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: async (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: async (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call - let requesterSteamID64 = steamID64; - let ownercheck = commandHandler.data.cachefile.ownerid.includes(requesterSteamID64); + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + let requesterSteamID64 = resInfo.userID; + let ownercheck = owners.includes(requesterSteamID64); - /* --------- Check for disabled cmd or if update is queued --------- */ + + /* --------- Various checks --------- */ + if (!resInfo.userID) { + respond(commandHandler.data.lang.nouserid); // Reject usage of command without an userID to avoid cooldown bypass + return logger("err", "The unfavorite command was called without resInfo.userID! Blocking the command as I'm unable to apply cooldowns, which is required for this command!"); + } if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained if (commandHandler.controller.info.activeLogin) return respond(commandHandler.data.lang.activerelog); // Bot is waiting for relog if (commandHandler.data.config.maxComments == 0 && !ownercheck) return respond(commandHandler.data.lang.commandowneronly); // Command is restricted to owners only @@ -371,7 +385,7 @@ module.exports.unfavorite = { let failedcmdreference = ""; if (Object.keys(commandHandler.controller.activeRequests[id].failed).length > 0) { - failedcmdreference = `\nTo get detailed information why which request failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/wiki/Errors,-FAQ-&-Common-problems`; + failedcmdreference = `\nTo get detailed information why which request failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/errors_doc.md`; } // Send finished message diff --git a/src/commands/core/friend.js b/src/commands/core/friend.js index f4dc85df..acfb2d4e 100644 --- a/src/commands/core/friend.js +++ b/src/commands/core/friend.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:57:36 + * Last Modified: 10.07.2023 13:02:11 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -38,12 +38,11 @@ module.exports.addFriend = { * The addFriend command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained @@ -107,35 +106,41 @@ module.exports.unfriend = { * The unfriend command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained - // Unfriend message sender with all bot accounts if no id was provided - if (!args[0]) { + // Check for no args again as the default behavior from above might be unavailable when calling from outside of the Steam Chat + if (!args[0] && !resInfo.fromSteamChat) return respond(commandHandler.data.lang.noidparam); + + // Unfriend message sender with all bot accounts if no id was provided and the command was called from the steam chat + if (!args[0] && resInfo.userID && resInfo.fromSteamChat) { respond(commandHandler.data.lang.unfriendcmdsuccess); - logger("info", `Removing friend ${steamID64} from all bot accounts...`); + logger("info", `Removing friend ${resInfo.userID} from all bot accounts...`); commandHandler.controller.getBots().forEach((e, i) => { setTimeout(() => { - e.user.removeFriend(new SteamID(steamID64)); + e.user.removeFriend(new SteamID(resInfo.userID)); }, 1000 * i); }); } else { + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + // Unfriending a specific user is owner only - if (!commandHandler.data.cachefile.ownerid.includes(steamID64)) return respond(commandHandler.data.lang.commandowneronly); + if (!owners.includes(resInfo.userID)) return respond(commandHandler.data.lang.commandowneronly); commandHandler.controller.handleSteamIdResolving(args[0], "profile", (err, res) => { if (err) return respond(commandHandler.data.lang.invalidprofileid + "\n\nError: " + err); - if (commandHandler.data.cachefile.ownerid.includes(res)) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.idisownererror); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained + if (commandHandler.data.cachefile.ownerid.includes(res)) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.idisownererror); // Check for the "original" ownerid array here, we don't care about non Steam IDs commandHandler.controller.getBots().forEach((e, i) => { setTimeout(() => { @@ -169,12 +174,11 @@ module.exports.unfriendall = { * The unfriendall command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained @@ -201,7 +205,7 @@ module.exports.unfriendall = { setTimeout(() => { let friendSteamID = new SteamID(String(friend)); - if (!commandHandler.data.cachefile.ownerid.includes(friend)) { + if (!commandHandler.data.cachefile.ownerid.includes(friend)) { // Check for the "original" ownerid array here, we don't care about non Steam IDs logger("info", `Removing friend ${friendSteamID.getSteamID64()} from all bot accounts...`, false, false, logger.animation("loading")); commandHandler.controller.getBots()[i].user.removeFriend(friendSteamID); } else { diff --git a/src/commands/core/general.js b/src/commands/core/general.js index f730b959..c4d6e16e 100644 --- a/src/commands/core/general.js +++ b/src/commands/core/general.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 11:25:17 + * Last Modified: 26.07.2023 16:03:51 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -23,7 +23,7 @@ const CommandHandler = require("../commandHandler.js"); // eslint-disable-line module.exports.help = { - names: ["h", "help", "commands"], + names: ["help", "h", "commands"], description: "Returns a list of commands available to you and a link to the commands documentation wiki page", args: [], ownersOnly: false, @@ -32,18 +32,21 @@ module.exports.help = { * The help command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + // Construct comment text for owner or non owner let commentText; - if (commandHandler.data.cachefile.ownerid.includes(steamID64)) { + if (owners.includes(resInfo.userID)) { if (commandHandler.controller.getBots().length > 1 || commandHandler.data.config.maxOwnerComments) commentText = `'${resInfo.cmdprefix}comment (amount/"all") [profileid] [custom, quotes]' - ${commandHandler.data.lang.helpcommentowner1.replace("maxOwnerComments", commandHandler.data.config.maxOwnerComments)}`; else commentText = `'${resInfo.cmdprefix}comment ("1") [profileid] [custom, quotes]' - ${commandHandler.data.lang.helpcommentowner2}`; } else { @@ -67,8 +70,8 @@ module.exports.help = { '${resInfo.cmdprefix}owner' - ${commandHandler.data.lang.helpowner} ${yourgroupText} - ${commandHandler.data.lang.helpreadothercmdshere} ' https://github.com/3urobeat/steam-comment-service-bot/wiki/Commands-documentation ' - `.replace(/ {4}/gm, "")); // Remove all the whitespaces that are added by the proper code indentation here + ${commandHandler.data.lang.helpreadothercmdshere} ' https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/commands_doc.md ' + `.replace(/^( {4})+/gm, "")); // Remove all the whitespaces that are added by the proper code indentation here } }; @@ -83,15 +86,18 @@ module.exports.info = { * The info command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call - commandHandler.data.lastCommentDB.findOne({ id: steamID64 }, async (err, doc) => { + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + + commandHandler.data.lastCommentDB.findOne({ id: resInfo.userID }, async (err, doc) => { let lastReq = await commandHandler.data.getLastCommentRequest(); let userLastReq = "Never"; @@ -101,16 +107,16 @@ module.exports.info = { respond(` -----------------------------------~~~~~------------------------------------ >   ${commandHandler.data.datafile.mestr}'s Comment Bot [Version ${commandHandler.data.datafile.versionstr}] (More info: ${resInfo.cmdprefix}about) - >   Uptime: ${Number(Math.round(((new Date() - commandHandler.controller.info.bootStartTimestamp) / 3600000)+"e"+2)+"e-"+2)} hours | Branch: ${commandHandler.data.datafile.branch} - >   'node.js' Version: ${process.version} | RAM Usage (RSS): ${Math.round(process.memoryUsage()["rss"] / 1024 / 1024 * 100) / 100} MB - >   Accounts: ${commandHandler.controller.getBots().length} | maxComments/owner: ${commandHandler.data.config.maxComments}/${commandHandler.data.config.maxOwnerComments} | delay: ${commandHandler.data.config.commentdelay} + >   ${`Uptime: ${Number(Math.round(((new Date() - commandHandler.controller.info.bootStartTimestamp) / 3600000)+"e"+2)+"e-"+2)} hours`.padEnd(24, " ")} | Branch: ${commandHandler.data.datafile.branch} + >   ${`'node.js' Version: ${process.version}`.padEnd(25, " ")} | RAM Usage (RSS): ${Math.round(process.memoryUsage()["rss"] / 1024 / 1024 * 100) / 100} MB + >   ${`Accounts: ${commandHandler.controller.getBots().length}`.padEnd(24, " ")} | Active Plugins: ${Object.keys(commandHandler.controller.pluginSystem.pluginList).length} | - >   Your steam64ID: ${steamID64} + >   Your ID: ${resInfo.userID} | Steam Chat? ${resInfo.fromSteamChat ? "Yes" : "No"} | Owner? ${owners.includes(resInfo.userID) ? "Yes" : "No"} >   Your last request: ${userLastReq} >   Last processed request: ${(new Date(lastReq)).toISOString().replace(/T/, " ").replace(/\..+/, "")} (GMT time) >   I have commented ${commandHandler.controller.info.commentCounter} times since my last restart and completed request! -----------------------------------~~~~~------------------------------------ - `.replace(/ {4}/gm, "")); // Remove all the whitespaces that are added by the proper code indentation here + `.replace(/^( {4})+/gm, "")); // Remove all the whitespaces that are added by the proper code indentation here /* eslint-enable no-irregular-whitespace */ }); } @@ -127,12 +133,11 @@ module.exports.ping = { * The ping command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call let pingStart = Date.now(); @@ -155,12 +160,11 @@ module.exports.about = { * The about command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call respond(commandHandler.data.datafile.aboutstr); @@ -178,12 +182,11 @@ module.exports.owner = { * The owner command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call // Check if no owner link is set @@ -205,12 +208,11 @@ module.exports.test = { * The test command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: async (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: async (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // eslint-disable-line /* // Do not remove, these are handleSteamIdResolving test cases. Might be useful to include later in steamid-resolving lib test suite diff --git a/src/commands/core/group.js b/src/commands/core/group.js index 61da6386..f21962fa 100644 --- a/src/commands/core/group.js +++ b/src/commands/core/group.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 16:01:25 + * Last Modified: 10.07.2023 21:25:38 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -15,7 +15,7 @@ */ -const SteamID = require("steamid"); +const SteamID = require("steamid"); const CommandHandler = require("../commandHandler.js"); // eslint-disable-line @@ -30,22 +30,22 @@ module.exports.group = { * The group command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.data.config.yourgroup.length < 1 || !commandHandler.data.cachefile.configgroup64id) return respond(commandHandler.data.lang.groupcmdnolink); // No group info at all? stop. - if (commandHandler.data.cachefile.configgroup64id && Object.keys(commandHandler.controller.main.user.myGroups).includes(commandHandler.data.cachefile.configgroup64id)) { - commandHandler.controller.main.user.inviteToGroup(steamID64, commandHandler.data.cachefile.configgroup64id); + // Send user an invite if a group is set in the config and userID is a Steam ID by checking fromSteamChat + if (resInfo.userID && resInfo.fromSteamChat && commandHandler.data.cachefile.configgroup64id && Object.keys(commandHandler.controller.main.user.myGroups).includes(commandHandler.data.cachefile.configgroup64id)) { + commandHandler.controller.main.user.inviteToGroup(resInfo.userID, commandHandler.data.cachefile.configgroup64id); respond(commandHandler.data.lang.groupcmdinvitesent); if (commandHandler.data.cachefile.configgroup64id != "103582791464712227") { // https://steamcommunity.com/groups/3urobeatGroup - commandHandler.controller.main.user.inviteToGroup(steamID64, new SteamID("103582791464712227")); + commandHandler.controller.main.user.inviteToGroup(resInfo.userID, new SteamID("103582791464712227")); } return; // Id? send invite and stop } @@ -73,12 +73,11 @@ module.exports.joinGroup = { * The joinGroup command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained @@ -119,12 +118,11 @@ module.exports.leaveGroup = { * The leaveGroup command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained @@ -165,12 +163,11 @@ module.exports.leaveAllGroups = { * The leaveAllGroups command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained diff --git a/src/commands/core/requests.js b/src/commands/core/requests.js index e8ccd349..cf841bac 100644 --- a/src/commands/core/requests.js +++ b/src/commands/core/requests.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:58:36 + * Last Modified: 10.07.2023 13:04:06 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -37,32 +37,40 @@ module.exports.abort = { * The abort command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Check if bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained + let userID = resInfo.userID; + + // Check for no userID and no id param as both can be missing if called from outside the Steam Chat + if (!userID && !args[0]) return respond(commandHandler.data.lang.noidparam); + commandHandler.controller.handleSteamIdResolving(args[0], null, (err, res) => { if (res) { let activeReqEntry = commandHandler.controller.activeRequests[res]; + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + // Refuse if user is not an owner and the request is not from them - if (!commandHandler.data.cachefile.ownerid.includes(steamID64) && (activeReqEntry && activeReqEntry.requestedby != steamID64)) return respond(commandHandler.data.lang.commandowneronly); + if (!owners.includes(resInfo.userID) && (activeReqEntry && activeReqEntry.requestedby != resInfo.userID)) return respond(commandHandler.data.lang.commandowneronly); else logger("debug", "CommandHandler abort cmd: Non-owner provided ID as parameter but is requester of that request. Permitting abort..."); - steamID64 = res; // If user provided an id as argument then use that instead of their id + userID = res; // If user provided an id as argument then use that instead of their id } - if (!commandHandler.controller.activeRequests[steamID64] || commandHandler.controller.activeRequests[steamID64].status != "active") return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.abortcmdnoprocess); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained + if (!commandHandler.controller.activeRequests[userID] || commandHandler.controller.activeRequests[userID].status != "active") return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.abortcmdnoprocess); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained // Set new status for this request - commandHandler.controller.activeRequests[steamID64].status = "aborted"; + commandHandler.controller.activeRequests[userID].status = "aborted"; - logger("info", `Aborting active process for ID ${steamID64}...`); + logger("info", `Aborting active process for ID ${userID}...`); respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.abortcmdsuccess); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained }); } @@ -87,12 +95,11 @@ module.exports.resetCooldown = { * The resetcooldown command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (args[0] && args[0] == "global") { // Check if user wants to reset the global cooldown (will reset all until entries in activeRequests) @@ -106,15 +113,20 @@ module.exports.resetCooldown = { } else { + let userID = resInfo.userID; + + // Check for no userID and no id param as both can be missing if called from outside the Steam Chat + if (!userID && !args[0]) return respond(commandHandler.data.lang.noidparam); + commandHandler.controller.handleSteamIdResolving(args[0], "profile", (err, res) => { if (err) return respond(commandHandler.data.lang.invalidprofileid + "\n\nError: " + err); - if (res) steamID64 = res; // Change steamID64 to the provided id + if (res) userID = res; // Change steamID64 to the provided id if (commandHandler.data.config.commentcooldown == 0) return respond(commandHandler.data.lang.resetcooldowncmdcooldowndisabled); // Is the cooldown enabled? - commandHandler.data.lastCommentDB.update({ id: steamID64 }, { $set: { time: Date.now() - (commandHandler.data.config.commentcooldown * 60000) } }, (err) => { + commandHandler.data.lastCommentDB.update({ id: userID }, { $set: { time: Date.now() - (commandHandler.data.config.commentcooldown * 60000) } }, (err) => { if (err) return respond("Error updating database entry: " + err); - else respond(commandHandler.data.lang.resetcooldowncmdsuccess.replace("profileid", steamID64.toString())); + else respond(commandHandler.data.lang.resetcooldowncmdsuccess.replace("profileid", userID.toString())); }); }); } @@ -140,35 +152,43 @@ module.exports.failed = { * The failed command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call + let userID = resInfo.userID; + + // Check for no userID and no id param as both can be missing if called from outside the Steam Chat + if (!userID && !args[0]) return respond(commandHandler.data.lang.noidparam); + commandHandler.controller.handleSteamIdResolving(args[0], null, (err, res) => { if (res) { let activeReqEntry = commandHandler.controller.activeRequests[res]; + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + // Refuse if user is not an owner and the request is not from them - if (!commandHandler.data.cachefile.ownerid.includes(steamID64) && (activeReqEntry && activeReqEntry.requestedby != steamID64)) return respond(commandHandler.data.lang.commandowneronly); + if (!owners.includes(userID) && (activeReqEntry && activeReqEntry.requestedby != userID)) return respond(commandHandler.data.lang.commandowneronly); else logger("debug", "CommandHandler failed cmd: Non-owner provided ID as parameter but is requester of that request. Permitting data retrieval..."); - steamID64 = res; // If user provided an id as argument then use that instead of their id + userID = res; // If user provided an id as argument then use that instead of their id } - if (!commandHandler.controller.activeRequests[steamID64] || Object.keys(commandHandler.controller.activeRequests[steamID64].failed).length < 1) return respond(commandHandler.data.lang.failedcmdnothingfound); + if (!commandHandler.controller.activeRequests[userID] || Object.keys(commandHandler.controller.activeRequests[userID].failed).length < 1) return respond(commandHandler.data.lang.failedcmdnothingfound); // Get timestamp of request - let requestTime = new Date(commandHandler.controller.activeRequests[steamID64].until).toISOString().replace(/T/, " ").replace(/\..+/, ""); + let requestTime = new Date(commandHandler.controller.activeRequests[userID].until).toISOString().replace(/T/, " ").replace(/\..+/, ""); // Group errors and convert them to string using helper function - let failedcommentsstr = failedCommentsObjToString(commandHandler.controller.activeRequests[steamID64].failed); + let failedcommentsstr = failedCommentsObjToString(commandHandler.controller.activeRequests[userID].failed); // Get start of message from lang file and add data - let messagestart = commandHandler.data.lang.failedcmdmsg.replace("steamID64", steamID64).replace("requesttime", requestTime); + let messagestart = commandHandler.data.lang.failedcmdmsg.replace("steamID64", userID).replace("requesttime", requestTime); // Send message and limit to 500 chars as this call can cause many messages to be sent respondModule(context, { prefix: "/pre", charLimit: 500, ...resInfo }, messagestart + "\nc = Comment, b = Bot, p = Proxy\n\n" + failedcommentsstr); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained @@ -187,12 +207,11 @@ module.exports.sessions = { * The sessions command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call let str = ""; @@ -232,21 +251,23 @@ module.exports.mySessions = { * The mysessions command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call let str = ""; + // Check for no userID as the default behavior might be unavailable when calling from outside of the Steam Chat + if (!resInfo.userID) return respond(commandHandler.data.lang.nouserid); // In this case the cmd doesn't have an ID param so send this message instead of noidparam + if (Object.keys(commandHandler.controller.activeRequests).length > 0) { // Only loop through object if it isn't empty let objlength = Object.keys(commandHandler.controller.activeRequests).length; // Save this before the loop as deleting entries will change this number and lead to the loop finished check never triggering Object.keys(commandHandler.controller.activeRequests).forEach((e, i) => { if (Date.now() < commandHandler.controller.activeRequests[e].until + (commandHandler.data.config.botaccountcooldown * 60000)) { // Check if entry is not finished yet - if (commandHandler.controller.activeRequests[e].requestedby == steamID64) str += `- Status: ${commandHandler.controller.activeRequests[e].status} | ${commandHandler.controller.activeRequests[e].amount} iterations with ${commandHandler.controller.activeRequests[e].accounts.length} accounts by ${commandHandler.controller.activeRequests[e].requestedby} for ${commandHandler.controller.activeRequests[e].type} ${Object.keys(commandHandler.controller.activeRequests)[i]}`; + if (commandHandler.controller.activeRequests[e].requestedby == resInfo.userID) str += `- Status: ${commandHandler.controller.activeRequests[e].status} | ${commandHandler.controller.activeRequests[e].amount} iterations with ${commandHandler.controller.activeRequests[e].accounts.length} accounts by ${commandHandler.controller.activeRequests[e].requestedby} for ${commandHandler.controller.activeRequests[e].type} ${Object.keys(commandHandler.controller.activeRequests)[i]}`; } else { delete commandHandler.controller.activeRequests[e]; // Remove entry from object if it is finished to keep the object clean } diff --git a/src/commands/core/settings.js b/src/commands/core/settings.js index 229acd66..019b4530 100644 --- a/src/commands/core/settings.js +++ b/src/commands/core/settings.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:59:10 + * Last Modified: 26.07.2023 16:49:28 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -21,7 +21,7 @@ const CommandHandler = require("../commandHandler.js"); // eslint-disable-line module.exports.settings = { - names: ["set", "settings", "config"], + names: ["settings", "set", "config"], description: "Change a value in the config", args: [ { @@ -45,26 +45,31 @@ module.exports.settings = { * The settings command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call let config = commandHandler.data.config; // Only send current settings if no arguments were provided if (!args[0]) { - fs.readFile("./config.json", function(err, data) { // Use readFile to get an unprocessed object - if (err) return respond(commandHandler.data.lang.settingscmdfailedread + err); + let stringifiedconfig = JSON.stringify(commandHandler.data.config, function(k, v) { // Credit: https://stackoverflow.com/a/46217335/12934162 + if (v instanceof Array) return JSON.stringify(v); + return v; + }, 4) + .replace(/"\[/g, "[") + .replace(/\]"/g, "]") + .replace(/\\"/g, '"') + .replace(/""/g, '""'); - // Remove first and last character which are brackets and remove leading and trailing whitespaces from all lines - let currentsettingsarr = data.toString().slice(1, -1).split("\n").map(s => s.trim()); + // Remove first and last character which are brackets and remove leading and trailing whitespaces from all lines + let currentsettingsarr = stringifiedconfig.toString().slice(1, -1).split("\n").map(s => s.trim()); + + // Send message with code prefix and only allow cuts at newlines + respondModule(context, { prefix: "/code", cutChars: ["\n"], ...resInfo }, commandHandler.data.lang.settingscmdcurrentsettings + "\n" + currentsettingsarr.join("\n")); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained - // Send message with code prefix and only allow cuts at newlines - respondModule(context, { prefix: "/code", cutChars: ["\n"], ...resInfo }, commandHandler.data.lang.settingscmdcurrentsettings + "\n" + currentsettingsarr.join("\n")); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained - }); return; } diff --git a/src/commands/core/system.js b/src/commands/core/system.js index 30d9ed5a..117bd42e 100644 --- a/src/commands/core/system.js +++ b/src/commands/core/system.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:59:30 + * Last Modified: 26.07.2023 16:42:30 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -30,12 +30,11 @@ module.exports.restart = { * The restart command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.restartcmdrestarting); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained commandHandler.controller.restart(JSON.stringify({ skippedaccounts: commandHandler.controller.info.skippedaccounts })); @@ -53,12 +52,11 @@ module.exports.stop = { * The stop command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.stopcmdstopping); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained commandHandler.controller.stop(); @@ -76,12 +74,11 @@ module.exports.reload = { * The reload command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: async (commandHandler, args, respondModule, context, resInfo) => { // Reload commandHandler commandHandler.reloadCommands(); @@ -89,6 +86,9 @@ module.exports.reload = { // Reload pluginSystem commandHandler.controller.pluginSystem.reloadPlugins(); + // Reload data + await commandHandler.data._importFromDisk(); + // Send response message respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.reloadcmdreloaded); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained @@ -114,12 +114,11 @@ module.exports.update = { * The update command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((modResInfo, txt) => respondModule(context, modResInfo, txt)); // Shorten each call. Updater takes resInfo as param and can modify it, so we need to pass the modified resInfo object here // If the first argument is true then we shall force an update @@ -136,7 +135,7 @@ module.exports.update = { module.exports.output = { - names: ["output", "log"], + names: ["log", "output"], description: "Shows the last 15 lines of the log", args: [], ownersOnly: true, @@ -145,12 +144,11 @@ module.exports.output = { * The output command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { fs.readFile("./output.txt", function (err, data) { if (err) logger("error", "error getting last 15 lines from output for log cmd: " + err); @@ -179,12 +177,11 @@ module.exports.eval = { * The eval command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call if (!commandHandler.data.advancedconfig.enableevalcmd) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.evalcmdturnedoff); // Pass new resInfo object which contains prefix and everything the original resInfo obj contained diff --git a/src/commands/core/vote.js b/src/commands/core/vote.js index eac39433..ff87fd1f 100644 --- a/src/commands/core/vote.js +++ b/src/commands/core/vote.js @@ -4,7 +4,7 @@ * Created Date: 28.05.2023 12:02:24 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:59:45 + * Last Modified: 24.07.2023 19:42:37 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -47,19 +47,26 @@ module.exports.upvote = { * The upvote command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: async (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: async (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call - let requesterSteamID64 = steamID64; - let ownercheck = commandHandler.data.cachefile.ownerid.includes(requesterSteamID64); + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + let requesterSteamID64 = resInfo.userID; + let ownercheck = owners.includes(requesterSteamID64); - /* --------- Check for disabled cmd or if update is queued --------- */ + + /* --------- Various checks --------- */ + if (!resInfo.userID) { + respond(commandHandler.data.lang.nouserid); // Reject usage of command without an userID to avoid cooldown bypass + return logger("err", "The upvote command was called without resInfo.userID! Blocking the command as I'm unable to apply cooldowns, which is required for this command!"); + } if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained if (commandHandler.controller.info.activeLogin) return respond(commandHandler.data.lang.activerelog); // Bot is waiting for relog if (commandHandler.data.config.maxComments == 0 && !ownercheck) return respond(commandHandler.data.lang.commandowneronly); // Command is restricted to owners only @@ -193,7 +200,7 @@ module.exports.upvote = { let failedcmdreference = ""; if (Object.keys(commandHandler.controller.activeRequests[id].failed).length > 0) { - failedcmdreference = `\nTo get detailed information why which request failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/wiki/Errors,-FAQ-&-Common-problems`; + failedcmdreference = `\nTo get detailed information why which request failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/errors_doc.md`; } // Send finished message @@ -235,19 +242,26 @@ module.exports.downvote = { * The upvote command * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args Array of arguments that will be passed to the command - * @param {string} steamID64 Steam ID of the user that executed this command * @param {function(object, object, string): void} respondModule Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. * @param {object} context The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). */ - run: async (commandHandler, args, steamID64, respondModule, context, resInfo) => { + run: async (commandHandler, args, respondModule, context, resInfo) => { let respond = ((txt) => respondModule(context, resInfo, txt)); // Shorten each call - let requesterSteamID64 = steamID64; - let ownercheck = commandHandler.data.cachefile.ownerid.includes(requesterSteamID64); + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + let requesterSteamID64 = resInfo.userID; + let ownercheck = owners.includes(requesterSteamID64); - /* --------- Check for disabled cmd or if update is queued --------- */ + + /* --------- Various checks --------- */ + if (!resInfo.userID) { + respond(commandHandler.data.lang.nouserid); // Reject usage of command without an userID to avoid cooldown bypass + return logger("err", "The downvote command was called without resInfo.userID! Blocking the command as I'm unable to apply cooldowns, which is required for this command!"); + } if (commandHandler.controller.info.readyAfter == 0) return respondModule(context, { prefix: "/me", ...resInfo }, commandHandler.data.lang.botnotready); // Bot isn't fully started yet - Pass new resInfo object which contains prefix and everything the original resInfo obj contained if (commandHandler.controller.info.activeLogin) return respond(commandHandler.data.lang.activerelog); // Bot is waiting for relog if (commandHandler.data.config.maxComments == 0 && !ownercheck) return respond(commandHandler.data.lang.commandowneronly); // Command is restricted to owners only @@ -381,7 +395,7 @@ module.exports.downvote = { let failedcmdreference = ""; if (Object.keys(commandHandler.controller.activeRequests[id].failed).length > 0) { - failedcmdreference = `\nTo get detailed information why which request failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/wiki/Errors,-FAQ-&-Common-problems`; + failedcmdreference = `\nTo get detailed information why which request failed please type '${resInfo.cmdprefix}failed'. You can read why your error was probably caused here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/errors_doc.md`; } // Send finished message diff --git a/src/commands/helpers/getCommentArgs.js b/src/commands/helpers/getCommentArgs.js index 36c9c435..d3eda352 100644 --- a/src/commands/helpers/getCommentArgs.js +++ b/src/commands/helpers/getCommentArgs.js @@ -4,7 +4,7 @@ * Created Date: 28.02.2022 11:55:06 * Author: 3urobeat * - * Last Modified: 04.07.2023 19:30:48 + * Last Modified: 10.07.2023 12:51:53 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -23,13 +23,17 @@ const CommandHandler = require("../commandHandler.js"); // eslint-disable-line * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args The command arguments * @param {string} requesterSteamID64 The steamID64 of the requesting user - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). * @param {function(string): void} respond The shortened respondModule call * @returns {Promise.<{ maxRequestAmount: number, commentcmdUsage: string, numberOfComments: number, profileID: string, idType: string, quotesArr: Array. }>} Resolves promise with object containing all relevant data when done */ module.exports.getCommentArgs = (commandHandler, args, requesterSteamID64, resInfo, respond) => { return new Promise((resolve) => { + // Get the correct ownerid array for this request + let owners = commandHandler.data.cachefile.ownerid; + if (resInfo.ownerIDs && resInfo.ownerIDs.length > 0) owners = resInfo.ownerIDs; + let maxRequestAmount = commandHandler.data.config.maxComments; // Set to default value and if the requesting user is an owner it gets changed below let numberOfComments = 0; let quotesArr = commandHandler.data.quotes; @@ -41,7 +45,7 @@ module.exports.getCommentArgs = (commandHandler, args, requesterSteamID64, resIn /* --------- Define command usage messages & maxRequestAmount for each user's privileges --------- */ let commentcmdUsage; - if (commandHandler.data.cachefile.ownerid.includes(requesterSteamID64)) { + if (owners.includes(requesterSteamID64)) { maxRequestAmount = commandHandler.data.config.maxOwnerComments; if (maxRequestAmount > 1) commentcmdUsage = commandHandler.data.lang.commentcmdusageowner.replace(/cmdprefix/g, resInfo.cmdprefix).replace("maxRequestAmount", maxRequestAmount); @@ -77,7 +81,7 @@ module.exports.getCommentArgs = (commandHandler, args, requesterSteamID64, resIn /* --------- Check profileid argument if it was provided --------- */ if (args[1]) { - if (commandHandler.data.cachefile.ownerid.includes(requesterSteamID64) || args[1] == requesterSteamID64) { // Check if user is a bot owner or if he provided his own profile id + if (owners.includes(requesterSteamID64) || args[1] == requesterSteamID64) { // Check if user is a bot owner or if he provided his own profile id let arg = args[1]; commandHandler.controller.handleSteamIdResolving(arg, null, (err, res, type) => { diff --git a/src/commands/helpers/getSharedfileArgs.js b/src/commands/helpers/getSharedfileArgs.js index 038fe00e..2af68ede 100644 --- a/src/commands/helpers/getSharedfileArgs.js +++ b/src/commands/helpers/getSharedfileArgs.js @@ -4,7 +4,7 @@ * Created Date: 28.05.2023 12:18:49 * Author: 3urobeat * - * Last Modified: 04.07.2023 19:31:41 + * Last Modified: 09.07.2023 13:31:26 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -23,7 +23,7 @@ const CommandHandler = require("../commandHandler.js"); // eslint-disable-line * @param {CommandHandler} commandHandler The commandHandler object * @param {Array} args The command arguments * @param {string} cmd Either "upvote", "downvote", "favorite" or "unfavorite", depending on which command is calling this function - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {CommandHandler.resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). * @param {function(string): void} respond The shortened respondModule call * @returns {Promise.<{ amount: number|string, id: string }>} If the user provided a specific amount, amount will be a number. If user provided "all" or "max", it will be returned as an unmodified string for getVoteBots.js to handle */ diff --git a/src/controller/controller.js b/src/controller/controller.js index 9ec3a47b..700c5e8e 100644 --- a/src/controller/controller.js +++ b/src/controller/controller.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 08.07.2023 00:36:10 + * Last Modified: 08.07.2023 00:47:49 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -68,7 +68,7 @@ const Controller = function() { * It is used by the steamChatInteraction helper but can be used in plugins as well. * @param {string} txt The string to cut * @param {number} limit Maximum length for each part. The function will attempt to cut txt into parts that don't exceed this amount. - * @param {Array} cutChars Optional: Custom chars to search after for cutting string in parts. Default: [" ", "\n", "\r"] + * @param {Array.} cutChars Optional: Custom chars to search after for cutting string in parts. Default: [" ", "\n", "\r"] * @param {number} threshold Optional: Maximum amount that limit can be reduced to find the last space or line break. If no match is found within this limit a word will be cut. Default: 15% of total length * @returns {Array} Returns all parts of the string in an array */ diff --git a/src/controller/events/ready.js b/src/controller/events/ready.js index 1f7833c1..1bafe872 100644 --- a/src/controller/events/ready.js +++ b/src/controller/events/ready.js @@ -4,7 +4,7 @@ * Created Date: 29.03.2023 12:23:29 * Author: 3urobeat * - * Last Modified: 29.06.2023 22:35:03 + * Last Modified: 10.07.2023 09:33:30 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -152,7 +152,7 @@ Controller.prototype._readyEvent = function() { // Message all owners that are friends if firststart is true that the bot just updated itself if (this.data.datafile.firststart) { this.data.cachefile.ownerid.forEach((e) => { - if (!nonFriendOwners.includes(e)) this.main.sendChatMessage(this.main, { steamID64: e }, `I have updated myself to version ${this.data.datafile.versionstr}!\nWhat's new: ${this.data.datafile.whatsnew}`); + if (!nonFriendOwners.includes(e)) this.main.sendChatMessage(this.main, { userID: e }, `I have updated myself to version ${this.data.datafile.versionstr}!\nWhat's new: ${this.data.datafile.whatsnew}`); }); } diff --git a/src/controller/helpers/friendlist.js b/src/controller/helpers/friendlist.js index ebc3bb7f..deca36b1 100644 --- a/src/controller/helpers/friendlist.js +++ b/src/controller/helpers/friendlist.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 08.07.2023 00:36:19 + * Last Modified: 10.07.2023 13:10:14 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -88,7 +88,7 @@ Controller.prototype.friendListCapacityCheck = function(bot, callback) { let steamID = new SteamID(e.id); // Unfriend user and send him/her a message // TODO: Maybe only do this from the main bot? - bot.sendChatMessage(bot, { steamID64: steamID.getSteamID64() }, this.data.lang.userunfriend.replace("forceFriendlistSpaceTime", this.data.advancedconfig.forceFriendlistSpaceTime)); + bot.sendChatMessage(bot, { userID: steamID.getSteamID64() }, this.data.lang.userunfriend.replace("forceFriendlistSpaceTime", this.data.advancedconfig.forceFriendlistSpaceTime)); bot.user.removeFriend(steamID); logger("info", `[Bot ${bot.index}] Force-Unfriended ${e.id} after being inactive for ${this.data.advancedconfig.forceFriendlistSpaceTime} days to keep 1 empty slot on the friendlist`); @@ -134,8 +134,8 @@ Controller.prototype._lastcommentUnfriendCheck = function() { this.getBots().forEach((f, j) => { let thisbot = f.user; - if (thisbot.myFriends[e.id] == 3 && !this.data.cachefile.ownerid.includes(e.id)) { // Check if the targeted user is still friend - if (j == 0) this.main.sendChatMessage(this.main, { steamID64: e.id }, this.data.lang.userforceunfriend.replace("unfriendtime", this.data.config.unfriendtime)); + if (thisbot.myFriends[e.id] && thisbot.myFriends[e.id] == 3 && !this.data.cachefile.ownerid.includes(e.id)) { // Check if the targeted user is still friend and not an owner + if (j == 0) this.main.sendChatMessage(this.main, { userID: e.id }, this.data.lang.userforceunfriend.replace("unfriendtime", this.data.config.unfriendtime)); setTimeout(() => { thisbot.removeFriend(new SteamID(e.id)); // Unfriend user with each bot @@ -143,7 +143,8 @@ Controller.prototype._lastcommentUnfriendCheck = function() { }, 1000 * j); // Delay every iteration so that we don't make a ton of requests at once (IP) } - if (!this.data.cachefile.ownerid.includes(e.id)) this.data.lastCommentDB.remove({ id: e.id }); // Entry gets removed no matter what but we are nice and let the owner stay. Thank me later! <3 + // Disabled db cleanup as entries from plugins would be deleted as well. SteamID does not recognize Discord IDs for example as invalid so we cannot check for that + // if (!this.data.cachefile.ownerid.includes(e.id)) this.data.lastCommentDB.remove({ id: e.id }); // Entry gets removed no matter what but we are nice and let the owner stay. Thank me later! <3 }); }, 1000 * i); // Delay every iteration so that we don't make a ton of requests at once (account) diff --git a/src/controller/helpers/misc.js b/src/controller/helpers/misc.js index d246b501..c3c9d154 100644 --- a/src/controller/helpers/misc.js +++ b/src/controller/helpers/misc.js @@ -4,7 +4,7 @@ * Created Date: 25.03.2023 14:02:56 * Author: 3urobeat * - * Last Modified: 04.07.2023 19:40:22 + * Last Modified: 08.07.2023 00:47:33 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -127,7 +127,7 @@ module.exports.checkConnection = (url, throwTimeout) => { * It is used by the steamChatInteraction helper but can be used in plugins as well. * @param {string} txt The string to cut * @param {number} limit Maximum length for each part. The function will attempt to cut txt into parts that don't exceed this amount. - * @param {Array} cutChars Optional: Custom chars to search after for cutting string in parts. Default: [" ", "\n", "\r"] + * @param {Array.} cutChars Optional: Custom chars to search after for cutting string in parts. Default: [" ", "\n", "\r"] * @param {number} threshold Optional: Maximum amount that limit can be reduced to find the last space or line break. If no match is found within this limit a word will be cut. Default: 15% of total length * @returns {Array} Returns all parts of the string in an array */ diff --git a/src/controller/login.js b/src/controller/login.js index 3d805d36..1b64d689 100644 --- a/src/controller/login.js +++ b/src/controller/login.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 04.07.2023 17:55:45 + * Last Modified: 23.07.2023 13:50:31 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -134,9 +134,10 @@ Controller.prototype.login = function(firstLogin) { if (thisbot.status == Bot.EStatus.OFFLINE) return; // Keep waiting if we are on the last iteration and user object is not fully populated yet, this takes a few seconds after login. Make sure to check for limitations of last entry in array instead of this iteration to not break when the this last acc got skipped - let lastBot = Object.values(this.bots)[Object.values(this.bots).filter(e => e.status == Bot.EStatus.ONLINE).length - 1]; // Get index of the last acc marked as online. I know, this line really sucks readability-wise + let onlineBots = this.getBots(); + let lastBot = onlineBots[onlineBots.length - 1]; - if (i + 1 == Object.keys(this.data.logininfo).length && !lastBot.user.limitations) { + if (i + 1 == Object.keys(this.data.logininfo).length && lastBot && !lastBot.user.limitations) { // Only attempt to check if a lastBot was found, this can otherwise cause an infinite error loop return logger("info", `Last account logged in, waiting for user object of Bot ${lastBot.index} to populate...`, true, true, logger.animation("waiting")); } diff --git a/src/data/data.json b/src/data/data.json index adfc906c..14cb3a3e 100644 --- a/src/data/data.json +++ b/src/data/data.json @@ -1,6 +1,6 @@ { - "version": "21305", - "versionstr": "2.13.5", + "version": "21306", + "versionstr": "2.13.6", "branch": "master", "filetostart": "./src/starter.js", "filetostarturl": "https://raw.githubusercontent.com/3urobeat/steam-comment-service-bot/master/src/starter.js", @@ -8,7 +8,7 @@ "aboutstr": "This bot was created by 3urobeat.\nGitHub: https://github.com/3urobeat/steam-comment-service-bot \nSteam: https://steamcommunity.com/id/3urobeat \nIf you like my work, any donation would be appreciated! https://paypal.me/3urobeat", "firststart": true, "compatibilityfeaturedone": false, - "whatsnew": "Improved plugin system and other parts related to it, (potentially) fixed getting sharedfile error, improved code quality, improved a few wiki pages and minor other changes and fixes.", + "whatsnew": "Reworked how commands accept and use IDs to greatly improve plugin support, fixed error when loading sharedfiles with incomplete breadcrumbs, fixed bug where the bot was waiting for a skipped account's object to be populated and more.", "timesloggedin": 0, "totallogintime": 0 } \ No newline at end of file diff --git a/src/data/lang/defaultlang.json b/src/data/lang/defaultlang.json index 7793a6ba..ddc9fc7b 100644 --- a/src/data/lang/defaultlang.json +++ b/src/data/lang/defaultlang.json @@ -1,5 +1,5 @@ { - "note": "This file contains nearly all messages the bot sends to users. If you want to modify messages, read here: https://github.com/3urobeat/steam-comment-service-bot/wiki/How-to-add-custom-messages", + "note": "This file contains nearly all messages the bot sends to users. If you want to modify messages, read here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/customlang_doc.md", "updaterautoupdatedisabled": "You have disabled the automatic updater. Would you like to update now?\nIf yes, please force an update using the command: cmdprefixupdate true", @@ -19,7 +19,7 @@ "commentuserprofileprivate": "Your/the receiving profile seems to be private. Please edit the privacy settings to allow comments and try again!", "commenterroroccurred": "Oops, an error occurred! I sadly wasn't able to comment.", "commentprocessstarted": "Estimated wait time for numberOfComments comments: waittime.", - "commentfailedcmdreference": "To get detailed information why which comment failed please type 'cmdprefixfailed'. You can read what probably caused your error here: https://github.com/3urobeat/steam-comment-service-bot/wiki/Errors,-FAQ-&-Common-problems", + "commentfailedcmdreference": "To get detailed information why which comment failed please type 'cmdprefixfailed'. You can read what probably caused your error here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/errors_doc.md", "comment429stop": "Stopped comment process because all proxies had a HTTP 429 (IP cooldown) error. Please try again later. Failed: failedamount/numberOfComments", "commentretrying": "failedamount/numberOfComments comments have failed. I'm going to retry the failed comments in untilStr. (Attempt thisattempt/maxattempt)", @@ -45,7 +45,9 @@ "usernotfriend": "Please add me before using a command!", "botnotready": "The bot is not completely started yet. Please wait a moment before using a command.", "commandnotfound": "I don't know that command. Type cmdprefixhelp for more info.", - "commandowneronly": "This command is only available for owners.\nIf you are the botowner, make sure you added your ownerid to the config.json.", + "commandowneronly": "This command is only available for owners.\nIf you are the botowner, make sure you added your ownerid to the config.json.\nIf this request originates from a plugin, make sure to pass the userID & ownerIDs parameters.", + "nouserid": "The command was called without a userID! Blocking the command as I'm either unable to apply cooldowns or the default behavior of this command cannot be used without one. This is a coding issue which must be fixed by a developer.", + "noidparam": "Please provide an ID!\nThe default behavior of this command might be unavailable in this context, for example when the command was used from outside the Steam Chat or the developer forgot to pass a userID to enable it.", "invalidnumber": "This does not seem to be a valid number!\n\nCommand usage: cmdusage", "invalidprofileid": "This does not seem to be a valid ID or link or you provided the wrong ID type for this command!\nPlease make sure that you either provide a full link, only the vanity or only the ID, pointing to an existing profile, group or sharedfile.", diff --git a/src/data/lang/russian.json b/src/data/lang/russian.json index ec803d7e..e56e4c21 100644 --- a/src/data/lang/russian.json +++ b/src/data/lang/russian.json @@ -1,5 +1,5 @@ { - "note": "Этот файл содержит почти все сообщения, которые бот посылает пользователям. Если вы хотите изменить сообщения, прочтите эту страницу: https://github.com/3urobeat/steam-comment-service-bot/wiki/How-to-add-custom-messages", + "note": "Этот файл содержит почти все сообщения, которые бот посылает пользователям. Если вы хотите изменить сообщения, прочтите эту страницу: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/customlang_doc.md", "updaterautoupdatedisabled": "Вы отключили автоматическое обновление. Хотите ли вы обновить его сейчас?\nЕсли да, пожалуйста, запустите обновление с помощью команды: cmdprefixupdate true", @@ -19,7 +19,7 @@ "commentuserprofileprivate": "Ваш/полученный профиль кажется скрытым. Пожалуйста, измените настройки приватности, чтобы разрешить комментарии, и повторите попытку!", "commenterroroccurred": "Опаньки, произошла ошибка! К сожалению, я не смог её прокомментировать.", "commentprocessstarted": "Примерное время ожидания для numberOfComments комментариев: waittime.", - "commentfailedcmdreference": "Чтобы получить подробную информацию о том, почему комментарий не сработал, введите «cmdprefixfailed». Вы можете прочитать, что, вероятно, вызвало вашу ошибку, здесь: https://github.com/3urobeat/steam-comment-service-bot/wiki/Errors,-FAQ-&-Common-problems", + "commentfailedcmdreference": "Чтобы получить подробную информацию о том, почему комментарий не сработал, введите «cmdprefixfailed». Вы можете прочитать, что, вероятно, вызвало вашу ошибку, здесь: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/errors_doc.md", "comment429stop": "Остановлен процесс комментирования, поскольку все прокси-серверы выдали ошибку HTTP 429 (восстановление IP). Пожалуйста, повторите попытку позже. Не удалось: failedamount/numberOfComments", "commentretrying": "failedamount/numberOfComments комментариев были неудачными. Я собираюсь повторить попытку неудачных комментариев в untilStr. (Попытка thisattempt/maxattempt)", diff --git a/src/dataManager/dataCheck.js b/src/dataManager/dataCheck.js index 18582ae9..d4c544c6 100644 --- a/src/dataManager/dataCheck.js +++ b/src/dataManager/dataCheck.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 07.07.2023 15:32:44 + * Last Modified: 26.07.2023 15:43:26 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -108,7 +108,7 @@ DataManager.prototype.checkData = function() { return reject(new Error("Commentdelay times maxcomments exceeds 32bit integer limit!")); } if (this.config.randomizeAccounts && Object.keys(this.logininfo).length <= 5 && maxCommentsOverall > Object.keys(this.logininfo).length * 2) { - logWarn("warn", `${logger.colors.fgred}I wouldn't recommend using randomizeAccounts with 5 or less accounts when each account can/has to comment multiple times. The chance of an account getting a cooldown is higher.\n Please make sure your commentdelay is set adequately to reduce the chance of this happening.`, true); + logWarn("warn", `${logger.colors.fgred}I wouldn't recommend using randomizeAccounts with 5 or less accounts when each account can/has to comment multiple times. The chance of an account getting a cooldown is higher.\n Please make sure your commentdelay is set adequately to reduce the chance of this happening.`, true); } if (this.advancedconfig.loginDelay < 500) { // Don't allow a logindelay below 500ms logWarn("error", `${logger.colors.fgred}I won't allow a logindelay below 500ms as this will probably get you blocked by Steam nearly instantly. I recommend setting it to 2500.\n If you are using one proxy per account you might try setting it to 500 (on your own risk!). Aborting...`, true); diff --git a/src/dataManager/dataExport.js b/src/dataManager/dataExport.js index 91cb8b80..64163d0f 100644 --- a/src/dataManager/dataExport.js +++ b/src/dataManager/dataExport.js @@ -4,7 +4,7 @@ * Created Date: 04.07.2023 21:29:42 * Author: 3urobeat * - * Last Modified: 04.07.2023 23:04:19 + * Last Modified: 24.07.2023 19:43:58 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -157,7 +157,7 @@ DataManager.prototype.writeLogininfoToDisk = function() { DataManager.prototype.writeProxiesToDisk = function() { logger("debug", "DataManager dataExport: Writing to proxies.txt..."); - let comment = "//Comment: This file is used to provide proxies to spread your accounts over multiple IPs. Read the instructions here: https://github.com/3urobeat/steam-comment-service-bot/wiki/Adding-proxies"; + let comment = "//Comment: This file is used to provide proxies to spread your accounts over multiple IPs. Read the instructions here: https://github.com/3urobeat/steam-comment-service-bot/blob/master/docs/wiki/adding_proxies.md"; fs.writeFile(srcdir + "/../proxies.txt", comment + this.proxies.join("\n"), (err) => { if (err) logger("error", "DataManager: Error writing proxies to proxies.txt: " + err); diff --git a/src/dataManager/dataImport.js b/src/dataManager/dataImport.js index e6f3515a..f5d4b305 100644 --- a/src/dataManager/dataImport.js +++ b/src/dataManager/dataImport.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 04.07.2023 22:47:47 + * Last Modified: 26.07.2023 17:07:58 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -32,6 +32,8 @@ DataManager.prototype._importFromDisk = async function () { function loadCache() { return new Promise((resolve) => { try { + delete require.cache[require.resolve(srcdir + "/data/cache.json")]; // Delete cache to enable reloading data + resolve(require(srcdir + "/data/cache.json")); } catch (err) { if (err) { @@ -59,6 +61,8 @@ DataManager.prototype._importFromDisk = async function () { function loadData() { return new Promise((resolve) => { try { + delete require.cache[require.resolve(srcdir + "/data/data.json")]; // Delete cache to enable reloading data + resolve(require(srcdir + "/data/data.json")); } catch (err) { if (err) { @@ -77,6 +81,8 @@ DataManager.prototype._importFromDisk = async function () { function loadConfig() { return new Promise((resolve) => { try { + delete require.cache[require.resolve(srcdir + "/../config.json")]; // Delete cache to enable reloading data + resolve(require(srcdir + "/../config.json")); } catch (err) { if (err) { @@ -113,6 +119,8 @@ DataManager.prototype._importFromDisk = async function () { function loadAdvancedConfig() { return new Promise((resolve) => { try { + delete require.cache[require.resolve(srcdir + "/../advancedconfig.json")]; // Delete cache to enable reloading data + resolve(require(srcdir + "/../advancedconfig.json")); } catch (err) { if (err) { @@ -166,6 +174,8 @@ DataManager.prototype._importFromDisk = async function () { try { // Only check if file exists (it is not shipped by default anymore since 2.12.1). If it doesn't an empty obj will be returned, leading to empty logininfo err msg in checkData() if (fs.existsSync("./logininfo.json")) { + delete require.cache[require.resolve(srcdir + "/../logininfo.json")]; // Delete cache to enable reloading data + logininfo = require(srcdir + "/../logininfo.json"); // Reformat to use new logininfo object structure and use accountName as key instead of bot0 etc to allow the order to change @@ -263,6 +273,8 @@ DataManager.prototype._importFromDisk = async function () { function loadLanguage() { return new Promise((resolve) => { try { + delete require.cache[require.resolve(srcdir + "/data/lang/defaultlang.json")]; // Delete cache to enable reloading data + resolve(require(srcdir + "/data/lang/defaultlang.json")); } catch (err) { if (err) { @@ -285,6 +297,8 @@ DataManager.prototype._importFromDisk = async function () { // Try importing customlang.json try { + delete require.cache[require.resolve(srcdir + "/../customlang.json")]; // Delete cache to enable reloading data + customlang = require(srcdir + "/../customlang.json"); } catch (err) { logger("error", "It seems like you made a mistake (probably Syntax) in your customlang.json! I will not use any custom message.\nError: " + err); diff --git a/src/dataManager/dataManager.js b/src/dataManager/dataManager.js index a9125d4e..e3732084 100644 --- a/src/dataManager/dataManager.js +++ b/src/dataManager/dataManager.js @@ -4,7 +4,7 @@ * Created Date: 21.03.2023 22:34:51 * Author: 3urobeat * - * Last Modified: 04.07.2023 22:26:01 + * Last Modified: 26.07.2023 16:37:37 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -79,7 +79,7 @@ const DataManager = function (controller) { /** * Stores the login information for every bot account provided via the `logininfo.json` or `accounts.txt` files. - * @type {{[key: string]: { accountName: string, password: string, sharedSecret: string, steamGuardCode: null, machineName: string, deviceFriendlyName: string }}} + * @type {{[key: string]: { accountName: string, password: string, sharedSecret?: string, steamGuardCode?: null, machineName?: string, deviceFriendlyName?: string }}} */ this.logininfo = {}; @@ -104,6 +104,9 @@ const DataManager = function (controller) { */ this.tokensDB = {}; + // Stores a reference to the active handleExpiringTokens interval to prevent duplicates on reloads + this._handleExpiringTokensInterval = null; + // Dynamically load all helper files const loadHelpersFromFolder = (folder) => { fs.readdirSync(folder).forEach(async (file) => { diff --git a/src/dataManager/helpers/handleCooldowns.js b/src/dataManager/helpers/handleCooldowns.js index 46278db2..47ef6e0c 100644 --- a/src/dataManager/helpers/handleCooldowns.js +++ b/src/dataManager/helpers/handleCooldowns.js @@ -4,7 +4,7 @@ * Created Date: 13.04.2023 17:58:23 * Author: 3urobeat * - * Last Modified: 04.07.2023 17:51:07 + * Last Modified: 10.07.2023 12:47:30 * Modified By: 3urobeat * * Copyright (c) 2023 3urobeat @@ -35,7 +35,7 @@ DataManager.prototype.getUserCooldown = function(id) { this.lastCommentDB.findOne({ id: id }, (err, doc) => { if (!doc) { // Check if no entry was found and BAIL THE FUCK OUT - logger("warn", `User '${id}' has no lastComment database entry! Permitting request and hoping an entry will be inserted afterwards.\n If this warning appears multiple times for the same user you need to take action. Need help? https://github.com/3urobeat/steam-comment-service-bot/issues/new/choose`); + logger("warn", `User '${id}' has no lastComment database entry! Permitting request and hoping an entry will be inserted afterwards.\n If this warning appears multiple times for the same user you need to take action. Need help? https://github.com/3urobeat/steam-comment-service-bot/issues/new/choose\n If this warning is caused by a plugin for a userID from outside the Steam Chat for their first request you can ignore this warning as the behavior is to be expected.`); return resolve(obj); } @@ -93,6 +93,8 @@ DataManager.prototype.getUserCooldown = function(id) { * @param {number} timestamp Unix timestamp of the last interaction the user received */ DataManager.prototype.setUserCooldown = function(id, timestamp) { + if (!id) return logger("error", "CRITICAL: setUserCooldown was called without an ID! I am unable to apply a cooldown for this user!\n Not fixing this error will render the cooldown system useless and exposes your accounts to the risk of getting banned!"); + logger("debug", `DataManager setUserCooldown(): Updating lastcomment db entry for ${id} to ${timestamp}.`); this.lastCommentDB.update({ id: id }, { $set: { time: timestamp } }, { upsert: true }, (err) => { diff --git a/src/dataManager/helpers/handleExpiringTokens.js b/src/dataManager/helpers/handleExpiringTokens.js index 7b56108d..fd45aa36 100644 --- a/src/dataManager/helpers/handleExpiringTokens.js +++ b/src/dataManager/helpers/handleExpiringTokens.js @@ -4,7 +4,7 @@ * Created Date: 14.10.2022 14:58:25 * Author: 3urobeat * - * Last Modified: 04.07.2023 20:00:28 + * Last Modified: 26.07.2023 16:53:28 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -72,7 +72,7 @@ DataManager.prototype._startExpiringTokensCheckInterval = function() { _this.cachefile.ownerid.forEach((e, i) => { setTimeout(() => { // eslint-disable-next-line no-control-regex - _this.controller.main.sendChatMessage(_this.controller.main, { steamID64: e }, msg.replace(/\x1B\[[0-9]+m/gm, "") + "!\nHead over to the terminal to refresh the token(s) now if you wish."); // Remove color codes from string + _this.controller.main.sendChatMessage(_this.controller.main, { userID: e }, msg.replace(/\x1B\[[0-9]+m/gm, "") + "!\nHead over to the terminal to refresh the token(s) now if you wish."); // Remove color codes from string }, 1500 * i); }); @@ -83,10 +83,13 @@ DataManager.prototype._startExpiringTokensCheckInterval = function() { }); } + // Clear existing interval if there is one + if (this._handleExpiringTokensInterval) clearInterval(this._handleExpiringTokensInterval); + // Set interval to scan every 24 hours let lastScanTime = Date.now(); - setInterval(() => { + this._handleExpiringTokensInterval = setInterval(() => { if (lastScanTime + 86400000 > Date.now()) return; // Abort if last run was less than 24h ago scanDatabase(); diff --git a/src/libraryPatches/CSteamSharedFile.js b/src/libraryPatches/CSteamSharedFile.js index 109e4a88..a1a3eca2 100644 --- a/src/libraryPatches/CSteamSharedFile.js +++ b/src/libraryPatches/CSteamSharedFile.js @@ -120,18 +120,20 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) { // Determine type by looking at the second breadcrumb. Find the first separator as it has a unique name and go to the next element which holds our value of interest - let breadcrumb = $(".breadcrumbs > .breadcrumb_separator").next().get(0).children[0].data || ""; + let breadcrumb = $(".breadcrumbs > .breadcrumb_separator").next().get(0) || $(".breadcrumbs").get(0).children[1]; // Some artworks only have one breadcrumb like "username's Artwork" so let's check that as a backup - if (breadcrumb.includes("Screenshot")) { - sharedfile.type = ESharedFileType.Screenshot; - } + if (breadcrumb) { // If neither could be found then leave type at null + if (breadcrumb.children[0].data.includes("Screenshot")) { + sharedfile.type = ESharedFileType.Screenshot; + } - if (breadcrumb.includes("Artwork")) { - sharedfile.type = ESharedFileType.Artwork; - } + if (breadcrumb.children[0].data.includes("Artwork")) { + sharedfile.type = ESharedFileType.Artwork; + } - if (breadcrumb.includes("Guide")) { - sharedfile.type = ESharedFileType.Guide; + if (breadcrumb.children[0].data.includes("Guide")) { + sharedfile.type = ESharedFileType.Guide; + } } diff --git a/src/libraryPatches/README.md b/src/libraryPatches/README.md index bd78cfae..97f42257 100644 --- a/src/libraryPatches/README.md +++ b/src/libraryPatches/README.md @@ -13,7 +13,8 @@ These are the patches being applied: - Re-enable primaryGroup profile setting: [#287](https://github.com/DoctorMcKay/node-steamcommunity/pull/287) & [#307](https://github.com/DoctorMcKay/node-steamcommunity/pull/307) - Add sharedfiles voteUp & voteDown support - Fix resolving vanity for private profiles returning error: [#315](https://github.com/DoctorMcKay/node-steamcommunity/pull/315) -- (Potentially) Fix sharedfile data scraping failing as a non-english page was returned by Steam +- Fix sharedfile data scraping failing as a non-english page was returned by Steam: [#316](https://github.com/DoctorMcKay/node-steamcommunity/pull/316) +- Fix scraping sharedfile type failing when incomplete breadcrumb was returned: [#316](https://github.com/DoctorMcKay/node-steamcommunity/pull/316) These patches have been applied in the past: - Add full sharedfiles support: [#306](https://github.com/DoctorMcKay/node-steamcommunity/pull/306) diff --git a/src/updater/helpers/prepareUpdate.js b/src/updater/helpers/prepareUpdate.js index f6b46344..0f05ba63 100644 --- a/src/updater/helpers/prepareUpdate.js +++ b/src/updater/helpers/prepareUpdate.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 04.07.2023 20:16:06 + * Last Modified: 09.07.2023 13:31:38 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -22,7 +22,7 @@ const Controller = require("../../controller/controller.js"); // eslint-disable- * Wait for active requests and log off all bot accounts * @param {Controller} controller Reference to the controller object * @param {function(object, string): void} respondModule If defined, this function will be called with the result of the check. This allows to integrate checking for updates into commands or plugins. Passes resInfo and txt as parameters. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {import("../../commands/commandHandler.js").resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). * @returns {Promise.} Resolves when we can proceed */ module.exports.run = (controller, respondModule, resInfo) => { diff --git a/src/updater/updater.js b/src/updater/updater.js index 6839ae38..bf9be4c0 100644 --- a/src/updater/updater.js +++ b/src/updater/updater.js @@ -4,7 +4,7 @@ * Created Date: 09.07.2021 16:26:00 * Author: 3urobeat * - * Last Modified: 04.07.2023 20:17:07 + * Last Modified: 09.07.2023 13:31:31 * Modified By: 3urobeat * * Copyright (c) 2021 3urobeat @@ -49,7 +49,7 @@ module.exports = Updater; * Checks for any available update and installs it. * @param {boolean} forceUpdate If true an update will be forced, even if disableAutoUpdate is true or the newest version is already installed * @param {function(object, string): void} respondModule If defined, this function will be called with the result of the check. This allows to integrate checking for updates into commands or plugins. Passes resInfo and txt as parameters. - * @param {object} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). + * @param {import("../commands/commandHandler.js").resInfo} resInfo Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). * @returns {Promise.} Promise that will be resolved with false when no update was found or with true when the update check or download was completed. Expect a restart when true was returned. */ Updater.prototype.run = function(forceUpdate, respondModule, resInfo) { diff --git a/types/types.d.ts b/types/types.d.ts index e86d5e11..d854071a 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -51,7 +51,6 @@ declare class Bot { /** * Our commandHandler respondModule implementation - Sends a message to a Steam user * @param _this - The Bot object context - * @param resInfo - Object containing information passed to command by friendMessage event * @param txt - The text to send * @param retry - Internal: true if this message called itself again to send failure message * @param part - Internal: Index of which part to send for messages larger than 750 chars @@ -114,7 +113,6 @@ declare class Bot { /** * Our commandHandler respondModule implementation - Sends a message to a Steam user * @param _this - The Bot object context - * @param resInfo - Object containing information passed to command by friendMessage event * @param txt - The text to send * @param retry - Internal: true if this message called itself again to send failure message * @param part - Internal: Index of which part to send for messages larger than 750 chars @@ -196,19 +194,37 @@ declare class CommandHandler { * Finds a loaded command by name and runs it * @param name - The name of the command * @param args - Array of arguments that will be passed to the command - * @param steamID64 - SteamID64 of the requesting user which is used to check for ownerOnly and will be passed to the command * @param respondModule - Function that will be called to respond to the user's request. Passes context, resInfo and txt as parameters. - * @param context - The context (this.) of the object calling this command. Will be passed to respondModule() as first parameter. - * @param resInfo - Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). Please also include a "cmdprefix" key & value pair if your command handler uses a prefix other than "!". + * @param context - The context (`this.`) of the object calling this command. Will be passed to respondModule() as first parameter to make working in this function easier. + * @param resInfo - Object containing additional information * @returns `true` if command was found, `false` if not */ - runCommand(name: string, args: any[], steamID64: number, respondModule: (...params: any[]) => any, context: any, resInfo: any): boolean; + runCommand(name: string, args: any[], respondModule: (...params: any[]) => any, context: any, resInfo: resInfo): boolean; /** * Reloads all core commands. Does NOT reload commands registered at runtime. Please consider reloading the pluginSystem as well. */ reloadCommands(): void; } +/** + * @property [cmdprefix] - Prefix your command execution handler uses. This will be used in response messages referencing commands. Default: ! + * @property userID - ID of the user who executed this command. Will be used for command default behavior (e.g. commenting on the requester's profile), to check for owner privileges, apply cooldowns and maybe your respondModule implementation for responding. Strongly advised to include. + * @property [ownerIDs] - Can be provided to overwrite `config.ownerid` for owner privilege checks. Useful if you are implementing a different platform and so `userID` won't be a steamID64 (e.g. discord) + * @property [charLimit] - Supported by the Steam Chat Message handler: Overwrites the default index from which response messages will be cut up into parts + * @property [cutChars] - Custom chars to search after for cutting string in parts to overwrite cutStringsIntelligently's default: [" ", "\n", "\r"] + * @property [fromSteamChat] - Set to true if your command handler is receiving messages from the Steam Chat and `userID` is therefore a `steamID64`. Will be used to enable command default behavior (e.g. commenting on the requester's profile) + * @property [prefix] - Do not provide this argument, you'll receive it from commands: Steam Chat Message prefixes like /me. Can be ignored or translated to similar prefixes your platform might support + */ +declare type resInfo = { + cmdprefix?: string; + userID: string; + ownerIDs?: string[]; + charLimit?: number; + cutChars?: string[]; + fromSteamChat?: boolean; + prefix?: string; +}; + /** * Internal: Do the actual commenting, activeRequests entry with all relevant information was processed by the comment command function above. * @param commandHandler - The commandHandler object @@ -218,7 +234,7 @@ declare class CommandHandler { * @param commentArgs - All arguments this postComment function needs, without callback. It will be applied and a callback added as last param. Include a key called "quote" to dynamically replace it with a random quote. * @param receiverSteamID64 - steamID64 of the profile to receive the comments */ -declare function comment(commandHandler: CommandHandler, resInfo: any, respond: (...params: any[]) => any, postComment: (...params: any[]) => any, commentArgs: any, receiverSteamID64: string): void; +declare function comment(commandHandler: CommandHandler, resInfo: CommandHandler.resInfo, respond: (...params: any[]) => any, postComment: (...params: any[]) => any, commentArgs: any, receiverSteamID64: string): void; /** * Retrieves arguments from a comment request. If request is invalid (for example too many comments requested) an error message will be sent @@ -229,7 +245,7 @@ declare function comment(commandHandler: CommandHandler, resInfo: any, respond: * @param respond - The shortened respondModule call * @returns Resolves promise with object containing all relevant data when done */ -declare function getCommentArgs(commandHandler: CommandHandler, args: any[], requesterSteamID64: string, resInfo: any, respond: (...params: any[]) => any): Promise<{ maxRequestAmount: number; commentcmdUsage: string; numberOfComments: number; profileID: string; idType: string; quotesArr: string[]; }>; +declare function getCommentArgs(commandHandler: CommandHandler, args: any[], requesterSteamID64: string, resInfo: CommandHandler.resInfo, respond: (...params: any[]) => any): Promise<{ maxRequestAmount: number; commentcmdUsage: string; numberOfComments: number; profileID: string; idType: string; quotesArr: string[]; }>; /** * Finds all needed and currently available bot accounts for a comment request. @@ -261,7 +277,7 @@ declare function getAvailableBotsForFavorizing(commandHandler: CommandHandler, a * @param respond - The shortened respondModule call * @returns If the user provided a specific amount, amount will be a number. If user provided "all" or "max", it will be returned as an unmodified string for getVoteBots.js to handle */ -declare function getSharedfileArgs(commandHandler: CommandHandler, args: any[], cmd: string, resInfo: any, respond: (...params: any[]) => any): Promise<{ amount: number | string; id: string; }>; +declare function getSharedfileArgs(commandHandler: CommandHandler, args: any[], cmd: string, resInfo: CommandHandler.resInfo, respond: (...params: any[]) => any): Promise<{ amount: number | string; id: string; }>; /** * Finds all needed and currently available bot accounts for a vote request. @@ -592,7 +608,7 @@ declare function checkConnection(url: string, throwTimeout: boolean): Promise<{ * @param threshold - Optional: Maximum amount that limit can be reduced to find the last space or line break. If no match is found within this limit a word will be cut. Default: 15% of total length * @returns Returns all parts of the string in an array */ -declare function cutStringsIntelligently(txt: string, limit: number, cutChars: any[], threshold: number): any[]; +declare function cutStringsIntelligently(txt: string, limit: number, cutChars: string[], threshold: number): any[]; /** * Attempts to reinstall all modules @@ -1265,7 +1281,6 @@ declare class Updater { * Checks for any available update and installs it. * @param forceUpdate - If true an update will be forced, even if disableAutoUpdate is true or the newest version is already installed * @param respondModule - If defined, this function will be called with the result of the check. This allows to integrate checking for updates into commands or plugins. Passes resInfo and txt as parameters. - * @param resInfo - Object containing additional information your respondModule might need to process the response (for example the userID who executed the command). * @returns Promise that will be resolved with false when no update was found or with true when the update check or download was completed. Expect a restart when true was returned. */ run(forceUpdate: boolean, respondModule: (...params: any[]) => any, resInfo: any): Promise;