├── .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 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
4 |
5 |
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 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------