Compare commits

..

91 Commits

Author SHA1 Message Date
Alex Corn
051d376508 3.35.2 2018-04-10 01:08:14 -04:00
Alex Corn
913dff68ef Fixed crashes if somehow market items are really broken (fixes #197) 2018-04-10 01:06:52 -04:00
Alex Corn
754847ee88 Make CMarketItem not broken for non-CSGO apps (fixes #196) 2018-04-10 01:01:03 -04:00
Alex Corn
645e03b89c 3.35.1 2018-04-01 23:36:04 -04:00
Alex Corn
aa302bc09e Fixed various profile url requests 2018-04-01 23:35:56 -04:00
Alex Corn
1c0f8d4bca 3.35.0 2018-04-01 03:39:00 -04:00
Alex Corn
a7bd694f4b Added inventory gift redemption 2018-04-01 03:38:45 -04:00
Alex Corn
9e380e71f5 jsdoc 2018-04-01 03:31:07 -04:00
Alex Corn
e97e63fe6d Added ability to get gem value for an item and to turn items into gems 2018-04-01 03:22:00 -04:00
Alex Corn
f6c7c74ab6 Fixed Node.js process being kept running unnecessarily 2018-04-01 03:15:13 -04:00
Alex Corn
8846082f60 Fixed getMarketApps not returning all apps 2018-04-01 02:51:46 -04:00
Alexander Corn
eb609b5c42 Added some more explicit error handling if we try to do things while logged out 2018-02-27 01:40:59 -05:00
Alex Corn
238a36de0f v3.34.1 2018-01-22 02:02:56 -05:00
Alex Corn
c38111b61a Merge pull request #189 from Aareksio/patch-1
Fix infinite loop in getWebApiKey
2018-01-22 01:48:17 -05:00
Arkadiusz Sygulski
0eb27a15d2 Fix infinite loop in getWebApiKey
Fix infinite loop in getWebApiKey when trying to get web api key on account without email address confirmed
2018-01-22 03:48:45 +01:00
Alexander Corn
0ab3a94493 Use writeFileSync instead of writeFile without callback 2018-01-16 14:40:07 -05:00
Alexander Corn
7c2ec65b8c Add .editorconfig 2017-12-30 02:25:07 -05:00
Alexander Corn
8e761ffe6c 3.34.0 2017-12-28 00:34:23 -05:00
Alexander Corn
06f04887fd Updated CONTRIBUTING.md 2017-12-28 00:34:15 -05:00
Alexander Corn
e5f986a8bf Added author info 2017-12-28 00:33:01 -05:00
Alexander Corn
5da6e40ebb Fixed some code style issues 2017-12-28 00:32:06 -05:00
Alex Corn
0b8dd57a40 Merge pull request #186 from hell00world/master
Added changeTradeURL method
2017-12-28 00:21:00 -05:00
hell00world
6f8456882a Added changeTradeURL method 2017-12-28 00:19:57 +01:00
Alex Corn
0b57b47ecb Minor tweaks 2017-12-26 16:39:23 -05:00
Alex Corn
b5f50a6614 Style update 2017-12-26 16:34:19 -05:00
Alex Corn
4257140c15 Merge remote-tracking branch 'origin/master' 2017-12-26 16:34:02 -05:00
Alex Corn
724badd175 Merge pull request #163 from potato20/disable-mobile-flag
Flag to disable mobile login
2017-12-26 16:33:56 -05:00
Alex Corn
a9455b0ff6 Style updates, use HTTPS, coerce steamids to SteamId objects 2017-12-26 16:32:21 -05:00
Alex Corn
b3dfdd47d8 Fixed missing brace 2017-12-26 16:32:04 -05:00
Alex Corn
1efac6bc70 Merge pull request #159 from Jarzon/groupcomments
Steam group comments
2017-12-26 16:28:16 -05:00
Alex Corn
eacb85c711 3.33.2 2017-11-30 08:59:58 -05:00
Alex Corn
8eab34bf18 Fixed errors not being handled in getTradeURL 2017-11-30 08:59:49 -05:00
Alexander Corn
b45e8d97ed 3.33.1 2017-10-21 16:52:11 -04:00
Alex Corn
6abb14a9bc Merge pull request #183 from borianm/patch-1
Fix for issue #182
2017-10-21 16:48:23 -04:00
Alexander Corn
6049d7a259 Added deprecation notice to getInventoryHistory 2017-10-20 19:28:26 -04:00
AirboN
e0320b27d7 Fixed issue with undefined author in events 2017-10-17 00:48:06 +02:00
AirboN
7986a87b3f Fixed issue with undefined author in events 2017-10-17 00:42:26 +02:00
Alexander Corn
cf7e5f8995 3.33.0 2017-10-06 12:01:10 -04:00
Alexander Corn
a03dad9507 Added getTradeURL method 2017-10-02 14:55:29 -04:00
Alexander Corn
7da7ecfc56 3.32.3 2017-09-29 00:13:17 -04:00
Alex Corn
6bf3f26f2f Merge pull request #175 from Visagalis/patch-1
Typo/bug in GetGroupHistory code.
2017-09-29 00:13:00 -04:00
Jason Vaillancourt
dc2e5d09a7 Merge branch 'master' into groupcomments 2017-08-20 20:13:41 -04:00
Alexander Corn
d716e8a494 3.32.2 2017-08-17 13:12:44 -04:00
Alexander Corn
458a179852 Fixed reference to undefined self 2017-08-17 13:12:36 -04:00
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
Julius
d7095aa7c5 Typo/bug in GetGroupHistory code.
Typo which makes group history objects to contain incorrect actor (it contains same actor as user).
2017-08-08 21:52:12 +03: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
Jarzon
3bba7025d4 Changes for code consistency 2017-03-15 13:08:22 -04:00
Jarzon
e2f94cb1e7 Convert the number to a string 2017-03-15 12:55:48 -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
Jarzon
91285ba1c7 Merge branch 'master' into groupcomments 2017-03-04 09:04:24 -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
Potato 2.0
c6ab392c37 Add disableMobile flag to login() so user has option to not pretend we're logging in from a phone 2017-02-18 14:13:15 -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
Jarzon
530bbcb9eb Pass a Error obj to the callback 2017-02-08 15:22:50 -05:00
Jarzon
f7ebe68bd8 Adding .deleteComment() 2017-02-08 15:08:34 -05:00
Jarzon
e2dae00af2 Simplifying the request's options 2017-02-07 18:34:12 -05:00
Jarzon
bbd80d4154 Fixing not getting the html in the comment 2017-02-02 07:53:18 -05:00
Jarzon
c180557f52 Using decodeSteamTime() to parse date
Adding to .decodeSteamTime() the ability to parse steam relative time
2017-02-01 13:30:38 -05:00
Jarzon
b3174b9f0b Revert using chrono to parse the date 2017-02-01 12:03:34 -05:00
Jarzon
12f1ddf6a0 Missing header Content-Type 2017-02-01 11:27:58 -05:00
Jarzon
db3aa4ace8 Catch the community id and the custom url of the author 2017-01-31 08:48:38 -05:00
Jarzon
f1b742dff4 Adding Chrono to parse relative date 2017-01-31 06:26:11 -05:00
Jarzon
444649ee8c Adding .getAllComments() 2017-01-31 06:16:38 -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
27 changed files with 1009 additions and 185 deletions

13
.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

1
.npmrc Normal file
View File

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

View File

@@ -28,6 +28,7 @@ Submitting a pull request? Great! Thanks for contributing your time and code! Pl
- Tabs for indentation - Tabs for indentation
- camelCase for variables and functions - camelCase for variables and functions
- Opening braces on the same line as the if/for/while statement - Opening braces on the same line as the if/for/while statement
- No omitting unnecessary braces
- etc. - etc.
- Please avoid breaking changes. If you make a breaking change that can be done in a backwards-compatible manner, I won't accept it. - Please avoid breaking changes. If you make a breaking change that can be done in a backwards-compatible manner, I won't accept it.
- Please don't increment the version number in `package.json`. I'll do that myself when I publish it to npm. - Please don't increment the version number in `package.json`. I'll do that myself when I publish it to npm.

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2015 Alexander Corn Copyright (c) 2015 Alex Corn
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

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. 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 **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/). requests and bug reports. Instead, post in the [dedicated forum](https://dev.doctormckay.com/forum/8-node-steamcommunity/).
Such issues may be ignored!** Such issues may be ignored!**

View File

@@ -12,7 +12,7 @@ SteamCommunity.prototype.getMarketItem = function(appid, hashName, currency, cal
callback(err); callback(err);
return; return;
} }
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
if($('.market_listing_table_message') && $('.market_listing_table_message').text().trim() == 'There are no listings for this item.') { if($('.market_listing_table_message') && $('.market_listing_table_message').text().trim() == 'There are no listings for this item.') {
callback(new Error("There are no listings for this item.")); callback(new Error("There are no listings for this item."));
@@ -35,7 +35,7 @@ function CMarketItem(appid, hashName, community, body, $) {
this._hashName = hashName; this._hashName = hashName;
this._community = community; this._community = community;
this._$ = $; this._$ = $;
this._country = "US"; this._country = "US";
var match = body.match(/var g_strCountryCode = "([^"]+)";/); var match = body.match(/var g_strCountryCode = "([^"]+)";/);
if(match) { if(match) {
@@ -47,14 +47,14 @@ function CMarketItem(appid, hashName, community, body, $) {
if(match) { if(match) {
this._language = match[1]; this._language = match[1];
} }
this.commodity = false; this.commodity = false;
var match = body.match(/Market_LoadOrderSpread\(\s*(\d+)\s*\);/); match = body.match(/Market_LoadOrderSpread\(\s*(\d+)\s*\);/);
if(match) { if(match) {
this.commodity = true; this.commodity = true;
this.commodityID = parseInt(match[1], 10); this.commodityID = parseInt(match[1], 10);
} }
this.medianSalePrices = null; this.medianSalePrices = null;
match = body.match(/var line1=([^;]+);/); match = body.match(/var line1=([^;]+);/);
if(match) { if(match) {
@@ -78,7 +78,8 @@ function CMarketItem(appid, hashName, community, body, $) {
if (match) { if (match) {
try { try {
this.assets = JSON.parse(match[1]); this.assets = JSON.parse(match[1]);
this.assets = this.assets['730']['2']; this.assets = this.assets[appid];
this.assets = this.assets[Object.keys(this.assets)[0]];
this.firstAsset = this.assets[Object.keys(this.assets)[0]]; this.firstAsset = this.assets[Object.keys(this.assets)[0]];
} catch (e) { } catch (e) {
// ignore // ignore
@@ -112,29 +113,29 @@ CMarketItem.prototype.updatePriceForCommodity = function(currency, callback) {
callback(err); callback(err);
return; return;
} }
if(body.success != 1) { if(body.success != 1) {
if(callback) { if(callback) {
callback(new Error("Error " + body.success)); callback(new Error("Error " + body.success));
} }
return; return;
} }
var match = (body.sell_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/); var match = (body.sell_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/);
if(match) { if(match) {
self.quantity = parseInt(match[1], 10); self.quantity = parseInt(match[1], 10);
} }
self.buyQuantity = 0; self.buyQuantity = 0;
match = (body.buy_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/); match = (body.buy_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/);
if(match) { if(match) {
self.buyQuantity = parseInt(match[1], 10); self.buyQuantity = parseInt(match[1], 10);
} }
self.lowestPrice = parseInt(body.lowest_sell_order, 10); self.lowestPrice = parseInt(body.lowest_sell_order, 10);
self.highestBuyOrder = parseInt(body.highest_buy_order, 10); self.highestBuyOrder = parseInt(body.highest_buy_order, 10);
// TODO: The tables? // TODO: The tables?
if(callback) { if(callback) {
callback(null); callback(null);

View File

@@ -3,26 +3,26 @@ var Cheerio = require('cheerio');
SteamCommunity.prototype.marketSearch = function(options, callback) { SteamCommunity.prototype.marketSearch = function(options, callback) {
var qs = {}; var qs = {};
if(typeof options === 'string') { if(typeof options === 'string') {
qs.query = options; qs.query = options;
} else { } else {
qs.query = options.query || ''; qs.query = options.query || '';
qs.appid = options.appid; qs.appid = options.appid;
qs.search_descriptions = options.searchDescriptions ? 1 : 0; qs.search_descriptions = options.searchDescriptions ? 1 : 0;
if(qs.appid) { if(qs.appid) {
for(var i in options) { for(var i in options) {
if(['query', 'appid', 'searchDescriptions'].indexOf(i) != -1) { if(['query', 'appid', 'searchDescriptions'].indexOf(i) != -1) {
continue; continue;
} }
// This is a tag // This is a tag
qs['category_' + qs.appid + '_' + i + '[]'] = 'tag_' + options[i]; qs['category_' + qs.appid + '_' + i + '[]'] = 'tag_' + options[i];
} }
} }
} }
qs.start = 0; qs.start = 0;
qs.count = 100; qs.count = 100;
qs.sort_column = 'price'; qs.sort_column = 'price';
@@ -80,10 +80,10 @@ SteamCommunity.prototype.marketSearch = function(options, callback) {
function CMarketSearchResult(row) { function CMarketSearchResult(row) {
var match = row.attr('href').match(/\/market\/listings\/(\d+)\/([^\?\/]+)/); var match = row.attr('href').match(/\/market\/listings\/(\d+)\/([^\?\/]+)/);
this.appid = parseInt(match[1], 10); this.appid = parseInt(match[1], 10);
this.market_hash_name = decodeURIComponent(match[2]); this.market_hash_name = decodeURIComponent(match[2]);
this.image = row.find('.market_listing_item_img').attr('src').match(/^https?:\/\/[^\/]+\/economy\/image\/[^\/]+\//)[0]; this.image = ((row.find('.market_listing_item_img').attr('src') || "").match(/^https?:\/\/[^\/]+\/economy\/image\/[^\/]+\//) || [])[0];
this.price = parseInt(row.find('.market_listing_their_price .market_table_value span.normal_price').text().replace(/[^\d]+/g, ''), 10); this.price = parseInt(row.find('.market_listing_their_price .market_table_value span.normal_price').text().replace(/[^\d]+/g, ''), 10);
this.quantity = parseInt(row.find('.market_listing_num_listings_qty').text().replace(/[^\d]+/g, ''), 10); this.quantity = parseInt(row.find('.market_listing_num_listings_qty').text().replace(/[^\d]+/g, ''), 10);
} }

View File

@@ -113,3 +113,39 @@ CSteamGroup.prototype.kick = function(steamID, callback) {
CSteamGroup.prototype.getHistory = function(page, callback) { CSteamGroup.prototype.getHistory = function(page, callback) {
this._community.getGroupHistory(this.steamID, page, callback); this._community.getGroupHistory(this.steamID, page, callback);
}; };
CSteamGroup.prototype.getAllComments = function(from, count, callback) {
this._community.getAllGroupComments(this.steamID, from, count, callback);
};
CSteamGroup.prototype.deleteComment = function(cid, callback) {
this._community.deleteGroupComment(this.steamID, cid, 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) { CSteamUser.getAvatarURL = function(hash, size, protocol) {
size = size || ''; size = size || '';
protocol = protocol || 'http://'; 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; var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + hash.substring(0, 2) + "/" + hash;
if(size == 'full' || size == 'medium') { if(size == 'full' || size == 'medium') {

View File

@@ -1,32 +1,9 @@
var SteamCommunity = require('../index.js'); var SteamCommunity = require('../index.js');
var SteamID = require('steamid'); var SteamID = require('steamid');
SteamCommunity.ChatState = { SteamCommunity.ChatState = require('../resources/EChatState.js');
"Offline": 0, SteamCommunity.PersonaState = require('../resources/EPersonaState.js');
"LoggingOn": 1, SteamCommunity.PersonaStateFlag = require('../resources/EPersonaStateFlag.js');
"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.prototype.chatLogon = function(interval, uiMode) { SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
if(this.chatState == SteamCommunity.ChatState.LoggingOn || this.chatState == SteamCommunity.ChatState.LoggedOn) { if(this.chatState == SteamCommunity.ChatState.LoggingOn || this.chatState == SteamCommunity.ChatState.LoggedOn) {
@@ -181,7 +158,7 @@ SteamCommunity.prototype._chatPoll = function() {
}, },
"json": true "json": true
}, function(err, response, body) { }, function(err, response, body) {
if(self.chatState == SteamCommunity.ChatState.Offline) { if (self.chatState == SteamCommunity.ChatState.Offline) {
return; return;
} }
@@ -242,12 +219,20 @@ SteamCommunity.prototype._relogWebChat = function() {
}; };
SteamCommunity.prototype._chatUpdatePersona = function(steamID) { SteamCommunity.prototype._chatUpdatePersona = function(steamID) {
if (!this.chatFriends || this.chatState == SteamCommunity.ChatState.Offline) {
return; // we no longer care
}
this.emit('debug', 'Updating persona data for ' + steamID); this.emit('debug', 'Updating persona data for ' + steamID);
var self = this; var self = this;
this.httpRequest({ this.httpRequest({
"uri": "https://steamcommunity.com/chat/friendstate/" + steamID.accountid, "uri": "https://steamcommunity.com/chat/friendstate/" + steamID.accountid,
"json": true "json": true
}, function(err, response, body) { }, function(err, response, body) {
if (!self.chatFriends || self.chatState == SteamCommunity.ChatState.Offline) {
return; // welp
}
if(err || response.statusCode != 200) { if(err || response.statusCode != 200) {
self.emit('debug', 'Chat update persona error: ' + (err ? err.message : "HTTP error " + response.statusCode)); self.emit('debug', 'Chat update persona error: ' + (err ? err.message : "HTTP error " + response.statusCode));
setTimeout(function() { setTimeout(function() {

View File

@@ -243,6 +243,10 @@ SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowK
}; };
function request(community, url, key, time, tag, params, json, callback) { 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 = params || {};
params.p = SteamTotp.getDeviceID(community.steamID); params.p = SteamTotp.getDeviceID(community.steamID);
params.a = community.steamID.getSteamID64(); params.a = community.steamID.getSteamID64();

View File

@@ -2,6 +2,8 @@ var SteamCommunity = require('../index.js');
var SteamID = require('steamid'); var SteamID = require('steamid');
var xml2js = require('xml2js'); var xml2js = require('xml2js');
var Cheerio = require('cheerio'); var Cheerio = require('cheerio');
var Helpers = require('./helpers.js');
var EResult = SteamCommunity.EResult;
SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link, addresses, addressIdx) { SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link, addresses, addressIdx) {
members = members || []; members = members || [];
@@ -142,7 +144,7 @@ SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback
headline: announcement.title[0], headline: announcement.title[0],
content: announcement.description[0], content: announcement.description[0],
date: new Date(announcement.pubDate[0]), date: new Date(announcement.pubDate[0]),
author: announcement.author[0], author: (typeof announcement.author === 'undefined') ? null : announcement.author[0],
aid: splitLink[splitLink.length - 1] aid: splitLink[splitLink.length - 1]
} }
}).filter(function(announcement) { }).filter(function(announcement) {
@@ -152,7 +154,7 @@ SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback
return callback(null, announcements); return callback(null, announcements);
}); });
}, "steamcommunity"); }, "steamcommunity");
} };
SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content, callback) { SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content, callback) {
if(typeof gid === 'string') { if(typeof gid === 'string') {
@@ -493,7 +495,7 @@ SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
sid.universe = SteamID.Universe.PUBLIC; sid.universe = SteamID.Universe.PUBLIC;
sid.type = SteamID.Type.INDIVIDUAL; sid.type = SteamID.Type.INDIVIDUAL;
sid.instance = SteamID.Instance.DESKTOP; sid.instance = SteamID.Instance.DESKTOP;
sid.accountid = $(users[0]).data('miniprofile'); sid.accountid = $(users[1]).data('miniprofile');
data.actor = sid; data.actor = sid;
} }
@@ -517,3 +519,179 @@ SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
callback(null, output); callback(null, output);
}, "steamcommunity"); }, "steamcommunity");
}; };
SteamCommunity.prototype.getAllGroupComments = function(gid, from, count, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var options = {
"uri": "https://steamcommunity.com/comment/Clan/render/" + gid.getSteamID64() + "/-1/",
"form": {
"start": from,
"count": count
}
};
var self = this;
this.httpRequestPost(options, function(err, response, body) {
if (err) {
callback(err);
return;
}
var comments = [];
var $ = Cheerio.load(JSON.parse(body).comments_html);
$(".commentthread_comment_content").each(function () {
var comment = {};
var cachedSelector;
var $selector = $(this).find(".commentthread_author_link");
comment.authorName = $($selector).find("bdi").text();
comment.authorId = $($selector).attr("href").replace(/https?:\/\/steamcommunity.com\/(id|profiles)\//, "");
comment.date = Helpers.decodeSteamTime($(this).find(".commentthread_comment_timestamp").text().trim());
$selector = $(this).find(".commentthread_comment_text");
comment.commentId = $($selector).attr("id").replace("comment_content_", "");
comment.text = $($selector).html().trim();
comments.push(comment);
});
callback(null, comments);
}, "steamcommunity");
};
SteamCommunity.prototype.deleteGroupComment = function(gid, cid, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
if (typeof cid !== 'string') {
cid = cid.toString();
}
var options = {
"uri": "https://steamcommunity.com/comment/Clan/delete/" + gid.getSteamID64() + "/-1/",
"form": {
"sessionid": this.getSessionID(),
"gidcomment": cid
}
};
var self = this;
this.httpRequestPost(options, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "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

@@ -13,11 +13,26 @@ exports.isSteamID = function(input) {
}; };
exports.decodeSteamTime = function(time) { exports.decodeSteamTime = function(time) {
var parts = time.split('@'); var date = new Date();
if (!parts[0].match(/,/)) {
// no year, assume current year if (time.includes("@")) {
parts[0] += ", " + (new Date()).getFullYear(); var parts = time.split('@');
if (!parts[0].includes(",")) {
// no year, assume current year
parts[0] += ", " + date.getFullYear();
}
date = new Date(parts.join('@').replace(/(am|pm)/, ' $1') + " UTC"); // add a space so JS can decode it
} else {
// Relative date
var amount = time.replace(/(\d) (minutes|hour|hours) ago/, "$1");
if(time.includes("minutes")) {
date.setMinutes(date.getMinutes() - amount);
} else if(time.match(/hour|hours/)) {
date.setHours(date.getHours() - amount);
}
} }
return new Date(parts.join('@').replace(/(am|pm)/, ' $1') + " UTC"); // add a space so JS can decode it return date;
}; };

View File

@@ -98,7 +98,7 @@ SteamCommunity.prototype._checkHttpError = function(err, response, callback, bod
return 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"); err = new Error("Family View Restricted");
callback(err, response, body); callback(err, response, body);
return err; return err;

View File

@@ -13,6 +13,11 @@ var Async = require('async');
* With "prev" set, you will request 30 trades that were completed MORE RECENTLY than after_time (and optionally after_trade) * With "prev" set, you will request 30 trades that were completed MORE RECENTLY than after_time (and optionally after_trade)
*/ */
/**
* @deprecated Use GetTradeHistory instead: https://lab.xpaw.me/steam_api_documentation.html#IEconService_GetTradeHistory_v1
* @param {object} options
* @param {function} callback
*/
SteamCommunity.prototype.getInventoryHistory = function(options, callback) { SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
if (typeof options === 'function') { if (typeof options === 'function') {
callback = options; callback = options;
@@ -49,7 +54,7 @@ SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
if (!$('.inventory_history_pagingrow').html()) { if (!$('.inventory_history_pagingrow').html()) {
callback("Malformed page: no paging row found"); callback(new Error("Malformed page: no paging row found"));
return; return;
} }

View File

@@ -1,6 +1,10 @@
var SteamCommunity = require('../index.js'); var SteamCommunity = require('../index.js');
var Cheerio = require('cheerio'); var Cheerio = require('cheerio');
/**
* Get a list of all apps on the market
* @param {function} callback - First argument is null|Error, second is an object of appid => name
*/
SteamCommunity.prototype.getMarketApps = function(callback) { SteamCommunity.prototype.getMarketApps = function(callback) {
var self = this; var self = this;
this.httpRequest('https://steamcommunity.com/market/', function (err, response, body) { this.httpRequest('https://steamcommunity.com/market/', function (err, response, body) {
@@ -11,8 +15,8 @@ SteamCommunity.prototype.getMarketApps = function(callback) {
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
if ($('.market_search_game_button_group')) { if ($('.market_search_game_button_group')) {
apps = {}; let apps = {};
$('.market_search_game_button_group > a').each(function (i, element) { $('.market_search_game_button_group a.game_button').each(function (i, element) {
var e = Cheerio.load(element); var e = Cheerio.load(element);
var name = e('.game_button_game_name').text().trim(); var name = e('.game_button_game_name').text().trim();
var url = element.attribs.href; var url = element.attribs.href;
@@ -25,3 +29,149 @@ SteamCommunity.prototype.getMarketApps = function(callback) {
} }
}, "steamcommunity"); }, "steamcommunity");
}; };
/**
* Check if an item is eligible to be turned into gems and if so, get its gem value
* @param {int} appid
* @param {int|string} assetid
* @param {function} callback
*/
SteamCommunity.prototype.getGemValue = function(appid, assetid, callback) {
this._myProfile({
"endpoint": "ajaxgetgoovalue/",
"qs": {
"sessionid": this.getSessionID(),
"appid": appid,
"contextid": 6,
"assetid": assetid
},
"checkHttpError": false,
"json": true
}, null, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body.goo_value || !body.strTitle) {
callback(new Error("Malformed response"));
return;
}
callback(null, {"promptTitle": body.strTitle, "gemValue": parseInt(body.goo_value, 10)});
});
};
/**
* Turn an eligible item into gems.
* @param {int} appid
* @param {int|string} assetid
* @param {int} expectedGemsValue
* @param {function} callback
*/
SteamCommunity.prototype.turnItemIntoGems = function(appid, assetid, expectedGemsValue, callback) {
this._myProfile({
"endpoint": "ajaxgrindintogoo/",
"json": true,
"checkHttpError": false
}, {
"appid": appid,
"contextid": 6,
"assetid": assetid,
"goo_value_expected": expectedGemsValue,
"sessionid": this.getSessionID()
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body['goo_value_received '] || !body.goo_value_total) { // lol valve
callback(new Error("Malformed response"));
return;
}
callback(null, {"gemsReceived": parseInt(body['goo_value_received '], 10), "totalGems": parseInt(body.goo_value_total, 10)});
})
};
/**
* Get details about a gift in your inventory.
* @param {string} giftID
* @param {function} callback
*/
SteamCommunity.prototype.getGiftDetails = function(giftID, callback) {
this.httpRequestPost({
"uri": "https://steamcommunity.com/gifts/" + giftID + "/validateunpack",
"form": {
"sessionid": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body.packageid || !body.gift_name) {
callback(new Error("Malformed response"));
return;
}
callback(null, {
"giftName": body.gift_name,
"packageID": parseInt(body.packageid, 10),
"owned": body.owned
});
});
};
/**
* Unpack a gift in your inventory to your library.
* @param {string} giftID
* @param {function} callback
*/
SteamCommunity.prototype.redeemGift = function(giftID, callback) {
this.httpRequestPost({
"uri": "https://steamcommunity.com/gifts/" + giftID + "/unpack",
"form": {
"sessionid": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
callback(null);
});
};

View File

@@ -21,7 +21,7 @@ SteamCommunity.prototype.setupProfile = function(callback) {
if(!callback) { if(!callback) {
return; return;
} }
if(err || response.statusCode != 200) { if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error("HTTP error " + response.statusCode));
} else { } else {
@@ -37,82 +37,82 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
if(callback) { if(callback) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error("HTTP error " + response.statusCode));
} }
return; return;
} }
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
var form = $('#editForm'); var form = $('#editForm');
if(!form) { if(!form) {
if(callback) { if(callback) {
callback(new Error("Malformed response")); callback(new Error("Malformed response"));
} }
return; return;
} }
var values = {}; var values = {};
form.serializeArray().forEach(function(item) { form.serializeArray().forEach(function(item) {
values[item.name] = item.value; values[item.name] = item.value;
}); });
for(var i in settings) { for(var i in settings) {
if(!settings.hasOwnProperty(i)) { if(!settings.hasOwnProperty(i)) {
continue; continue;
} }
switch(i) { switch(i) {
case 'name': case 'name':
values.personaName = settings[i]; values.personaName = settings[i];
break; break;
case 'realName': case 'realName':
values.real_name = settings[i]; values.real_name = settings[i];
break; break;
case 'summary': case 'summary':
values.summary = settings[i]; values.summary = settings[i];
break; break;
case 'country': case 'country':
values.country = settings[i]; values.country = settings[i];
break; break;
case 'state': case 'state':
values.state = settings[i]; values.state = settings[i];
break; break;
case 'city': case 'city':
values.city = settings[i]; values.city = settings[i];
break; break;
case 'customURL': case 'customURL':
values.customURL = settings[i]; values.customURL = settings[i];
break; break;
case 'background': case 'background':
// The assetid of our desired profile background // The assetid of our desired profile background
values.profile_background = settings[i]; values.profile_background = settings[i];
break; break;
case 'featuredBadge': case 'featuredBadge':
// Currently, game badges aren't supported // Currently, game badges aren't supported
values.favorite_badge_badgeid = settings[i]; values.favorite_badge_badgeid = settings[i];
break; break;
case 'primaryGroup': case 'primaryGroup':
if(typeof settings[i] === 'object' && settings[i].getSteamID64) { if(typeof settings[i] === 'object' && settings[i].getSteamID64) {
values.primary_group_steamid = settings[i].getSteamID64(); values.primary_group_steamid = settings[i].getSteamID64();
} else { } else {
values.primary_group_steamid = new SteamID(settings[i]).getSteamID64(); values.primary_group_steamid = new SteamID(settings[i]).getSteamID64();
} }
break; break;
// TODO: profile showcases // TODO: profile showcases
} }
} }
self._myProfile("edit", values, function(err, response, body) { self._myProfile("edit", values, function(err, response, body) {
if (settings.customURL) { if (settings.customURL) {
delete self._profileURL; delete self._profileURL;
@@ -122,10 +122,10 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
if(callback) { if(callback) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error("HTTP error " + response.statusCode));
} }
return; return;
} }
// Check for an error // Check for an error
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
var error = $('#errorText .formRowFields'); var error = $('#errorText .formRowFields');
@@ -135,11 +135,11 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
if(callback) { if(callback) {
callback(new Error(error)); callback(new Error(error));
} }
return; return;
} }
} }
if(callback) { if(callback) {
callback(null); callback(null);
} }
@@ -154,58 +154,58 @@ SteamCommunity.prototype.profileSettings = function(settings, callback) {
if(callback) { if(callback) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error("HTTP error " + response.statusCode));
} }
return; return;
} }
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
var form = $('#editForm'); var form = $('#editForm');
if(!form) { if(!form) {
if(callback) { if(callback) {
callback(new Error("Malformed response")); callback(new Error("Malformed response"));
} }
return; return;
} }
var values = {}; var values = {};
form.serializeArray().forEach(function(item) { form.serializeArray().forEach(function(item) {
values[item.name] = item.value; values[item.name] = item.value;
}); });
for(var i in settings) { for(var i in settings) {
if(!settings.hasOwnProperty(i)) { if(!settings.hasOwnProperty(i)) {
continue; continue;
} }
switch(i) { switch(i) {
case 'profile': case 'profile':
values.privacySetting = settings[i]; values.privacySetting = settings[i];
break; break;
case 'comments': case 'comments':
values.commentSetting = CommentPrivacyState[settings[i]]; values.commentSetting = CommentPrivacyState[settings[i]];
break; break;
case 'inventory': case 'inventory':
values.inventoryPrivacySetting = settings[i]; values.inventoryPrivacySetting = settings[i];
break; break;
case 'inventoryGifts': case 'inventoryGifts':
values.inventoryGiftPrivacy = settings[i] ? 1 : 0; values.inventoryGiftPrivacy = settings[i] ? 1 : 0;
break; break;
} }
} }
self._myProfile("edit/settings", values, function(err, response, body) { self._myProfile("edit/settings", values, function(err, response, body) {
if(err || response.statusCode != 200) { if(err || response.statusCode != 200) {
if(callback) { if(callback) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error("HTTP error " + response.statusCode));
} }
return; return;
} }
if(callback) { if(callback) {
callback(null); callback(null);
} }
@@ -219,6 +219,12 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
format = null; format = null;
} }
// are we logged in?
if (!this.steamID) {
callback(new Error("Not Logged In"));
return;
}
var self = this; var self = this;
if(image instanceof Buffer) { if(image instanceof Buffer) {

View File

@@ -218,16 +218,16 @@ SteamCommunity.prototype.getUserAliases = function(userID, callback) {
}; };
SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) { SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
if(typeof userID === 'string') { if (typeof userID === 'string') {
userID = new SteamID(userID); userID = new SteamID(userID);
} }
if(typeof userID === 'function') { if (typeof userID === 'function') {
callback = userID; callback = userID;
userID = this.steamID; userID = this.steamID;
} }
if(!userID) { if (!userID) {
callback(new Error("No SteamID specified and not logged in")); callback(new Error("No SteamID specified and not logged in"));
return; return;
} }
@@ -240,8 +240,19 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
} }
var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/); var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
if(!match) { if (!match) {
callback(new Error("Malformed response")); 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; return;
} }
@@ -340,15 +351,21 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
* @param {int} appID - The Steam application ID of the game for which you want an inventory * @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 {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 {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 * @param {function} callback
*/ */
SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, callback) { SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, language, callback) {
var self = this; var self = this;
if(typeof userID === 'string') { if (typeof userID === 'string') {
userID = new SteamID(userID); userID = new SteamID(userID);
} }
if (typeof language === 'function') {
callback = language;
language = "english";
}
var pos = 1; var pos = 1;
get([], []); get([], []);
@@ -359,7 +376,7 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
"Referer": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory" "Referer": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory"
}, },
"qs": { "qs": {
"l": "english", // Default language "l": language, // Default language
"count": 5000, // Max items per 'page' "count": 5000, // Max items per 'page'
"start_assetid": start "start_assetid": start
}, },
@@ -368,7 +385,7 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
if (err) { if (err) {
if (err.message == "HTTP error 403" && body === null) { if (err.message == "HTTP error 403" && body === null) {
// 403 with a body of "null" means the inventory/profile is private. // 403 with a body of "null" means the inventory/profile is private.
if(userID.getSteamID64() == self.steamID.getSteamID64()) { if (self.steamID && userID.getSteamID64() == self.steamID.getSteamID64()) {
// We can never get private profile error for our own inventory! // We can never get private profile error for our own inventory!
self._notifySessionExpired(err); self._notifySessionExpired(err);
} }
@@ -377,10 +394,28 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
return; 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); callback(err);
return; 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 || !body.success || !body.assets || !body.descriptions) {
if (body) { if (body) {
// Dunno if the error/Error property even exists on this new endpoint // Dunno if the error/Error property even exists on this new endpoint
@@ -413,20 +448,16 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
var quickDescriptionLookup = {}; var quickDescriptionLookup = {};
function getDescription(descriptions, classID, instanceID) { function getDescription(descriptions, classID, instanceID) {
instanceID = instanceID || '0'; // instanceID can be undefined, in which case it's 0. var key = classID + '_' + (instanceID || '0'); // instanceID can be undefined, in which case it's 0.
var key = classID + '_' + instanceID;
if (quickDescriptionLookup[key]) { if (quickDescriptionLookup[key]) {
return quickDescriptionLookup[key]; return quickDescriptionLookup[key];
} }
for (var i = 0; i < descriptions.length; i++) { for (var i = 0; i < descriptions.length; i++) {
quickDescriptionLookup[key] = descriptions[i]; quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i];
if (descriptions[i].classid == classID && descriptions[i].instanceid == instanceID) {
return descriptions[i];
}
} }
return quickDescriptionLookup[key];
} }
}; };

View File

@@ -14,6 +14,10 @@ SteamCommunity.prototype.getWebApiKey = function(domain, callback) {
if(body.match(/<h2>Access Denied<\/h2>/)) { if(body.match(/<h2>Access Denied<\/h2>/)) {
return callback(new Error("Access Denied")); return callback(new Error("Access Denied"));
} }
if(body.match(/You must have a validated email address to create a Steam Web API key./)) {
return callback(new Error("You must have a validated email address to create a Steam Web API key."));
}
var match = body.match(/<p>Key: ([0-9A-F]+)<\/p>/); var match = body.match(/<p>Key: ([0-9A-F]+)<\/p>/);
if(match) { if(match) {

View File

@@ -79,7 +79,7 @@ function doLogin(accountName, password, authCode, captcha) {
console.log("Writing secrets to twofactor_" + community.steamID.getSteamID64() + ".json"); console.log("Writing secrets to twofactor_" + community.steamID.getSteamID64() + ".json");
console.log("Revocation code: " + response.revocation_code); console.log("Revocation code: " + response.revocation_code);
fs.writeFile("twofactor_" + community.steamID.getSteamID64() + ".json", JSON.stringify(response, null, "\t")); fs.writeFileSync("twofactor_" + community.steamID.getSteamID64() + ".json", JSON.stringify(response, null, "\t"));
promptActivationCode(response); promptActivationCode(response);
}); });

211
index.js
View File

@@ -1,21 +1,20 @@
require('@doctormckay/stats-reporter').setup(require('./package.json'));
var Request = require('request'); var Request = require('request');
var RSA = require('node-bignumber').Key; var RSA = require('node-bignumber').Key;
var hex2b64 = require('node-bignumber').hex2b64; var hex2b64 = require('node-bignumber').hex2b64;
var SteamID = require('steamid'); 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); require('util').inherits(SteamCommunity, require('events').EventEmitter);
module.exports = SteamCommunity; module.exports = SteamCommunity;
SteamCommunity.SteamID = SteamID; SteamCommunity.SteamID = SteamID;
SteamCommunity.ConfirmationType = { SteamCommunity.ConfirmationType = require('./resources/EConfirmationType.js');
// 1 is unknown, possibly "Invalid" SteamCommunity.EResult = require('./resources/EResult.js')
"Trade": 2,
"MarketListing": 3
// 4 is opt-out or other like account confirmation?
};
function SteamCommunity(options) { function SteamCommunity(options) {
options = options || {}; options = options || {};
@@ -45,7 +44,7 @@ function SteamCommunity(options) {
defaults.localAddress = options.localAddress; 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); this.request = this.request.defaults(defaults);
// English // English
@@ -56,27 +55,38 @@ function SteamCommunity(options) {
} }
SteamCommunity.prototype.login = function(details, callback) { 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('||'); var parts = details.steamguard.split('||');
this._setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), true); this._setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), true);
} }
var disableMobile = details.disableMobile;
var self = this; var self = this;
// Delete the cache // Delete the cache
delete self._profileURL; 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 // headers required to convince steam that we're logging in from a mobile device so that we can get the oAuth data
var mobileHeaders = { var mobileHeaders = {};
"X-Requested-With": "com.valvesoftware.android.steam.community", if (!disableMobile) {
"Referer": "https://steamcommunity.com/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client", mobileHeaders = {
"User-Agent": "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", "X-Requested-With": "com.valvesoftware.android.steam.community",
"Accept": "text/javascript, text/html, application/xml, text/xml, */*" "Referer": "https://steamcommunity.com/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client",
}; "User-Agent": "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
"Accept": "text/javascript, text/html, application/xml, text/xml, */*"
};
this._setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"));
this._setCookie(Request.cookie("mobileClient=android"));
} else {
mobileHeaders = {"Referer": "https://steamcommunity.com/login"};
}
this._setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"));
this._setCookie(Request.cookie("mobileClient=android"));
this.httpRequestPost("https://steamcommunity.com/login/getrsakey/", { this.httpRequestPost("https://steamcommunity.com/login/getrsakey/", {
"form": {"username": details.accountName}, "form": {"username": details.accountName},
"headers": mobileHeaders, "headers": mobileHeaders,
@@ -89,33 +99,39 @@ SteamCommunity.prototype.login = function(details, callback) {
return; return;
} }
if(!body.publickey_mod || !body.publickey_exp) { if (!body.publickey_mod || !body.publickey_exp) {
deleteMobileCookies(); deleteMobileCookies();
callback(new Error("Invalid RSA key received")); callback(new Error("Invalid RSA key received"));
return; return;
} }
var key = new RSA(); var key = new RSA();
key.setPublic(body.publickey_mod, body.publickey_exp); key.setPublic(body.publickey_mod, body.publickey_exp);
var formObj = {
"captcha_text": details.captcha || "",
"captchagid": self._captchaGid,
"emailauth": details.authCode || "",
"emailsteamid": "",
"password": hex2b64(key.encrypt(details.password)),
"remember_login": "true",
"rsatimestamp": body.timestamp,
"twofactorcode": details.twoFactorCode || "",
"username": details.accountName,
"loginfriendlyname": "",
"donotcache": Date.now()
};
if(!disableMobile){
formObj.oauth_client_id = "DE45CD61";
formObj.oauth_scope = "read_profile write_profile read_client write_client";
formObj.loginfriendlyname = "#login_emailauth_friendlyname_mobile";
}
self.httpRequestPost({ self.httpRequestPost({
"uri": "https://steamcommunity.com/login/dologin/", "uri": "https://steamcommunity.com/login/dologin/",
"json": true, "json": true,
"form": { "form": formObj,
"captcha_text": details.captcha || "",
"captchagid": self._captchaGid,
"emailauth": details.authCode || "",
"emailsteamid": "",
"password": hex2b64(key.encrypt(details.password)),
"remember_login": "true",
"rsatimestamp": body.timestamp,
"twofactorcode": details.twoFactorCode || "",
"username": details.accountName,
"oauth_client_id": "DE45CD61",
"oauth_scope": "read_profile write_profile read_client write_client",
"loginfriendlyname": "#login_emailauth_friendlyname_mobile",
"donotcache": Date.now()
},
"headers": mobileHeaders "headers": mobileHeaders
}, function(err, response, body) { }, function(err, response, body) {
deleteMobileCookies(); deleteMobileCookies();
@@ -126,38 +142,51 @@ SteamCommunity.prototype.login = function(details, callback) {
} }
var error; var error;
if(!body.success && body.emailauth_needed) { if (!body.success && body.emailauth_needed) {
// Steam Guard (email) // Steam Guard (email)
error = new Error("SteamGuard"); error = new Error("SteamGuard");
error.emaildomain = body.emaildomain; error.emaildomain = body.emaildomain;
callback(error); callback(error);
} else if(!body.success && body.requires_twofactor) { } else if (!body.success && body.requires_twofactor) {
// Steam Guard (app) // Steam Guard (app)
callback(new Error("SteamGuardMobile")); callback(new Error("SteamGuardMobile"));
} else if(!body.success && body.captcha_needed && body.message.match(/Please verify your humanity/)) { } else if (!body.success && body.captcha_needed && body.message.match(/Please verify your humanity/)) {
error = new Error("CAPTCHA"); error = new Error("CAPTCHA");
error.captchaurl = "https://steamcommunity.com/login/rendercaptcha/?gid=" + body.captcha_gid; error.captchaurl = "https://steamcommunity.com/login/rendercaptcha/?gid=" + body.captcha_gid;
self._captchaGid = body.captcha_gid; self._captchaGid = body.captcha_gid;
callback(error); callback(error);
} else if(!body.success) { } else if (!body.success) {
callback(new Error(body.message || "Unknown error")); callback(new Error(body.message || "Unknown error"));
} else if(!body.oauth) { } else if (!disableMobile && !body.oauth) {
callback(new Error("Malformed response")); callback(new Error("Malformed response"));
} else { } else {
var sessionID = generateSessionID(); var sessionID = generateSessionID();
var oAuth = JSON.parse( body.oauth ); var oAuth;
self._setCookie(Request.cookie('sessionid=' + sessionID)); self._setCookie(Request.cookie('sessionid=' + sessionID));
self.steamID = new SteamID(oAuth.steamid);
self.oAuthToken = oAuth.oauth_token;
var cookies = self._jar.getCookieString("https://steamcommunity.com").split(';').map(function(cookie) { var cookies = self._jar.getCookieString("https://steamcommunity.com").split(';').map(function(cookie) {
return cookie.trim(); return cookie.trim();
}); });
if (!disableMobile){
oAuth = JSON.parse(body.oauth);
self.steamID = new SteamID(oAuth.steamid);
self.oAuthToken = oAuth.oauth_token;
}else{
for(var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split('=');
if(parts[0] == 'steamLogin') {
self.steamID = new SteamID(decodeURIComponent(parts[1]).split('||')[0])
break;
}
}
self.oAuthToken = null;
}
// Find the Steam Guard cookie // Find the Steam Guard cookie
var steamguard = null; var steamguard = null;
for(var i = 0; i < cookies.length; i++) { for(var i = 0; i < cookies.length; i++) {
@@ -169,8 +198,8 @@ SteamCommunity.prototype.login = function(details, callback) {
} }
self.setCookies(cookies); self.setCookies(cookies);
callback(null, sessionID, cookies, steamguard, oAuth.oauth_token); callback(null, sessionID, cookies, steamguard, disableMobile ? null : oAuth.oauth_token);
} }
}, "steamcommunity"); }, "steamcommunity");
}, "steamcommunity"); }, "steamcommunity");
@@ -249,7 +278,7 @@ SteamCommunity.prototype.getSessionID = function() {
return decodeURIComponent(match[2]); return decodeURIComponent(match[2]);
} }
} }
var sessionID = generateSessionID(); var sessionID = generateSessionID();
this._setCookie(Request.cookie('sessionid=' + sessionID)); this._setCookie(Request.cookie('sessionid=' + sessionID));
return sessionID; return sessionID;
@@ -271,22 +300,34 @@ SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
if(!callback) { if(!callback) {
return; return;
} }
if (err) { if (err) {
callback(err); callback(err);
return; return;
} }
if(!body || typeof body.success !== 'boolean') { if (!body || typeof body.success !== 'boolean') {
callback("Invalid response"); callback("Invalid response");
return; return;
} }
if(!body.success) { if (!body.success) {
callback("Incorrect PIN"); switch (body.eresult) {
case 15:
callback("Incorrect PIN");
break;
case 25:
callback("Too many invalid PIN attempts");
break;
default:
callback("Error " + body.eresult);
}
return; return;
} }
callback(); callback();
}.bind(this), "steamcommunity"); }.bind(this), "steamcommunity");
}; };
@@ -306,7 +347,7 @@ SteamCommunity.prototype.getNotifications = function(callback) {
callback(new Error("Malformed response")); callback(new Error("Malformed response"));
return; return;
} }
var notifications = { var notifications = {
"trades": body.notifications[1] || 0, "trades": body.notifications[1] || 0,
"gameTurns": body.notifications[2] || 0, "gameTurns": body.notifications[2] || 0,
@@ -346,16 +387,49 @@ SteamCommunity.prototype.loggedIn = function(callback) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error("HTTP error " + response.statusCode));
return; return;
} }
if(response.statusCode == 403) { if(response.statusCode == 403) {
callback(null, true, true); callback(null, true, true);
return; return;
} }
callback(null, !!response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/), false); callback(null, !!response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/), false);
}, "steamcommunity"); }, "steamcommunity");
}; };
SteamCommunity.prototype.getTradeURL = function(callback) {
this._myProfile("/tradeoffers/privacy", null, (err, response, body) => {
if (err) {
callback(err);
return;
}
var match = body.match(/https?:\/\/(www.)?steamcommunity.com\/tradeoffer\/new\/?\?partner=\d+(&|&amp;)token=([a-zA-Z0-9-_]+)/);
if (match) {
var token = match[3];
callback(null, match[0], token);
} else {
callback(new Error("Malformed response"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.changeTradeURL = function(callback) {
this._myProfile("/tradeoffers/newtradeurl", {"sessionid": this.getSessionID()}, (err, response, body) => {
if (!callback) {
return;
}
if (!body || typeof body !== "string" || body.length < 3 || body.indexOf('"') !== 0) {
callback(new Error("Malformed response"));
return;
}
var newToken = body.replace(/"/g, ''); //"t1o2k3e4n" => t1o2k3e4n
callback(null, "https://steamcommunity.com/tradeoffer/new/?partner=" + this.steamID.accountid + "&token=" + newToken, newToken);
}, "steamcommunity");
};
SteamCommunity.prototype._myProfile = function(endpoint, form, callback) { SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
var self = this; var self = this;
@@ -377,17 +451,16 @@ SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
self._profileURL = match[1]; self._profileURL = match[1];
setTimeout(function () { setTimeout(function () {
delete self._profileURL; // delete the cache delete self._profileURL; // delete the cache
}, 60000); }, 60000).unref();
completeRequest(match[1]); completeRequest(match[1]);
}, "steamcommunity"); }, "steamcommunity");
} }
function completeRequest(url) { function completeRequest(url) {
var options = { var options = endpoint.endpoint ? endpoint : {};
"uri": "https://steamcommunity.com" + url + "/" + endpoint, options.uri = "https://steamcommunity.com" + url + "/" + (endpoint.endpoint || endpoint);
"method": "GET" options.method = "GET";
};
if (form) { if (form) {
options.method = "POST"; options.method = "POST";
@@ -416,4 +489,4 @@ require('./classes/CSteamUser.js');
/** /**
@callback SteamCommunity~genericErrorCallback @callback SteamCommunity~genericErrorCallback
@param {Error|null} err - An Error object on failure, or null on success @param {Error|null} err - An Error object on failure, or null on success
*/ */

View File

@@ -1,24 +1,33 @@
{ {
"name": "steamcommunity", "name": "steamcommunity",
"version": "3.30.2", "version": "3.35.2",
"description": "Provides an interface for logging into and interacting with the Steam Community website", "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", "homepage": "https://github.com/DoctorMcKay/node-steamcommunity",
"bugs": { "bugs": {
"url": "https://github.com/DoctorMcKay/node-steamcommunity/issues" "url": "https://github.com/DoctorMcKay/node-steamcommunity/issues"
}, },
"license": "MIT", "license": "MIT",
"author": {
"name": "Alex Corn",
"email": "mckay@doctormckay.com",
"url": "https://www.doctormckay.com"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git" "url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
}, },
"dependencies": { "dependencies": {
"request": "^2.61.0", "@doctormckay/stats-reporter": "^1.0.2",
"request": "^2.81.0",
"node-bignumber": "^1.2.1", "node-bignumber": "^1.2.1",
"steamid": "^1.0.0", "steamid": "^1.0.0",
"xml2js": "^0.4.11", "xml2js": "^0.4.11",
"cheerio": "0.19.0", "cheerio": "0.22.0",
"async": "^1.4.2", "async": "^2.1.4",
"steam-totp": "^1.3.0" "steam-totp": "^1.3.0"
}, },
"engines": { "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",
};