node-steamcommunity/index.js

396 lines
11 KiB
JavaScript
Raw Normal View History

2014-10-01 03:18:38 +08:00
var Request = require('request');
var RSA = require('node-bignumber').Key;
var hex2b64 = require('node-bignumber').hex2b64;
2014-12-17 13:55:43 +08:00
var SteamID = require('steamid');
2014-10-01 03:18:38 +08:00
2015-12-27 07:17:01 +08:00
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";
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;
function SteamCommunity(localAddress) {
2014-12-17 13:48:19 +08:00
this._jar = Request.jar();
2015-07-18 10:26:29 +08:00
this._captchaGid = -1;
2015-06-18 12:22:05 +08:00
this.chatState = SteamCommunity.ChatState.Offline;
var defaults = {
"jar": this._jar,
2015-12-27 07:17:01 +08:00
"timeout": 50000,
"headers": {
"User-Agent": USER_AGENT
}
};
if(localAddress) {
defaults.localAddress = localAddress;
}
this.request = Request.defaults(defaults);
2015-07-11 04:00:56 +08:00
// English
this._jar.setCookie(Request.cookie('Steam_Language=english'), 'https://steamcommunity.com');
2015-08-07 12:28:24 +08:00
// UTC
this._jar.setCookie(Request.cookie('timezoneOffset=0,0'), 'https://steamcommunity.com');
2014-10-01 03:18:38 +08:00
}
2014-12-17 13:48:19 +08:00
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');
2014-10-01 03:18:38 +08:00
}
var self = this;
// 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",
"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._jar.setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"), "https://steamcommunity.com");
this._jar.setCookie(Request.cookie("mobileClient=android"), "https://steamcommunity.com");
this.request.post("https://steamcommunity.com/login/getrsakey/", {
"form": {
"username": details.accountName
},
"headers": mobileHeaders
}, function(err, response, body) {
// Remove the mobile cookies
2014-10-01 03:18:38 +08:00
if(err) {
deleteMobileCookies();
2014-10-01 03:18:38 +08:00
callback(err);
return;
}
var json;
try {
json = JSON.parse(body);
} catch(e) {
deleteMobileCookies();
2014-10-01 03:18:38 +08:00
callback(e);
return;
}
if(!json.publickey_mod || !json.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(json.publickey_mod, json.publickey_exp);
var form = {
2015-07-18 10:26:29 +08:00
"captcha_text": details.captcha || "",
"captchagid": self._captchaGid,
2014-12-17 13:48:19 +08:00
"emailauth": details.authCode || "",
2014-10-01 03:18:38 +08:00
"emailsteamid": "",
2014-12-17 13:48:19 +08:00
"password": hex2b64(key.encrypt(details.password)),
"remember_login": "true",
2014-10-01 03:18:38 +08:00
"rsatimestamp": json.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"
2014-10-01 03:18:38 +08:00
};
self.request.post({
"uri": "https://steamcommunity.com/login/dologin/",
"json": true,
"form": form,
"headers": mobileHeaders
}, function(err, response, body) {
deleteMobileCookies();
if(self._checkHttpError(err, response, callback)) {
2014-10-01 03:18:38 +08:00
return;
}
if(!body.success && body.emailauth_needed) {
// Steam Guard (email)
var error = new Error("SteamGuard");
error.emaildomain = body.emaildomain;
callback(error);
} else if(!body.success && body.requires_twofactor) {
// Steam Guard (app)
callback(new Error("SteamGuardMobile"));
} else if(!body.success && body.captcha_needed) {
var error = new Error("CAPTCHA");
error.captchaurl = "https://steamcommunity.com/public/captcha.php?gid=" + body.captcha_gid;
self._captchaGid = body.captcha_gid;
callback(error);
} else if(!body.success) {
callback(new Error(body.message || "Unknown error"));
2014-10-01 03:18:38 +08:00
} else {
2014-12-17 13:48:19 +08:00
var sessionID = generateSessionID();
var oAuth = JSON.parse( body.oauth );
2014-12-17 13:48:19 +08:00
self._jar.setCookie(Request.cookie('sessionid=' + sessionID), 'http://steamcommunity.com');
2014-10-01 03:18:38 +08:00
self.steamID = new SteamID(oAuth.steamid);
self.oAuthToken = oAuth.oauth_token;
2014-12-17 13:48:19 +08:00
var cookies = self._jar.getCookieString("https://steamcommunity.com").split(';').map(function(cookie) {
return cookie.trim();
});
2014-10-01 03:18:38 +08:00
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
}
2015-12-27 07:34:11 +08:00
callback(null, sessionID, cookies, steamguard, oAuth.oauth_token);
2014-10-01 03:18:38 +08:00
}
});
});
function deleteMobileCookies() {
var cookie = Request.cookie('mobileClientVersion=');
cookie.expires = new Date(0);
self._jar.setCookie(cookie, "https://steamcommunity.com");
cookie = Request.cookie('mobileClient=');
cookie.expires = new Date(0);
self._jar.setCookie(cookie, "https://steamcommunity.com");
}
2014-12-17 13:48:19 +08:00
};
2015-12-27 07:15:09 +08:00
SteamCommunity.prototype.oAuthLogin = function(steamID, token, callback) {
if(typeof steamID !== 'object') {
steamID = new SteamID(steamID);
}
var self = this;
this.request.post({
"uri": "https://api.steampowered.com/IMobileAuthService/GetWGToken/v1/",
"form": {
"access_token": token
},
"json": true
}, function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
return;
}
if(!body.response || !body.response.token || !body.response.token_secure) {
callback(new Error("Malformed response"));
return;
}
var cookies = [
'steamLogin=' + steamID.getSteamID64() + '||' + body.response.token,
'steamLoginSecure=' + steamID.getSteamID64() + '||' + body.response.token_secure,
'sessionid=' + self.getSessionID()
];
self.setCookies(cookies);
callback(null, self.getSessionID(), cookies);
});
};
2014-12-17 13:48:19 +08:00
SteamCommunity.prototype.setCookies = function(cookies) {
var self = this;
cookies.forEach(function(cookie) {
var cookieName = cookie.match(/(.+)=/)[1];
if(cookieName == 'steamLogin') {
2014-12-17 13:55:43 +08:00
self.steamID = new SteamID(cookie.match(/=(\d+)/)[1]);
2014-12-17 13:48:19 +08:00
}
self._jar.setCookie(Request.cookie(cookie), (cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/) ? "https://" : "http://") + "steamcommunity.com");
});
};
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]);
}
}
var sessionID = generateSessionID();
this._jar.setCookie(Request.cookie('sessionid=' + sessionID), "http://steamcommunity.com");
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) {
2015-06-10 02:45:07 +08:00
this.request.post("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(self._checkHttpError(err, response, callback)) {
return;
2015-05-12 03:48:00 +08:00
}
if(!body || typeof body.success !== 'boolean') {
return callback("Invalid response");
}
if(!body.success) {
return callback("Incorrect PIN");
}
callback();
}.bind(this));
};
2015-05-26 01:37:01 +08:00
SteamCommunity.prototype.getNotifications = function(callback) {
2015-11-11 12:46:17 +08:00
var self = this;
2015-06-10 02:45:07 +08:00
this.request.get("https://steamcommunity.com/actions/RefreshNotificationArea", function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
return;
2015-05-26 01:37:01 +08:00
}
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);
}
}
callback(null, notifications);
});
};
2015-05-26 01:20:47 +08:00
SteamCommunity.prototype.resetItemNotifications = function(callback) {
var self = this;
2015-06-10 02:45:07 +08:00
this.request.get("https://steamcommunity.com/my/inventory", function(err, response, body) {
2015-05-26 01:20:47 +08:00
if(!callback) {
return;
}
if(self._checkHttpError(err, response, callback)) {
return;
2015-05-26 01:20:47 +08:00
}
callback(null);
2015-05-26 01:20:47 +08:00
});
};
2015-07-18 09:10:08 +08:00
SteamCommunity.prototype.loggedIn = function(callback) {
this.request("https://steamcommunity.com/my", {"followRedirect": 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);
2015-07-18 09:10:08 +08:00
});
};
SteamCommunity.prototype._checkCommunityError = function(html, callback) {
if(html.match(/<h1>Sorry!<\/h1>/)) {
var match = html.match(/<h3>(.+)<\/h3>/);
callback(new Error(match ? match[1] : "Unknown error occurred"));
return true;
}
return false;
};
SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
var self = this;
2015-06-10 02:45:07 +08:00
this.request("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;
}
2015-06-10 02:45:07 +08:00
(form ? self.request.post : self.request)("https://steamcommunity.com" + match[1] + "/" + endpoint, form ? {"form": form} : {}, callback);
});
};
SteamCommunity.prototype._checkHttpError = function(err, response, callback) {
if(err) {
callback(err);
return true;
}
if(response.statusCode >= 300 && response.statusCode <= 399 && response.headers.location.indexOf('/login') != -1) {
callback(new Error("Not Logged In"));
return true;
}
if(response.statusCode >= 400) {
var error = new Error("HTTP error " + response.statusCode);
error.code = response.statusCode;
callback(error);
return true;
}
return false;
};
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
*/