From e911a0ba75ca0c152619544e0f32f4376a195588 Mon Sep 17 00:00:00 2001 From: 3urobeat <35304405+HerrEurobeat@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:08:25 +0200 Subject: [PATCH 1/5] Added eslint config and enforced styling --- .eslintrc.json | 43 ++++++++++++++++++ .gitignore | 1 - index.js | 10 ++--- src/bot.js | 78 ++++++++++++++++----------------- src/helpers/comment.js | 24 +++++----- src/helpers/getQuote.js | 26 +++++------ src/helpers/loadDestinations.js | 52 +++++++++++----------- 7 files changed, 138 insertions(+), 96 deletions(-) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..fc22d25 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,43 @@ +{ + "env": { + "commonjs": true, + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly", + + "started": true, + "srcdir": true, + "botisloggedin": true, + "logger": true, + "config": true, + "advancedconfig": true, + "extdata": true, + "cachefile": true + }, + "parserOptions": { + "ecmaVersion": 11 + }, + "rules": { + "no-redeclare": "off", + "no-unreachable": "error", + "no-unexpected-multiline": "error", + + // Styling + "camelcase": "warn", + "capitalized-comments": ["warn", "always", { "ignoreConsecutiveComments": true }], + "comma-spacing": ["warn", { "before": false, "after": true }], + "func-call-spacing": ["error", "never"], + "no-tabs": "error", + "no-trailing-spaces": "error", + "no-extra-semi": "error", + "semi": ["error", "always"], + "semi-spacing": "error", + "semi-style": ["error", "last"], + "quotes": ["error", "double", { "avoidEscape": true }], + "spaced-comment": "warn" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 96deb5b..47a51d1 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ node_modules mass-comment-bot.code-workspace -.eslintrc.json output.txt \ No newline at end of file diff --git a/index.js b/index.js index f791be7..280120d 100644 --- a/index.js +++ b/index.js @@ -3,17 +3,17 @@ * Project: mass-comment-bot * Created Date: 11.08.2019 12:10:00 * Author: 3urobeat - * + * * Last Modified: 23.01.2022 13:36:59 * Modified By: 3urobeat - * + * * Copyright (c) 2022 3urobeat - * + * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see . */ -//Abfahrt! +// Abfahrt! require("./src/bot.js").run(); \ No newline at end of file diff --git a/src/bot.js b/src/bot.js index bf27012..677785a 100644 --- a/src/bot.js +++ b/src/bot.js @@ -3,15 +3,15 @@ * Project: steam-mass-comment-bot * Created Date: 23.01.2022 13:30:05 * Author: 3urobeat - * - * Last Modified: 25.01.2022 13:54:25 + * + * Last Modified: 19.10.2022 17:07:37 * Modified By: 3urobeat - * + * * Copyright (c) 2022 3urobeat - * + * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see . */ @@ -31,16 +31,16 @@ var logininfo; */ module.exports.run = () => { - //Configure my logging library (https://github.com/HerrEurobeat/output-logger#options-1) + // Configure my logging library (https://github.com/HerrEurobeat/output-logger#options-1) logger.options({ msgstructure: `[${logger.Const.ANIMATION}] [${logger.Const.DATE} | ${logger.Const.TYPE}] ${logger.Const.MESSAGE}`, paramstructure: [logger.Const.TYPE, logger.Const.MESSAGE, "nodate", "remove", logger.Const.ANIMATION], outputfile: "./output.txt", animationdelay: 250 - }) + }); + - - const bot = new SteamUser(); + const bot = new SteamUser(); const community = new SteamCommunity(); logger("", "\n", true); @@ -48,67 +48,67 @@ module.exports.run = () => { logger("", "---------------------------------------------------------\n", true); - //Try loading files and show custom error message if unable to do so + // Try loading files and show custom error message if unable to do so try { config = require("../config.json"); } catch (err) { - logger("error", `Error trying to read config.json! Did you make a syntax mistake?\n Please follow the syntax of the template exactly as explained here: https://github.com/HerrEurobeat/steam-mass-comment-bot#setup. Exiting...`, true); + logger("error", "Error trying to read config.json! Did you make a syntax mistake?\n Please follow the syntax of the template exactly as explained here: https://github.com/HerrEurobeat/steam-mass-comment-bot#setup. Exiting...", true); process.exit(1); } try { logininfo = require("../logininfo.json"); } catch (err) { - logger("error", `Error trying to read logininfo.json! Did you make a syntax mistake?\n Please follow the syntax of the template exactly. Exiting...`, true); + logger("error", "Error trying to read logininfo.json! Did you make a syntax mistake?\n Please follow the syntax of the template exactly. Exiting...", true); process.exit(1); } - - //Display commentdelay warning message if too low - if (config.commentdelay < 5000) logger("warn", "I strongly advise not setting the commentdelay below 5000ms!\n You might be running in danger of getting banned for spamming or will at least get a cooldown rather quickly.") + + // Display commentdelay warning message if too low + if (config.commentdelay < 5000) logger("warn", "I strongly advise not setting the commentdelay below 5000ms!\n You might be running in danger of getting banned for spamming or will at least get a cooldown rather quickly."); if (config.commentdelay <= 500) { logger("error", "Your commentdelay is way to low! This will either give you a cooldown instantly or outright ban you for spamming! Aborting..."); process.exit(1); } - //Start logging in + // Start logging in logger("info", "Logging in...", false, false); bot.logOn({ accountName: logininfo.accountName, password: logininfo.password - }) + }); bot.on("loggedOn", () => { logger("", "", true); logger("info", "Account logged in!"); - //start playing games if enabled - if (config.playingGames.length > 0) bot.gamesPlayed(config.playingGames) + // Start playing games if enabled + if (config.playingGames.length > 0) bot.gamesPlayed(config.playingGames); - //Get ids + // Get ids logger("info", "Getting profile & group ids from URLs in config...", false, true, logger.animation("loading")); var loadDestinations = require("./helpers/loadDestinations.js"); - loadDestinations.loadProfiles(logger, (profiles) => { //sorry for the slight callback hell that is now coming + loadDestinations.loadProfiles(logger, (profiles) => { // Sorry for the slight callback hell that is now coming loadDestinations.loadGroups(logger, (groups) => { require("./helpers/getQuote.js").getQuote(logger, (quotes) => { - //Check if nothing was found to comment on + // Check if nothing was found to comment on if (profiles.length == 0 && groups.length == 0) { logger("error", "No profiles and groups found to comment on/in! Exiting..."); process.exit(1); } - //Check if no quotes were provided + // Check if no quotes were provided if (quotes.length == 0) { logger("error", "No comments found in comments.txt! Please provide messages I can choose from in comments.txt! Exiting..."); process.exit(1); } - //Show ready message + // Show ready message logger("", "", true); logger("", "*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*", true); logger("", `> Logged in as ${logininfo.accountName} and loaded ${profiles.length + groups.length} IDs!`, true); @@ -118,7 +118,7 @@ module.exports.run = () => { logger("", "", true); - //Start commenting on profiles + // Start commenting on profiles setTimeout(() => { if (profiles.length > 0) logger("info", `Starting to comment on ${profiles.length} profiles...`); @@ -126,11 +126,11 @@ module.exports.run = () => { commentFile.commentProfile(profiles, quotes, logger, community, (failedProfiles) => { if (groups.length > 0) logger("info", "Starting to comment on groups..."); - + setTimeout(() => { commentFile.commentGroup(groups, quotes, logger, community, (failedGroups) => { logger("info", "Finished commenting!\n"); - + if (failedProfiles.length > 0) { logger("info", "Failed profiles: \n" + failedProfiles.join("\n")); logger("", "", true); @@ -139,7 +139,7 @@ module.exports.run = () => { logger("info", "Failed groups: \n" + failedGroups.join("\n")); logger("", "", true); } - + logger("info", "Exiting..."); process.exit(0); }); @@ -151,24 +151,24 @@ module.exports.run = () => { }); }); - - //Set cookies to be able to comment later - bot.on("webSession", (sessionID, cookies) => { + + // Set cookies to be able to comment later + bot.on("webSession", (sessionID, cookies) => { community.setCookies(cookies); }); - //Respond with afkMessage if enabled in config - bot.on('friendMessage', (steamID, message) => { - var steamID64 = new SteamID(String(steamID)).getSteamID64() + // Respond with afkMessage if enabled in config + bot.on("friendMessage", (steamID, message) => { + var steamID64 = new SteamID(String(steamID)).getSteamID64(); - logger("info", `Friend message from ${steamID64}: ${message}`) + logger("info", `Friend message from ${steamID64}: ${message}`); if (config.afkMessage.length > 0) { - logger("info", "Responding with: " + config.afkMessage) + logger("info", "Responding with: " + config.afkMessage); - bot.chat.sendFriendMessage(steamID, config.afkMessage) + bot.chat.sendFriendMessage(steamID, config.afkMessage); } - + }); -} +}; diff --git a/src/helpers/comment.js b/src/helpers/comment.js index 9286f77..d01117a 100644 --- a/src/helpers/comment.js +++ b/src/helpers/comment.js @@ -3,15 +3,15 @@ * Project: steam-mass-comment-bot * Created Date: 23.01.2022 16:32:05 * Author: 3urobeat - * + * * Last Modified: 25.01.2022 14:05:07 * Modified By: 3urobeat - * + * * Copyright (c) 2022 3urobeat - * + * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see . */ @@ -41,7 +41,7 @@ module.exports.commentProfile = (profiles, quotes, logger, community, callback) community.postUserComment(e, randomstring(quotes), (err) => { if (err) { - logger("warn", `Comment on profile ${e} failed! Error: ${err}`) + logger("warn", `Comment on profile ${e} failed! Error: ${err}`); failedProfiles.push(e); if (err.includes("HTTP error 429") || err.includes("You've been posting too frequently, and can't make another post right now")) { @@ -51,12 +51,12 @@ module.exports.commentProfile = (profiles, quotes, logger, community, callback) } } - //Check if we processed all profiles and make a callback + // Check if we processed all profiles and make a callback if (profiles.length == i + 1) callback(failedProfiles); - }) + }); }, config.commentdelay * i); }); -} +}; /** @@ -78,7 +78,7 @@ module.exports.commentProfile = (profiles, quotes, logger, community, callback) community.postGroupComment(e, randomstring(quotes), (err) => { if (err) { - logger("warn", `Comment in group ${e} failed! Error: ${err}`) + logger("warn", `Comment in group ${e} failed! Error: ${err}`); failedGroups.push(e); if (err.includes("HTTP error 429") || err.includes("You've been posting too frequently, and can't make another post right now")) { @@ -88,9 +88,9 @@ module.exports.commentProfile = (profiles, quotes, logger, community, callback) } } - //Check if we processed all profiles and make a callback + // Check if we processed all profiles and make a callback if (groups.length == i + 1) callback(failedGroups); - }) + }); }, config.commentdelay * i); }); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/helpers/getQuote.js b/src/helpers/getQuote.js index 50ada4f..98a96f5 100644 --- a/src/helpers/getQuote.js +++ b/src/helpers/getQuote.js @@ -3,15 +3,15 @@ * Project: steam-mass-comment-bot * Created Date: 25.01.2022 11:39:11 * Author: 3urobeat - * + * * Last Modified: 25.01.2022 11:58:08 * Modified By: 3urobeat - * + * * Copyright (c) 2022 3urobeat - * + * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see . */ @@ -21,25 +21,25 @@ const fs = require("fs"); * Gets a random quote from comments.txt * @param {function} [callback] Called with `quotes` (Array) on completion. */ -module.exports.getQuote = (logger, callback) => { +module.exports.getQuote = (logger, callback) => { logger("info", "Loading quotes from comments.txt...", false, true); - var quotes = [] - var quotes = fs.readFileSync("./comments.txt", "utf8").split("\n") //get all quotes from the quotes.txt file into an array - var quotes = quotes.filter(str => str != "") //remove empty quotes as empty comments will not work/make no sense + var quotes = []; + var quotes = fs.readFileSync("./comments.txt", "utf8").split("\n"); // Get all quotes from the quotes.txt file into an array + var quotes = quotes.filter(str => str != ""); // Remove empty quotes as empty comments will not work/make no sense - quotes.forEach((e, i) => { //multi line strings that contain \n will get splitted to \\n -> remove second \ so that node-steamcommunity understands the quote when commenting + quotes.forEach((e, i) => { // Multi line strings that contain \n will get splitted to \\n -> remove second \ so that node-steamcommunity understands the quote when commenting if (e.length > 999) { logger("warn", `The quote.txt line ${i} is longer than the limit of 999 characters. This quote will be ignored for now.`, true, false); - quotes.splice(i, 1); //remove this item from the array + quotes.splice(i, 1); // Remove this item from the array return; } quotes[i] = e.replace(/\\n/g, "\n").replace("\\n", "\n"); - //make callback on last iteration + // Make callback on last iteration if (quotes.length <= i + 1) { callback(quotes); } - }) -} \ No newline at end of file + }); +}; \ No newline at end of file diff --git a/src/helpers/loadDestinations.js b/src/helpers/loadDestinations.js index bc7f417..6cb8c06 100644 --- a/src/helpers/loadDestinations.js +++ b/src/helpers/loadDestinations.js @@ -3,15 +3,15 @@ * Project: steam-mass-comment-bot * Created Date: 23.01.2022 15:28:34 * Author: 3urobeat - * + * * Last Modified: 24.01.2022 16:45:14 * Modified By: 3urobeat - * + * * Copyright (c) 2022 3urobeat - * + * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see . */ @@ -36,38 +36,38 @@ module.exports.loadProfiles = (logger, callback) => { setTimeout(() => { steamidResolver.customUrlTosteamID64(e, (err, steamID64) => { if (err) { - logger("err", `I couldn't get the ID of ${e}. It will be excluded. Error: ${err}`) + logger("err", `I couldn't get the ID of ${e}. It will be excluded. Error: ${err}`); skipped.push(e); } - + profiles.push(steamID64); - }) + }); }, 500 * resolverCalls); } else { if (e != "") profiles.push(e); else emptyStr++; } - }) - + }); + var profileInterval = setInterval(() => { - + if (profiles.length + skipped.length + emptyStr == config.profiles.length) { clearInterval(profileInterval); - //Write result to make it + // Write result to make it config.profiles = profiles; skipped.map(e => config.profiles.push(e)); fs.writeFile("./config.json", JSON.stringify(config, null, 4), (err) => { - if (err) logger("err", "Error writing resolved profile ids to config: " + err) + if (err) logger("err", "Error writing resolved profile ids to config: " + err); callback(profiles); - }) + }); } }, 500); -} +}; /** * Loads groups from config and converts them, if necessary, to steamid64 @@ -86,36 +86,36 @@ module.exports.loadProfiles = (logger, callback) => { setTimeout(() => { steamidResolver.groupUrlToGroupID64(e, (err, groupID64) => { if (err) { - logger("err", `I couldn't get the ID of ${e}. It will be excluded. Error: ${err}`) + logger("err", `I couldn't get the ID of ${e}. It will be excluded. Error: ${err}`); skipped.push(e); } - + groups.push(groupID64); - }) + }); }, 500 * resolverCalls); } else { if (e != "") groups.push(e); else emptyStr++; } - }) + }); + - var groupInterval = setInterval(() => { - + if (groups.length + skipped.length + emptyStr == config.groups.length) { clearInterval(groupInterval); - //Write result to make it + // Write result to make it config.groups = groups; skipped.map(e => config.groups.push(e)); fs.writeFile("./config.json", JSON.stringify(config, null, 4), (err) => { - if (err) logger("err", "Error writing resolved group ids to config: " + err) + if (err) logger("err", "Error writing resolved group ids to config: " + err); callback(groups); - }) + }); } - + }, 500); - -} \ No newline at end of file + +}; \ No newline at end of file From 7db398639968914d58396c0eaa4a5c9868608c0d Mon Sep 17 00:00:00 2001 From: 3urobeat <35304405+HerrEurobeat@users.noreply.github.com> Date: Tue, 25 Oct 2022 10:38:08 +0200 Subject: [PATCH 2/5] Added support for Steam's new login flow and updated dependencies --- package-lock.json | 262 ++++++++++++++++-- package.json | 8 +- src/sessions/events/sessionEvents.js | 50 ++++ src/sessions/helpers/handle2FA.js | 98 +++++++ .../helpers/handleCredentialsLoginError.js | 35 +++ src/sessions/helpers/tokenStorageHandler.js | 97 +++++++ src/sessions/sessionHandler.js | 122 ++++++++ src/tokens.db | 0 8 files changed, 648 insertions(+), 24 deletions(-) create mode 100644 src/sessions/events/sessionEvents.js create mode 100644 src/sessions/helpers/handle2FA.js create mode 100644 src/sessions/helpers/handleCredentialsLoginError.js create mode 100644 src/sessions/helpers/tokenStorageHandler.js create mode 100644 src/sessions/sessionHandler.js create mode 100644 src/tokens.db diff --git a/package-lock.json b/package-lock.json index 1caf423..e5f1ca2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "2.0.0", "license": "GPL-3.0", "dependencies": { - "output-logger": "^2.0.0", - "steam-user": "^4.20.2", - "steamcommunity": "^3.43.1", + "@seald-io/nedb": "^3.1.0", + "output-logger": "^2.2.3", + "steam-session": "^0.0.3-alpha", + "steam-user": "^4.25.0", + "steamcommunity": "^3.44.2", "steamid": "^2.0.0", "steamid-resolver": "^1.2.2" } @@ -104,6 +106,21 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "node_modules/@seald-io/binary-search-tree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz", + "integrity": "sha512-+pYGvPFAk7wUR+ONMOlc6A+LUN4kOCFwyPLjyaeS7wVibADPHWYJNYsNtyIAwjF1AXQkuaXElnIc4XjKt55QZA==" + }, + "node_modules/@seald-io/nedb": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-3.1.0.tgz", + "integrity": "sha512-5G0hCQGJjOelOutvW1l4VD581XMhTPxpj1BUaCWTEM2MPXR9TzIr0MKMnEjnTA5nEKfujPyvVW7iF3etm1/gKQ==", + "dependencies": { + "@seald-io/binary-search-tree": "^1.0.2", + "localforage": "^1.9.0", + "util": "^0.12.4" + } + }, "node_modules/@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", @@ -196,6 +213,28 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -477,6 +516,25 @@ "node": ">=8.0.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -660,6 +718,11 @@ "node": ">=6.9.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -907,6 +970,22 @@ "node": ">=0.6.0" } }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1065,9 +1144,9 @@ } }, "node_modules/output-logger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/output-logger/-/output-logger-2.0.0.tgz", - "integrity": "sha512-uJSiYBboAhyL2VGaLhvdEkkdlTEM45TylLQ3+oCDSA0/btJEgft/wiZ6LPVCDm7AWEf0TL5RHMD4jckCs9tSEw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/output-logger/-/output-logger-2.2.3.tgz", + "integrity": "sha512-jgTDwBp2rj7jxPgxgBupIfvFmOuDCv3UKox5O2qByw2SGHaTuE4CsUH9kgRF4ykhE5Yjaex0MUryPlw5tKeVPQ==", "dependencies": { "fs": "*", "readline": "^1.3.0" @@ -1286,6 +1365,48 @@ "cuint": "^0.2.1" } }, + "node_modules/steam-session": { + "version": "0.0.3-alpha", + "resolved": "https://registry.npmjs.org/steam-session/-/steam-session-0.0.3-alpha.tgz", + "integrity": "sha512-PmgzM6ExQYc3UEBwdQqUMbbLldTCftl9Ibrs0lnhoLofV+IgvlBRr7udMSVpCIcyoFslw7Yw9giE6e9QgWAHWg==", + "dependencies": { + "axios": "^0.27.2", + "node-bignumber": "^1.2.2", + "protobufjs": "^7.1.0", + "steamid": "^2.0.0" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/steam-session/node_modules/long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + }, + "node_modules/steam-session/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/steam-totp": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/steam-totp/-/steam-totp-2.1.1.tgz", @@ -1295,9 +1416,9 @@ } }, "node_modules/steam-user": { - "version": "4.20.2", - "resolved": "https://registry.npmjs.org/steam-user/-/steam-user-4.20.2.tgz", - "integrity": "sha512-mHscRYnsAnzegIyZIBXcagNEyqu66HyIZtjv9oxyR9hM5DUoDWbZxQkeboKmhqBPprpocGHd9e3YZLZcH5aThw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/steam-user/-/steam-user-4.25.0.tgz", + "integrity": "sha512-GKfWxPDtbCeKneCO6Oq7fcdfCZj7hs8Yt8mAalU9tf1rbGcRxnx0tFxZ15FNWiA8YrUvC/ZJAgNuZnMpSUzDnA==", "dependencies": { "@bbob/parser": "^2.2.0", "@doctormckay/stdlib": "^1.11.1", @@ -1328,9 +1449,9 @@ } }, "node_modules/steamcommunity": { - "version": "3.43.1", - "resolved": "https://registry.npmjs.org/steamcommunity/-/steamcommunity-3.43.1.tgz", - "integrity": "sha512-Nx+Iwas1EWpSIwEEfGw5jLm6Jrl0W/VL+0Ms9gRLnfqH8Ca1PEba7mDhar/Ny4RUdSmgrfy1ZBV99csjkZ5bog==", + "version": "3.44.2", + "resolved": "https://registry.npmjs.org/steamcommunity/-/steamcommunity-3.44.2.tgz", + "integrity": "sha512-plrl5TdWEJI/tkiK3u3mIzFap+1kkHWT/GmBrScN8TXdsFCdt7cyYTL3EFAp2KkFmQ5dZ0zzS3u+Ss6TyyYodw==", "dependencies": { "async": "^2.6.3", "cheerio": "0.22.0", @@ -1668,6 +1789,21 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "@seald-io/binary-search-tree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz", + "integrity": "sha512-+pYGvPFAk7wUR+ONMOlc6A+LUN4kOCFwyPLjyaeS7wVibADPHWYJNYsNtyIAwjF1AXQkuaXElnIc4XjKt55QZA==" + }, + "@seald-io/nedb": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-3.1.0.tgz", + "integrity": "sha512-5G0hCQGJjOelOutvW1l4VD581XMhTPxpj1BUaCWTEM2MPXR9TzIr0MKMnEjnTA5nEKfujPyvVW7iF3etm1/gKQ==", + "requires": { + "@seald-io/binary-search-tree": "^1.0.2", + "localforage": "^1.9.0", + "util": "^0.12.4" + } + }, "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", @@ -1740,6 +1876,27 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1974,6 +2131,11 @@ "resolved": "https://registry.npmjs.org/file-manager/-/file-manager-2.0.0.tgz", "integrity": "sha512-AX9jtqrrHK9JT4v3J7uMZGkDNiuuG4y4T6LoNm3lKzT/vReLCY8mnRWIpaG2ffNEpJHSkiwKejpu8x8THEYPzg==" }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -2107,6 +2269,11 @@ "queue": "6.0.1" } }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2273,6 +2440,22 @@ "verror": "1.10.0" } }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "requires": { + "immediate": "~3.0.5" + } + }, + "localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "requires": { + "lie": "3.1.1" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2401,9 +2584,9 @@ } }, "output-logger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/output-logger/-/output-logger-2.0.0.tgz", - "integrity": "sha512-uJSiYBboAhyL2VGaLhvdEkkdlTEM45TylLQ3+oCDSA0/btJEgft/wiZ6LPVCDm7AWEf0TL5RHMD4jckCs9tSEw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/output-logger/-/output-logger-2.2.3.tgz", + "integrity": "sha512-jgTDwBp2rj7jxPgxgBupIfvFmOuDCv3UKox5O2qByw2SGHaTuE4CsUH9kgRF4ykhE5Yjaex0MUryPlw5tKeVPQ==", "requires": { "fs": "*", "readline": "^1.3.0" @@ -2577,15 +2760,52 @@ } } }, + "steam-session": { + "version": "0.0.3-alpha", + "resolved": "https://registry.npmjs.org/steam-session/-/steam-session-0.0.3-alpha.tgz", + "integrity": "sha512-PmgzM6ExQYc3UEBwdQqUMbbLldTCftl9Ibrs0lnhoLofV+IgvlBRr7udMSVpCIcyoFslw7Yw9giE6e9QgWAHWg==", + "requires": { + "axios": "^0.27.2", + "node-bignumber": "^1.2.2", + "protobufjs": "^7.1.0", + "steamid": "^2.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + } + } + }, "steam-totp": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/steam-totp/-/steam-totp-2.1.1.tgz", "integrity": "sha512-d+tjnr3wwDkbrKFxjYZ0uK4CSF09oJwCmlGH8SdOlTDkbtBPuNhPKY0XzZxQVltZF6/JkEYj+uz+kBr6UrY7BQ==" }, "steam-user": { - "version": "4.20.2", - "resolved": "https://registry.npmjs.org/steam-user/-/steam-user-4.20.2.tgz", - "integrity": "sha512-mHscRYnsAnzegIyZIBXcagNEyqu66HyIZtjv9oxyR9hM5DUoDWbZxQkeboKmhqBPprpocGHd9e3YZLZcH5aThw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/steam-user/-/steam-user-4.25.0.tgz", + "integrity": "sha512-GKfWxPDtbCeKneCO6Oq7fcdfCZj7hs8Yt8mAalU9tf1rbGcRxnx0tFxZ15FNWiA8YrUvC/ZJAgNuZnMpSUzDnA==", "requires": { "@bbob/parser": "^2.2.0", "@doctormckay/stdlib": "^1.11.1", @@ -2615,9 +2835,9 @@ } }, "steamcommunity": { - "version": "3.43.1", - "resolved": "https://registry.npmjs.org/steamcommunity/-/steamcommunity-3.43.1.tgz", - "integrity": "sha512-Nx+Iwas1EWpSIwEEfGw5jLm6Jrl0W/VL+0Ms9gRLnfqH8Ca1PEba7mDhar/Ny4RUdSmgrfy1ZBV99csjkZ5bog==", + "version": "3.44.2", + "resolved": "https://registry.npmjs.org/steamcommunity/-/steamcommunity-3.44.2.tgz", + "integrity": "sha512-plrl5TdWEJI/tkiK3u3mIzFap+1kkHWT/GmBrScN8TXdsFCdt7cyYTL3EFAp2KkFmQ5dZ0zzS3u+Ss6TyyYodw==", "requires": { "async": "^2.6.3", "cheerio": "0.22.0", diff --git a/package.json b/package.json index cc0b550..42e00b4 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,11 @@ "description": "Comment with a few clicks under a ton of steam profiles & groups!", "main": "index.js", "dependencies": { - "output-logger": "^2.0.0", - "steam-user": "^4.20.2", - "steamcommunity": "^3.43.1", + "@seald-io/nedb": "^3.1.0", + "output-logger": "^2.2.3", + "steam-session": "^0.0.3-alpha", + "steam-user": "^4.25.0", + "steamcommunity": "^3.44.2", "steamid": "^2.0.0", "steamid-resolver": "^1.2.2" }, diff --git a/src/sessions/events/sessionEvents.js b/src/sessions/events/sessionEvents.js new file mode 100644 index 0000000..5849a6a --- /dev/null +++ b/src/sessions/events/sessionEvents.js @@ -0,0 +1,50 @@ +/* + * File: sessionEvents.js + * Project: steam-idler + * Created Date: 09.10.2022 12:52:30 + * Author: 3urobeat + * + * Last Modified: 18.10.2022 19:11:57 + * Modified By: 3urobeat + * + * Copyright (c) 2022 3urobeat + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ + + +const sessionHandler = require("../sessionHandler.js"); + + +sessionHandler.prototype._attachEvents = function() { + + this.session.on("authenticated", () => { // Success + logger("debug", `[${this.thisbot}] getRefreshToken(): Login request successful, '${this.session.accountName}' authenticated. Resolving Promise...`); + + this._resolvePromise(this.session.refreshToken); + }); + + + this.session.on("timeout", () => { // Login attempt took too long, failure + + // TODO: Retry? + + logger("warn", `[${this.thisbot}] Login attempt timed out!`); + + this._resolvePromise(null); + }); + + + this.session.on("error", (err) => { // Failure + logger("error", `Failed to get a session for account '${this.logOnOptions.accountName}'! Error: ${err}`); // Session.accountName is only defined on success + + // TODO: When does this event fire? Do I need to do something else? + // TODO: Retry until advancedconfig.maxLogOnRetries? + + this._resolvePromise(null); + }); + +}; + diff --git a/src/sessions/helpers/handle2FA.js b/src/sessions/helpers/handle2FA.js new file mode 100644 index 0000000..ed2d4f5 --- /dev/null +++ b/src/sessions/helpers/handle2FA.js @@ -0,0 +1,98 @@ +/* + * File: handle2FA.js + * Project: steam-idler + * Created Date: 09.10.2022 12:59:31 + * Author: 3urobeat + * + * Last Modified: 18.10.2022 19:55:47 + * Modified By: 3urobeat + * + * Copyright (c) 2022 3urobeat + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ + + +const SteamSession = require("steam-session"); // Only needed for the enum definitions below + +const sessionHandler = require("../sessionHandler.js"); + + +/** + * Internal - Handles submitting 2FA code + * @param {Object} res Response object from startWithCredentials() promise + */ +sessionHandler.prototype._handle2FA = function(res) { + + logger("debug", `[${this.thisbot}] getRefreshToken(): Received startWithCredentials() actionRequired response. Type: ${res.validActions[0].type} | Detail: ${res.validActions[0].detail}`); + + // Get 2FA code/prompt confirmation from user, mentioning the correct source + switch (res.validActions[0].type) { + case SteamSession.EAuthSessionGuardType.EmailCode: // Type 2 + logger("info", `Please enter the Steam Guard Code from your email address at ${res.validActions[0].detail}. Skipping automatically in 1.5 minutes if you don't respond...`, true); + + this._get2FAUserInput(); + break; + + case SteamSession.EAuthSessionGuardType.DeviceConfirmation: // Type 4 (more convenient than type 3, both can be active at the same time so we check for this one first) + logger("info", "Please confirm this login request in your Steam Mobile App.", false, false, logger.animation("waiting")); + break; + + case SteamSession.EAuthSessionGuardType.DeviceCode: // Type 3 + logger("info", "Please enter the Steam Guard Code from your Steam Mobile App. Skipping automatically in 1.5 minutes if you don't respond...", true); + + this._get2FAUserInput(); + break; + + case SteamSession.EAuthSessionGuardType.EmailConfirmation: // Type 5 + logger("info", "Please confirm this login request via the confirmation email sent to you.", false, false, logger.animation("waiting")); + break; + + default: // Dunno what to do with the other types + logger("error", `Failed to get login session! Unexpected 2FA type ${res.validActions[0].type} for account '${this.logOnOptions.accountName}'! Sorry, I need to skip this account...`); + + this._resolvePromise(null); + return; + } +}; + + +// Helper function to get 2FA code from user and passing it to accept function or skipping account if desired +sessionHandler.prototype._get2FAUserInput = function() { + + let question = `[${this.logOnOptions.accountName}] Steam Guard Code (leave empty and press ENTER to skip account): `; + let timeout = 90000; + + // Ask user for code + logger.readInput(question, timeout, (text) => { + if (!text || text == "") { // No response or manual skip + if (text == null) logger("info", "Skipping account because you didn't respond in 1.5 minutes...", true); // No need to check for main acc as timeout is disabled for it + + logger("info", `[${this.thisbot}] steamGuard input empty, skipping account...`, false, true); + + this._resolvePromise(null); + } else { // User entered code + logger("info", `[${this.thisbot}] Accepting Steam Guard Code...`, false, true); + this._acceptSteamGuardCode(text.toString().trim()); // Pass code to accept function + } + }); +}; + + +// Helper function to make accepting and re-requesting invalid steam guard codes easier +sessionHandler.prototype._acceptSteamGuardCode = function(code) { + + this.session.submitSteamGuardCode(code) + .then(() => { // Success + logger("debug", `[${this.thisbot}] acceptSteamGuardCode(): User supplied correct code, authenticated event should trigger.`); + }) + .catch((err) => { // Invalid code, ask again + logger("warn", `Your code seems to be wrong, please try again or skip this account! ${err}`); + + // Ask user again + this._get2FAUserInput(); + }); + +}; \ No newline at end of file diff --git a/src/sessions/helpers/handleCredentialsLoginError.js b/src/sessions/helpers/handleCredentialsLoginError.js new file mode 100644 index 0000000..9fb951e --- /dev/null +++ b/src/sessions/helpers/handleCredentialsLoginError.js @@ -0,0 +1,35 @@ +/* + * File: handleCredentialsLoginError.js + * Project: steam-idler + * Created Date: 09.10.2022 13:22:39 + * Author: 3urobeat + * + * Last Modified: 18.10.2022 19:14:12 + * Modified By: 3urobeat + * + * Copyright (c) 2022 3urobeat + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ + + +const { EResult } = require("steam-session"); +const sessionHandler = require("../sessionHandler.js"); + + +// Helper function to make handling login errors easier +sessionHandler.prototype._handleCredentialsLoginError = function(err) { + + // Log error message + logger("", "", true); + logger("error", `[${this.thisbot}] Couldn't log in. ${err} (${err.eresult})`, true); + + // Add additional messages for specific errors to hopefully help the user diagnose the cause + if (err.eresult == EResult.InvalidPassword) logger("", `Note: The error "InvalidPassword" (${err.eresult}) can also be caused by a wrong Username or shared_secret!\n Try leaving the shared_secret field empty and check username & password.`, true); + + // Skips account + this._resolvePromise(null); + +}; diff --git a/src/sessions/helpers/tokenStorageHandler.js b/src/sessions/helpers/tokenStorageHandler.js new file mode 100644 index 0000000..32c4b67 --- /dev/null +++ b/src/sessions/helpers/tokenStorageHandler.js @@ -0,0 +1,97 @@ +/* + * File: tokenStorageHandler.js + * Project: steam-idler + * Created Date: 10.10.2022 12:53:20 + * Author: 3urobeat + * + * Last Modified: 18.10.2022 19:15:40 + * Modified By: 3urobeat + * + * Copyright (c) 2022 3urobeat + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ + + +const sessionHandler = require("../sessionHandler.js"); + + +// Helper function which decodes a JsonWebToken - https://stackoverflow.com/a/38552302 +sessionHandler.prototype._decodeJWT = function(token) { + let payload = token.split(".")[1]; // Remove header and signature as we only care about the payload + let decoded = Buffer.from(payload, "base64"); // Decode + + // Try to parse json object + try { + let parsed = JSON.parse(decoded.toString()); + return parsed; + } catch (err) { + logger("err", `Failed to parse JWT from tokens.db! Please report this issue if it keeps occurring! Getting a new session...\nError: ${err}`, true); + return null; + } // No need to invalidate token here as the new session will get a new token and overwrite the existing one anyway +}; + + +/** + * Internal - Attempts to get a token for this account from tokens.db and checks if it's valid + * @param {function} [callback] Called with `refreshToken` (String) on success or `null` on failure + */ +sessionHandler.prototype._getTokenFromStorage = function(callback) { + + // Search tokens database with accountName for a valid token so we can skip creating a new session + this.tokensdb.findOne({ accountName: this.logOnOptions.accountName }, (err, doc) => { + if (err) { + logger("warn", `Database error! Failed to check for existing token for accountName '${this.logOnOptions.accountName}', returning null to get a new session. Please report this issue if it keeps occurring!\nError: ${err}`, true); + return callback(null); + } + + // If we still have a token stored then check if it is still valid + if (doc) { + // Decode the token we've found + let jwtObj = this._decodeJWT(doc.token); + if (!jwtObj) return callback(null); // Get new session if _decodeJWT() failed + + // Define valid until str to use it in log msg + let validUntilStr = `${(new Date(jwtObj.exp * 1000)).toISOString().replace(/T/, " ").replace(/\..+/, "")} (GMT time)`; + + // Compare expire value (unix timestamp in seconds) to current date + if (jwtObj.exp * 1000 > Date.now()) { + logger("info", `[${this.thisbot}] Found valid token until '${validUntilStr}' in tokens.db! Logging in with it to reuse session...`, false, true); + callback(doc.token); + } else { + logger("info", `[${this.thisbot}] Found invalid token in tokens.db. It was valid till '${validUntilStr}'. Logging in with credentials to get a new session...`, false, true); + callback(null); + } + } else { + logger("info", `[${this.thisbot}] No token found in tokens.db. Logging in with credentials to get a new session...`, false, true); + callback(null); + } + }); +}; + + +/** + * Internal - Saves a new token for this account to tokens.db + * @param {String} token The refreshToken to store + */ +sessionHandler.prototype._saveTokenToStorage = function(token) { + logger("debug", `_saveTokenToStorage(): Updating tokens.db entry for accountName '${this.logOnOptions.accountName}'...`); + + // Update db entry for this account. Upsert is enabled so a new doc will be inserted if none exists yet + this.tokensdb.updateAsync({ accountName: this.logOnOptions.accountName }, { $set: { token: token } }, { upsert: true }); +}; + + +/** + * External - Removes a token from tokens.db. Intended to be called from the steam-user login error event when an invalid token was used so the next login attempt will create a new one. + * @param tokensdb tokensdb + * @param thisbot thisbot + * @param {String} accountName Name of the account to invalidate the token of + */ +module.exports.invalidateTokenInStorage = function(tokensdb, thisbot, accountName) { // Tokensdb needs to be passed manually atm as calling the function too fast otherwise fails + logger("debug", `invalidateTokenInStorage(): Removing refreshToken for accountName '${accountName}' from tokens.db...`); + + tokensdb.removeAsync({ accountName: accountName }, { multi: true }); +}; diff --git a/src/sessions/sessionHandler.js b/src/sessions/sessionHandler.js new file mode 100644 index 0000000..00b4011 --- /dev/null +++ b/src/sessions/sessionHandler.js @@ -0,0 +1,122 @@ +/* + * File: sessionHandler.js + * Project: steam-idler + * Created Date: 09.10.2022 12:47:27 + * Author: 3urobeat + * + * Last Modified: 25.10.2022 09:45:40 + * Modified By: 3urobeat + * + * Copyright (c) 2022 3urobeat + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ + + +// This sessionHandler module is a modified version from my Steam Comment Service Bot: https://github.com/HerrEurobeat/steam-comment-service-bot + +const SteamUser = require("steam-user"); // eslint-disable-line +const SteamSession = require("steam-session"); // eslint-disable-line +const nedb = require("@seald-io/nedb"); + + +/** + * Constructor - Object oriented approach for handling session for one account + * @param {SteamUser} bot The bot instance of the calling account + * @param {String} thisbot The thisbot string of the calling account + * @param {Number} loginindex The loginindex of the calling account + * @param {Object} logOnOptions Object containing username, password and optionally steamGuardCode + */ +const sessionHandler = function(bot, thisbot, loginindex, logOnOptions) { + + // Make parameters given to the constructor available + this.bot = bot; + this.thisbot = thisbot; + this.loginindex = loginindex; + this.logOnOptions = logOnOptions; + + // Define vars that will be populated + this.getTokenPromise = null; // Can be called from a helper later on + this.session = null; + + // Load tokens database + this.tokensdb = new nedb({ filename: "./src/tokens.db", autoload: true }); + + // Load helper files + require("./events/sessionEvents"); + require("./helpers/handle2FA.js"); + require("./helpers/handleCredentialsLoginError"); + require("./helpers/tokenStorageHandler.js"); + +}; + +// Make object accessible from outside +module.exports = sessionHandler; + + +/** + * Handles getting a refresh token for steam-user to auth with + * @returns {Promise} `refreshToken` on success or `null` on failure + */ +sessionHandler.prototype.getToken = function() { // I'm not allowed to use arrow styled functions here... (https://stackoverflow.com/questions/59344601/javascript-nodejs-typeerror-cannot-set-property-validation-of-undefined) + return new Promise((resolve) => { + logger("debug", `[${this.thisbot}] getToken(): Created new object for token request`); + + // Save promise resolve function so any other function of this object can resolve the promise itself + this.getTokenPromise = resolve; + + // First ask tokenStorageHandler if we already have a valid token for this account in storage + this._getTokenFromStorage((token) => { + // Instantly resolve promise if we still have a valid token on hand, otherwise start credentials login flow + if (token) { + resolve(token); + } else { + this._attemptCredentialsLogin(); // Start first attempt of logging in + } + }); + }); +}; + + +/** + * Internal - Handles resolving the getToken() promise + * @param {String} token The token to resolve with or null on failure + */ +sessionHandler.prototype._resolvePromise = function(token) { + + // Skip this account if token is null or stop bot if this is the main account + if (!token) { + logger("error", `[${this.thisbot}] Couldn't log in!`); + this.session.cancelLoginAttempt(); // Cancel this login attempt just to be sure + } else { + // Save most recent valid token to tokens.db + this._saveTokenToStorage(token); + } + + this.getTokenPromise(token); +}; + + +/** + * Internal - Attempts to log into account with credentials + */ +sessionHandler.prototype._attemptCredentialsLogin = function() { + + // Init new session + this.session = new SteamSession.LoginSession(SteamSession.EAuthTokenPlatformType.SteamClient); + + // Attach event listeners + this._attachEvents(); + + // Login with credentials supplied in logOnOptions + this.session.startWithCredentials(this.logOnOptions) + .then((res) => { + if (res.actionRequired) this._handle2FA(res); // Let handle2FA helper handle 2FA if a code is requested + }) + .catch((err) => { + if (err) this._handleCredentialsLoginError(err); // Let handleCredentialsLoginError helper handle a login error + }); + +}; \ No newline at end of file diff --git a/src/tokens.db b/src/tokens.db new file mode 100644 index 0000000..e69de29 From 43adb412eee6f959ca1a3be03fe4e78359101b3f Mon Sep 17 00:00:00 2001 From: 3urobeat <35304405+HerrEurobeat@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:58:44 +0200 Subject: [PATCH 3/5] Make logger global --- src/bot.js | 14 ++++++++------ src/helpers/comment.js | 8 +++----- src/helpers/getQuote.js | 4 ++-- src/helpers/loadDestinations.js | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/bot.js b/src/bot.js index 677785a..f557866 100644 --- a/src/bot.js +++ b/src/bot.js @@ -4,7 +4,7 @@ * Created Date: 23.01.2022 13:30:05 * Author: 3urobeat * - * Last Modified: 19.10.2022 17:07:37 + * Last Modified: 25.10.2022 14:56:43 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -39,6 +39,8 @@ module.exports.run = () => { animationdelay: 250 }); + global.logger = logger; // Make logger accessible in sessionHandler + const bot = new SteamUser(); const community = new SteamCommunity(); @@ -92,9 +94,9 @@ module.exports.run = () => { var loadDestinations = require("./helpers/loadDestinations.js"); - loadDestinations.loadProfiles(logger, (profiles) => { // Sorry for the slight callback hell that is now coming - loadDestinations.loadGroups(logger, (groups) => { - require("./helpers/getQuote.js").getQuote(logger, (quotes) => { + loadDestinations.loadProfiles((profiles) => { // Sorry for the slight callback hell that is now coming + loadDestinations.loadGroups((groups) => { + require("./helpers/getQuote.js").getQuote((quotes) => { // Check if nothing was found to comment on if (profiles.length == 0 && groups.length == 0) { @@ -124,11 +126,11 @@ module.exports.run = () => { const commentFile = require("./helpers/comment.js"); - commentFile.commentProfile(profiles, quotes, logger, community, (failedProfiles) => { + commentFile.commentProfile(profiles, quotes, community, (failedProfiles) => { if (groups.length > 0) logger("info", "Starting to comment on groups..."); setTimeout(() => { - commentFile.commentGroup(groups, quotes, logger, community, (failedGroups) => { + commentFile.commentGroup(groups, quotes, community, (failedGroups) => { logger("info", "Finished commenting!\n"); if (failedProfiles.length > 0) { diff --git a/src/helpers/comment.js b/src/helpers/comment.js index d01117a..0f82ea7 100644 --- a/src/helpers/comment.js +++ b/src/helpers/comment.js @@ -4,7 +4,7 @@ * Created Date: 23.01.2022 16:32:05 * Author: 3urobeat * - * Last Modified: 25.01.2022 14:05:07 + * Last Modified: 25.10.2022 14:55:40 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -26,11 +26,10 @@ const randomstring = arr => arr[Math.floor(Math.random() * arr.length)]; * Comments on all profiles * @param {Array} profiles Array of profiles to comment on * @param {Array} quotes Array of quotes - * @param {Function} logger The logger function * @param {SteamCommunity} community The SteamCommunity instance * @param {function} [callback] Called with `failedGroups` (Array) on completion */ -module.exports.commentProfile = (profiles, quotes, logger, community, callback) => { +module.exports.commentProfile = (profiles, quotes, community, callback) => { var failedProfiles = []; if (profiles.length == 0) return callback(failedProfiles); @@ -63,11 +62,10 @@ module.exports.commentProfile = (profiles, quotes, logger, community, callback) * Comments in all groups * @param {Array} groups Array of groups to comment in * @param {Array} quotes Array of quotes - * @param {Function} logger The logger function * @param {SteamCommunity} community The SteamCommunity instance * @param {function} [callback] Called with `failedGroups` (Array) on completion */ - module.exports.commentGroup = (groups, quotes, logger, community, callback) => { + module.exports.commentGroup = (groups, quotes, community, callback) => { var failedGroups = []; if (groups.length == 0) return callback(failedGroups); diff --git a/src/helpers/getQuote.js b/src/helpers/getQuote.js index 98a96f5..2f63f94 100644 --- a/src/helpers/getQuote.js +++ b/src/helpers/getQuote.js @@ -4,7 +4,7 @@ * Created Date: 25.01.2022 11:39:11 * Author: 3urobeat * - * Last Modified: 25.01.2022 11:58:08 + * Last Modified: 25.10.2022 14:55:45 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -21,7 +21,7 @@ const fs = require("fs"); * Gets a random quote from comments.txt * @param {function} [callback] Called with `quotes` (Array) on completion. */ -module.exports.getQuote = (logger, callback) => { +module.exports.getQuote = (callback) => { logger("info", "Loading quotes from comments.txt...", false, true); var quotes = []; diff --git a/src/helpers/loadDestinations.js b/src/helpers/loadDestinations.js index 6cb8c06..e64ba8b 100644 --- a/src/helpers/loadDestinations.js +++ b/src/helpers/loadDestinations.js @@ -4,7 +4,7 @@ * Created Date: 23.01.2022 15:28:34 * Author: 3urobeat * - * Last Modified: 24.01.2022 16:45:14 + * Last Modified: 25.10.2022 14:55:50 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -22,7 +22,7 @@ const fs = require("fs"); /** * Loads profiles from config and converts them, if necessary, to steamid64 */ -module.exports.loadProfiles = (logger, callback) => { +module.exports.loadProfiles = (callback) => { var profiles = []; var resolverCalls = 0; From d56d5144b5ac409a44be31db5468f26f814e0f27 Mon Sep 17 00:00:00 2001 From: 3urobeat <35304405+HerrEurobeat@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:03:51 +0200 Subject: [PATCH 4/5] Use new sessionhandler to get login token --- src/bot.js | 17 ++++++++++------- src/helpers/loadDestinations.js | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/bot.js b/src/bot.js index f557866..ff6c17c 100644 --- a/src/bot.js +++ b/src/bot.js @@ -4,7 +4,7 @@ * Created Date: 23.01.2022 13:30:05 * Author: 3urobeat * - * Last Modified: 25.10.2022 14:56:43 + * Last Modified: 25.10.2022 14:59:08 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -20,7 +20,8 @@ const SteamUser = require("steam-user"); const SteamCommunity = require("steamcommunity"); const SteamID = require("steamid"); -const data = require("./data.json"); +const sessionHandler = require("./sessions/sessionHandler.js"); +const data = require("./data.json"); var config; var logininfo; @@ -29,7 +30,7 @@ var logininfo; /** * Starts the bot, logs in and starts commenting */ -module.exports.run = () => { +module.exports.run = async () => { // Configure my logging library (https://github.com/HerrEurobeat/output-logger#options-1) logger.options({ @@ -77,11 +78,13 @@ module.exports.run = () => { // Start logging in logger("info", "Logging in...", false, false); - bot.logOn({ - accountName: logininfo.accountName, - password: logininfo.password - }); + let session = new sessionHandler(bot, logininfo.accountName, 0, { accountName: logininfo.accountName, password: logininfo.password }); + let token = await session.getToken(); + if (!token) process.exit(1); // Exit if no token could be retrieved + + bot.logOn({ refreshToken: token }); + // Attach steam-user loggedOn event bot.on("loggedOn", () => { logger("", "", true); logger("info", "Account logged in!"); diff --git a/src/helpers/loadDestinations.js b/src/helpers/loadDestinations.js index e64ba8b..17520ba 100644 --- a/src/helpers/loadDestinations.js +++ b/src/helpers/loadDestinations.js @@ -4,7 +4,7 @@ * Created Date: 23.01.2022 15:28:34 * Author: 3urobeat * - * Last Modified: 25.10.2022 14:55:50 + * Last Modified: 25.10.2022 15:01:30 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -72,7 +72,7 @@ module.exports.loadProfiles = (callback) => { /** * Loads groups from config and converts them, if necessary, to steamid64 */ - module.exports.loadGroups = (logger, callback) => { + module.exports.loadGroups = (callback) => { var groups = []; var resolverCalls = 0; From 0ec7d6fdeaa1f57c7c233cafc66db5d26a3a2029 Mon Sep 17 00:00:00 2001 From: 3urobeat <35304405+HerrEurobeat@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:32:53 +0200 Subject: [PATCH 5/5] Added progress bars to comment functions --- src/helpers/comment.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/helpers/comment.js b/src/helpers/comment.js index 0f82ea7..ad568f3 100644 --- a/src/helpers/comment.js +++ b/src/helpers/comment.js @@ -4,7 +4,7 @@ * Created Date: 23.01.2022 16:32:05 * Author: 3urobeat * - * Last Modified: 25.10.2022 14:55:40 + * Last Modified: 25.10.2022 15:32:18 * Modified By: 3urobeat * * Copyright (c) 2022 3urobeat @@ -34,6 +34,9 @@ module.exports.commentProfile = (profiles, quotes, community, callback) => { if (profiles.length == 0) return callback(failedProfiles); + // Create a new 0% progress bar + logger.createProgressBar(); + profiles.forEach((e, i) => { setTimeout(() => { logger("info", `Commenting on profile ${e}...`, false, false, logger.animation("loading")); @@ -50,6 +53,9 @@ module.exports.commentProfile = (profiles, quotes, community, callback) => { } } + // Calculate progress and update progress bar + logger.setProgressBar((i + 1) / profiles.length * 100); + // Check if we processed all profiles and make a callback if (profiles.length == i + 1) callback(failedProfiles); }); @@ -70,6 +76,9 @@ module.exports.commentProfile = (profiles, quotes, community, callback) => { if (groups.length == 0) return callback(failedGroups); + // Create a new 0% progress bar + logger.createProgressBar(); + groups.forEach((e, i) => { setTimeout(() => { logger("info", `Commenting in group ${e}...`, false, false, logger.animation("loading")); @@ -86,6 +95,9 @@ module.exports.commentProfile = (profiles, quotes, community, callback) => { } } + // Calculate progress and update progress bar + logger.setProgressBar((i + 1) / groups.length * 100); + // Check if we processed all profiles and make a callback if (groups.length == i + 1) callback(failedGroups); });