Compare commits

...

51 Commits

Author SHA1 Message Date
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
Alexander Corn
5928c3b76a 3.26.0 2016-09-12 01:33:30 -04:00
Alexander Corn
1b60712b8b Updated getNotifications to use the new JSON endpoint 2016-09-12 01:33:11 -04:00
Alexander Corn
335a955abd Fixed cookies not being set 2016-09-12 01:24:39 -04:00
Alexander Corn
6d534df8ba Set cookies on all Steam domains, and properly flag as secure 2016-09-12 01:17:00 -04:00
Alexander Corn
9022a9a45e 3.25.0 2016-09-08 19:45:05 -04:00
Alexander Corn
ce4d7ae026 Pull trade offer ID directly from listing page 2016-09-08 19:23:15 -04:00
Kysune
b863377c87 Problem when search "Key:" in polish language. 2016-09-01 21:05:46 +02:00
Alexander Corn
20785ee92b 3.24.0 2016-08-31 22:20:49 -04:00
Alexander Corn
3afb393f58 Updated getInventoryHistory for new pagination scheme 2016-08-31 22:20:36 -04:00
Alexander Corn
79543187f0 Fixed timezones being incorrect in trade history entries 2016-08-31 22:20:24 -04:00
Alexander Corn
3139aec463 Reformat inventoryhistory.js 2016-08-31 21:48:17 -04:00
Alexander Corn
bb917af86e 3.23.3 2016-08-10 18:55:36 -04:00
Alexander Corn
17f51b9caa Fixed double callbacks on some json errors (fixes #123) 2016-08-10 18:55:23 -04:00
Alexander Corn
dd638df118 3.23.2 2016-08-09 15:45:32 -04:00
Alexander Corn
b937a05442 Make sure we actually get json back when we want it (fixes #122) 2016-08-09 15:45:21 -04:00
Alexander Corn
9c34d18cd9 3.23.1 2016-08-07 00:04:56 -04:00
Alexander Corn
02e3798124 Handle some cases where the session expires while we're using webchat 2016-08-07 00:04:30 -04:00
Alexander Corn
5d9922cc33 3.23.0 2016-07-13 13:12:39 -04:00
Alexander Corn
153011e1c9 Make _community non-enumerable on CConfirmation 2016-07-13 13:11:56 -04:00
Alexander Corn
575c196cb7 Added acceptAllConfirmations method 2016-07-13 13:11:44 -04:00
Alexander Corn
47a24d19cf Added ability to act on multiple confirmations 2016-07-13 13:11:36 -04:00
Alexander Corn
0eac19b71c 3.22.0 2016-07-10 01:02:48 -04:00
Alexander Corn
ca73ecc786 Parse times in getUserAliases into Date objects 2016-07-10 01:02:36 -04:00
Alexander Corn
e8733613d7 Merge pull request #117 from andrewda/master
Add ability to get user aliases
2016-07-10 00:52:13 -04:00
Andrew Dassonville
89a1df67ec Add ability to get user aliases 2016-07-06 16:20:42 -07:00
Alexander Corn
3ba0a8592e 3.21.2 2016-07-01 18:30:32 -04:00
Alexander Corn
57fa7a679f Fixed market search results including query string (fixes #113) 2016-07-01 18:29:56 -04:00
Alexander Corn
446e315183 Fixed market search result prices being horribly wrong 2016-07-01 18:27:39 -04:00
Alexander Corn
c6a9b74471 3.21.1 2016-06-29 23:08:54 -04:00
Alexander Corn
018e18cb5d Give "Family View Restricted" error if access denied due to Family View 2016-06-29 23:08:40 -04:00
Alexander Corn
303ce10e73 3.21.0 2016-06-27 16:57:12 -04:00
Alexander Corn
60e0e63a92 Fixed marketSearch (fixes #112) 2016-06-27 16:56:16 -04:00
Luke
17e5b94782 Added deleting events in CSteamGroup (#109)
* Added deleteEvent()

* Added deleteGroupEvent()
2016-06-21 22:39:23 -04:00
Alexander Corn
beaf012846 3.20.0 2016-06-15 15:07:45 -04:00
Alexander Corn
38c20a342c Merge pull request #105 from luop90/master
Added editing events to CSteamGroup
2016-06-15 14:17:41 -04:00
Luke
a406cfbfe2 Fixed a valid server object being overwritten 2016-06-15 11:25:44 -06:00
Luke Henke
c970eee9d3 Added editing events to CSteamGroup 2016-06-14 20:29:11 -06:00
Alexander Corn
38e3eefb33 3.19.10 2016-06-14 16:08:03 -04:00
Alexander Corn
a2e26459d9 Emit chatLogOnFailed on error, and handle fatal errors (fixes #104) 2016-06-14 16:07:46 -04:00
Alexander Corn
c1b04ee743 Fixed crash if chat poll returns no response
https://dev.doctormckay.com/topic/383-error-in-node-steamcommunity/
2016-06-14 16:02:04 -04:00
14 changed files with 508 additions and 185 deletions

View File

@@ -1,17 +1,32 @@
var SteamCommunity = require('../index.js');
module.exports = CConfirmation;
function CConfirmation(community, data) {
this._community = community;
Object.defineProperty(this, "_community", {"value": community});
this.id = data.id;
this.type = data.type;
this.creator = data.creator;
this.key = data.key;
this.title = data.title;
this.receiving = data.receiving;
this.time = data.time;
this.icon = data.icon;
this.offerID = this.type == SteamCommunity.ConfirmationType.Trade ? this.creator : null;
}
CConfirmation.prototype.getOfferID = function(time, key, callback) {
if (this.type && this.creator) {
if (this.type != SteamCommunity.ConfirmationType.Trade) {
callback(new Error("Not a trade confirmation"));
return;
}
callback(null, this.creator);
return;
}
this._community.getConfirmationOfferID(this.id, time, key, callback);
};

View File

@@ -27,60 +27,63 @@ SteamCommunity.prototype.marketSearch = function(options, callback) {
qs.count = 100;
qs.sort_column = 'price';
qs.sort_dir = 'asc';
performSearch.call(this, this.httpRequest, qs, [], callback);
var self = this;
var results = [];
performSearch();
function performSearch() {
self.httpRequest({
"uri": "https://steamcommunity.com/market/search/render/",
"qs": qs,
"headers": {
"referer": "https://steamcommunity.com/market/search"
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if(!body.success) {
callback(new Error("Success is not true"));
return;
}
if(!body.results_html) {
callback(new Error("No results_html in response"));
return;
}
var $ = Cheerio.load(body.results_html);
var $errorMsg = $('.market_listing_table_message');
if($errorMsg.length > 0) {
callback(new Error($errorMsg.text()));
return;
}
var rows = $('.market_listing_row_link');
for(var i = 0; i < rows.length; i++) {
results.push(new CMarketSearchResult($(rows[i])));
}
if(body.start + body.pagesize >= body.total_count) {
callback(null, results);
} else {
qs.start += body.pagesize;
performSearch();
}
}, "steamcommunity");
}
};
function performSearch(request, qs, results, callback) {
var self = this;
request({
"uri": "https://steamcommunity.com/market/search/render/",
"qs": qs,
"headers": {
"referer": "https://steamcommunity.com/market/search"
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if(!body.success) {
callback(new Error("Success is not true"));
return;
}
if(!body.results_html) {
callback(new Error("No results_html in response"));
return;
}
var $ = Cheerio.load(body.results_html);
if($('.market_listing_table_message').length > 0) {
callback(new Error($('.market_listing_table_message').text()));
return;
}
var rows = $('.market_listing_row_link');
for(var i = 0; i < rows.length; i++) {
results.push(new CMarketSearchResult($(rows[i])));
}
if(body.start + body.pagesize >= body.total_count) {
callback(null, results);
} else {
qs.start += body.pagesize;
performSearch.call(self, request, qs, results, callback);
}
}, "steamcommunity");
}
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.market_hash_name = decodeURIComponent(match[2]);
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').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);
}

View File

@@ -7,24 +7,24 @@ SteamCommunity.prototype.getSteamGroup = function(id, callback) {
if(typeof id !== 'string' && !Helpers.isSteamID(id)) {
throw new Error("id parameter should be a group URL string or a SteamID object");
}
if(typeof id === 'object' && (id.universe != SteamID.Universe.PUBLIC || id.type != SteamID.Type.CLAN)) {
throw new Error("SteamID must stand for a clan account in the public universe");
}
var self = this;
this.httpRequest("https://steamcommunity.com/" + (typeof id === 'string' ? "groups/" + id : "gid/" + id.toString()) + "/memberslistxml/?xml=1", function(err, response, body) {
if (err) {
callback(err);
return;
}
xml2js.parseString(body, function(err, result) {
if(err) {
callback(err);
return;
}
callback(null, new CSteamGroup(self, result.memberList));
});
}, "steamcommunity");
@@ -32,7 +32,7 @@ SteamCommunity.prototype.getSteamGroup = function(id, callback) {
function CSteamGroup(community, groupData) {
this._community = community;
this.steamID = new SteamID(groupData.groupID64[0]);
this.name = groupData.groupDetails[0].groupName[0];
this.url = groupData.groupDetails[0].groupURL[0];
@@ -48,7 +48,7 @@ function CSteamGroup(community, groupData) {
CSteamGroup.prototype.getAvatarURL = function(size, protocol) {
size = size || '';
protocol = protocol || 'http://';
var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + this.avatarHash.substring(0, 2) + "/" + this.avatarHash;
if(size == 'full' || size == 'medium') {
return url + "_" + size + ".jpg";
@@ -62,7 +62,7 @@ CSteamGroup.prototype.getMembers = function(addresses, callback) {
callback = addresses;
addresses = null;
}
this._community.getGroupMembers(this.steamID, callback, null, null, addresses, 0);
};
@@ -94,6 +94,14 @@ CSteamGroup.prototype.scheduleEvent = function(name, type, description, time, se
this._community.scheduleGroupEvent(this.steamID, name, type, description, time, server, callback);
};
CSteamGroup.prototype.editEvent = function(id, name, type, description, time, server, callback) {
this._community.editGroupEvent(this.steamID, id, name, type, description, time, server, callback);
};
CSteamGroup.prototype.deleteEvent = function (id, callback) {
this._community.deleteGroupEvent(this.steamID, id, callback);
};
CSteamGroup.prototype.setPlayerOfTheWeek = function(steamID, callback) {
this._community.setGroupPlayerOfTheWeek(this.steamID, steamID, callback);
};

View File

@@ -150,6 +150,10 @@ CSteamUser.prototype.inviteToGroup = function(groupID, callback) {
this._community.inviteUserToGroup(this.steamID, groupID, callback);
};
CSteamUser.prototype.getAliases = function(callback) {
this._community.getUserAliases(this.steamID, callback);
};
CSteamUser.prototype.getInventoryContexts = function(callback) {
this._community.getUserInventoryContexts(this.steamID, callback);
};

View File

@@ -42,9 +42,17 @@ SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
var self = this;
this.getWebApiOauthToken(function(err, token) {
if(err) {
var fatal = err.message.indexOf('not authorized') != -1;
if (!fatal) {
self.chatState = SteamCommunity.ChatState.LogOnFailed;
setTimeout(self.chatLogon.bind(self), 5000);
} else {
self.chatState = SteamCommunity.ChatState.Offline;
}
self.emit('chatLogOnFailed', err, fatal);
self.emit('debug', "Cannot get oauth token: " + err.message);
self.chatState = SteamCommunity.ChatState.LogOnFailed;
setTimeout(self.chatLogon.bind(self), 5000);
return;
}
@@ -57,15 +65,17 @@ SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
"json": true
}, function(err, response, body) {
if(err || response.statusCode != 200) {
self.emit('debug', 'Error logging into webchat: ' + (err ? err.message : "HTTP error " + response.statusCode));
self.chatState = SteamCommunity.ChatState.LogOnFailed;
self.emit('chatLogOnFailed', err ? err : new Error("HTTP error " + response.statusCode), false);
self.emit('debug', 'Error logging into webchat: ' + (err ? err.message : "HTTP error " + response.statusCode));
setTimeout(self.chatLogon.bind(self), 5000);
return;
}
if(body.error != 'OK') {
self.emit('debug', 'Error logging into webchat: ' + body.error);
self.chatState = SteamCommunity.ChatState.LogOnFailed;
self.emit('chatLogOnFailed', new Error(body.error), false);
self.emit('debug', 'Error logging into webchat: ' + body.error);
setTimeout(self.chatLogon.bind(self), 5000);
return;
}
@@ -74,7 +84,8 @@ SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
"umqid": body.umqid,
"message": body.message,
"accessToken": token,
"interval": interval
"interval": interval,
"uiMode": uiMode
};
self.chatFriends = {};
@@ -178,11 +189,19 @@ SteamCommunity.prototype._chatPoll = function() {
if(err || response.statusCode != 200) {
self.emit('debug', 'Error in chat poll: ' + (err ? err.message : "HTTP error " + response.statusCode));
if (err.message == "Not Logged On") {
self._relogWebChat();
}
return;
}
if(body.error != 'OK') {
self.emit('debug', 'Error in chat poll: ' + body.error);
if(!body || body.error != 'OK') {
self.emit('debug', 'Error in chat poll: ' + (body && body.error ? body.error : "Malformed response"));
if (body && body.error && body.error == "Not Logged On") {
self._relogWebChat();
}
return;
}
@@ -215,6 +234,13 @@ SteamCommunity.prototype._chatPoll = function() {
}, "steamcommunity");
};
SteamCommunity.prototype._relogWebChat = function() {
this.emit('debug', "Relogging web chat");
clearTimeout(this._chat.timer);
this.chatState = SteamCommunity.ChatState.Offline;
this.chatLogon(this._chat.interval, this._chat.uiMode);
};
SteamCommunity.prototype._chatUpdatePersona = function(steamID) {
this.emit('debug', 'Updating persona data for ' + steamID);
var self = this;

View File

@@ -52,6 +52,8 @@ SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
var img = conf.find('.mobileconf_list_entry_icon img');
confs.push(new CConfirmation(self, {
"id": conf.data('confid'),
"type": conf.data('type'),
"creator": conf.data('creator'),
"key": conf.data('key'),
"title": conf.find('.mobileconf_list_entry_description>div:nth-of-type(1)').text().trim(),
"receiving": conf.find('.mobileconf_list_entry_description>div:nth-of-type(2)').text().trim(),
@@ -108,15 +110,15 @@ SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, ca
/**
* Confirm or cancel a given confirmation.
* @param {int} confID - The ID of the confirmation in question
* @param {string} confKey - The confirmation key associated with the confirmation in question (not a TOTP key, the `key` property of CConfirmation)
* @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
*/
SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time, key, accept, callback) {
request(this, "ajaxop", key, time, accept ? "allow" : "cancel", {
request(this, (confID instanceof Array) ? "multiajaxop" : "ajaxop", key, time, accept ? "allow" : "cancel", {
"op": accept ? "allow" : "cancel",
"cid": confID,
"ck": confKey
@@ -144,6 +146,86 @@ 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 || [];
SteamTotp.getTimeOffset(function(err, offset) {
if (err) {
callback(err);
return;
}
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;
this.getConfirmations(time, confKey, function(err, confs) {
if (err) {
callback(err);
return;
}
if (confs.length == 0) {
callback(null, []);
return;
}
self.respondToConfirmation(confs.map(function(conf) { return conf.id; }), confs.map(function(conf) { return conf.key; }), time, allowKey, true, function(err) {
if (err) {
callback(err);
return;
}
callback(err, confs);
});
});
};
function request(community, url, key, time, tag, params, json, callback) {
params = params || {};
params.p = SteamTotp.getDeviceID(community.steamID);
@@ -153,11 +235,19 @@ function request(community, url, key, time, tag, params, json, callback) {
params.m = "android";
params.tag = tag;
community.httpRequestGet({
var req = {
"method": url == 'multiajaxop' ? 'POST' : 'GET',
"uri": "https://steamcommunity.com/mobileconf/" + url,
"qs": params,
"json": !!json
}, function(err, response, body) {
};
if (req.method == "GET") {
req.qs = params;
} else {
req.form = params;
}
community.httpRequest(req, function(err, response, body) {
if (err) {
callback(err);
return;
@@ -261,24 +351,13 @@ SteamCommunity.prototype.checkConfirmations = function() {
return; // No new ones
}
// We have new confirmations! Grab a key to get details.
self._confirmationCheckerGetKey('details', function(err, key) {
newOnes.forEach(function(conf) {
self._knownConfirmations[conf.id] = conf; // Add it to our list of known confirmations
if(err) {
self._confirmationQueue.push(conf);
} else {
// Get its offer ID, if we can
conf.getOfferID(key.time, key.key, function(err, offerID) {
conf.offerID = offerID ? offerID : null;
self._confirmationQueue.push(conf);
});
}
});
resetTimer();
// We have new confirmations!
newOnes.forEach(function(conf) {
self._knownConfirmations[conf.id] = conf; // Add it to our list of known confirmations
self._confirmationQueue.push(conf);
});
resetTimer();
});
});

View File

@@ -120,7 +120,7 @@ SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback
var self = this;
this.httpRequest({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/rss/"
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/rss/"
}, function(err, response, body) {
if (err) {
callback(err);
@@ -242,7 +242,7 @@ SteamCommunity.prototype.scheduleGroupEvent = function(gid, name, type, descript
server = {"ip": "", "password": ""};
} else if(typeof server === 'string') {
server = {"ip": server, "password": ""};
} else {
} else if(typeof server !== 'object') {
server = {"ip": "", "password": ""};
}
@@ -286,6 +286,88 @@ SteamCommunity.prototype.scheduleGroupEvent = function(gid, name, type, descript
}, "steamcommunity");
};
SteamCommunity.prototype.editGroupEvent = function (gid, id, name, type, description, time, server, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
// Event types: ChatEvent - Chat, OtherEvent - A lil somethin somethin, PartyEvent - Party!, MeetingEvent - Important meeting, SpecialCauseEvent - Special cause (charity ball?), MusicAndArtsEvent - Music or Art type thing, SportsEvent - Sporting endeavor, TripEvent - Out of town excursion
// Passing a number for type will make it a game event for that appid
if (typeof server === 'function') {
callback = server;
server = {"ip": "", "password": ""};
} else if (typeof server === 'string') {
server = {"ip": server, "password": ""};
} else if (typeof server !== 'object') {
server = {"ip": "", "password": ""};
}
var form = {
"sessionid": this.getSessionID(),
"action": "updateEvent",
"eventID": id,
"tzOffset": new Date().getTimezoneOffset() * -60,
"name": name,
"type": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? "GameEvent" : type),
"appID": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? type : ''),
"serverIP": server.ip,
"serverPassword": server.password,
"notes": description,
"eventQuickTime": "now"
};
if (time === null) {
form.startDate = 'MM/DD/YY';
form.startHour = '12';
form.startMinute = '00';
form.startAMPM = 'PM';
form.timeChoice = 'quick';
} else {
form.startDate = (time.getMonth() + 1 < 10 ? '0' : '') + (time.getMonth() + 1) + '/' + (time.getDate() < 10 ? '0' : '') + time.getDate() + '/' + time.getFullYear().toString().substring(2);
form.startHour = (time.getHours() === 0 ? '12' : (time.getHours() > 12 ? time.getHours() - 12 : time.getHours()));
form.startMinute = (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
form.startAMPM = (time.getHours() <= 12 ? 'AM' : 'PM');
form.timeChoice = 'specific';
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.toString() + "/eventEdit",
"form": form
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.deleteGroupEvent = function(gid, id, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var form = {
"sessionid": this.getSessionID(),
"action": "deleteEvent",
"eventID": id
};
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.toString() + "/eventEdit",
"form": form
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.setGroupPlayerOfTheWeek = function(gid, steamID, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);

View File

@@ -11,3 +11,13 @@ exports.isSteamID = function(input) {
return keys.length == 4;
};
exports.decodeSteamTime = function(time) {
var parts = time.split('@');
if (!parts[0].match(/,/)) {
// no year, assume current year
parts[0] += ", " + (new Date()).getFullYear();
}
return new Date(parts.join('@').replace(/(am|pm)/, ' $1') + " UTC"); // add a space so JS can decode it
};

View File

@@ -50,16 +50,22 @@ SteamCommunity.prototype.httpRequest = function(uri, options, callback, source)
var httpError = options.checkHttpError !== false && self._checkHttpError(err, response, callback);
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 && options.checkJsonError !== false && !body ? new Error("Malformed JSON response") : null;
self.emit('postHttpRequest', requestID, source, options, httpError || communityError || tradeError || null, response, body, {
self.emit('postHttpRequest', requestID, source, options, httpError || communityError || tradeError || jsonError || null, response, body, {
"hasCallback": hasCallback,
"httpError": httpError,
"communityError": communityError,
"tradeError": tradeError
"tradeError": tradeError,
"jsonError": jsonError
});
if(hasCallback && !(httpError || communityError || tradeError)) {
callback.apply(self, arguments);
if (hasCallback && !(httpError || communityError || tradeError)) {
if (jsonError) {
callback.call(self, jsonError, response);
} else {
callback.apply(self, arguments);
}
}
});
}
@@ -80,19 +86,25 @@ SteamCommunity.prototype._notifySessionExpired = function(err) {
};
SteamCommunity.prototype._checkHttpError = function(err, response, callback) {
if(err) {
if (err) {
callback(err);
return err;
}
if(response.statusCode >= 300 && response.statusCode <= 399 && response.headers.location.indexOf('/login') != -1) {
if (response.statusCode >= 300 && response.statusCode <= 399 && response.headers.location.indexOf('/login') != -1) {
err = new Error("Not Logged In");
callback(err);
this._notifySessionExpired(err);
return err;
}
if(response.statusCode >= 400) {
if (response.statusCode == 403 && response.body && 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);
return err;
}
if (response.statusCode >= 400) {
err = new Error("HTTP error " + response.statusCode);
err.code = response.statusCode;
callback(err);

View File

@@ -5,118 +5,158 @@ var request = require('request');
var Cheerio = require('cheerio');
var Async = require('async');
/*
* Inventory history in a nutshell.
*
* There are no more page numbers. Now you have to request after_time and optionally after_trade.
* Without "prev" set, you will request 30 trades that were completed FURTHER IN THE PAST 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)
*/
SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
if(typeof options === 'function') {
if (typeof options === 'function') {
callback = options;
options = {};
}
options.page = options.page || 1;
this.httpRequest("https://steamcommunity.com/my/inventoryhistory?l=english&p=" + options.page, function(err, response, body) {
if(err) {
options.direction = options.direction || "past";
var qs = "?l=english";
if (options.startTime) {
if (options.startTime instanceof Date) {
options.startTime = Math.floor(options.startTime.getTime() / 1000);
}
qs += "&after_time=" + options.startTime;
if (options.startTrade) {
qs += "&after_trade=" + options.startTrade;
}
}
if (options.direction == "future") {
qs += "&prev=1";
}
this._myProfile("inventoryhistory" + qs, null, function(err, response, body) {
if (err) {
callback(err);
return;
}
var output = {};
var vanityURLs = [];
var $ = Cheerio.load(body);
var html = $('.inventory_history_pagingrow').html();
if(!html) {
if (!$('.inventory_history_pagingrow').html()) {
callback("Malformed page: no paging row found");
return;
}
var match = html.match(/(\d+) - (\d+) of (\d+) History Items/);
output.first = parseInt(match[1], 10);
output.last = parseInt(match[2], 10);
output.totalTrades = parseInt(match[3], 10);
// Load the inventory item data
var match2 = body.match(/var g_rgHistoryInventory = (.*);/);
if(!match2) {
if (!match2) {
callback(new Error("Malformed page: no trade found"));
return;
}
var historyInventory = JSON.parse(match2[1]);
try {
var historyInventory = JSON.parse(match2[1]);
} catch (ex) {
callback(new Error("Malformed page: no well-formed trade data found"));
return;
}
var i;
// See if we've got paging buttons
var $paging = $('.inventory_history_nextbtn .pagebtn:not(.disabled)');
var href;
for (i = 0; i < $paging.length; i++) {
href = $paging[i].attribs.href;
if (href.match(/prev=1/)) {
output.firstTradeTime = new Date(href.match(/after_time=(\d+)/)[1] * 1000);
output.firstTradeID = href.match(/after_trade=(\d+)/)[1];
} else {
output.lastTradeTime = new Date(href.match(/after_time=(\d+)/)[1] * 1000);
output.lastTradeID = href.match(/after_trade=(\d+)/)[1];
}
}
output.trades = [];
var trades = $('.tradehistoryrow');
var item, trade, profileLink, items, j, econItem, timeMatch, time;
for(var i = 0; i < trades.length; i++) {
for (i = 0; i < trades.length; i++) {
item = $(trades[i]);
trade = {};
trade.onHold = !!item.find('span:nth-of-type(2)').text().match(/Trade on Hold/i);
timeMatch = item.find('.tradehistory_timestamp').html().match(/(\d+):(\d+)(am|pm)/);
if(timeMatch[1] == 12 && timeMatch[3] == 'am') {
if (timeMatch[1] == 12 && timeMatch[3] == 'am') {
timeMatch[1] = 0;
}
if(timeMatch[1] < 12 && timeMatch[3] == 'pm') {
if (timeMatch[1] < 12 && timeMatch[3] == 'pm') {
timeMatch[1] = parseInt(timeMatch[1], 10) + 12;
}
time = (timeMatch[1] < 10 ? '0' : '') + timeMatch[1] + ':' + timeMatch[2] + ':00';
trade.date = new Date(item.find('.tradehistory_date').html() + ' ' + time);
trade.date = new Date(item.find('.tradehistory_date').html() + ' ' + time + ' UTC');
trade.partnerName = item.find('.tradehistory_event_description a').html();
trade.partnerSteamID = null;
trade.partnerVanityURL = null;
trade.itemsReceived = [];
trade.itemsGiven = [];
profileLink = item.find('.tradehistory_event_description a').attr('href');
if(profileLink.indexOf('/profiles/') != -1) {
if (profileLink.indexOf('/profiles/') != -1) {
trade.partnerSteamID = new SteamID(profileLink.match(/(\d+)$/)[1]);
} else {
trade.partnerVanityURL = profileLink.match(/\/([^\/]+)$/)[1];
if(options.resolveVanityURLs && vanityURLs.indexOf(trade.partnerVanityURL) == -1) {
if (options.resolveVanityURLs && vanityURLs.indexOf(trade.partnerVanityURL) == -1) {
vanityURLs.push(trade.partnerVanityURL);
}
}
items = item.find('.history_item');
for(j = 0; j < items.length; j++) {
for (j = 0; j < items.length; j++) {
match = body.match(new RegExp("HistoryPageCreateItemHover\\( '" + $(items[j]).attr('id') + "', (\\d+), '(\\d+)', '(\\d+|class_\\d+_instance_\\d+|class_\\d+)', '(\\d+)' \\);"));
econItem = historyInventory[match[1]][match[2]][match[3]];
if($(items[j]).attr('id').indexOf('received') != -1) {
if ($(items[j]).attr('id').indexOf('received') != -1) {
trade.itemsReceived.push(new CEconItem(econItem));
} else {
trade.itemsGiven.push(new CEconItem(econItem));
}
}
output.trades.push(trade);
}
if(options.resolveVanityURLs) {
if (options.resolveVanityURLs) {
Async.map(vanityURLs, resolveVanityURL, function(err, results) {
if(err) {
if (err) {
callback(err);
return;
}
for(i = 0; i < output.trades.length; i++) {
if(output.trades[i].partnerSteamID || !output.trades[i].partnerVanityURL) {
for (i = 0; i < output.trades.length; i++) {
if (output.trades[i].partnerSteamID || !output.trades[i].partnerVanityURL) {
continue;
}
// Find the vanity URL
for(j = 0; j < results.length; j++) {
if(results[j].vanityURL == output.trades[i].partnerVanityURL) {
for (j = 0; j < results.length; j++) {
if (results[j].vanityURL == output.trades[i].partnerVanityURL) {
output.trades[i].partnerSteamID = new SteamID(results[j].steamID);
break;
}
}
}
callback(null, output);
});
} else {
@@ -127,17 +167,17 @@ SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
function resolveVanityURL(vanityURL, callback) {
request("https://steamcommunity.com/id/" + vanityURL + "/?xml=1", function(err, response, body) {
if(err) {
if (err) {
callback(err);
return;
}
var match = body.match(/<steamID64>(\d+)<\/steamID64>/);
if(!match || !match[1]) {
if (!match || !match[1]) {
callback(new Error("Couldn't find Steam ID"));
return;
}
callback(null, {"vanityURL": vanityURL, "steamID": match[1]});
});
}

View File

@@ -1,6 +1,7 @@
var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
var CEconItem = require('../classes/CEconItem.js');
var Helpers = require('./helpers.js');
SteamCommunity.prototype.addFriend = function(userID, callback) {
if(typeof userID === 'string') {
@@ -190,6 +191,32 @@ SteamCommunity.prototype.inviteUserToGroup = function(userID, groupID, callback)
}, "steamcommunity");
};
SteamCommunity.prototype.getUserAliases = function(userID, callback) {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
this.httpRequestGet({
"uri": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/ajaxaliases",
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if (typeof body !== 'object') {
callback(new Error("Malformed response"));
return;
}
callback(null, body.map(function(entry) {
entry.timechanged = Helpers.decodeSteamTime(entry.timechanged);
return entry;
}));
}, "steamcommunity");
};
SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);

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

@@ -10,6 +10,12 @@ 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?
};
function SteamCommunity(options) {
options = options || {};
@@ -43,20 +49,23 @@ function SteamCommunity(options) {
this.request = this.request.defaults(defaults);
// English
this._jar.setCookie(Request.cookie('Steam_Language=english'), 'https://steamcommunity.com');
this._setCookie(Request.cookie('Steam_Language=english'));
// UTC
this._jar.setCookie(Request.cookie('timezoneOffset=0,0'), 'https://steamcommunity.com');
this._setCookie(Request.cookie('timezoneOffset=0,0'));
}
SteamCommunity.prototype.login = function(details, callback) {
if(details.steamguard) {
var parts = details.steamguard.split('||');
this._jar.setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), 'https://steamcommunity.com');
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",
@@ -65,8 +74,8 @@ SteamCommunity.prototype.login = function(details, callback) {
"Accept": "text/javascript, text/html, application/xml, text/xml, */*"
};
this._jar.setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"), "https://steamcommunity.com");
this._jar.setCookie(Request.cookie("mobileClient=android"), "https://steamcommunity.com");
this._setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"));
this._setCookie(Request.cookie("mobileClient=android"));
this.httpRequestPost("https://steamcommunity.com/login/getrsakey/", {
"form": {"username": details.accountName},
@@ -140,7 +149,7 @@ SteamCommunity.prototype.login = function(details, callback) {
} else {
var sessionID = generateSessionID();
var oAuth = JSON.parse( body.oauth );
self._jar.setCookie(Request.cookie('sessionid=' + sessionID), 'http://steamcommunity.com');
self._setCookie(Request.cookie('sessionid=' + sessionID));
self.steamID = new SteamID(oAuth.steamid);
self.oAuthToken = oAuth.oauth_token;
@@ -158,6 +167,8 @@ SteamCommunity.prototype.login = function(details, callback) {
break;
}
}
self.setCookies(cookies);
callback(null, sessionID, cookies, steamguard, oAuth.oauth_token);
}
@@ -167,11 +178,11 @@ SteamCommunity.prototype.login = function(details, callback) {
function deleteMobileCookies() {
var cookie = Request.cookie('mobileClientVersion=');
cookie.expires = new Date(0);
self._jar.setCookie(cookie, "https://steamcommunity.com");
self._setCookie(cookie);
cookie = Request.cookie('mobileClient=');
cookie.expires = new Date(0);
self._jar.setCookie(cookie, "https://steamcommunity.com");
self._setCookie(cookie);
}
};
@@ -209,6 +220,15 @@ SteamCommunity.prototype.oAuthLogin = function(steamguard, token, callback) {
}, "steamcommunity");
};
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");
this._jar.setCookie(cookie.clone(), protocol + "://help.steampowered.com");
};
SteamCommunity.prototype.setCookies = function(cookies) {
var self = this;
cookies.forEach(function(cookie) {
@@ -216,8 +236,8 @@ SteamCommunity.prototype.setCookies = function(cookies) {
if(cookieName == 'steamLogin') {
self.steamID = new SteamID(cookie.match(/=(\d+)/)[1]);
}
self._jar.setCookie(Request.cookie(cookie), (cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/) ? "https://" : "http://") + "steamcommunity.com");
self._setCookie(Request.cookie(cookie), !!(cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/)));
});
};
@@ -231,7 +251,7 @@ SteamCommunity.prototype.getSessionID = function() {
}
var sessionID = generateSessionID();
this._jar.setCookie(Request.cookie('sessionid=' + sessionID), "http://steamcommunity.com");
this._setCookie(Request.cookie('sessionid=' + sessionID));
return sessionID;
};
@@ -273,37 +293,34 @@ SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
SteamCommunity.prototype.getNotifications = function(callback) {
var self = this;
this.httpRequestGet("https://steamcommunity.com/actions/RefreshNotificationArea", function(err, response, body) {
this.httpRequestGet({
"uri": "https://steamcommunity.com/actions/GetNotificationCounts",
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
var notifications = {
"comments": 0,
"items": 0,
"invites": 0,
"gifts": 0,
"chat": 0,
"trades": 0
};
var items = {
"comments": /(\d+) new comments?/,
"items": /(\d+) new items? in your inventory/,
"invites": /(\d+) new invites?/,
"gifts": /(\d+) new gifts?/,
"chat": /(\d+) unread chat messages?/,
"trades": /(\d+) new trade notifications?/
};
var match;
for(var i in items) {
if(match = body.match(items[i])) {
notifications[i] = parseInt(match[1], 10);
}
if (!body || !body.notifications) {
callback(new Error("Malformed response"));
return;
}
var notifications = {
"trades": body.notifications[1] || 0,
"gameTurns": body.notifications[2] || 0,
"moderatorMessages": body.notifications[3] || 0,
"comments": body.notifications[4] || 0,
"items": body.notifications[5] || 0,
"invites": body.notifications[6] || 0,
// dunno about 7
"gifts": body.notifications[8] || 0,
"chat": body.notifications[9] || 0,
"helpRequestReplies": body.notifications[10] || 0,
"accountAlerts": body.notifications[11] || 0
};
callback(null, notifications);
}, "steamcommunity");
};

View File

@@ -1,6 +1,6 @@
{
"name": "steamcommunity",
"version": "3.19.9",
"version": "3.27.2",
"description": "Provides an interface for logging into and interacting with the Steam Community website",
"keywords": ["steam", "steam community"],
"homepage": "https://github.com/DoctorMcKay/node-steamcommunity",