├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── misc.xml ├── vcs.xml ├── jsLibraryMappings.xml ├── modules.xml ├── steam-store.iml ├── inspectionProfiles │ └── Project_Default.xml ├── steamstore.iml └── codeStyleSettings.xml ├── .npmrc ├── components ├── helpers.js ├── gifts.js ├── licenses.js └── account.js ├── package.json ├── .gitignore ├── index.js ├── resources ├── EPurchaseResult.js └── EResult.js └── README.md /.idea/.name: -------------------------------------------------------------------------------- 1 | steam-store -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/steam-store.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/steamstore.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /components/helpers.js: -------------------------------------------------------------------------------- 1 | const EResult = require('../resources/EResult.js'); 2 | 3 | /** 4 | * Get an Error object for a particular EResult 5 | * @param {int|EResult|object} eresult - Either an EResult value, or an object that contains an eresult property. If undefined or is an object without an eresult property, will assume "Fail". 6 | * @returns {null|Error} 7 | */ 8 | exports.eresultError = function(eresult) { 9 | if (typeof eresult == 'undefined') { 10 | eresult = EResult.Fail; 11 | } else if (typeof eresult == 'object') { 12 | eresult = eresult.eresult || EResult.Fail; 13 | } 14 | 15 | if (eresult == EResult.OK) { 16 | // no error 17 | return null; 18 | } 19 | 20 | let err = new Error(EResult[eresult] || ("Error " + eresult)); 21 | err.eresult = eresult; 22 | return err; 23 | }; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "steamstore", 3 | "version": "2.3.0", 4 | "description": "Interacts with the store.steampowered.com site for account management", 5 | "keywords": [ 6 | "steam", 7 | "steam store", 8 | "steam account", 9 | "steampowered" 10 | ], 11 | "homepage": "https://github.com/DoctorMcKay/node-steamstore", 12 | "bugs": { 13 | "url": "https://github.com/DoctorMcKay/node-steamstore/issues" 14 | }, 15 | "license": "MIT", 16 | "author": { 17 | "name": "Alex Corn", 18 | "email": "mckay@doctormckay.com", 19 | "url": "https://www.doctormckay.com" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/DoctorMcKay/node-steamstore.git" 24 | }, 25 | "dependencies": { 26 | "@doctormckay/stdlib": "^1.7.0", 27 | "cheerio": "^0.22.0", 28 | "request": "^2.88.0", 29 | "steamid": "^1.1.0" 30 | }, 31 | "engines": { 32 | "node": ">=8.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 25 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test.js 2 | node_modules/ 3 | 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 5 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 6 | 7 | # User-specific stuff: 8 | .idea/**/workspace.xml 9 | .idea/**/tasks.xml 10 | .idea/dictionaries 11 | .idea/httpRequests/ 12 | 13 | # Sensitive or high-churn files: 14 | .idea/**/dataSources/ 15 | .idea/**/dataSources.ids 16 | .idea/**/dataSources.local.xml 17 | .idea/**/sqlDataSources.xml 18 | .idea/**/dynamic.xml 19 | .idea/**/uiDesigner.xml 20 | 21 | # Gradle: 22 | .idea/**/gradle.xml 23 | .idea/**/libraries 24 | 25 | # CMake 26 | cmake-build-debug/ 27 | cmake-build-release/ 28 | 29 | # Mongo Explorer plugin: 30 | .idea/**/mongoSettings.xml 31 | 32 | ## File-based project format: 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Cursive Clojure plugin 47 | .idea/replstate.xml 48 | 49 | # Crashlytics plugin (for Android Studio and IntelliJ) 50 | com_crashlytics_export_strings.xml 51 | crashlytics.properties 52 | crashlytics-build.properties 53 | fabric.properties 54 | -------------------------------------------------------------------------------- /components/gifts.js: -------------------------------------------------------------------------------- 1 | const StdLib = require('@doctormckay/stdlib'); 2 | const SteamID = require('steamid'); 3 | 4 | const SteamStore = require('../index.js'); 5 | 6 | /** 7 | * Send a Steam inventory gift to another user. 8 | * @param {string} giftID 9 | * @param {SteamID|string} recipient - Recipient's SteamID 10 | * @param {string} recipientName 11 | * @param {string} message 12 | * @param {string} closing 13 | * @param {string} signature 14 | * @param {function} [callback] 15 | * @returns {Promise} 16 | */ 17 | SteamStore.prototype.sendGift = function(giftID, recipient, recipientName, message, closing, signature, callback) { 18 | return StdLib.Promises.callbackPromise(null, callback, true, (accept, reject) => { 19 | let accountid = 0; 20 | 21 | if (!recipient.accountid) { 22 | // Recipient is not a SteamID object. Might be a string, might be a BigNumber object, etc. 23 | accountid = new SteamID(recipient.toString()).accountid; 24 | } else { 25 | // Recipient is a SteamID object. 26 | accountid = recipient.accountid; 27 | } 28 | 29 | this.request.post({ 30 | "uri": "https://checkout.steampowered.com/checkout/sendgiftsubmit/", 31 | "headers": { 32 | "Referer": "https://checkout.steampowered.com/checkout/sendgift/" + giftID 33 | }, 34 | "form": { 35 | "GifteeAccountID": accountid, 36 | "GifteeEmail": "", 37 | "GifteeName": recipientName, 38 | "GiftMessage": message, 39 | "GiftSentiment": closing, 40 | "GiftSignature": signature, 41 | "GiftGID": giftID, 42 | "SessionID": this.getSessionID() 43 | }, 44 | "json": true 45 | }, (err, response, body) => { 46 | if (this._checkHttpError(err, response, reject)) { 47 | return; 48 | } 49 | 50 | if (body.success == 1) { 51 | return accept(); 52 | } else { 53 | return reject(new Error("EResult " + body.success)); 54 | } 55 | }); 56 | }); 57 | }; 58 | -------------------------------------------------------------------------------- /components/licenses.js: -------------------------------------------------------------------------------- 1 | const StdLib = require('@doctormckay/stdlib'); 2 | 3 | const Helpers = require('./helpers.js'); 4 | 5 | const SteamStore = require('../index.js'); 6 | 7 | /** 8 | * Add an eligible free-on-demand license to your Steam account. 9 | * @param {int} subID - The ID of the free-on-demand sub you want to claim 10 | * @param {function} [callback] 11 | * @returns {Promise} 12 | */ 13 | SteamStore.prototype.addFreeLicense = function(subID, callback) { 14 | return StdLib.Promises.callbackPromise(null, callback, true, (resolve, reject) => { 15 | this.request.post({ 16 | "uri": "https://store.steampowered.com/freelicense/addfreelicense", 17 | "form": { 18 | "action": "add_to_cart", 19 | "sessionid": this.getSessionID(), 20 | "subid": subID 21 | } 22 | }, (err, response, body) => { 23 | if (this._checkHttpError(err, response, reject)) { 24 | return; 25 | } 26 | 27 | let match = body.match(/([^<]+)<\/span>/); 28 | if (match) { 29 | return reject(new Error(match[1])); 30 | } 31 | 32 | if (!body.includes('

Success!

')) { 33 | return reject(new Error('Malformed response')); 34 | } 35 | 36 | return resolve(); 37 | }); 38 | }); 39 | }; 40 | 41 | /** 42 | * Remove an eligible complimentary license from your account. 43 | * @param {int} subID - The ID of the complimentary license you want to remove 44 | * @param {function} [callback] 45 | * @returns {Promise} 46 | */ 47 | SteamStore.prototype.removeLicense = function(subID, callback) { 48 | return StdLib.Promises.callbackPromise(null, callback, true, (resolve, reject) => { 49 | this.request.post({ 50 | uri: 'https://store.steampowered.com/account/removelicense', 51 | form: { 52 | sessionid: this.getSessionID(), 53 | packageid: subID 54 | }, 55 | json: true 56 | }, (err, response, body) => { 57 | if (this._checkHttpError(err, response, reject)) { 58 | return; 59 | } 60 | 61 | if (!body || !body.success) { 62 | return reject(new Error('Malformed response')); 63 | } 64 | 65 | if (body.success == 1) { 66 | return resolve(); 67 | } else { 68 | return reject(Helpers.eresultError(body.success)); 69 | } 70 | }); 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Request = require('request'); 2 | const SteamID = require('steamid'); 3 | 4 | const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"; 5 | 6 | module.exports = SteamStore; 7 | 8 | /** 9 | * Create a new SteamStore instance. 10 | * @param {object} [options] 11 | * @constructor 12 | */ 13 | function SteamStore(options) { 14 | options = options || {}; 15 | 16 | this._jar = Request.jar(); 17 | 18 | let defaults = { 19 | "jar": this._jar, 20 | "timeout": options.timeout || 50000, 21 | "gzip": true, 22 | "headers": { 23 | "User-Agent": options.userAgent || USER_AGENT 24 | } 25 | }; 26 | 27 | this.request = options.request || Request.defaults({"forever": true}); // "forever" indicates that we want a keep-alive agent 28 | this.request = this.request.defaults(defaults); 29 | 30 | // UTC, English 31 | this.setCookie("timezoneOffset=0,0"); 32 | this.setCookie("Steam_Language=english"); 33 | } 34 | 35 | /** 36 | * Set a single cookie on this SteamStore instance. 37 | * @param {string} cookie - In format "cookieName=cookieValue" 38 | */ 39 | SteamStore.prototype.setCookie = function(cookie) { 40 | let cookieName = cookie.match(/(.+)=/)[1]; 41 | if (cookieName == 'steamLogin' || cookieName == 'steamLoginSecure') { 42 | this.steamID = new SteamID(cookie.match(/=(\d+)/)[1]); 43 | } 44 | 45 | let requestCookie = Request.cookie(cookie); 46 | 47 | let isSecure = !!cookieName.match(/(^steamMachineAuth|^steamLoginSecure$)/); 48 | let protocol = isSecure ? 'https' : 'http'; 49 | 50 | if (requestCookie.domain) { 51 | this._jar.setCookie(requestCookie.clone(), protocol + '://' + requestCookie.domain); 52 | } else { 53 | this._jar.setCookie(Request.cookie(cookie), protocol + "://store.steampowered.com"); 54 | this._jar.setCookie(Request.cookie(cookie), protocol + "://steamcommunity.com"); 55 | } 56 | }; 57 | 58 | /** 59 | * Set multiple cookies. 60 | * @param {string[]} cookies 61 | */ 62 | SteamStore.prototype.setCookies = function(cookies) { 63 | cookies.forEach(this.setCookie.bind(this)); 64 | }; 65 | 66 | /** 67 | * Get this SteamStore instance's sessionid CSRF token. 68 | * @returns {string} 69 | */ 70 | SteamStore.prototype.getSessionID = function() { 71 | let cookies = this._jar.getCookieString("http://store.steampowered.com").split(';'); 72 | for (let i = 0; i < cookies.length; i++) { 73 | let match = cookies[i].trim().match(/([^=]+)=(.+)/); 74 | if (match[1] == 'sessionid') { 75 | return decodeURIComponent(match[2]); 76 | } 77 | } 78 | 79 | let sessionID = generateSessionID(); 80 | this.setCookie("sessionid=" + sessionID); 81 | return sessionID; 82 | }; 83 | 84 | function generateSessionID() { 85 | return Math.floor(Math.random() * 1000000000); 86 | } 87 | 88 | SteamStore.prototype._checkHttpError = function(err, response, callback) { 89 | if (err) { 90 | callback(err); 91 | return true; 92 | } 93 | 94 | if (response.statusCode >= 300 && response.statusCode <= 399 && response.headers.location.indexOf('/login') != -1) { 95 | callback(new Error("Not Logged In")); 96 | return true; 97 | } 98 | 99 | if (response.statusCode >= 400) { 100 | let error = new Error("HTTP error " + response.statusCode); 101 | error.code = response.statusCode; 102 | callback(error); 103 | return true; 104 | } 105 | 106 | return false; 107 | }; 108 | 109 | require('./components/account.js'); 110 | require('./components/gifts.js'); 111 | require('./components/licenses.js'); 112 | -------------------------------------------------------------------------------- /resources/EPurchaseResult.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @enum EPurchaseResult 3 | */ 4 | module.exports = { 5 | "NoDetail": 0, 6 | "AVSFailure": 1, 7 | "InsufficientFunds": 2, 8 | "ContactSupport": 3, 9 | "Timeout": 4, 10 | "InvalidPackage": 5, 11 | "InvalidPaymentMethod": 6, 12 | "InvalidData": 7, 13 | "OthersInProgress": 8, 14 | "AlreadyPurchased": 9, 15 | "WrongPrice": 10, 16 | "FraudCheckFailed": 11, 17 | "CancelledByUser": 12, 18 | "RestrictedCountry": 13, 19 | "BadActivationCode": 14, 20 | "DuplicateActivationCode": 15, 21 | "UseOtherPaymentMethod": 16, 22 | "UseOtherFunctionSource": 17, 23 | "InvalidShippingAddress": 18, 24 | "RegionNotSupported": 19, 25 | "AcctIsBlocked": 20, 26 | "AcctNotVerified": 21, 27 | "InvalidAccount": 22, 28 | "StoreBillingCountryMismatch": 23, 29 | "DoesNotOwnRequiredApp": 24, 30 | "CanceledByNewTransaction": 25, 31 | "ForceCanceledPending": 26, 32 | "FailCurrencyTransProvider": 27, 33 | "FailedCyberCafe": 28, 34 | "NeedsPreApproval": 29, 35 | "PreApprovalDenied": 30, 36 | "WalletCurrencyMismatch": 31, 37 | "EmailNotValidated": 32, 38 | "ExpiredCard": 33, 39 | "TransactionExpired": 34, 40 | "WouldExceedMaxWallet": 35, 41 | "MustLoginPS3AppForPurchase": 36, 42 | "CannotShipToPOBox": 37, 43 | "InsufficientInventory": 38, 44 | "CannotGiftShippedGoods": 39, 45 | "CannotShipInternationally": 40, 46 | "BillingAgreementCancelled": 41, 47 | "InvalidCoupon": 42, 48 | "ExpiredCoupon": 43, 49 | "AccountLocked": 44, 50 | "OtherAbortableInProgress": 45, 51 | "ExceededSteamLimit": 46, 52 | "OverlappingPackagesInCart": 47, 53 | "NoWallet": 48, 54 | "NoCachedPaymentMethod": 49, 55 | "CannotRedeemCodeFromClient": 50, 56 | "PurchaseAmountNoSupportedByProvider": 51, 57 | "OverlappingPackagesInPendingTransaction": 52, 58 | "RateLimited": 53, 59 | "OwnsExcludedApp": 54, 60 | "CreditCardBinMismatchesType": 55, 61 | "CartValueTooHigh": 56, 62 | "BillingAgreementAlreadyExists": 57, 63 | "POSACodeNotActivated": 58, 64 | "CannotShipToCountry": 59, 65 | "HungTransactionCancelled": 60, 66 | "PaypalInternalError": 61, 67 | "UnknownGlobalCollectError": 62, 68 | "InvalidTaxAddress": 63, 69 | "PhysicalProductLimitExceeded": 64, 70 | "PurchaseCannotBeReplayed": 65, 71 | "DelayedCompletion": 66, 72 | "BundleTypeCannotBeGifted": 67, 73 | 74 | // Value-to-name mapping for convenience 75 | "0": "NoDetail", 76 | "1": "AVSFailure", 77 | "2": "InsufficientFunds", 78 | "3": "ContactSupport", 79 | "4": "Timeout", 80 | "5": "InvalidPackage", 81 | "6": "InvalidPaymentMethod", 82 | "7": "InvalidData", 83 | "8": "OthersInProgress", 84 | "9": "AlreadyPurchased", 85 | "10": "WrongPrice", 86 | "11": "FraudCheckFailed", 87 | "12": "CancelledByUser", 88 | "13": "RestrictedCountry", 89 | "14": "BadActivationCode", 90 | "15": "DuplicateActivationCode", 91 | "16": "UseOtherPaymentMethod", 92 | "17": "UseOtherFunctionSource", 93 | "18": "InvalidShippingAddress", 94 | "19": "RegionNotSupported", 95 | "20": "AcctIsBlocked", 96 | "21": "AcctNotVerified", 97 | "22": "InvalidAccount", 98 | "23": "StoreBillingCountryMismatch", 99 | "24": "DoesNotOwnRequiredApp", 100 | "25": "CanceledByNewTransaction", 101 | "26": "ForceCanceledPending", 102 | "27": "FailCurrencyTransProvider", 103 | "28": "FailedCyberCafe", 104 | "29": "NeedsPreApproval", 105 | "30": "PreApprovalDenied", 106 | "31": "WalletCurrencyMismatch", 107 | "32": "EmailNotValidated", 108 | "33": "ExpiredCard", 109 | "34": "TransactionExpired", 110 | "35": "WouldExceedMaxWallet", 111 | "36": "MustLoginPS3AppForPurchase", 112 | "37": "CannotShipToPOBox", 113 | "38": "InsufficientInventory", 114 | "39": "CannotGiftShippedGoods", 115 | "40": "CannotShipInternationally", 116 | "41": "BillingAgreementCancelled", 117 | "42": "InvalidCoupon", 118 | "43": "ExpiredCoupon", 119 | "44": "AccountLocked", 120 | "45": "OtherAbortableInProgress", 121 | "46": "ExceededSteamLimit", 122 | "47": "OverlappingPackagesInCart", 123 | "48": "NoWallet", 124 | "49": "NoCachedPaymentMethod", 125 | "50": "CannotRedeemCodeFromClient", 126 | "51": "PurchaseAmountNoSupportedByProvider", 127 | "52": "OverlappingPackagesInPendingTransaction", 128 | "53": "RateLimited", 129 | "54": "OwnsExcludedApp", 130 | "55": "CreditCardBinMismatchesType", 131 | "56": "CartValueTooHigh", 132 | "57": "BillingAgreementAlreadyExists", 133 | "58": "POSACodeNotActivated", 134 | "59": "CannotShipToCountry", 135 | "60": "HungTransactionCancelled", 136 | "61": "PaypalInternalError", 137 | "62": "UnknownGlobalCollectError", 138 | "63": "InvalidTaxAddress", 139 | "64": "PhysicalProductLimitExceeded", 140 | "65": "PurchaseCannotBeReplayed", 141 | "66": "DelayedCompletion", 142 | "67": "BundleTypeCannotBeGifted" 143 | }; 144 | -------------------------------------------------------------------------------- /resources/EResult.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @enum EResult 3 | */ 4 | module.exports = { 5 | "Invalid": 0, 6 | "OK": 1, 7 | "Fail": 2, 8 | "NoConnection": 3, 9 | "InvalidPassword": 5, 10 | "LoggedInElsewhere": 6, 11 | "InvalidProtocolVer": 7, 12 | "InvalidParam": 8, 13 | "FileNotFound": 9, 14 | "Busy": 10, 15 | "InvalidState": 11, 16 | "InvalidName": 12, 17 | "InvalidEmail": 13, 18 | "DuplicateName": 14, 19 | "AccessDenied": 15, 20 | "Timeout": 16, 21 | "Banned": 17, 22 | "AccountNotFound": 18, 23 | "InvalidSteamID": 19, 24 | "ServiceUnavailable": 20, 25 | "NotLoggedOn": 21, 26 | "Pending": 22, 27 | "EncryptionFailure": 23, 28 | "InsufficientPrivilege": 24, 29 | "LimitExceeded": 25, 30 | "Revoked": 26, 31 | "Expired": 27, 32 | "AlreadyRedeemed": 28, 33 | "DuplicateRequest": 29, 34 | "AlreadyOwned": 30, 35 | "IPNotFound": 31, 36 | "PersistFailed": 32, 37 | "LockingFailed": 33, 38 | "LogonSessionReplaced": 34, 39 | "ConnectFailed": 35, 40 | "HandshakeFailed": 36, 41 | "IOFailure": 37, 42 | "RemoteDisconnect": 38, 43 | "ShoppingCartNotFound": 39, 44 | "Blocked": 40, 45 | "Ignored": 41, 46 | "NoMatch": 42, 47 | "AccountDisabled": 43, 48 | "ServiceReadOnly": 44, 49 | "AccountNotFeatured": 45, 50 | "AdministratorOK": 46, 51 | "ContentVersion": 47, 52 | "TryAnotherCM": 48, 53 | "PasswordRequiredToKickSession": 49, 54 | "AlreadyLoggedInElsewhere": 50, 55 | "Suspended": 51, 56 | "Cancelled": 52, 57 | "DataCorruption": 53, 58 | "DiskFull": 54, 59 | "RemoteCallFailed": 55, 60 | "PasswordNotSet": 56, // obsolete "renamed to PasswordUnset" 61 | "PasswordUnset": 56, 62 | "ExternalAccountUnlinked": 57, 63 | "PSNTicketInvalid": 58, 64 | "ExternalAccountAlreadyLinked": 59, 65 | "RemoteFileConflict": 60, 66 | "IllegalPassword": 61, 67 | "SameAsPreviousValue": 62, 68 | "AccountLogonDenied": 63, 69 | "CannotUseOldPassword": 64, 70 | "InvalidLoginAuthCode": 65, 71 | "AccountLogonDeniedNoMailSent": 66, // obsolete "renamed to AccountLogonDeniedNoMail" 72 | "AccountLogonDeniedNoMail": 66, 73 | "HardwareNotCapableOfIPT": 67, 74 | "IPTInitError": 68, 75 | "ParentalControlRestricted": 69, 76 | "FacebookQueryError": 70, 77 | "ExpiredLoginAuthCode": 71, 78 | "IPLoginRestrictionFailed": 72, 79 | "AccountLocked": 73, // obsolete "renamed to AccountLockedDown" 80 | "AccountLockedDown": 73, 81 | "AccountLogonDeniedVerifiedEmailRequired": 74, 82 | "NoMatchingURL": 75, 83 | "BadResponse": 76, 84 | "RequirePasswordReEntry": 77, 85 | "ValueOutOfRange": 78, 86 | "UnexpectedError": 79, 87 | "Disabled": 80, 88 | "InvalidCEGSubmission": 81, 89 | "RestrictedDevice": 82, 90 | "RegionLocked": 83, 91 | "RateLimitExceeded": 84, 92 | "AccountLogonDeniedNeedTwoFactorCode": 85, // obsolete "renamed to AccountLoginDeniedNeedTwoFactor" 93 | "AccountLoginDeniedNeedTwoFactor": 85, 94 | "ItemOrEntryHasBeenDeleted": 86, // obsolete "renamed to ItemDeleted" 95 | "ItemDeleted": 86, 96 | "AccountLoginDeniedThrottle": 87, 97 | "TwoFactorCodeMismatch": 88, 98 | "TwoFactorActivationCodeMismatch": 89, 99 | "AccountAssociatedToMultiplePlayers": 90, // obsolete "renamed to AccountAssociatedToMultiplePartners" 100 | "AccountAssociatedToMultiplePartners": 90, 101 | "NotModified": 91, 102 | "NoMobileDeviceAvailable": 92, // obsolete "renamed to NoMobileDevice" 103 | "NoMobileDevice": 92, 104 | "TimeIsOutOfSync": 93, // obsolete "renamed to TimeNotSynced" 105 | "TimeNotSynced": 93, 106 | "SMSCodeFailed": 94, 107 | "TooManyAccountsAccessThisResource": 95, // obsolete "renamed to AccountLimitExceeded" 108 | "AccountLimitExceeded": 95, 109 | "AccountActivityLimitExceeded": 96, 110 | "PhoneActivityLimitExceeded": 97, 111 | "RefundToWallet": 98, 112 | "EmailSendFailure": 99, 113 | "NotSettled": 100, 114 | "NeedCaptcha": 101, 115 | "GSLTDenied": 102, 116 | "GSOwnerDenied": 103, 117 | "InvalidItemType": 104, 118 | "IPBanned": 105, 119 | 120 | // Value-to-name mapping for convenience 121 | "0": "Invalid", 122 | "1": "OK", 123 | "2": "Fail", 124 | "3": "NoConnection", 125 | "5": "InvalidPassword", 126 | "6": "LoggedInElsewhere", 127 | "7": "InvalidProtocolVer", 128 | "8": "InvalidParam", 129 | "9": "FileNotFound", 130 | "10": "Busy", 131 | "11": "InvalidState", 132 | "12": "InvalidName", 133 | "13": "InvalidEmail", 134 | "14": "DuplicateName", 135 | "15": "AccessDenied", 136 | "16": "Timeout", 137 | "17": "Banned", 138 | "18": "AccountNotFound", 139 | "19": "InvalidSteamID", 140 | "20": "ServiceUnavailable", 141 | "21": "NotLoggedOn", 142 | "22": "Pending", 143 | "23": "EncryptionFailure", 144 | "24": "InsufficientPrivilege", 145 | "25": "LimitExceeded", 146 | "26": "Revoked", 147 | "27": "Expired", 148 | "28": "AlreadyRedeemed", 149 | "29": "DuplicateRequest", 150 | "30": "AlreadyOwned", 151 | "31": "IPNotFound", 152 | "32": "PersistFailed", 153 | "33": "LockingFailed", 154 | "34": "LogonSessionReplaced", 155 | "35": "ConnectFailed", 156 | "36": "HandshakeFailed", 157 | "37": "IOFailure", 158 | "38": "RemoteDisconnect", 159 | "39": "ShoppingCartNotFound", 160 | "40": "Blocked", 161 | "41": "Ignored", 162 | "42": "NoMatch", 163 | "43": "AccountDisabled", 164 | "44": "ServiceReadOnly", 165 | "45": "AccountNotFeatured", 166 | "46": "AdministratorOK", 167 | "47": "ContentVersion", 168 | "48": "TryAnotherCM", 169 | "49": "PasswordRequiredToKickSession", 170 | "50": "AlreadyLoggedInElsewhere", 171 | "51": "Suspended", 172 | "52": "Cancelled", 173 | "53": "DataCorruption", 174 | "54": "DiskFull", 175 | "55": "RemoteCallFailed", 176 | "56": "PasswordUnset", 177 | "57": "ExternalAccountUnlinked", 178 | "58": "PSNTicketInvalid", 179 | "59": "ExternalAccountAlreadyLinked", 180 | "60": "RemoteFileConflict", 181 | "61": "IllegalPassword", 182 | "62": "SameAsPreviousValue", 183 | "63": "AccountLogonDenied", 184 | "64": "CannotUseOldPassword", 185 | "65": "InvalidLoginAuthCode", 186 | "66": "AccountLogonDeniedNoMail", 187 | "67": "HardwareNotCapableOfIPT", 188 | "68": "IPTInitError", 189 | "69": "ParentalControlRestricted", 190 | "70": "FacebookQueryError", 191 | "71": "ExpiredLoginAuthCode", 192 | "72": "IPLoginRestrictionFailed", 193 | "73": "AccountLockedDown", 194 | "74": "AccountLogonDeniedVerifiedEmailRequired", 195 | "75": "NoMatchingURL", 196 | "76": "BadResponse", 197 | "77": "RequirePasswordReEntry", 198 | "78": "ValueOutOfRange", 199 | "79": "UnexpectedError", 200 | "80": "Disabled", 201 | "81": "InvalidCEGSubmission", 202 | "82": "RestrictedDevice", 203 | "83": "RegionLocked", 204 | "84": "RateLimitExceeded", 205 | "85": "AccountLoginDeniedNeedTwoFactor", 206 | "86": "ItemDeleted", 207 | "87": "AccountLoginDeniedThrottle", 208 | "88": "TwoFactorCodeMismatch", 209 | "89": "TwoFactorActivationCodeMismatch", 210 | "90": "AccountAssociatedToMultiplePartners", 211 | "91": "NotModified", 212 | "92": "NoMobileDevice", 213 | "93": "TimeNotSynced", 214 | "94": "SMSCodeFailed", 215 | "95": "AccountLimitExceeded", 216 | "96": "AccountActivityLimitExceeded", 217 | "97": "PhoneActivityLimitExceeded", 218 | "98": "RefundToWallet", 219 | "99": "EmailSendFailure", 220 | "100": "NotSettled", 221 | "101": "NeedCaptcha", 222 | "102": "GSLTDenied", 223 | "103": "GSOwnerDenied", 224 | "104": "InvalidItemType", 225 | "105": "IPBanned" 226 | }; 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # steamstore 2 | 3 | A module for interacting with the Steam store site from Node.js. Currently not a lot of functionality is supported. 4 | 5 | ## Logging In 6 | 7 | This module cannot facilitate logins to the store site directly. You'll need to use something like 8 | [`steam-user`](https://www.npmjs.com/package/steam-user) or 9 | [`steamcommunity`](https://www.npmjs.com/package/steamcommunity) to login, and then use 10 | [`setCookies`](#setcookiescookies) to set your login cookies in this module. 11 | 12 | The cookies are the same for the store site and for the community site. 13 | 14 | # Patterns 15 | 16 | **Please read this section in its entirety before starting work with `SteamStore`.** 17 | 18 | ### Callbacks and Promises 19 | 20 | All methods listed in this document that accept a callback also return a `Promise`. You may use either callbacks or 21 | promises. 22 | 23 | Legacy callbacks return their data spanning across multiple arguments. All promises (which return any data at all) 24 | return a single object containing one or more properties. The names of these properties for legacy callbacks are the 25 | names of the callback arguments listed in this readme. Newer callbacks return a single object `response` argument, which 26 | is identical to the promise output for that method. 27 | 28 | Some methods indicate that their callback is required or optional. **You are never required to use callbacks over 29 | promises**, but if a callback is listed as optional then an unhandled promise rejection will not raise a warning/error. 30 | If a callback is listed as required and you neither supply a callback nor handle the promise rejection, then a 31 | promise rejection will raise a warning, and eventually a crash in a future Node.js release. 32 | 33 | # Properties 34 | 35 | ### steamID 36 | **v1.1.0 or later is required to use this property** 37 | 38 | A [`SteamID`](https://www.npmjs.com/package/steamid) object for the currently logged-in user. 39 | 40 | # Methods 41 | 42 | ### Constructor([options]) 43 | - `options` - An optional object containing zero or more of these properties: 44 | - `timeout` - The timeout to use for HTTP(S) requests, in milliseconds; default is 50000 (50 seconds) 45 | - `userAgent` - The user-agent header value; default is Chrome 56 46 | - `request` - A [`request`](https://www.npmjs.com/package/request) instance 47 | - This allows you to specify your own defaults on the request instance 48 | - These options will always be overridden on the request instance: `jar`, `timeout`, `gzip`, `headers['User-Agent']` 49 | 50 | Constructs a new instance of `steamstore`. Example: 51 | 52 | ```js 53 | const SteamStore = require('steamstore'); 54 | let store = new SteamStore(); 55 | ``` 56 | 57 | or 58 | 59 | ```js 60 | const SteamStore = require('steamstore'); 61 | let store = new SteamStore({"timeout": 30000}); 62 | ``` 63 | 64 | ### setCookie(cookie) 65 | - `cookie` - The cookie, in "name=value" string format 66 | 67 | Sets a single cookie to `steamstore`'s internal cookie jar. 68 | 69 | ### setCookies(cookies) 70 | - `cookies` - An array of cookies, in "name=value" string format 71 | 72 | Simply calls [`setCookie`](#setcookiecookie) for each cookie in the array. 73 | 74 | ### getSessionID() 75 | 76 | Returns the value of the `sessionid` cookie, or creates a new random one and adds it to the cookie jar if not present. 77 | 78 | ### addPhoneNumber(number[, bypassConfirmation], callback) 79 | - `number` - Your phone number, with a leading plus and the country code 80 | - Example: `+18885550123` 81 | - `bypassConfirmation` - `true` if you want to ignore any confirmation-level errors (see below). Default `false` 82 | - `callback` - A function to be called when the request completes 83 | - `err` - An `Error` object on failure, or `null` on success. The `confirmation` property will be `true` if this is 84 | a confirmation-level error which can be overridden by setting `bypassConfirmation` to `true` 85 | 86 | Adds a new phone number to your account. This triggers a verification Email to be sent. You can continue the process by 87 | calling [`sendPhoneNumberVerificationMessage`](#sendphonenumberverificationmessagecallback) 88 | 89 | ### sendPhoneNumberVerificationMessage(callback) 90 | - `callback` - A function to be called when the request completes 91 | - `err` - An `Error` object on failure, or `null` on success 92 | 93 | **v2.3.0 or later is required to use this method** 94 | 95 | Call this method after you've clicked on the link in the email you received after you called `addPhoneNumber()`. 96 | This triggers a verification SMS to be sent. You can provide the verification code to 97 | [`verifyPhoneNumber`](#verifyphonenumbercode-callback) to finalize the process. 98 | 99 | ### resendVerificationSMS(callback) 100 | - `callback` - A function to be called when the request completes 101 | - `err` - An `Error` object on failure, or `null` on success 102 | 103 | Asks the Steam servers to resend the verification SMS to your pending-confirmation phone number. This will fail if you 104 | request it too soon after the last SMS was sent. 105 | 106 | ### verifyPhoneNumber(code, callback) 107 | - `code` - Your SMS verification code 108 | - `callback` - A function to be called when the request completes 109 | - `err` - An `Error` object on failure, or `null` on success 110 | 111 | Verify your pending-verification phone number using the SMS code. 112 | 113 | ### removePhoneNumber(callback) 114 | - `callback` - A function to be called when the request completes 115 | - `err` - An `Error` object on failure, or `null` on success 116 | 117 | Starts the process to remove your phone number from your account. This will send an SMS verification code to your phone. 118 | Call [`confirmRemovePhoneNumber`](#confirmremovephonenumbercode-callback) with the code to finalize the process. 119 | 120 | ### confirmRemovePhoneNumber(code, callback) 121 | - `code` - Your SMS verification code 122 | - `callback` - A function to be called when the request completes 123 | - `err` - An `Error` object on failure, or `null` on success 124 | 125 | Finalizes the process of removing your phone number from your account. 126 | 127 | ### hasPhone(callback) 128 | - `callback` - A function to be called when the request completes 129 | - `err` - An `Error` object on failure, or `null` on success 130 | - `hasVerifiedPhone` - `true` if your account has a phone number linked, `false` if not 131 | - `lastDigits` - If you have a phone number, this is a string containing the last 4 digits of your number 132 | 133 | **v1.3.0 or later is required to use this method** 134 | 135 | Checks whether your account has a phone number linked or not. 136 | 137 | ### getAccountData(callback) 138 | - `callback` - A function to be called when the request completes 139 | - `err` - An `Error` object on failure, or `null` on success 140 | - `ownedApps` - An array containing the AppID of each app which your account owns 141 | - `ownedPackages` - An array containing the PackageID of each package which your account owns 142 | - `wishlistedApps` - An array containing the AppID of each app that's on your wishlist 143 | - `ignoredApps` - An array containing the AppID of each app which you've clicked "Not Interested" on 144 | - `suggestedTags` - An object containing the tags which are suggested for you. Keys are TagIDs, values are the tag names. 145 | 146 | **v1.1.0 or later is required to use this method** 147 | 148 | Gets information about products that your account owns, ignores, wants, or is recommended. 149 | 150 | ### sendGift(giftID, recipient, recipientName, message, closing, signature[, callback]) 151 | - `giftID` - The gift's ID (also known as the asset ID of the item in your Steam gift inventory) 152 | - `recipient` - The recipient's SteamID (as a `SteamID` object or string) to send it over Steam. You need to be friends with the user. 153 | - `recipientName` - The name of the recipient to put in the gift email/popup 154 | - `message` - The message to include in the email/popup 155 | - `closing` - The closing to include in the email/popup 156 | - `signature` - Your name to include in the email/popup 157 | - `callback` - Optional. Called when the request completes. 158 | - `err` - An `Error` object on failure, or `null` on success 159 | 160 | **v1.4.0 or later is required to use this method** 161 | 162 | Sends a Steam gift in your inventory to another user. The gift will remain in your inventory until the recipient accepts it. 163 | You can re-send a gift which you've already sent. Gifts don't have to be tradable in order to be sent. 164 | 165 | ### createWallet(walletCode, billingAddress, callback) 166 | - `walletCode` - A Steam wallet code you want to redeem 167 | - `billingAddress` - An object containing these properties: 168 | - `address` - Your street address 169 | - `city` - Your city 170 | - `country` - Your country code, e.g. "US" 171 | - `state` - Your state, e.g. "FL" 172 | - `postalCode` - Your postal/ZIP code 173 | - `callback` - Required. Called when the requested data is available. 174 | - `err` - An `Error` object if the request fails, or `null` otherwise 175 | - `eresult` - An `EResult` value from `SteamStore.EResult` 176 | - `detail` - A value from `SteamStore.EPurchaseResult` or `undefined` 177 | - `redeemable` - `true` if this code can be redeemed, `false` if not 178 | - `amount` - If redeemable, this is how much the code is worth, in its currency's lowest denomination (e.g. USD cents) 179 | - `currencyCode` - If redeemable, this is the currency of `amount` 180 | 181 | **v1.7.0 or later is required to use this method** 182 | 183 | Before you can redeem a Steam Wallet code, your account needs to have a wallet. If you don't yet have a wallet you can 184 | use this method to create one. Creating a wallet requires you to provide a wallet code, but the code won't be redeemed 185 | until you actually call `redeemWalletCode`. 186 | 187 | ### checkWalletCode() 188 | 189 | **THIS METHOD IS NO LONGER FUNCTIONAL. IT WILL BE REMOVED IN A FUTURE RELEASE.** 190 | 191 | ### redeemWalletCode(walletCode[, callback]) 192 | - `walletCode` - The Steam wallet code you want to redeem 193 | - `callback` - Optional. Called when the request completes. 194 | - `err` - An `Error` object if the request fails, or `null` on success 195 | - `eresult` - An `EResult` value from `SteamStore.EResult` 196 | - `detail` - A value from `SteamStore.EPurchaseResult` or `undefined` 197 | - `formattedNewWalletBalance` - If redeemed successfully, this is your new wallet balance as a string, formatted for human display (e.g. `$20.00`) 198 | - `amount` - If redeemable, this is how much the code is worth, in its currency's lowest denomination (e.g. USD cents) 199 | 200 | **v1.5.0 or later is required to use this method** 201 | 202 | Attempts to redeem a Steam Wallet code on your account. This will call `checkWalletCode` first, and if the code is not 203 | redeemable, the callback will be invoked with an `Error` passed as the first parameter. That `Error` will have message 204 | `Wallet code is not valid`, with `eresult` and `purchaseresultdetail` properties defined on that `Error` object. 205 | 206 | ### getWalletBalance([callback]) 207 | - `callback` - Called when the request completes. 208 | - `err` - An `Error` object if the request fails, or `null` on success 209 | - `response` - The response object 210 | - `formattedBalance` - Your wallet balance as a string, formatted in your local currency (e.g. `"$1.23"`) 211 | 212 | **v2.1.0 or later is required to use this method** 213 | 214 | Gets your current Steam wallet balance. 215 | 216 | ### setDisplayLanguages(primary[, secondary][, callback]) 217 | - `primary` - Your desired primary (display) language, as a string (e.g. `english` or `danish`) 218 | - `secondary` - Your desired secondary languages, as an array of strings of the same format as `primary` 219 | - `callback` - Optional. Called when the request completes. 220 | - `err` - An `Error` object on failure, or `null` on success 221 | 222 | **v1.5.0 or later is required to use this method** 223 | 224 | Updates your account's display languages for the Steam store. 225 | 226 | ### addFreeLicense(subID[, callback]) 227 | - `subID` - The ID of the free-on-demand package you would like to claim 228 | - `callback` - Optional. Called when the request completes. 229 | - `err` - An `Error` object on failure, or `null` on success 230 | 231 | **v2.0.0 or later is required to use this method** 232 | 233 | Request to add an eligible free-on-demand package to your Steam account. 234 | 235 | ### removeLicense(subID[, callback]) 236 | - `subID` - The ID of the complimentary license you would like to remove 237 | - `callback` - Optional. Called when the request completes. 238 | - `err` - An `Error` objecton failure, or `null` on success 239 | 240 | **v2.2.0 or later is required to use this method** 241 | 242 | Removes an eligible complimentary license from your Steam account. 243 | -------------------------------------------------------------------------------- /components/account.js: -------------------------------------------------------------------------------- 1 | const Cheerio = require('cheerio'); 2 | const StdLib = require('@doctormckay/stdlib'); 3 | 4 | const SteamStore = require('../index.js'); 5 | 6 | const EPurchaseResult = require('../resources/EPurchaseResult.js'); 7 | SteamStore.prototype.EPurchaseResult = EPurchaseResult; 8 | 9 | const EResult = require('../resources/EResult.js'); 10 | SteamStore.prototype.EResult = EResult; 11 | 12 | /** 13 | * Add a phone number to your account. 14 | * @param {string} number 15 | * @param {boolean} [bypassConfirmation=false] 16 | * @param {function} [callback] 17 | * @returns {Promise} 18 | */ 19 | SteamStore.prototype.addPhoneNumber = function(number, bypassConfirmation, callback) { 20 | return StdLib.Promises.callbackPromise(null, callback, (accept, reject) => { 21 | if (typeof bypassConfirmation === 'function') { 22 | callback = bypassConfirmation; 23 | bypassConfirmation = false; 24 | } 25 | 26 | this.request.post({ 27 | "uri": "https://store.steampowered.com/phone/add_ajaxop", 28 | "form": { 29 | "op": "get_phone_number", 30 | "input": number, 31 | "sessionID": this.getSessionID(), 32 | "confirmed": bypassConfirmation ? 1 : 0, 33 | "checkfortos": 1, 34 | "bisediting": 0, 35 | "token": 0 36 | }, 37 | "json": true 38 | }, (err, response, body) => { 39 | if (this._checkHttpError(err, response, reject)) { 40 | return; 41 | } 42 | 43 | let error; 44 | 45 | if (body.success) { 46 | if (body.state != "email_verification") { 47 | error = new Error("Unknown state " + body.state); 48 | error.confirmation = false; 49 | 50 | return reject(error); 51 | } 52 | 53 | return accept(); 54 | } 55 | 56 | if (body.errorText) { 57 | error = new Error(body.errorText); 58 | error.confirmation = false; 59 | return reject(error); 60 | } 61 | 62 | if (body.requiresConfirmation) { 63 | error = new Error(body.confirmationText); 64 | error.confirmation = true; 65 | return reject(error); 66 | } 67 | 68 | error = new Error("Malformed response"); 69 | error.confirmation = false; 70 | return reject(error); 71 | }); 72 | }); 73 | }; 74 | 75 | /** 76 | * Confirm that you have clicked the link in your email before adding a phone number. 77 | * @param {function} [callback] 78 | * @returns {Promise} 79 | */ 80 | SteamStore.prototype.sendPhoneNumberVerificationMessage = function(callback) { 81 | return StdLib.Promises.callbackPromise(null, callback, (accept, reject) => { 82 | this.request.post({ 83 | "uri": "https://store.steampowered.com/phone/add_ajaxop", 84 | "form": { 85 | "op": "email_verification", 86 | "input": "", 87 | "sessionID": this.getSessionID(), 88 | "confirmed": 1, 89 | "checkfortos": 1, 90 | "bisediting": 0, 91 | "token": 0 92 | }, 93 | "json": true 94 | }, (err, response, body) => { 95 | if (this._checkHttpError(err, response, reject)) { 96 | return; 97 | } 98 | 99 | if (body.success) { 100 | if (body.state != "get_sms_code") { 101 | return reject(new Error("Unknown state " + body.state)); 102 | } 103 | 104 | return accept(); 105 | } 106 | 107 | if (body.errorText) { 108 | return reject(new Error(body.errorText)); 109 | } 110 | 111 | return reject(new Error("Malformed response")); 112 | }); 113 | }); 114 | }; 115 | 116 | /** 117 | * Request that Steam resends your phone verification SMS message. 118 | * @param {function} [callback] 119 | * @returns {Promise} 120 | */ 121 | SteamStore.prototype.resendVerificationSMS = function(callback) { 122 | return StdLib.Promises.callbackPromise(null, callback, (accept, reject) => { 123 | this.request.post({ 124 | "uri": "https://store.steampowered.com/phone/add_ajaxop", 125 | "form": { 126 | "op": "resend_sms", 127 | "input": "", 128 | "sessionID": this.getSessionID(), 129 | "confirmed": 0, 130 | "checkfortos": 1, 131 | "bisediting": 0, 132 | "token": 0 133 | }, 134 | "json": true 135 | }, (err, response, body) => { 136 | if (this._checkHttpError(err, response, reject)) { 137 | return; 138 | } 139 | 140 | if (body.success) { 141 | if (body.state != "get_sms_code") { 142 | return reject(new Error("Unknown state " + body.state)); 143 | } 144 | 145 | return accept(); 146 | } 147 | 148 | if (body.errorText) { 149 | return reject(new Error(body.errorText)); 150 | } 151 | 152 | return reject(new Error("Malformed response")); 153 | }); 154 | }); 155 | }; 156 | 157 | /** 158 | * Verify your phone number using the SMS verification code you were sent. 159 | * @param {string} code 160 | * @param {function} [callback] 161 | * @returns {Promise} 162 | */ 163 | SteamStore.prototype.verifyPhoneNumber = function(code, callback) { 164 | return StdLib.Promises.callbackPromise(null, callback, (accept, reject) => { 165 | this.request.post({ 166 | "uri": "https://store.steampowered.com/phone/add_ajaxop", 167 | "form": { 168 | "op": "get_sms_code", 169 | "input": code, 170 | "sessionID": this.getSessionID(), 171 | "confirmed": 1, 172 | "checkfortos": 1, 173 | "bisediting": 0, 174 | "token": 0 175 | }, 176 | "json": true 177 | }, (err, response, body) => { 178 | if (this._checkHttpError(err, response, reject)) { 179 | return; 180 | } 181 | 182 | if (body.success) { 183 | if (body.state != "done") { 184 | return reject(new Error("Unknown state " + body.state)); 185 | } 186 | 187 | return accept(); 188 | } 189 | 190 | if (body.errorText) { 191 | return reject(new Error(body.errorText)); 192 | } 193 | 194 | return reject(new Error("Malformed response")); 195 | }); 196 | }); 197 | }; 198 | 199 | /** 200 | * Request to remove the phone number from your account. 201 | * @param {function} [callback] 202 | * @returns {Promise} 203 | */ 204 | SteamStore.prototype.removePhoneNumber = function(callback) { 205 | return StdLib.Promises.callbackPromise(null, callback, (accept, reject) => { 206 | this.request.post({ 207 | "uri": "https://store.steampowered.com/phone/remove_confirm_sms", 208 | "form": { 209 | "sessionID": this.getSessionID(), 210 | "bWasEdit": "" 211 | } 212 | }, (err, response, body) => { 213 | if (this._checkHttpError(err, response, reject)) { 214 | return; 215 | } 216 | 217 | accept(); 218 | }); 219 | }); 220 | }; 221 | 222 | /** 223 | * Confirm the removal of your phone number using the code sent via SMS. 224 | * @param {string} code 225 | * @param {function} [callback] 226 | * @returns {Promise} 227 | */ 228 | SteamStore.prototype.confirmRemovePhoneNumber = function(code, callback) { 229 | return StdLib.Promises.callbackPromise(null, callback, (accept, reject) => { 230 | this.request.post({ 231 | "uri": "https://store.steampowered.com/phone/remove_confirm_smscode_entry", 232 | "form": { 233 | "sessionID": this.getSessionID(), 234 | "bWasEdit": "", 235 | "smscode": code 236 | } 237 | }, (err, response, body) => { 238 | if (this._checkHttpError(err, response, reject)) { 239 | return; 240 | } 241 | 242 | let match = body.match(/id="errortext"[^>]+>([^<]+)<\/div>/); 243 | if (match) { 244 | return reject(new Error(match[1].trim())); 245 | } 246 | 247 | return accept(); 248 | }); 249 | }); 250 | }; 251 | 252 | /** 253 | * Get your account's data such as owned apps, owned packages, wishlisted apps, and ignored apps. 254 | * @param {function} [callback] 255 | * @returns {Promise} 256 | */ 257 | SteamStore.prototype.getAccountData = function(callback) { 258 | return StdLib.Promises.callbackPromise(['ownedApps', 'ownedPackages', 'wishlistedApps', 'ignoredApps', 'suggestedTags'], callback, (accept, reject) => { 259 | this.request.get({ 260 | "uri": "https://store.steampowered.com/dynamicstore/userdata/", 261 | "qs": { 262 | "id": this.steamID.accountid 263 | }, 264 | "json": true 265 | }, (err, response, body) => { 266 | if (this._checkHttpError(err, response, reject)) { 267 | return; 268 | } 269 | 270 | if (!body.rgWishlist || !body.rgOwnedPackages || !body.rgOwnedApps || !body.rgRecommendedTags || !body.rgIgnoredApps) { 271 | return reject(new Error("Malformed response")); 272 | } 273 | 274 | let tags = {}; 275 | body.rgRecommendedTags.forEach(function(tag) { 276 | tags[tag.tagid] = tag.name; 277 | }); 278 | 279 | return accept({ 280 | "ownedApps": body.rgOwnedApps, 281 | "ownedPackages": body.rgOwnedPackages, 282 | "wishlistedApps": body.rgWishlist, 283 | "ignoredApps": body.rgIgnoredApps, 284 | "suggestedTags": tags 285 | }); 286 | }); 287 | }); 288 | }; 289 | 290 | /** 291 | * Check whether your account has a linked verified phone number. 292 | * @param {function} [callback] 293 | * @returns {Promise} 294 | */ 295 | SteamStore.prototype.hasPhone = function(callback) { 296 | return StdLib.Promises.callbackPromise(['hasVerifiedPhone', 'lastDigits'], callback, (accept, reject) => { 297 | this.request.get("https://store.steampowered.com/account/", (err, response, body) => { 298 | if (this._checkHttpError(err, response, reject)) { 299 | return; 300 | } 301 | 302 | let $ = Cheerio.load(body); 303 | let $phone = $('.phone_header_description .account_data_field'); 304 | let match; 305 | 306 | if ($phone && (match = $phone.text().trim().match(/([0-9]{2})($|\s)/))) { 307 | // Has phone number 308 | return accept({ 309 | "hasVerifiedPhone": true, 310 | "lastDigits": match[1] 311 | }); 312 | } 313 | 314 | // See if we have an add-number link 315 | if ($('a[href*="/phone/add"]').length) { 316 | return accept({ 317 | "hasVerifiedPhone": false 318 | }); 319 | } 320 | 321 | return reject(new Error("Malformed response")); 322 | }); 323 | }); 324 | }; 325 | 326 | /** 327 | * Set the display language(s) for your Steam account. 328 | * @param {string} primaryLanguage 329 | * @param {string[]} [secondaryLanguages] 330 | * @param {function} [callback] 331 | * @returns {Promise} 332 | */ 333 | SteamStore.prototype.setDisplayLanguages = function(primaryLanguage, secondaryLanguages, callback) { 334 | return StdLib.Promises.callbackPromise(null, callback, true, (accept, reject) => { 335 | if (typeof secondaryLanguages === 'function') { 336 | callback = secondaryLanguages; 337 | secondaryLanguages = undefined; 338 | } 339 | 340 | this.request.post({ 341 | "uri": "https://store.steampowered.com/account/savelanguagepreferences", 342 | "form": { 343 | "sessionid": this.getSessionID(), 344 | "primary_language": primaryLanguage, 345 | "secondary_languages": secondaryLanguages || [] 346 | } 347 | }, (err, response, body) => { 348 | if (this._checkHttpError(err, response, reject)) { 349 | return; 350 | } 351 | 352 | if (body.success != EResult.OK) { 353 | return reject(new Error(EResult[body.success] || "Error " + body.success)); 354 | } else { 355 | return accept(); 356 | } 357 | }); 358 | }); 359 | }; 360 | 361 | /** 362 | * Create a Steam Wallet for your account, using a Steam Wallet code. This *will not* redeem the code. 363 | * @param {string} code - The Steam Wallet gift code you want to use to create your wallet 364 | * @param {{address, city, country, state, postalCode}} billingAddress 365 | * @param {function} [callback] 366 | * @returns {Promise} 367 | */ 368 | SteamStore.prototype.createWallet = function(code, billingAddress, callback) { 369 | return StdLib.Promises.callbackPromise([ 370 | 'eresult', 371 | 'detail', 372 | 'redeemable', 373 | 'amount', 374 | 'currencyCode' 375 | ], callback, (accept, reject) => { 376 | this.request.post({ 377 | "uri": "https://store.steampowered.com/account/ajaxcreatewalletandcheckfunds/", 378 | "form": { 379 | "wallet_code": code, 380 | "CreateFromAddress": "1", 381 | "Address": billingAddress.address, 382 | "City": billingAddress.city, 383 | "Country": billingAddress.country, 384 | "State": billingAddress.state, 385 | "PostCode": billingAddress.postalCode, 386 | "sessionid": this.getSessionID() 387 | }, 388 | "json": true 389 | }, (err, res, body) => { 390 | if (this._checkHttpError(err, res, reject)) { 391 | return; 392 | } 393 | 394 | if (!body.success && !body.detail && !body.wallet) { 395 | return reject(new Error("Malformed response")); 396 | } 397 | 398 | accept({ 399 | "eresult": body.success, 400 | "detail": body.detail, 401 | "redeemable": body.success == EResult.OK && (body.detail || EPurchaseResult.NoDetail) == EPurchaseResult.NoDetail, 402 | "amount": body.wallet && body.wallet.amount, 403 | "currencyCode": body.wallet && body.wallet.currencycode 404 | }); 405 | }); 406 | }); 407 | }; 408 | 409 | /** 410 | * Check to make sure a Steam wallet code is valid. 411 | * @param {string} code 412 | * @param {function} [callback] 413 | * @returns {Promise} 414 | * @deprecated No longer functional. Will be removed in next release. 415 | */ 416 | SteamStore.prototype.checkWalletCode = function(code, callback) { 417 | return StdLib.Promises.callbackPromise(null, callback, (resolve, reject) => { 418 | reject(new Error('checkWalletCode() is no longer functional')); 419 | }); 420 | }; 421 | 422 | /** 423 | * Redeem a Steam Wallet code. 424 | * @param {string} code 425 | * @param {function} [callback] 426 | * @returns {Promise} 427 | */ 428 | SteamStore.prototype.redeemWalletCode = function(code, callback) { 429 | return StdLib.Promises.callbackPromise([ 430 | 'eresult', 431 | 'detail', 432 | 'formattedNewWalletBalance', 433 | 'amount' 434 | ], callback, true, async (accept, reject) => { 435 | this.request.post({ 436 | uri: 'https://store.steampowered.com/account/ajaxredeemwalletcode/', 437 | form: { 438 | 'wallet_code': code, 439 | 'sessionid': this.getSessionID() 440 | }, 441 | 'json': true 442 | }, (err, response, body) => { 443 | if (this._checkHttpError(err, response, reject)) { 444 | return; 445 | } 446 | 447 | if (!body || (!body.success && !body.detail)) { 448 | return reject(new Error('Malformed response')); 449 | } 450 | 451 | if (body.success == EResult.Fail && [EPurchaseResult.BadActivationCode, EPurchaseResult.DuplicateActivationCode].includes(body.detail)) { 452 | let err = new Error('Wallet code is not valid'); 453 | err.eresult = body.success; 454 | err.purchaseresultdetail = body.detail; 455 | return reject(err); 456 | } 457 | 458 | return accept({ 459 | eresult: body.success, 460 | detail: body.detail, 461 | formattedNewWalletBalance: body.formattednewwalletbalance, 462 | amount: body.amount 463 | }); 464 | }); 465 | }); 466 | }; 467 | 468 | /** 469 | * Get the current formatted balance of your Steam wallet. 470 | * @param {function} [callback] 471 | * @returns {Promise} 472 | */ 473 | SteamStore.prototype.getWalletBalance = function(callback) { 474 | return StdLib.Promises.callbackPromise(null, callback, (accept, reject) => { 475 | this.request.get({ 476 | "uri": "https://store.steampowered.com/steamaccount/addfunds" 477 | }, (err, res, body) => { 478 | if (this._checkHttpError(err, res, reject)) { 479 | return; 480 | } 481 | 482 | let $ = Cheerio.load(body); 483 | let formattedBalance = $('.accountBalance .accountData.price').text(); 484 | 485 | if (!formattedBalance) { 486 | return reject(new Error('Unable to get wallet balance; perhaps your account doesn\'t have a wallet yet.')); 487 | } 488 | 489 | return accept({formattedBalance}); 490 | }); 491 | }); 492 | }; 493 | --------------------------------------------------------------------------------