Compare commits

...

68 Commits

Author SHA1 Message Date
Alexander Corn
f5d76527ba 3.32.1 2017-08-15 21:43:14 -04:00
Alexander Corn
5875f47ebc Ignore chat friendstate responses if we no longer care 2017-08-15 21:43:10 -04:00
Alexander Corn
03e0a1bdac 3.32.0 2017-08-06 19:18:43 -04:00
Alexander Corn
a51973c9b9 Added CSteamGroup methods for group-join requests 2017-08-06 19:11:21 -04:00
Alexander Corn
96d87ee8e6 Added methods to deal with Steam group join requests 2017-08-06 19:08:45 -04:00
Alexander Corn
7beef548dc Merge pull request #174 from Unsigno/master
Updated getUserInventoryContexts
2017-07-11 12:32:27 -04:00
Unsigno
6d9ebabb17 Updated getUserInventoryContexts to return 0 inventories and handle private profile error 2017-07-11 14:02:01 +02:00
Alexander Corn
5d5d8548af 3.31.0 2017-06-15 10:06:54 -04:00
Alexander Corn
b5142ad0cc Added language argument to getUserInventoryContents 2017-06-15 10:04:37 -04:00
Alexander Corn
3045a5d0cc Added stats-reporter integration 2017-06-15 10:03:14 -04:00
Alexander Corn
2dcb123761 Update request dependency version
(make david-dm happy)
2017-06-11 13:26:52 -04:00
Alexander Corn
bd29d598b1 Throw Error if using confirmations without being logged in 2017-06-02 23:35:23 -04:00
Alexander Corn
7e39715427 Merge pull request #168 from morethanwords/patch-1
Update getUserInventoryContents
2017-04-21 14:23:25 -04:00
Eduard Kuzmenko
a97edfd492 Update users.js 2017-04-21 15:46:20 +03:00
Eduard Kuzmenko
68b1f21655 Update getUserInventoryContents
There will be a re-loop if description of the item not the last one in descriptions (and quickDescriptionLookup will rewrite everytime)
2017-04-21 15:26:45 +03:00
Alexander Corn
b4772baf39 3.30.7 2017-03-27 14:42:46 -04:00
Alexander Corn
47dc22ca0d Handle cases where inventories are private in getUserInventoryContexts 2017-03-27 14:39:41 -04:00
Alexander Corn
acbd71666b Merge pull request #165 from Aareksio/patch-1
Check if instance is logged in before comparing steamids (fixes #164)
2017-03-06 11:35:23 -05:00
Arkadiusz Sygulski
3c35026a32 Check if instance is logged in before comparing steamids (fixes #164) 2017-03-06 16:02:20 +01:00
Alexander Corn
aca375d9da Give different error message if family view unlock failed for rate-limit 2017-03-04 22:30:25 -05:00
Alexander Corn
758b749487 3.30.6 2017-02-25 19:12:42 -05:00
Alexander Corn
a9b16cef1e Fixed crash no non-string 403 responses
https://dev.doctormckay.com/topic/845-responsebodymatch-is-not-a-function/
2017-02-25 19:12:34 -05:00
Alexander Corn
4542793175 3.30.5 2017-02-12 02:22:43 -05:00
Alexander Corn
d18a0244ec Updated default user-agent to latest Chrome on Win10 x64 2017-02-12 02:22:17 -05:00
Alexander Corn
b1c772d9b9 Keep HTTP connections open between requests 2017-02-11 22:28:22 -05:00
Alexander Corn
b9ec1ccfb5 3.30.4 2017-01-18 15:51:44 -05:00
Alexander Corn
0c7c030f78 Fixed string in callback instead of Error (fixes #153) 2017-01-18 15:50:11 -05:00
Alexander Corn
e5f1f14aad Fixed empty inventory resulting in "Malformed response" (fixes #156)
Display Steam error message when available instead of "HTTP error 500"
2017-01-18 15:49:27 -05:00
Alexander Corn
0f72ea7d93 3.30.3 2017-01-04 00:10:40 -05:00
Alexander Corn
d1906a2c83 Fallback to the default "?" avatar if we don't have a hash (fixes #151) 2017-01-04 00:10:11 -05:00
Alexander Corn
3964af7ade Throw a descriptive error if accountName or password is missing in login
Closes #119
2017-01-04 00:07:33 -05:00
Alexander Corn
5a5c5373aa Bump dependency versions to make david happy 2017-01-04 00:03:18 -05:00
Alexander Corn
ec60e1b73e 3.30.2 2016-12-30 17:11:12 -05:00
Alexander Corn
a22a75d4e1 Merge pull request #150 from Vankxr/master
Check if requested inventory is ours.
2016-12-30 17:06:21 -05:00
Vankxr
313753bb05 Check if requested inventory is ours.
Should fix https://github.com/DoctorMcKay/node-steamcommunity/issues/149
2016-12-30 21:24:53 +00:00
Alexander Corn
459fe4d8b1 3.30.1 2016-12-15 00:53:46 -05:00
Alexander Corn
489c0441a5 Restore market_fee_app property to CEconItem for Steam Community items 2016-12-15 00:53:26 -05:00
Alexander Corn
d91e74d02a 3.30.0 2016-12-13 01:53:57 -05:00
Alexander Corn
d120d09970 Fixed tag data to be identical to old endpoint 2016-12-13 01:53:30 -05:00
Alexander Corn
84ce979a78 Fixed new inventory loading not working properly 2016-12-13 01:52:52 -05:00
Alexander Corn
88b0495386 Renamed getUserInventory, added back old one and marked it as deprecated 2016-12-13 01:32:17 -05:00
Alexander Corn
d367e8eae4 Added better paging controls for new inventory endpoint 2016-12-13 01:29:06 -05:00
Alexander Corn
9a3cf927c0 Fixed some more currency detection 2016-12-13 01:28:48 -05:00
Alexander Corn
56cdfff672 3.29.1 2016-12-13 01:07:12 -05:00
Alexander Corn
35e6dd16e7 Re-add "pos" property to inventory items for backwards compatibility 2016-12-13 01:07:00 -05:00
Alexander Corn
d0af8584c1 Send referer when fetching inventories 2016-12-13 00:50:29 -05:00
Alexander Corn
f1afcd1962 3.29.0 2016-12-13 00:46:23 -05:00
Alexander Corn
513b0bff1c Added some optimizations and backwards-compatibility for inv fetching 2016-12-13 00:46:02 -05:00
Alexander Corn
ab037b1931 Pass response body back to caller for inventory fetching 2016-12-13 00:45:38 -05:00
Alexander Corn
06df0ecd42 Handle currencies with new endpoint's data in CEconItem 2016-12-13 00:45:23 -05:00
João Silva
b789e13a70 Update inventory loading endpoint (#148)
* Update inventory loading endpoint

Should fix 429s :)

* Fix missing contextID parameter at CEconItem class

* Revert "Fix missing contextID parameter at CEconItem class"

This reverts commit 416321105b.

* Revert "Revert "Fix missing contextID parameter at CEconItem class""

This reverts commit 49ecd46954.

* Update #1
2016-12-13 00:23:02 -05:00
Alexander Corn
95a36b00c1 Remember time offset for purposes of acceptConfirmationForObject
Closes #147
2016-12-08 17:57:01 -05:00
Alexander Corn
41c87501c4 3.28.0 2016-10-28 19:24:05 -04:00
Alexander Corn
4f3ddcc787 Merge pull request #134 from Aareksio/confirmations
Emit `confirmationAccepted` after accepting the confirmation using `confirmationChecker`
2016-10-27 17:07:41 -04:00
Arkadiusz Sygulski
d10fa9983d Emit confirmationAccepted after accepting the confirmation 2016-10-27 22:30:58 +02:00
Arkadiusz Sygulski
666226731e Remove unused function: handleNewConfirmation 2016-10-27 22:18:31 +02:00
Alexander Corn
d096f47cac 3.27.2 2016-10-10 00:55:36 -04:00
Alexander Corn
a8eedb3067 Make it possible to skip json error check 2016-10-10 00:55:19 -04:00
Alexander Corn
ed12a54e81 3.27.1 2016-10-04 09:16:08 -04:00
Alexander Corn
eb341314ef Merge pull request #130 from Pavelas/master
Delete the profile URL cache after log in.
2016-10-04 09:15:03 -04:00
Pavelas
33cd9474ab Delete the profile URL cache after log in. 2016-10-04 10:26:20 +02:00
Alexander Corn
e8a43734be 3.27.0 2016-09-16 19:30:32 -04:00
Alexander Corn
8a27147f5c Added accountAlerts to getNotifications 2016-09-16 19:30:19 -04:00
Alexander Corn
d00d07f828 Added acceptConfirmationForObject convenience method 2016-09-16 19:03:49 -04:00
Alexander Corn
471c11d406 Merge pull request #128 from SzymonLisowiec/patch-1
Problem when search "Key:" in polish language.
2016-09-16 18:21:17 -04:00
Alexander Corn
dd3a9254aa 3.26.1 2016-09-12 11:22:29 -04:00
Alexander Corn
71a3893636 Fixed cookies not being set for non-community domains after logging in
Also fixed secure flag not working for cookies
2016-09-12 11:22:18 -04:00
Kysune
b863377c87 Problem when search "Key:" in polish language. 2016-09-01 21:05:46 +02:00
19 changed files with 814 additions and 112 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
package-lock=false

View File

@@ -9,6 +9,8 @@ This module provides an easy interface for the Steam Community website. This mod
It supports Steam Guard and CAPTCHAs.
This reports anonymous usage statistics to the author. [See here](https://github.com/DoctorMcKay/node-stats-reporter) for more information.
**Have a question about the module or coding in general? *Do not create a GitHub issue.* GitHub issues are for feature
requests and bug reports. Instead, post in the [dedicated forum](https://dev.doctormckay.com/forum/8-node-steamcommunity/).
Such issues may be ignored!**

View File

@@ -1,31 +1,40 @@
module.exports = CEconItem;
function CEconItem(item, descriptions, contextID) {
function CEconItem(item, description, contextID) {
var thing;
for(thing in item) {
if(item.hasOwnProperty(thing)) {
for (thing in item) {
if (item.hasOwnProperty(thing)) {
this[thing] = item[thing];
}
}
this.assetid = this.id = (this.id || this.assetid);
var isCurrency = !!(this.is_currency || this.currency) || typeof this.currencyid !== 'undefined'; // I don't want to put this on the object yet; it's nice to have the ids at the top of printed output
if (isCurrency) {
this.currencyid = this.id = (this.id || this.currencyid);
} else {
this.assetid = this.id = (this.id || this.assetid);
}
this.instanceid = this.instanceid || '0';
this.amount = parseInt(this.amount, 10);
this.contextid = this.contextid || contextID.toString();
// Merge the description
if(descriptions) {
var description = descriptions[this.classid + '_' + this.instanceid];
if(description) {
for(thing in description) {
if(description.hasOwnProperty(thing)) {
this[thing] = description[thing];
}
if (description) {
// Is this a listing of descriptions?
if (description[this.classid + '_' + this.instanceid]) {
description = description[this.classid + '_' + this.instanceid];
}
for (thing in description) {
if (description.hasOwnProperty(thing)) {
this[thing] = description[thing];
}
}
}
this.is_currency = !!this.is_currency;
this.is_currency = isCurrency;
this.tradable = !!this.tradable;
this.marketable = !!this.marketable;
this.commodity = !!this.commodity;
@@ -34,9 +43,34 @@ function CEconItem(item, descriptions, contextID) {
this.fraudwarnings = this.fraudwarnings || [];
this.descriptions = this.descriptions || [];
if(this.owner && JSON.stringify(this.owner) == '{}') {
if (this.owner && JSON.stringify(this.owner) == '{}') {
this.owner = null;
}
// Restore old property names of tags
if (this.tags) {
this.tags = this.tags.map(function(tag) {
return {
"internal_name": tag.internal_name,
"name": tag.localized_tag_name || tag.name,
"category": tag.category,
"color": tag.color || "",
"category_name": tag.localized_category_name || tag.category_name
};
});
}
// Restore market_fee_app, if applicable
var match;
if (this.appid == 753 && this.contextid == 6 && this.market_hash_name && (match = this.market_hash_name.match(/^(\d+)\-/))) {
this.market_fee_app = parseInt(match[1], 10);
}
if (this.actions === "") {
this.actions = [];
}
delete this.currency;
}
CEconItem.prototype.getImageURL = function() {
@@ -52,12 +86,12 @@ CEconItem.prototype.getLargeImageURL = function() {
};
CEconItem.prototype.getTag = function(category) {
if(!this.tags) {
if (!this.tags) {
return null;
}
for(var i = 0; i < this.tags.length; i++) {
if(this.tags[i].category == category) {
for (var i = 0; i < this.tags.length; i++) {
if (this.tags[i].category == category) {
return this.tags[i];
}
}

View File

@@ -113,3 +113,30 @@ CSteamGroup.prototype.kick = function(steamID, callback) {
CSteamGroup.prototype.getHistory = function(page, callback) {
this._community.getGroupHistory(this.steamID, page, callback);
};
/**
* Get requests to join this restricted group.
* @param {function} callback - First argument is null/Error, second is array of SteamID objects
*/
CSteamGroup.prototype.getJoinRequests = function(callback) {
this._community.getGroupJoinRequests(this.steamID, callback);
};
/**
* Respond to one or more join requests to this restricted group.
* @param {SteamID|string|SteamID[]|string[]} steamIDs - The SteamIDs of the users you want to approve or deny membership for (or a single value)
* @param {boolean} approve - True to put them in the group, false to deny their membership
* @param {function} callback - Takes only an Error object/null as the first argument
*/
CSteamGroup.prototype.respondToJoinRequests = function(steamIDs, approve, callback) {
this._community.respondToGroupJoinRequests(this.steamID, steamIDs, approve, callback);
};
/**
* Respond to *ALL* pending group-join requests for this group.
* @param {boolean} approve - True to allow everyone who requested into the group, false to not
* @param {function} callback - Takes only an Error object/null as the first argument
*/
CSteamGroup.prototype.respondToAllJoinRequests = function(approve, callback) {
this._community.respondToAllGroupJoinRequests(this.steamID, approve, callback);
};

View File

@@ -108,6 +108,8 @@ function CSteamUser(community, userData, customurl) {
CSteamUser.getAvatarURL = function(hash, size, protocol) {
size = size || '';
protocol = protocol || 'http://';
hash = hash || "72f78b4c8cc1f62323f8a33f6d53e27db57c2252"; // The default "?" avatar
var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + hash.substring(0, 2) + "/" + hash;
if(size == 'full' || size == 'medium') {
@@ -158,6 +160,25 @@ CSteamUser.prototype.getInventoryContexts = function(callback) {
this._community.getUserInventoryContexts(this.steamID, callback);
};
/**
* Get the contents of a user's inventory context.
* @deprecated Use CSteamUser#getInventoryContents instead
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param callback
*/
CSteamUser.prototype.getInventory = function(appID, contextID, tradableOnly, callback) {
this._community.getUserInventory(this.steamID, appID, contextID, tradableOnly, callback);
};
/**
* Get the contents of a user's inventory context.
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param callback
*/
CSteamUser.prototype.getInventoryContents = function(appID, contextID, tradableOnly, callback) {
this._community.getUserInventoryContents(this.steamID, appID, contextID, tradableOnly, callback);
};

View File

@@ -1,32 +1,9 @@
var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
SteamCommunity.ChatState = {
"Offline": 0,
"LoggingOn": 1,
"LogOnFailed": 2,
"LoggedOn": 3
};
SteamCommunity.PersonaState = {
"Offline": 0,
"Online": 1,
"Busy": 2,
"Away": 3,
"Snooze": 4,
"LookingToTrade": 5,
"LookingToPlay": 6,
"Max": 7
};
SteamCommunity.PersonaStateFlag = {
"HasRichPresence": 1,
"InJoinableGame": 2,
"OnlineUsingWeb": 256,
"OnlineUsingMobile": 512,
"OnlineUsingBigPicture": 1024
};
SteamCommunity.ChatState = require('../resources/EChatState.js');
SteamCommunity.PersonaState = require('../resources/EPersonaState.js');
SteamCommunity.PersonaStateFlag = require('../resources/EPersonaStateFlag.js');
SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
if(this.chatState == SteamCommunity.ChatState.LoggingOn || this.chatState == SteamCommunity.ChatState.LoggedOn) {
@@ -181,7 +158,7 @@ SteamCommunity.prototype._chatPoll = function() {
},
"json": true
}, function(err, response, body) {
if(self.chatState == SteamCommunity.ChatState.Offline) {
if (self.chatState == SteamCommunity.ChatState.Offline) {
return;
}
@@ -242,12 +219,20 @@ SteamCommunity.prototype._relogWebChat = function() {
};
SteamCommunity.prototype._chatUpdatePersona = function(steamID) {
if (!this.chatFriends || self.chatState == SteamCommunity.ChatState.Offline) {
return; // we no longer care
}
this.emit('debug', 'Updating persona data for ' + steamID);
var self = this;
this.httpRequest({
"uri": "https://steamcommunity.com/chat/friendstate/" + steamID.accountid,
"json": true
}, function(err, response, body) {
if (!self.chatFriends || self.chatState == SteamCommunity.ChatState.Offline) {
return; // welp
}
if(err || response.statusCode != 200) {
self.emit('debug', 'Chat update persona error: ' + (err ? err.message : "HTTP error " + response.statusCode));
setTimeout(function() {

View File

@@ -113,7 +113,7 @@ SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, ca
* @param {int|int[]} confID - The ID of the confirmation in question, or an array of confirmation IDs
* @param {string|string[]} confKey - The confirmation key associated with the confirmation in question (or an array of them) (not a TOTP key, the `key` property of CConfirmation)
* @param {int} time - The unix timestamp with which the following key was generated
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag "allow" (if accepting) or "cancel" (if not accepting)
* @param {string} key - The confirmation key that was generated using the preceding time and the tag "allow" (if accepting) or "cancel" (if not accepting)
* @param {boolean} accept - true if you want to accept the confirmation, false if you want to cancel it
* @param {SteamCommunity~genericErrorCallback} callback - Called when the request is complete
*/
@@ -146,6 +146,77 @@ SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time,
});
};
/**
* Accept a confirmation for a given object (trade offer or market listing) automatically.
* @param {string} identitySecret
* @param {number|string} objectID
* @param {SteamCommunity~genericErrorCallback} callback
*/
SteamCommunity.prototype.acceptConfirmationForObject = function(identitySecret, objectID, callback) {
var self = this;
this._usedConfTimes = this._usedConfTimes || [];
if (typeof this._timeOffset !== 'undefined') {
// time offset is already known and saved
doConfirmation();
} else {
SteamTotp.getTimeOffset(function(err, offset) {
if (err) {
callback(err);
return;
}
self._timeOffset = offset;
doConfirmation();
setTimeout(function() {
// Delete the saved time offset after 12 hours because why not
delete self._timeOffset;
}, 1000 * 60 * 60 * 12);
});
}
function doConfirmation() {
var offset = self._timeOffset;
var time = SteamTotp.time(offset);
self.getConfirmations(time, SteamTotp.getConfirmationKey(identitySecret, time, "conf"), function(err, confs) {
if (err) {
callback(err);
return;
}
var conf = confs.filter(function(conf) { return conf.creator == objectID; });
if (conf.length == 0) {
callback(new Error("Could not find confirmation for object " + objectID));
return;
}
conf = conf[0];
// make sure we don't reuse the same time
var localOffset = 0;
do {
time = SteamTotp.time(offset) + localOffset++;
} while (self._usedConfTimes.indexOf(time) != -1);
self._usedConfTimes.push(time);
if (self._usedConfTimes.length > 60) {
self._usedConfTimes.splice(0, self._usedConfTimes.length - 60); // we don't need to save more than 60 entries
}
conf.respond(time, SteamTotp.getConfirmationKey(identitySecret, time, "allow"), true, callback);
});
}
};
/**
* Send a single request to Steam to accept all outstanding confirmations (after loading the list). If one fails, the
* entire request will fail and there will be no way to know which failed without loading the list again.
* @param {number} time
* @param {string} confKey
* @param {string} allowKey
* @param {function} callback
*/
SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowKey, callback) {
var self = this;
@@ -172,6 +243,10 @@ SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowK
};
function request(community, url, key, time, tag, params, json, callback) {
if (!community.steamID) {
throw new Error("Must be logged in before trying to do anything with confirmations");
}
params = params || {};
params.p = SteamTotp.getDeviceID(community.steamID);
params.a = community.steamID.getSteamID64();
@@ -258,8 +333,9 @@ SteamCommunity.prototype.checkConfirmations = function() {
// We should accept this
self.emit('debug', "Accepting confirmation #" + conf.id);
var time = Math.floor(Date.now() / 1000);
conf.respond(time, SteamTotp.getConfirmationKey(self._identitySecret, time, "allow"), true, function() {
conf.respond(time, SteamTotp.getConfirmationKey(self._identitySecret, time, "allow"), true, function(err) {
// If there was an error and it wasn't actually accepted, we'll pick it up again
if (!err) self.emit('confirmationAccepted', conf);
delete self._knownConfirmations[conf.id];
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
});
@@ -311,28 +387,6 @@ SteamCommunity.prototype.checkConfirmations = function() {
self._confirmationTimer = setTimeout(self.checkConfirmations.bind(self), self._confirmationPollInterval);
}
}
function handleNewConfirmation(conf, handleNumber) {
self._knownConfirmations[conf.id] = conf;
// Delay them by 1 second per new confirmation that we see, so that keys won't be the same.
setTimeout(function() {
if(self._identitySecret) {
self.emit('debug', 'Accepting confirmation #' + conf.id);
var time = Math.floor(Date.now() / 1000);
conf.respond(time, SteamTotp.getConfirmationKey(self._identitySecret, time, "allow"), true, function(err) {
if (err) {
self.emit('debug', "Can't accept confirmation #" + conf.id + ": " + err.message);
}
// We'll just retry next time we poll
delete self._knownConfirmations[conf.id];
});
} else {
self.emit('newConfirmation', conf);
}
}, handleNumber * 1000);
}
};
SteamCommunity.prototype._confirmationCheckerGetKey = function(tag, callback) {

View File

@@ -2,6 +2,7 @@ var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
var xml2js = require('xml2js');
var Cheerio = require('cheerio');
var EResult = SteamCommunity.EResult;
SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link, addresses, addressIdx) {
members = members || [];
@@ -152,7 +153,7 @@ SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback
return callback(null, announcements);
});
}, "steamcommunity");
}
};
SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content, callback) {
if(typeof gid === 'string') {
@@ -517,3 +518,108 @@ SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
callback(null, output);
}, "steamcommunity");
};
/**
* Get requests to join a restricted group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {function} callback - First argument is null/Error, second is array of SteamID objects
*/
SteamCommunity.prototype.getGroupJoinRequests = function(gid, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
this.httpRequestGet("https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage", (err, res, body) => {
if (!body) {
callback(new Error("Malformed response"));
return;
}
var matches = body.match(/JoinRequests_ApproveDenyUser\(\W*['"](\d+)['"],\W0\W\)/g);
if (!matches) {
// no pending requests
callback(null, []);
return;
}
var requests = [];
for (var i = 0; i < matches.length; i++) {
requests.push(new SteamID("[U:1:" + matches[i].match(/JoinRequests_ApproveDenyUser\(\W*['"](\d+)['"],\W0\W\)/)[1] + "]"));
}
callback(null, requests);
}, "steamcommunity");
};
/**
* Respond to one or more join requests to a restricted group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {SteamID|string|SteamID[]|string[]} steamIDs - The SteamIDs of the users you want to approve or deny membership for (or a single value)
* @param {boolean} approve - True to put them in the group, false to deny their membership
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.respondToGroupJoinRequests = function(gid, steamIDs, approve, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var rgAccounts = (!Array.isArray(steamIDs) ? [steamIDs] : steamIDs).map(sid => sid.toString());
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage",
"form": {
"rgAccounts": rgAccounts,
"bapprove": approve ? "1" : "0",
"json": "1",
"sessionID": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (!callback) {
return;
}
if (body != EResult.OK) {
var err = new Error(EResult[body] || ("Error " + body));
err.eresult = body;
callback(err);
} else {
callback(null);
}
}, "steamcommunity");
};
/**
* Respond to *ALL* pending group-join requests for a particular group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {boolean} approve - True to allow everyone who requested into the group, false to not
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.respondToAllGroupJoinRequests = function(gid, approve, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage",
"form": {
"bapprove": approve ? "1" : "0",
"json": "1",
"action": "bulkrespond",
"sessionID": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (!callback) {
return;
}
if (body != EResult.OK) {
var err = new Error(EResult[body] || ("Error " + body));
err.eresult = body;
callback(err);
} else {
callback(null);
}
}, "steamcommunity");
};

View File

@@ -47,10 +47,10 @@ SteamCommunity.prototype.httpRequest = function(uri, options, callback, source)
self.request(options, function (err, response, body) {
var hasCallback = !!callback;
var httpError = options.checkHttpError !== false && self._checkHttpError(err, response, callback);
var httpError = options.checkHttpError !== false && self._checkHttpError(err, response, callback, body);
var communityError = !options.json && options.checkCommunityError !== false && self._checkCommunityError(body, httpError ? function () {} : callback); // don't fire the callback if hasHttpError did it already
var tradeError = !options.json && options.checkTradeError !== false && self._checkTradeError(body, httpError || communityError ? function () {} : callback); // don't fire the callback if either of the previous already did
var jsonError = options.json && !body ? new Error("Malformed JSON response") : null;
var jsonError = options.json && options.checkJsonError !== false && !body ? new Error("Malformed JSON response") : null;
self.emit('postHttpRequest', requestID, source, options, httpError || communityError || tradeError || jsonError || null, response, body, {
"hasCallback": hasCallback,
@@ -85,29 +85,29 @@ SteamCommunity.prototype._notifySessionExpired = function(err) {
this.emit('sessionExpired', err);
};
SteamCommunity.prototype._checkHttpError = function(err, response, callback) {
SteamCommunity.prototype._checkHttpError = function(err, response, callback, body) {
if (err) {
callback(err);
callback(err, response, body);
return err;
}
if (response.statusCode >= 300 && response.statusCode <= 399 && response.headers.location.indexOf('/login') != -1) {
err = new Error("Not Logged In");
callback(err);
callback(err, response, body);
this._notifySessionExpired(err);
return err;
}
if (response.statusCode == 403 && response.body && response.body.match(/<div id="parental_notice_instructions">Enter your PIN below to exit Family View.<\/div>/)) {
if (response.statusCode == 403 && typeof response.body === 'string' && response.body.match(/<div id="parental_notice_instructions">Enter your PIN below to exit Family View.<\/div>/)) {
err = new Error("Family View Restricted");
callback(err);
callback(err, response, body);
return err;
}
if (response.statusCode >= 400) {
err = new Error("HTTP error " + response.statusCode);
err.code = response.statusCode;
callback(err);
callback(err, response, body);
return err;
}

View File

@@ -49,7 +49,7 @@ SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
var $ = Cheerio.load(body);
if (!$('.inventory_history_pagingrow').html()) {
callback("Malformed page: no paging row found");
callback(new Error("Malformed page: no paging row found"));
return;
}

View File

@@ -218,16 +218,16 @@ SteamCommunity.prototype.getUserAliases = function(userID, callback) {
};
SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
if(typeof userID === 'string') {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
if(typeof userID === 'function') {
if (typeof userID === 'function') {
callback = userID;
userID = this.steamID;
}
if(!userID) {
if (!userID) {
callback(new Error("No SteamID specified and not logged in"));
return;
}
@@ -240,8 +240,19 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
}
var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
if(!match) {
callback(new Error("Malformed response"));
if (!match) {
var errorMessage = "Malformed response";
if(body.match(/0 items in their inventory\./)){
callback(null, {});
return;
}else if(body.match(/inventory is currently private\./)){
errorMessage = "Private inventory";
}else if(body.match(/profile\_private\_info/)){
errorMessage = "Private profile";
}
callback(new Error(errorMessage));
return;
}
@@ -257,10 +268,19 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
}, "steamcommunity");
};
/**
* Get the contents of a user's inventory context.
* @deprecated Use getUserInventoryContents instead
* @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param {function} callback
*/
SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, tradableOnly, callback) {
var self = this;
if(typeof userID === 'string') {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
@@ -270,6 +290,9 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
function get(inventory, currency, start) {
self.httpRequest({
"uri": "https://steamcommunity.com" + endpoint + "/inventory/json/" + appID + "/" + contextID,
"headers": {
"Referer": "https://steamcommunity.com" + endpoint + "/inventory"
},
"qs": {
"start": start,
"trading": tradableOnly ? 1 : undefined
@@ -281,34 +304,34 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
return;
}
if(!body || !body.success || !body.rgInventory || !body.rgDescriptions || !body.rgCurrency) {
if(body) {
if (!body || !body.success || !body.rgInventory || !body.rgDescriptions || !body.rgCurrency) {
if (body) {
callback(new Error(body.Error || "Malformed response"));
} else {
callback(new Error("Malformed response"));
}
return;
}
var i;
for(i in body.rgInventory) {
if(!body.rgInventory.hasOwnProperty(i)) {
for (i in body.rgInventory) {
if (!body.rgInventory.hasOwnProperty(i)) {
continue;
}
inventory.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
}
for(i in body.rgCurrency) {
if(!body.rgCurrency.hasOwnProperty(i)) {
for (i in body.rgCurrency) {
if (!body.rgCurrency.hasOwnProperty(i)) {
continue;
}
currency.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
}
if(body.more) {
if (body.more) {
var match = response.request.uri.href.match(/\/(profiles|id)\/([^\/]+)\//);
if(match) {
endpoint = "/" + match[1] + "/" + match[2];
@@ -321,3 +344,120 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
}, "steamcommunity");
}
};
/**
* Get the contents of a user's inventory context.
* @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param {string} [language] - The language of item descriptions to return. Omit for default (which may either be English or your account's chosen language)
* @param {function} callback
*/
SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, language, callback) {
var self = this;
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
if (typeof language === 'function') {
callback = language;
language = "english";
}
var pos = 1;
get([], []);
function get(inventory, currency, start) {
self.httpRequest({
"uri": "https://steamcommunity.com/inventory/" + userID.getSteamID64() + "/" + appID + "/" + contextID,
"headers": {
"Referer": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory"
},
"qs": {
"l": language, // Default language
"count": 5000, // Max items per 'page'
"start_assetid": start
},
"json": true
}, function(err, response, body) {
if (err) {
if (err.message == "HTTP error 403" && body === null) {
// 403 with a body of "null" means the inventory/profile is private.
if (self.steamID && userID.getSteamID64() == self.steamID.getSteamID64()) {
// We can never get private profile error for our own inventory!
self._notifySessionExpired(err);
}
callback(new Error("This profile is private."));
return;
}
if (err.message == "HTTP error 500" && body && body.error) {
err = new Error(body.error);
var match = body.error.match(/^(.+) \((\d+)\)$/);
if (match) {
err.message = match[1];
err.eresult = match[2];
callback(err);
return;
}
}
callback(err);
return;
}
if (body && body.success && body.total_inventory_count === 0) {
// Empty inventory
callback(null, [], [], 0);
return;
}
if (!body || !body.success || !body.assets || !body.descriptions) {
if (body) {
// Dunno if the error/Error property even exists on this new endpoint
callback(new Error(body.error || body.Error || "Malformed response"));
} else {
callback(new Error("Malformed response"));
}
return;
}
for (var i = 0; i < body.assets.length; i++) {
var description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid);
if (!tradableOnly || (description && description.tradable)) {
body.assets[i].pos = pos++;
(body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID));
}
}
if (body.more_items) {
get(inventory, currency, body.last_assetid);
} else {
callback(null, inventory, currency, body.total_inventory_count);
}
}, "steamcommunity");
}
// A bit of optimization; objects are hash tables so it's more efficient to look up by key than to iterate an array
var quickDescriptionLookup = {};
function getDescription(descriptions, classID, instanceID) {
var key = classID + '_' + (instanceID || '0'); // instanceID can be undefined, in which case it's 0.
if (quickDescriptionLookup[key]) {
return quickDescriptionLookup[key];
}
for (var i = 0; i < descriptions.length; i++) {
quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i];
}
return quickDescriptionLookup[key];
}
};

View File

@@ -3,7 +3,7 @@ var SteamCommunity = require('../index.js');
SteamCommunity.prototype.getWebApiKey = function(domain, callback) {
var self = this;
this.httpRequest({
"uri": "https://steamcommunity.com/dev/apikey",
"uri": "https://steamcommunity.com/dev/apikey?l=english",
"followRedirect": false
}, function(err, response, body) {
if (err) {
@@ -21,7 +21,7 @@ SteamCommunity.prototype.getWebApiKey = function(domain, callback) {
callback(null, match[1]);
} else {
// We need to register a new API key
self.httpRequestPost('https://steamcommunity.com/dev/registerkey', {
self.httpRequestPost('https://steamcommunity.com/dev/registerkey?l=english', {
"form": {
"domain": domain,
"agreeToTerms": "agreed",

View File

@@ -1,21 +1,19 @@
require('@doctormckay/stats-reporter').setup(require('./package.json'));
var Request = require('request');
var RSA = require('node-bignumber').Key;
var hex2b64 = require('node-bignumber').hex2b64;
var SteamID = require('steamid');
const USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36";
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36";
require('util').inherits(SteamCommunity, require('events').EventEmitter);
module.exports = SteamCommunity;
SteamCommunity.SteamID = SteamID;
SteamCommunity.ConfirmationType = {
// 1 is unknown, possibly "Invalid"
"Trade": 2,
"MarketListing": 3
// 4 is opt-out or other like account confirmation?
};
SteamCommunity.ConfirmationType = require('./resources/EConfirmationType.js');
SteamCommunity.EResult = require('./resources/EResult.js');
function SteamCommunity(options) {
options = options || {};
@@ -45,7 +43,7 @@ function SteamCommunity(options) {
defaults.localAddress = options.localAddress;
}
this.request = options.request || Request.defaults();
this.request = options.request || Request.defaults({"forever": true}); // "forever" indicates that we want a keep-alive agent
this.request = this.request.defaults(defaults);
// English
@@ -56,13 +54,20 @@ function SteamCommunity(options) {
}
SteamCommunity.prototype.login = function(details, callback) {
if(details.steamguard) {
if (!details.accountName || !details.password) {
throw new Error("Missing either accountName or password to login; both are needed");
}
if (details.steamguard) {
var parts = details.steamguard.split('||');
this._setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), true);
}
var self = this;
// Delete the cache
delete self._profileURL;
// headers required to convince steam that we're logging in from a mobile device so that we can get the oAuth data
var mobileHeaders = {
"X-Requested-With": "com.valvesoftware.android.steam.community",
@@ -164,6 +169,8 @@ SteamCommunity.prototype.login = function(details, callback) {
break;
}
}
self.setCookies(cookies);
callback(null, sessionID, cookies, steamguard, oAuth.oauth_token);
}
@@ -217,6 +224,7 @@ SteamCommunity.prototype.oAuthLogin = function(steamguard, token, callback) {
SteamCommunity.prototype._setCookie = function(cookie, secure) {
var protocol = secure ? "https" : "http";
cookie.secure = !!secure;
this._jar.setCookie(cookie.clone(), protocol + "://steamcommunity.com");
this._jar.setCookie(cookie.clone(), protocol + "://store.steampowered.com");
@@ -271,13 +279,25 @@ SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
return;
}
if(!body || typeof body.success !== 'boolean') {
if (!body || typeof body.success !== 'boolean') {
callback("Invalid response");
return;
}
if(!body.success) {
callback("Incorrect PIN");
if (!body.success) {
switch (body.eresult) {
case 15:
callback("Incorrect PIN");
break;
case 25:
callback("Too many invalid PIN attempts");
break;
default:
callback("Error " + body.eresult);
}
return;
}
@@ -311,8 +331,8 @@ SteamCommunity.prototype.getNotifications = function(callback) {
// dunno about 7
"gifts": body.notifications[8] || 0,
"chat": body.notifications[9] || 0,
"helpRequestReplies": body.notifications[10] || 0
// dunno about 11
"helpRequestReplies": body.notifications[10] || 0,
"accountAlerts": body.notifications[11] || 0
};
callback(null, notifications);

View File

@@ -1,8 +1,11 @@
{
"name": "steamcommunity",
"version": "3.26.0",
"version": "3.32.1",
"description": "Provides an interface for logging into and interacting with the Steam Community website",
"keywords": ["steam", "steam community"],
"keywords": [
"steam",
"steam community"
],
"homepage": "https://github.com/DoctorMcKay/node-steamcommunity",
"bugs": {
"url": "https://github.com/DoctorMcKay/node-steamcommunity/issues"
@@ -13,12 +16,13 @@
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
},
"dependencies": {
"request": "^2.61.0",
"@doctormckay/stats-reporter": "^1.0.2",
"request": "^2.81.0",
"node-bignumber": "^1.2.1",
"steamid": "^1.0.0",
"xml2js": "^0.4.11",
"cheerio": "0.19.0",
"async": "^1.4.2",
"cheerio": "0.22.0",
"async": "^2.1.4",
"steam-totp": "^1.3.0"
},
"engines": {

14
resources/EChatState.js Normal file
View File

@@ -0,0 +1,14 @@
/**
* @enum EChatState
*/
module.exports = {
"Offline": 0,
"LoggingOn": 1,
"LogOnFailed": 2,
"LoggedOn": 3,
"0": "Offline",
"1": "LoggingOn",
"2": "LogOnFailed",
"3": "LoggedOn"
};

View File

@@ -0,0 +1,12 @@
/**
* @enum EConfirmationType
*/
module.exports = {
// 1 is unknown, possibly "Invalid"
"Trade": 2,
"MarketListing": 3,
// 4 is opt-out or other like account confirmation?
"2": "Trade",
"3": "MarketListing"
};

View File

@@ -0,0 +1,23 @@
/**
* @enum EPersonaState
*/
module.exports = {
"Offline": 0,
"Online": 1,
"Busy": 2,
"Away": 3,
"Snooze": 4,
"LookingToTrade": 5,
"LookingToPlay": 6,
"Max": 7,
// Value-to-name mapping for convenience
"0": "Offline",
"1": "Online",
"2": "Busy",
"3": "Away",
"4": "Snooze",
"5": "LookingToTrade",
"6": "LookingToPlay",
"7": "Max",
};

View File

@@ -0,0 +1,27 @@
/**
* @enum EPersonaStateFlag
*/
module.exports = {
"HasRichPresence": 1,
"InJoinableGame": 2,
"Golden": 4, // removed "no longer has any effect"
"OnlineUsingWeb": 256, // removed "renamed to ClientTypeWeb"
"ClientTypeWeb": 256,
"OnlineUsingMobile": 512, // removed "renamed to ClientTypeMobile"
"ClientTypeMobile": 512,
"OnlineUsingBigPicture": 1024, // removed "renamed to ClientTypeTenfoot"
"ClientTypeTenfoot": 1024,
"OnlineUsingVR": 2048, // removed "renamed to ClientTypeVR"
"ClientTypeVR": 2048,
"LaunchTypeGamepad": 4096,
// Value-to-name mapping for convenience
"1": "HasRichPresence",
"2": "InJoinableGame",
"4": "Golden",
"256": "ClientTypeWeb",
"512": "ClientTypeMobile",
"1024": "ClientTypeTenfoot",
"2048": "ClientTypeVR",
"4096": "LaunchTypeGamepad",
};

232
resources/EResult.js Normal file
View File

@@ -0,0 +1,232 @@
/**
* @enum EResult
*/
module.exports = {
"Invalid": 0,
"OK": 1,
"Fail": 2,
"NoConnection": 3,
"InvalidPassword": 5,
"LoggedInElsewhere": 6,
"InvalidProtocolVer": 7,
"InvalidParam": 8,
"FileNotFound": 9,
"Busy": 10,
"InvalidState": 11,
"InvalidName": 12,
"InvalidEmail": 13,
"DuplicateName": 14,
"AccessDenied": 15,
"Timeout": 16,
"Banned": 17,
"AccountNotFound": 18,
"InvalidSteamID": 19,
"ServiceUnavailable": 20,
"NotLoggedOn": 21,
"Pending": 22,
"EncryptionFailure": 23,
"InsufficientPrivilege": 24,
"LimitExceeded": 25,
"Revoked": 26,
"Expired": 27,
"AlreadyRedeemed": 28,
"DuplicateRequest": 29,
"AlreadyOwned": 30,
"IPNotFound": 31,
"PersistFailed": 32,
"LockingFailed": 33,
"LogonSessionReplaced": 34,
"ConnectFailed": 35,
"HandshakeFailed": 36,
"IOFailure": 37,
"RemoteDisconnect": 38,
"ShoppingCartNotFound": 39,
"Blocked": 40,
"Ignored": 41,
"NoMatch": 42,
"AccountDisabled": 43,
"ServiceReadOnly": 44,
"AccountNotFeatured": 45,
"AdministratorOK": 46,
"ContentVersion": 47,
"TryAnotherCM": 48,
"PasswordRequiredToKickSession": 49,
"AlreadyLoggedInElsewhere": 50,
"Suspended": 51,
"Cancelled": 52,
"DataCorruption": 53,
"DiskFull": 54,
"RemoteCallFailed": 55,
"PasswordNotSet": 56, // removed "renamed to PasswordUnset"
"PasswordUnset": 56,
"ExternalAccountUnlinked": 57,
"PSNTicketInvalid": 58,
"ExternalAccountAlreadyLinked": 59,
"RemoteFileConflict": 60,
"IllegalPassword": 61,
"SameAsPreviousValue": 62,
"AccountLogonDenied": 63,
"CannotUseOldPassword": 64,
"InvalidLoginAuthCode": 65,
"AccountLogonDeniedNoMailSent": 66, // removed "renamed to AccountLogonDeniedNoMail"
"AccountLogonDeniedNoMail": 66,
"HardwareNotCapableOfIPT": 67,
"IPTInitError": 68,
"ParentalControlRestricted": 69,
"FacebookQueryError": 70,
"ExpiredLoginAuthCode": 71,
"IPLoginRestrictionFailed": 72,
"AccountLocked": 73, // removed "renamed to AccountLockedDown"
"AccountLockedDown": 73,
"AccountLogonDeniedVerifiedEmailRequired": 74,
"NoMatchingURL": 75,
"BadResponse": 76,
"RequirePasswordReEntry": 77,
"ValueOutOfRange": 78,
"UnexpectedError": 79,
"Disabled": 80,
"InvalidCEGSubmission": 81,
"RestrictedDevice": 82,
"RegionLocked": 83,
"RateLimitExceeded": 84,
"AccountLogonDeniedNeedTwoFactorCode": 85, // removed "renamed to AccountLoginDeniedNeedTwoFactor"
"AccountLoginDeniedNeedTwoFactor": 85,
"ItemOrEntryHasBeenDeleted": 86, // removed "renamed to ItemDeleted"
"ItemDeleted": 86,
"AccountLoginDeniedThrottle": 87,
"TwoFactorCodeMismatch": 88,
"TwoFactorActivationCodeMismatch": 89,
"AccountAssociatedToMultiplePlayers": 90, // removed "renamed to AccountAssociatedToMultiplePartners"
"AccountAssociatedToMultiplePartners": 90,
"NotModified": 91,
"NoMobileDeviceAvailable": 92, // removed "renamed to NoMobileDevice"
"NoMobileDevice": 92,
"TimeIsOutOfSync": 93, // removed "renamed to TimeNotSynced"
"TimeNotSynced": 93,
"SMSCodeFailed": 94,
"TooManyAccountsAccessThisResource": 95, // removed "renamed to AccountLimitExceeded"
"AccountLimitExceeded": 95,
"AccountActivityLimitExceeded": 96,
"PhoneActivityLimitExceeded": 97,
"RefundToWallet": 98,
"EmailSendFailure": 99,
"NotSettled": 100,
"NeedCaptcha": 101,
"GSLTDenied": 102,
"GSOwnerDenied": 103,
"InvalidItemType": 104,
"IPBanned": 105,
"GSLTExpired": 106,
"InsufficientFunds": 107,
"TooManyPending": 108,
// Value-to-name mapping for convenience
"0": "Invalid",
"1": "OK",
"2": "Fail",
"3": "NoConnection",
"5": "InvalidPassword",
"6": "LoggedInElsewhere",
"7": "InvalidProtocolVer",
"8": "InvalidParam",
"9": "FileNotFound",
"10": "Busy",
"11": "InvalidState",
"12": "InvalidName",
"13": "InvalidEmail",
"14": "DuplicateName",
"15": "AccessDenied",
"16": "Timeout",
"17": "Banned",
"18": "AccountNotFound",
"19": "InvalidSteamID",
"20": "ServiceUnavailable",
"21": "NotLoggedOn",
"22": "Pending",
"23": "EncryptionFailure",
"24": "InsufficientPrivilege",
"25": "LimitExceeded",
"26": "Revoked",
"27": "Expired",
"28": "AlreadyRedeemed",
"29": "DuplicateRequest",
"30": "AlreadyOwned",
"31": "IPNotFound",
"32": "PersistFailed",
"33": "LockingFailed",
"34": "LogonSessionReplaced",
"35": "ConnectFailed",
"36": "HandshakeFailed",
"37": "IOFailure",
"38": "RemoteDisconnect",
"39": "ShoppingCartNotFound",
"40": "Blocked",
"41": "Ignored",
"42": "NoMatch",
"43": "AccountDisabled",
"44": "ServiceReadOnly",
"45": "AccountNotFeatured",
"46": "AdministratorOK",
"47": "ContentVersion",
"48": "TryAnotherCM",
"49": "PasswordRequiredToKickSession",
"50": "AlreadyLoggedInElsewhere",
"51": "Suspended",
"52": "Cancelled",
"53": "DataCorruption",
"54": "DiskFull",
"55": "RemoteCallFailed",
"56": "PasswordUnset",
"57": "ExternalAccountUnlinked",
"58": "PSNTicketInvalid",
"59": "ExternalAccountAlreadyLinked",
"60": "RemoteFileConflict",
"61": "IllegalPassword",
"62": "SameAsPreviousValue",
"63": "AccountLogonDenied",
"64": "CannotUseOldPassword",
"65": "InvalidLoginAuthCode",
"66": "AccountLogonDeniedNoMail",
"67": "HardwareNotCapableOfIPT",
"68": "IPTInitError",
"69": "ParentalControlRestricted",
"70": "FacebookQueryError",
"71": "ExpiredLoginAuthCode",
"72": "IPLoginRestrictionFailed",
"73": "AccountLockedDown",
"74": "AccountLogonDeniedVerifiedEmailRequired",
"75": "NoMatchingURL",
"76": "BadResponse",
"77": "RequirePasswordReEntry",
"78": "ValueOutOfRange",
"79": "UnexpectedError",
"80": "Disabled",
"81": "InvalidCEGSubmission",
"82": "RestrictedDevice",
"83": "RegionLocked",
"84": "RateLimitExceeded",
"85": "AccountLoginDeniedNeedTwoFactor",
"86": "ItemDeleted",
"87": "AccountLoginDeniedThrottle",
"88": "TwoFactorCodeMismatch",
"89": "TwoFactorActivationCodeMismatch",
"90": "AccountAssociatedToMultiplePartners",
"91": "NotModified",
"92": "NoMobileDevice",
"93": "TimeNotSynced",
"94": "SMSCodeFailed",
"95": "AccountLimitExceeded",
"96": "AccountActivityLimitExceeded",
"97": "PhoneActivityLimitExceeded",
"98": "RefundToWallet",
"99": "EmailSendFailure",
"100": "NotSettled",
"101": "NeedCaptcha",
"102": "GSLTDenied",
"103": "GSOwnerDenied",
"104": "InvalidItemType",
"105": "IPBanned",
"106": "GSLTExpired",
"107": "InsufficientFunds",
"108": "TooManyPending",
};