node-steamcommunity/index.js

555 lines
15 KiB
JavaScript
Raw Normal View History

const hex2b64 = require('node-bignumber').hex2b64;
const Request = require('request');
const RSA = require('node-bignumber').Key;
const SteamID = require('steamid');
2014-10-01 03:18:38 +08:00
2019-04-19 13:20:01 +08:00
const Helpers = require('./components/helpers.js');
2018-07-11 14:22:39 +08:00
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";
2015-12-27 07:17:01 +08:00
2015-06-18 12:22:05 +08:00
require('util').inherits(SteamCommunity, require('events').EventEmitter);
2014-10-01 03:18:38 +08:00
module.exports = SteamCommunity;
2014-12-17 13:55:43 +08:00
SteamCommunity.SteamID = SteamID;
SteamCommunity.ConfirmationType = require('./resources/EConfirmationType.js');
2019-02-14 06:40:45 +08:00
SteamCommunity.EResult = require('./resources/EResult.js');
2017-12-28 13:32:06 +08:00
2014-12-17 13:55:43 +08:00
function SteamCommunity(options) {
options = options || {};
2014-12-17 13:48:19 +08:00
this._jar = Request.jar();
2015-07-18 10:26:29 +08:00
this._captchaGid = -1;
2016-03-05 08:35:04 +08:00
this._httpRequestID = 0;
2015-06-18 12:22:05 +08:00
this.chatState = SteamCommunity.ChatState.Offline;
var defaults = {
"jar": this._jar,
"timeout": options.timeout || 50000,
2016-01-04 03:38:02 +08:00
"gzip": true,
2015-12-27 07:17:01 +08:00
"headers": {
"User-Agent": options.userAgent || USER_AGENT
2015-12-27 07:17:01 +08:00
}
};
if (typeof options == "string") {
options = {
localAddress: options
};
}
this._options = options;
if (options.localAddress) {
defaults.localAddress = options.localAddress;
}
this.request = options.request || Request.defaults({"forever": true}); // "forever" indicates that we want a keep-alive agent
this.request = this.request.defaults(defaults);
2015-07-11 04:00:56 +08:00
// English
this._setCookie(Request.cookie('Steam_Language=english'));
2015-08-07 12:28:24 +08:00
// UTC
this._setCookie(Request.cookie('timezoneOffset=0,0'));
2014-10-01 03:18:38 +08:00
}
2014-12-17 13:48:19 +08:00
SteamCommunity.prototype.login = function(details, callback) {
if (!details.accountName || !details.password) {
throw new Error("Missing either accountName or password to login; both are needed");
}
if (details.steamguard) {
var parts = details.steamguard.split('||');
this._setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), true);
2014-10-01 03:18:38 +08:00
}
var disableMobile = details.disableMobile;
2014-10-01 03:18:38 +08:00
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 = {};
2017-12-27 05:39:23 +08:00
if (!disableMobile) {
mobileHeaders = {
"X-Requested-With": "com.valvesoftware.android.steam.community",
"Referer": "https://steamcommunity.com/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client",
"User-Agent": this._options.mobileUserAgent || details.mobileUserAgent || "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"));
2017-12-27 05:39:23 +08:00
} else {
mobileHeaders = {"Referer": "https://steamcommunity.com/login"};
}
this.httpRequestPost("https://steamcommunity.com/login/getrsakey/", {
"form": {"username": details.accountName},
"headers": mobileHeaders,
"json": true
}, function(err, response, body) {
// Remove the mobile cookies
if (err) {
deleteMobileCookies();
callback(err);
2014-10-01 03:18:38 +08:00
return;
}
2017-12-27 05:39:23 +08:00
if (!body.publickey_mod || !body.publickey_exp) {
deleteMobileCookies();
callback(new Error("Invalid RSA key received"));
return;
}
2014-10-01 03:18:38 +08:00
var key = new RSA();
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,
2017-12-27 05:39:23 +08:00
"loginfriendlyname": "",
"donotcache": Date.now()
};
if(!disableMobile){
formObj.oauth_client_id = "DE45CD61";
formObj.oauth_scope = "read_profile write_profile read_client write_client";
2017-12-27 05:39:23 +08:00
formObj.loginfriendlyname = "#login_emailauth_friendlyname_mobile";
}
self.httpRequestPost({
"uri": "https://steamcommunity.com/login/dologin/",
"json": true,
"form": formObj,
"headers": mobileHeaders
}, function(err, response, body) {
deleteMobileCookies();
if (err) {
callback(err);
2014-10-01 03:18:38 +08:00
return;
}
2016-03-01 05:24:29 +08:00
var error;
2017-12-27 05:39:23 +08:00
if (!body.success && body.emailauth_needed) {
// Steam Guard (email)
2016-03-01 05:24:29 +08:00
error = new Error("SteamGuard");
error.emaildomain = body.emaildomain;
callback(error);
2017-12-27 05:39:23 +08:00
} else if (!body.success && body.requires_twofactor) {
// Steam Guard (app)
callback(new Error("SteamGuardMobile"));
2017-12-27 05:39:23 +08:00
} else if (!body.success && body.captcha_needed && body.message.match(/Please verify your humanity/)) {
2016-03-01 05:24:29 +08:00
error = new Error("CAPTCHA");
error.captchaurl = "https://steamcommunity.com/login/rendercaptcha/?gid=" + body.captcha_gid;
self._captchaGid = body.captcha_gid;
callback(error);
2017-12-27 05:39:23 +08:00
} else if (!body.success) {
callback(new Error(body.message || "Unknown error"));
2017-12-27 05:39:23 +08:00
} else if (!disableMobile && !body.oauth) {
callback(new Error("Malformed response"));
2014-10-01 03:18:38 +08:00
} else {
2017-12-27 05:39:23 +08:00
var sessionID = generateSessionID();
var oAuth;
self._setCookie(Request.cookie('sessionid=' + sessionID));
2014-12-17 13:48:19 +08:00
var cookies = self._jar.getCookieString("https://steamcommunity.com").split(';').map(function(cookie) {
return cookie.trim();
});
2017-12-27 05:39:23 +08:00
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;
}
2014-12-17 13:48:19 +08:00
// Find the Steam Guard cookie
var steamguard = null;
for(var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split('=');
if(parts[0] == 'steamMachineAuth' + self.steamID) {
steamguard = self.steamID.toString() + '||' + decodeURIComponent(parts[1]);
2014-12-17 13:48:19 +08:00
break;
}
2014-10-01 03:18:38 +08:00
}
self.setCookies(cookies);
callback(null, sessionID, cookies, steamguard, disableMobile ? null : oAuth.oauth_token);
2014-10-01 03:18:38 +08:00
}
2016-03-05 08:35:04 +08:00
}, "steamcommunity");
}, "steamcommunity");
function deleteMobileCookies() {
var cookie = Request.cookie('mobileClientVersion=');
cookie.expires = new Date(0);
self._setCookie(cookie);
cookie = Request.cookie('mobileClient=');
cookie.expires = new Date(0);
self._setCookie(cookie);
}
2014-12-17 13:48:19 +08:00
};
SteamCommunity.prototype.oAuthLogin = function(steamguard, token, callback) {
steamguard = steamguard.split('||');
var steamID = new SteamID(steamguard[0]);
2015-12-27 07:15:09 +08:00
var self = this;
this.httpRequestPost({
2015-12-27 07:15:09 +08:00
"uri": "https://api.steampowered.com/IMobileAuthService/GetWGToken/v1/",
"form": {
"access_token": token
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
2015-12-27 07:15:09 +08:00
return;
}
if(!body.response || !body.response.token || !body.response.token_secure) {
callback(new Error("Malformed response"));
return;
}
var cookies = [
'steamLogin=' + encodeURIComponent(steamID.getSteamID64() + '||' + body.response.token),
'steamLoginSecure=' + encodeURIComponent(steamID.getSteamID64() + '||' + body.response.token_secure),
'steamMachineAuth' + steamID.getSteamID64() + '=' + steamguard[1],
2015-12-27 07:15:09 +08:00
'sessionid=' + self.getSessionID()
];
self.setCookies(cookies);
callback(null, self.getSessionID(), cookies);
2016-03-05 08:35:04 +08:00
}, "steamcommunity");
2015-12-27 07:15:09 +08:00
};
2019-02-14 06:40:45 +08:00
/**
* Get a token that can be used to log onto Steam using steam-user.
* @param {function} callback
*/
SteamCommunity.prototype.getClientLogonToken = function(callback) {
this.httpRequestGet({
"uri": "https://steamcommunity.com/chat/clientjstoken",
"json": true
}, (err, res, body) => {
if (err || res.statusCode != 200) {
callback(err ? err : new Error('HTTP error ' + res.statusCode));
return;
}
if (!body.logged_in) {
let e = new Error('Not Logged In');
callback(e);
this._notifySessionExpired(e);
return;
}
if (!body.steamid || !body.account_name || !body.token) {
2019-02-14 06:40:45 +08:00
callback(new Error('Malformed response'));
return;
}
callback(null, {
"steamID": new SteamID(body.steamid),
"accountName": body.account_name,
"webLogonToken": body.token
});
2019-02-14 06:40:45 +08:00
});
};
SteamCommunity.prototype._setCookie = function(cookie, secure) {
var protocol = secure ? "https" : "http";
cookie.secure = !!secure;
2016-09-12 13:24:39 +08:00
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");
};
2014-12-17 13:48:19 +08:00
SteamCommunity.prototype.setCookies = function(cookies) {
2018-07-11 14:12:37 +08:00
cookies.forEach((cookie) => {
2014-12-17 13:48:19 +08:00
var cookieName = cookie.match(/(.+)=/)[1];
2018-07-11 14:12:37 +08:00
if (cookieName == 'steamLogin' || cookieName == 'steamLoginSecure') {
this.steamID = new SteamID(cookie.match(/=(\d+)/)[1]);
2014-12-17 13:48:19 +08:00
}
2018-07-11 14:12:37 +08:00
this._setCookie(Request.cookie(cookie), !!(cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/)));
2014-12-17 13:48:19 +08:00
});
};
SteamCommunity.prototype.getSessionID = function() {
var cookies = this._jar.getCookieString("http://steamcommunity.com").split(';');
for(var i = 0; i < cookies.length; i++) {
var match = cookies[i].trim().match(/([^=]+)=(.+)/);
if(match[1] == 'sessionid') {
return decodeURIComponent(match[2]);
}
}
2014-12-17 13:48:19 +08:00
var sessionID = generateSessionID();
this._setCookie(Request.cookie('sessionid=' + sessionID));
2014-12-17 13:48:19 +08:00
return sessionID;
};
function generateSessionID() {
return require('crypto').randomBytes(12).toString('hex');
2015-11-27 14:28:31 +08:00
}
2014-12-17 14:48:17 +08:00
2015-05-12 03:48:00 +08:00
SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
var self = this;
this.httpRequestPost("https://steamcommunity.com/parental/ajaxunlock", {
2015-05-12 03:48:00 +08:00
"json": true,
"form": {
"pin": pin
}
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
2015-05-12 03:48:00 +08:00
}
if (!body || typeof body.success !== 'boolean') {
callback("Invalid response");
return;
2015-05-12 03:48:00 +08:00
}
if (!body.success) {
switch (body.eresult) {
case 15:
callback("Incorrect PIN");
break;
case 25:
callback("Too many invalid PIN attempts");
break;
default:
callback("Error " + body.eresult);
}
return;
2015-05-12 03:48:00 +08:00
}
2015-05-12 03:48:00 +08:00
callback();
2016-03-05 08:35:04 +08:00
}.bind(this), "steamcommunity");
2015-05-12 03:48:00 +08:00
};
2015-05-26 01:37:01 +08:00
SteamCommunity.prototype.getNotifications = function(callback) {
2015-11-11 12:46:17 +08:00
var self = this;
this.httpRequestGet({
"uri": "https://steamcommunity.com/actions/GetNotificationCounts",
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
2015-05-26 01:37:01 +08:00
}
if (!body || !body.notifications) {
callback(new Error("Malformed response"));
return;
}
2015-05-26 01:37:01 +08:00
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
2015-05-26 01:37:01 +08:00
};
2015-05-26 01:37:01 +08:00
callback(null, notifications);
2016-03-05 08:35:04 +08:00
}, "steamcommunity");
2015-05-26 01:37:01 +08:00
};
2015-05-26 01:20:47 +08:00
SteamCommunity.prototype.resetItemNotifications = function(callback) {
var self = this;
this.httpRequestGet("https://steamcommunity.com/my/inventory", function(err, response, body) {
2015-05-26 01:20:47 +08:00
if(!callback) {
return;
}
callback(err || null);
2016-03-05 08:35:04 +08:00
}, "steamcommunity");
2015-05-26 01:20:47 +08:00
};
2015-07-18 09:10:08 +08:00
SteamCommunity.prototype.loggedIn = function(callback) {
2016-03-07 14:32:27 +08:00
this.httpRequestGet({
"uri": "https://steamcommunity.com/my",
"followRedirect": false,
"checkHttpError": false
}, function(err, response, body) {
if(err || (response.statusCode != 302 && response.statusCode != 403)) {
callback(err || new Error("HTTP error " + response.statusCode));
2015-07-18 09:10:08 +08:00
return;
}
if(response.statusCode == 403) {
callback(null, true, true);
return;
}
callback(null, !!response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/), false);
2016-03-05 08:35:04 +08:00
}, "steamcommunity");
2015-07-18 09:10:08 +08:00
};
2017-10-03 02:55:29 +08:00
SteamCommunity.prototype.getTradeURL = function(callback) {
2019-04-19 13:20:01 +08:00
this._myProfile("tradeoffers/privacy", null, (err, response, body) => {
if (err) {
callback(err);
return;
}
2017-10-03 02:55:29 +08:00
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"));
}
2017-12-28 13:32:06 +08:00
}, "steamcommunity");
2017-10-03 02:55:29 +08:00
};
2017-12-28 13:32:06 +08:00
SteamCommunity.prototype.changeTradeURL = function(callback) {
2019-04-19 13:20:01 +08:00
this._myProfile("tradeoffers/newtradeurl", {"sessionid": this.getSessionID()}, (err, response, body) => {
2017-12-28 13:32:06 +08:00
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");
2017-12-28 07:19:57 +08:00
};
2019-04-19 13:20:01 +08:00
/**
* Clear your profile name (alias) history.
* @param {function} callback
*/
SteamCommunity.prototype.clearPersonaNameHistory = function(callback) {
this._myProfile("ajaxclearaliashistory/", {"sessionid": this.getSessionID()}, (err, res, body) => {
if (!callback) {
return;
}
if (err) {
return callback(err);
}
if (res.statusCode != 200) {
return callback(new Error("HTTP error " + res.statusCode));
}
try {
body = JSON.parse(body);
callback(Helpers.eresultError(body.success));
} catch (ex) {
return callback(new Error("Malformed response"));
}
});
};
SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
var self = this;
if (this._profileURL) {
completeRequest(this._profileURL);
} else {
this.httpRequest("https://steamcommunity.com/my", {"followRedirect": false}, function(err, response, body) {
if(err || response.statusCode != 302) {
callback(err || "HTTP error " + response.statusCode);
return;
}
var match = response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/);
if(!match) {
callback(new Error("Can't get profile URL"));
return;
}
self._profileURL = match[1];
setTimeout(function () {
delete self._profileURL; // delete the cache
}, 60000).unref();
completeRequest(match[1]);
}, "steamcommunity");
}
function completeRequest(url) {
2018-04-02 11:35:56 +08:00
var options = endpoint.endpoint ? endpoint : {};
options.uri = "https://steamcommunity.com" + url + "/" + (endpoint.endpoint || endpoint);
if (form) {
options.method = "POST";
options.form = form;
options.followAllRedirects = true;
} else if (!options.method) {
options.method = "GET";
}
2016-03-05 08:35:04 +08:00
self.httpRequest(options, callback, "steamcommunity");
}
};
require('./components/http.js');
2015-08-05 11:37:16 +08:00
require('./components/chat.js');
require('./components/profile.js');
require('./components/market.js');
require('./components/groups.js');
require('./components/users.js');
2015-09-10 00:04:28 +08:00
require('./components/inventoryhistory.js');
2015-11-27 14:28:31 +08:00
require('./components/webapi.js');
2015-11-27 14:45:58 +08:00
require('./components/twofactor.js');
2015-12-03 10:31:17 +08:00
require('./components/confirmations.js');
2015-06-29 08:19:42 +08:00
require('./classes/CMarketItem.js');
2015-07-11 03:46:29 +08:00
require('./classes/CMarketSearchResult.js');
2014-12-17 14:48:17 +08:00
require('./classes/CSteamGroup.js');
2015-04-26 02:46:25 +08:00
require('./classes/CSteamUser.js');
2015-12-03 11:20:16 +08:00
/**
@callback SteamCommunity~genericErrorCallback
@param {Error|null} err - An Error object on failure, or null on success
2017-12-28 07:19:57 +08:00
*/