mirror of
https://github.com/DoctorMcKay/node-steamcommunity.git
synced 2026-08-19 13:13:28 +08:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa8d897590 | ||
|
|
f7fe587312 | ||
|
|
08d14f19e6 | ||
|
|
cfed757eab | ||
|
|
63defbb643 | ||
|
|
6e20fbe299 | ||
|
|
59090242f9 | ||
|
|
00e82f7e79 | ||
|
|
7b901b147b | ||
|
|
0f7fc98ab5 | ||
|
|
2a12174a7c | ||
|
|
caa76ee84b | ||
|
|
bc8893fedd | ||
|
|
d3e90f6fd3 | ||
|
|
a61b50b7a2 | ||
|
|
39fe4a4106 | ||
|
|
0d991c2655 | ||
|
|
0a307de9b9 | ||
|
|
c0b8dcf3a2 | ||
|
|
dcf00b4ea1 | ||
|
|
ee0cc34512 | ||
|
|
c52533ea06 | ||
|
|
67774f86f7 | ||
|
|
2f03e09f50 | ||
|
|
2868b1f45f | ||
|
|
ac222ef8c3 | ||
|
|
4b8799ddd4 | ||
|
|
f02f85a2aa | ||
|
|
4948cbeac0 | ||
|
|
1c1ff82543 | ||
|
|
63015259af | ||
|
|
ad67805ad9 | ||
|
|
3347d87f29 | ||
|
|
c97351543a | ||
|
|
1067d4572e | ||
|
|
49a7165052 | ||
|
|
0fb2798424 | ||
|
|
e16136866c | ||
|
|
cae85433f9 | ||
|
|
ba6e29c935 | ||
|
|
de95e25867 | ||
|
|
e1820efeca | ||
|
|
7cee24199f | ||
|
|
90eb4d38eb | ||
|
|
b58745c8b7 | ||
|
|
53154043a3 | ||
|
|
6c77c5231c | ||
|
|
7c564c1453 | ||
|
|
b854d6d4d4 | ||
|
|
67867978f8 | ||
|
|
e1af63e171 | ||
|
|
e4da22c464 | ||
|
|
0f91adf819 | ||
|
|
d51c171a48 | ||
|
|
50276c3a0c | ||
|
|
b5b50cec50 | ||
|
|
cf4b474c70 | ||
|
|
bf2b4601ee | ||
|
|
f69519f547 | ||
|
|
29e18806cc | ||
|
|
c855b505c5 | ||
|
|
6cc96b51fa | ||
|
|
edf2891800 | ||
|
|
0fc1120fd3 | ||
|
|
a0aee350cc | ||
|
|
c25cb31e34 | ||
|
|
2a1d244114 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
node_modules/*
|
||||
node_modules/
|
||||
test.js
|
||||
dev/
|
||||
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
[](https://github.com/DoctorMcKay/node-steamcommunity/blob/master/LICENSE)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=N36YVAT42CZ4G&item_name=node%2dsteamcommunity¤cy_code=USD)
|
||||
|
||||
This module provides an easy interface for the Steam Community website. This module can be used to simply login to steamcommunity.com for use with other libraries, or to interact with steamcommunity.com.
|
||||
|
||||
It supports Steam Guard and CAPTCHAs.
|
||||
This module provides an easy interface for the Steam Community website. This module can be used to simply login to
|
||||
steamcommunity.com for use with other libraries, or to interact with steamcommunity.com.
|
||||
|
||||
**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/).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = CEconItem;
|
||||
|
||||
function CEconItem(item, description, contextID) {
|
||||
function CEconItem(item, description, contextID, assetProperties) {
|
||||
var thing;
|
||||
for (thing in item) {
|
||||
if (item.hasOwnProperty(thing)) {
|
||||
@@ -28,7 +28,7 @@ function CEconItem(item, description, contextID) {
|
||||
}
|
||||
|
||||
for (thing in description) {
|
||||
if (description.hasOwnProperty(thing)) {
|
||||
if (description.hasOwnProperty(thing) && !this.hasOwnProperty(thing)) {
|
||||
this[thing] = description[thing];
|
||||
}
|
||||
}
|
||||
@@ -68,10 +68,26 @@ function CEconItem(item, description, contextID) {
|
||||
|
||||
// Restore cache_expiration, if we can (for CS:GO items)
|
||||
if (this.appid == 730 && this.contextid == 2 && this.owner_descriptions) {
|
||||
let description = this.owner_descriptions.find(d => d.value && d.value.indexOf('Tradable After ') == 0);
|
||||
let description = this.owner_descriptions.find((d) => d.value && d.value.indexOf("Tradable/Marketable After ") == 0);
|
||||
if (description) {
|
||||
let date = new Date(description.value.substring(15).replace(/[,()]/g, ''));
|
||||
if (date) {
|
||||
let date;
|
||||
const match = description.value.match(/(\d{1,2}) (\w+) @ (\d{1,2}:\d{2})(am|pm)/i);
|
||||
if (match) {
|
||||
// New day-first format, e.g. "Tradable/Marketable After 17 Jul @ 3:00am"
|
||||
const [, day, month, time, meridian] = match;
|
||||
const now = new Date();
|
||||
let year = now.getFullYear();
|
||||
date = new Date(`${day} ${month} ${year} ${time} ${meridian}`);
|
||||
|
||||
if (date.getTime() < now.getTime()) {
|
||||
date = new Date(`${day} ${month} ${year + 1} ${time} ${meridian}`);
|
||||
}
|
||||
} else {
|
||||
// Legacy month-first format, e.g. "Tradable/Marketable After Jul 11, 2025 (2:00:00)"
|
||||
date = new Date(description.value.substring("Tradable/Marketable After ".length).replace(/[,()]/g, ""));
|
||||
}
|
||||
|
||||
if (date && !isNaN(date.getTime())) {
|
||||
this.cache_expiration = date.toISOString();
|
||||
}
|
||||
}
|
||||
@@ -86,6 +102,11 @@ function CEconItem(item, description, contextID) {
|
||||
this.actions = [];
|
||||
}
|
||||
|
||||
// Assign asset_properties if provided
|
||||
if (assetProperties) {
|
||||
this.asset_properties = assetProperties;
|
||||
}
|
||||
|
||||
// One wouldn't think that we need this if statement, but apparently v8 has a weird bug/quirk where deleting a
|
||||
// property results in greatly increased memory usage. Because that makes sense.
|
||||
if (this.currency) {
|
||||
|
||||
@@ -13,7 +13,7 @@ SteamCommunity.prototype.getSteamUser = function(id, callback) {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.httpRequest("http://steamcommunity.com/" + (typeof id === 'string' ? "id/" + id : "profiles/" + id.toString()) + "/?xml=1", function(err, response, body) {
|
||||
this.httpRequest("https://steamcommunity.com/" + (typeof id === 'string' ? "id/" + id : "profiles/" + id.toString()) + "/?xml=1", function(err, response, body) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
@@ -166,6 +166,14 @@ CSteamUser.prototype.inviteToGroup = function(groupID, callback) {
|
||||
this._community.inviteUserToGroup(this.steamID, groupID, callback);
|
||||
};
|
||||
|
||||
CSteamUser.prototype.follow = function(callback) {
|
||||
this._community.followUser(this.steamID, callback);
|
||||
};
|
||||
|
||||
CSteamUser.prototype.unfollow = function(callback) {
|
||||
this._community.unfollowUser(this.steamID, callback);
|
||||
};
|
||||
|
||||
CSteamUser.prototype.getAliases = function(callback) {
|
||||
this._community.getUserAliases(this.steamID, callback);
|
||||
};
|
||||
|
||||
@@ -261,7 +261,10 @@ function request(community, url, key, time, tag, params, json, callback) {
|
||||
var req = {
|
||||
method: url == 'multiajaxop' ? 'POST' : 'GET',
|
||||
uri: 'https://steamcommunity.com/mobileconf/' + url,
|
||||
json: !!json
|
||||
json: !!json,
|
||||
headers: {
|
||||
'user-agent': 'okhttp/4.9.2'
|
||||
}
|
||||
};
|
||||
|
||||
if (req.method == 'GET') {
|
||||
@@ -392,6 +395,22 @@ SteamCommunity.prototype.checkConfirmations = function() {
|
||||
}
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.acknowledgeTradeProtection = function(callback) {
|
||||
this.httpRequestPost({
|
||||
uri: 'https://steamcommunity.com//trade/new/acknowledge',
|
||||
form: {
|
||||
sessionid: this.getSessionID(),
|
||||
message: 1
|
||||
}
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
|
||||
callback && callback(null);
|
||||
}, 'steamcommunity');
|
||||
}
|
||||
|
||||
SteamCommunity.prototype._confirmationCheckerGetKey = function(tag, callback) {
|
||||
if(this._identitySecret) {
|
||||
if(tag == 'details') {
|
||||
|
||||
@@ -11,17 +11,17 @@ SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link
|
||||
if (!link) {
|
||||
if (typeof gid !== 'string') {
|
||||
// It's a SteamID object
|
||||
link = "http://steamcommunity.com/gid/" + gid.toString() + "/memberslistxml/?xml=1";
|
||||
link = "https://steamcommunity.com/gid/" + gid.toString() + "/memberslistxml/?xml=1";
|
||||
} else {
|
||||
try {
|
||||
var sid = new SteamID(gid);
|
||||
if (sid.type == SteamID.Type.CLAN && sid.isValid()) {
|
||||
link = "http://steamcommunity.com/gid/" + sid.getSteamID64() + "/memberslistxml/?xml=1";
|
||||
link = "https://steamcommunity.com/gid/" + sid.getSteamID64() + "/memberslistxml/?xml=1";
|
||||
} else {
|
||||
throw new Error("Doesn't particularly matter what this message is");
|
||||
}
|
||||
} catch (e) {
|
||||
link = "http://steamcommunity.com/groups/" + gid + "/memberslistxml/?xml=1";
|
||||
link = "https://steamcommunity.com/groups/" + gid + "/memberslistxml/?xml=1";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,11 +175,11 @@ SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content
|
||||
"languages[0][headline]": headline,
|
||||
"languages[0][body]": content
|
||||
};
|
||||
|
||||
|
||||
if(hidden) {
|
||||
form.is_hidden = "is_hidden"
|
||||
}
|
||||
|
||||
|
||||
this.httpRequestPost({
|
||||
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements",
|
||||
form
|
||||
@@ -669,7 +669,7 @@ SteamCommunity.prototype.respondToGroupJoinRequests = function(gid, steamIDs, ap
|
||||
if (typeof gid === 'string') {
|
||||
gid = new SteamID(gid);
|
||||
}
|
||||
|
||||
|
||||
var rgAccounts = (!Array.isArray(steamIDs) ? [steamIDs] : steamIDs).map(sid => sid.toString());
|
||||
|
||||
this.httpRequestPost({
|
||||
@@ -730,3 +730,69 @@ SteamCommunity.prototype.respondToAllGroupJoinRequests = function(gid, approve,
|
||||
}
|
||||
}, "steamcommunity");
|
||||
};
|
||||
|
||||
/**
|
||||
* Follows a curator page
|
||||
* @param {string|number} curatorId - ID of the curator (not a SteamID)
|
||||
* @param {function} callback - Takes only an Error object/null as the first argument
|
||||
*/
|
||||
SteamCommunity.prototype.followCurator = function(curatorId, callback) {
|
||||
this.httpRequestPost({
|
||||
"uri": "https://store.steampowered.com/curators/ajaxfollow",
|
||||
"form": {
|
||||
"clanid": curatorId,
|
||||
"sessionid": this.getSessionID(),
|
||||
"follow": 1
|
||||
},
|
||||
"json": true
|
||||
}, (err, res, body) => {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.success && body.success.success != SteamCommunity.EResult.OK) {
|
||||
callback(Helpers.eresultError(body.success.success));
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}, "steamcommunity");
|
||||
};
|
||||
|
||||
/**
|
||||
* Unfollows a curator page
|
||||
* @param {string|number} curatorId - ID of the curator (not a SteamID)
|
||||
* @param {function} callback - Takes only an Error object/null as the first argument
|
||||
*/
|
||||
SteamCommunity.prototype.unfollowCurator = function(curatorId, callback) {
|
||||
this.httpRequestPost({
|
||||
"uri": "https://store.steampowered.com/curators/ajaxfollow",
|
||||
"form": {
|
||||
"clanid": curatorId,
|
||||
"sessionid": this.getSessionID(),
|
||||
"follow": 0
|
||||
},
|
||||
"json": true
|
||||
}, (err, res, body) => {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.success && body.success.success != SteamCommunity.EResult.OK) {
|
||||
callback(Helpers.eresultError(body.success.success));
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}, "steamcommunity");
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const EResult = require('../resources/EResult.js');
|
||||
const request = require('request');
|
||||
const SteamID = require('steamid');
|
||||
const xml2js = require('xml2js');
|
||||
|
||||
const EResult = require('../resources/EResult.js');
|
||||
|
||||
exports.isSteamID = function(input) {
|
||||
var keys = Object.keys(input);
|
||||
if (keys.length != 4) {
|
||||
@@ -99,10 +101,28 @@ exports.resolveVanityURL = function(url, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
let steamID64 = parsed.profile.steamID64;
|
||||
let vanityURL = parsed.profile.customURL;
|
||||
let steamID64 = parsed.profile.steamID64[0];
|
||||
let vanityURL = parsed.profile.customURL[0];
|
||||
|
||||
callback(null, {"vanityURL": vanityURL, "steamID": steamID64});
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts `input` into a SteamID object, if it's a parseable string.
|
||||
* @param {SteamID|string} input
|
||||
* @return {SteamID}
|
||||
*/
|
||||
exports.steamID = function(input) {
|
||||
if (exports.isSteamID(input)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (typeof input != 'string') {
|
||||
throw new Error(`Input SteamID value "${input}" is not a string`);
|
||||
}
|
||||
|
||||
// This will throw if the input is not a well-formed SteamID
|
||||
return new SteamID(input);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
var URL = require('url');
|
||||
|
||||
var SteamCommunity = require('../index.js');
|
||||
|
||||
SteamCommunity.prototype.httpRequest = function(uri, options, callback, source) {
|
||||
@@ -19,6 +21,16 @@ SteamCommunity.prototype.httpRequest = function(uri, options, callback, source)
|
||||
delete this._httpRequestConvenienceMethod;
|
||||
}
|
||||
|
||||
// Add origin header if necessary
|
||||
// https://github.com/DoctorMcKay/node-steamcommunity/issues/351
|
||||
if ((options.method || 'GET').toUpperCase() != 'GET') {
|
||||
options.headers = options.headers || {};
|
||||
if (!options.headers.origin) {
|
||||
var parsedUrl = URL.parse(options.url);
|
||||
options.headers.origin = parsedUrl.protocol + '//' + parsedUrl.host;
|
||||
}
|
||||
}
|
||||
|
||||
var requestID = ++this._httpRequestID;
|
||||
source = source || "";
|
||||
|
||||
|
||||
114
components/login.js
Normal file
114
components/login.js
Normal file
@@ -0,0 +1,114 @@
|
||||
const {chrome} = require('@doctormckay/user-agents');
|
||||
|
||||
const SteamCommunity = require('../index.js');
|
||||
|
||||
/**
|
||||
* @typedef LogOnDetails
|
||||
* @property {string} accountName
|
||||
* @property {string} password
|
||||
* @property {string} [steamguard]
|
||||
* @property {string} [authCode]
|
||||
* @property {string} [twoFactorCode]
|
||||
* @property {boolean} disableMobile
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef LogOnResponse
|
||||
* @property {string} sessionID
|
||||
* @property {string[]} cookies
|
||||
* @property {string} steamguard
|
||||
* @property {string} [mobileAccessToken]
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {LogOnDetails} logOnDetails
|
||||
* @returns {Promise<LogOnResponse>}
|
||||
* @private
|
||||
*/
|
||||
SteamCommunity.prototype._modernLogin = function(logOnDetails) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!isNodeVersionNewEnough()) {
|
||||
return reject(new Error(`Node.js version is too old! Need >=12.22.0 or later, got ${process.versions.node}.`));
|
||||
}
|
||||
|
||||
if (this._options.request) {
|
||||
return reject(new Error('SteamCommunity.login() is incompatible with node-steamcommunity v3\'s usage of \'request\'. If you need to specify a custom \'request\' instance (e.g. when using a proxy), use https://www.npmjs.com/package/steam-session directly to log onto Steam.'));
|
||||
}
|
||||
|
||||
// Import this here so we don't cause problems on old Node versions if this code path isn't taken.
|
||||
const {LoginSession, EAuthTokenPlatformType, EAuthSessionGuardType} = require('steam-session');
|
||||
|
||||
let session = new LoginSession(
|
||||
logOnDetails.disableMobile
|
||||
? EAuthTokenPlatformType.WebBrowser
|
||||
: EAuthTokenPlatformType.MobileApp,
|
||||
{
|
||||
localAddress: this._options.localAddress,
|
||||
userAgent: this._options.userAgent || chrome()
|
||||
}
|
||||
);
|
||||
|
||||
session.on('authenticated', async () => {
|
||||
try {
|
||||
let webCookies = await session.getWebCookies();
|
||||
let sessionIdCookie = webCookies.find(c => c.startsWith('sessionid='));
|
||||
resolve({
|
||||
sessionID: sessionIdCookie.split('=')[1].split(';')[0].trim(),
|
||||
cookies: webCookies,
|
||||
steamguard: session.steamGuardMachineToken,
|
||||
mobileAccessToken: logOnDetails.disableMobile ? null : session.accessToken
|
||||
});
|
||||
} catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
});
|
||||
|
||||
session.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
session.on('timeout', () => {
|
||||
reject(new Error('Login timed out'));
|
||||
});
|
||||
|
||||
try {
|
||||
let startResult = await session.startWithCredentials({
|
||||
accountName: logOnDetails.accountName,
|
||||
password: logOnDetails.password,
|
||||
steamGuardMachineToken: logOnDetails.steamguard,
|
||||
steamGuardCode: logOnDetails.authCode || logOnDetails.twoFactorCode
|
||||
});
|
||||
|
||||
if (startResult.actionRequired) {
|
||||
// Cannot continue with login, need something from the user
|
||||
session.cancelLoginAttempt();
|
||||
|
||||
let emailMfaAction = startResult.validActions.find(action => action.type == EAuthSessionGuardType.EmailCode);
|
||||
if (emailMfaAction) {
|
||||
let err = new Error('SteamGuard');
|
||||
err.emaildomain = emailMfaAction.detail;
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return reject(new Error('SteamGuardMobile'));
|
||||
}
|
||||
} catch (ex) {
|
||||
return reject(ex);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function isNodeVersionNewEnough() {
|
||||
let [major, minor] = process.versions.node.split('.');
|
||||
|
||||
if (major < 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (major == 12 && minor < 22) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -106,6 +106,15 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
|
||||
values.customURL = settings[i];
|
||||
break;
|
||||
|
||||
case 'primaryGroup':
|
||||
if(typeof settings[i] === 'object' && settings[i].getSteamID64) {
|
||||
values.primary_group_steamid = settings[i].getSteamID64();
|
||||
} else {
|
||||
values.primary_group_steamid = new SteamID(settings[i]).getSteamID64();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// These don't work right now
|
||||
/*
|
||||
case 'background':
|
||||
@@ -117,15 +126,6 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
|
||||
// Currently, game badges aren't supported
|
||||
values.favorite_badge_badgeid = settings[i];
|
||||
break;
|
||||
|
||||
case 'primaryGroup':
|
||||
if(typeof settings[i] === 'object' && settings[i].getSteamID64) {
|
||||
values.primary_group_steamid = settings[i].getSteamID64();
|
||||
} else {
|
||||
values.primary_group_steamid = new SteamID(settings[i]).getSteamID64();
|
||||
}
|
||||
|
||||
break;
|
||||
*/
|
||||
// TODO: profile showcases
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ SteamCommunity.prototype.enableTwoFactor = function(callback) {
|
||||
// TODO: Send this as protobuf to more closely mimic official app behavior
|
||||
form: {
|
||||
steamid: this.steamID.getSteamID64(),
|
||||
authenticator_time: Math.floor(Date.now() / 1000),
|
||||
authenticator_type: ETwoFactorTokenType.ValveMobileApp,
|
||||
device_identifier: SteamTotp.getDeviceID(this.steamID),
|
||||
sms_phone_id: '1'
|
||||
sms_phone_id: '1',
|
||||
version: 2
|
||||
},
|
||||
json: true
|
||||
}, (err, response, body) => {
|
||||
|
||||
@@ -296,6 +296,66 @@ SteamCommunity.prototype.inviteUserToGroup = function(userID, groupID, callback)
|
||||
}, "steamcommunity");
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.followUser = function(userID, callback) {
|
||||
if(typeof userID === 'string') {
|
||||
userID = new SteamID(userID);
|
||||
}
|
||||
|
||||
this.httpRequestPost({
|
||||
"uri": `https://steamcommunity.com/profiles/${userID.toString()}/followuser/`,
|
||||
"form": {
|
||||
"sessionid": this.getSessionID(),
|
||||
},
|
||||
"json": true
|
||||
}, function(err, response, body) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.success && body.success != SteamCommunity.EResult.OK) {
|
||||
callback(Helpers.eresultError(body.success));
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}, "steamcommunity");
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.unfollowUser = function(userID, callback) {
|
||||
if(typeof userID === 'string') {
|
||||
userID = new SteamID(userID);
|
||||
}
|
||||
|
||||
this.httpRequestPost({
|
||||
"uri": `https://steamcommunity.com/profiles/${userID.toString()}/unfollowuser/`,
|
||||
"form": {
|
||||
"sessionid": this.getSessionID(),
|
||||
},
|
||||
"json": true
|
||||
}, function(err, response, body) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.success && body.success != SteamCommunity.EResult.OK) {
|
||||
callback(Helpers.eresultError(body.success));
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}, "steamcommunity");
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.getUserAliases = function(userID, callback) {
|
||||
if (typeof userID === 'string') {
|
||||
userID = new SteamID(userID);
|
||||
@@ -385,18 +445,7 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
|
||||
|
||||
var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
|
||||
if (!match) {
|
||||
var errorMessage = "Malformed response";
|
||||
|
||||
if(body.match(/0 items in their inventory\./)){
|
||||
callback(null, {});
|
||||
return;
|
||||
}else if(body.match(/inventory is currently private\./)){
|
||||
errorMessage = "Private inventory";
|
||||
}else if(body.match(/profile\_private\_info/)){
|
||||
errorMessage = "Private profile";
|
||||
}
|
||||
|
||||
callback(new Error(errorMessage));
|
||||
callback(new Error('Malformed response'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -408,6 +457,21 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(data).length == 0) {
|
||||
if (body.match(/inventory is currently private\./)) {
|
||||
callback(new Error('Private inventory'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.match(/profile_private_info/)) {
|
||||
callback(new Error('Private profile'));
|
||||
return;
|
||||
}
|
||||
|
||||
// If they truly have no items in their inventory, Steam will send g_rgAppContextData as [] instead of {}.
|
||||
data = {};
|
||||
}
|
||||
|
||||
callback(null, data);
|
||||
}, "steamcommunity");
|
||||
};
|
||||
@@ -472,7 +536,7 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
|
||||
continue;
|
||||
}
|
||||
|
||||
currency.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
|
||||
currency.push(new CEconItem(body.rgCurrency[i], body.rgDescriptions, contextID));
|
||||
}
|
||||
|
||||
if (body.more) {
|
||||
@@ -526,7 +590,7 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
|
||||
},
|
||||
"qs": {
|
||||
"l": language, // Default language
|
||||
"count": 2000, // Max items per 'page'
|
||||
"count": 1000, // Max items per 'page'
|
||||
"start_assetid": start
|
||||
},
|
||||
"json": true
|
||||
@@ -565,6 +629,13 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
|
||||
return;
|
||||
}
|
||||
|
||||
if (appID == 730 && body && body.success && !body.assets) {
|
||||
// CS inventory has no visible items. We need a special case for this because Valve is incapable of
|
||||
// doing anything not dumb.
|
||||
callback(null, [], [], body.total_inventory_count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!body || !body.success || !body.assets || !body.descriptions) {
|
||||
if (body) {
|
||||
// Dunno if the error/Error property even exists on this new endpoint
|
||||
@@ -576,12 +647,21 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
|
||||
return;
|
||||
}
|
||||
|
||||
// Build asset_properties lookup by assetid
|
||||
var assetPropertiesLookup = {};
|
||||
if (body.asset_properties) {
|
||||
for (var j = 0; j < body.asset_properties.length; j++) {
|
||||
assetPropertiesLookup[body.asset_properties[j].assetid] = body.asset_properties[j].asset_properties;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < body.assets.length; i++) {
|
||||
var description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid);
|
||||
|
||||
if (!tradableOnly || (description && description.tradable)) {
|
||||
body.assets[i].pos = pos++;
|
||||
(body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID));
|
||||
var assetProperties = assetPropertiesLookup[body.assets[i].assetid] || null;
|
||||
(body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID, assetProperties));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +1,152 @@
|
||||
var SteamCommunity = require('../index.js');
|
||||
const SteamCommunity = require('../index.js');
|
||||
|
||||
const Helpers = require('./helpers.js');
|
||||
|
||||
SteamCommunity.prototype.getWebApiKey = function(domain, callback) {
|
||||
var self = this;
|
||||
/**
|
||||
* Retrieves your account's Steam Web API key, if you already have one. If you don't yet have one, this will fail.
|
||||
* To create a Web API key, use `createWebApiKey()`.
|
||||
*
|
||||
* @param {null|function} unused - No longer used, kept for backward compatibility. You can omit this parameter and pass
|
||||
* your callback directly as the first parameter if you want.
|
||||
* @param {function} callback
|
||||
*/
|
||||
SteamCommunity.prototype.getWebApiKey = function(unused, callback) {
|
||||
if (typeof unused == 'function') {
|
||||
callback = unused;
|
||||
}
|
||||
|
||||
this.httpRequest({
|
||||
"uri": "https://steamcommunity.com/dev/apikey?l=english",
|
||||
"followRedirect": false
|
||||
}, function(err, response, body) {
|
||||
uri: 'https://steamcommunity.com/dev/apikey?l=english',
|
||||
followRedirect: false
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if(body.match(/<h2>Access Denied<\/h2>/)) {
|
||||
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.'));
|
||||
}
|
||||
|
||||
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."));
|
||||
if (body.match(/Your account requires (<a [^>]+>)?Steam Guard Mobile Authenticator/)) {
|
||||
return callback(new Error('Steam Guard Mobile Authenticator required to create a Steam Web API key'));
|
||||
}
|
||||
|
||||
var match = body.match(/<p>Key: ([0-9A-F]+)<\/p>/);
|
||||
if(match) {
|
||||
if (body.match(/<h2>Access Denied<\/h2>/)) {
|
||||
return callback(new Error('Access Denied'));
|
||||
}
|
||||
|
||||
let match = body.match(/<p>Key: ([0-9A-F]+)<\/p>/);
|
||||
if (match) {
|
||||
// We already have an API key registered
|
||||
callback(null, match[1]);
|
||||
} else {
|
||||
// We need to register a new API key
|
||||
self.httpRequestPost('https://steamcommunity.com/dev/registerkey?l=english', {
|
||||
"form": {
|
||||
"domain": domain,
|
||||
"agreeToTerms": "agreed",
|
||||
"sessionid": self.getSessionID(),
|
||||
"Submit": "Register"
|
||||
}
|
||||
}, function(err, response, body) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
callback(new Error('No API key created for this account'));
|
||||
}
|
||||
}, "steamcommunity");
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef CreateApiKeyOptions
|
||||
* @property {string} domain - The domain to associate with your API key
|
||||
* @property {string} [requestID] - If finalizing an existing create request, include the request ID
|
||||
* @property {string|Buffer} [identitySecret] - If you pass your identity_secret here, then steamcommunity will
|
||||
* internally handle accepting any confirmations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef CreateApiKeyResponse
|
||||
* @property {boolean} confirmationRequired
|
||||
* @property {string} [apiKey] - If creating your API key succeeded, this is the new key
|
||||
* @property {CreateApiKeyOptions} [finalizeOptions] - If confirmation is required to create a key, then accept the
|
||||
* confirmation, then call createWebApiKey again and pass this whole object for the `options` parameter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback createWebApiKeyCallback
|
||||
* @param {Error|null} err
|
||||
* @param {CreateApiKeyResponse} [result]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Starts the process to create a Steam Web API key. When the callback is fired, you will need to approve a mobile
|
||||
* confirmation in your app or using getConfirmations().
|
||||
*
|
||||
* @param {CreateApiKeyOptions} options
|
||||
* @param {createWebApiKeyCallback} callback
|
||||
*/
|
||||
SteamCommunity.prototype.createWebApiKey = function(options, callback) {
|
||||
if (!options.domain) {
|
||||
callback(new Error('Passing a domain is required to register an API key'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.httpRequestPost({
|
||||
uri: 'https://steamcommunity.com/dev/requestkey',
|
||||
form: {
|
||||
domain: options.domain,
|
||||
request_id: options.requestID || '0',
|
||||
sessionid: this.getSessionID(),
|
||||
agreeToTerms: 'true'
|
||||
},
|
||||
json: true
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// body.requires_confirmation is 1/0, but the Steam website doesn't check this value and instead only checks the
|
||||
// value of `success`. So let's just do that.
|
||||
|
||||
// This is a mess. I'm glad we have promises and await now.
|
||||
|
||||
switch (body.success) {
|
||||
case SteamCommunity.EResult.OK:
|
||||
if (body.api_key) {
|
||||
callback(null, {confirmationRequired: false, apiKey: body.api_key});
|
||||
return;
|
||||
}
|
||||
|
||||
self.getWebApiKey(domain, callback);
|
||||
}, "steamcommunity");
|
||||
// It's not been observed that we get result OK without api_key included, but the Steam website doesn't
|
||||
// use this value so let's be safe just in case it disappears in the future.
|
||||
this.getWebApiKey((err, key) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, {confirmationRequired: false, apiKey: key});
|
||||
});
|
||||
return;
|
||||
|
||||
case SteamCommunity.EResult.Pending:
|
||||
let finalizeOptions = {
|
||||
domain: options.domain,
|
||||
requestID: body.request_id || options.requestID
|
||||
}
|
||||
|
||||
if (options.identitySecret) {
|
||||
this.acceptConfirmationForObject(options.identitySecret, finalizeOptions.requestID, (err) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
this.createWebApiKey(finalizeOptions, callback);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
confirmationRequired: true,
|
||||
finalizeOptions: finalizeOptions
|
||||
});
|
||||
return;
|
||||
|
||||
default:
|
||||
callback(Helpers.eresultError(body.success));
|
||||
}
|
||||
}, "steamcommunity");
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
1
examples/.gitignore
vendored
Normal file
1
examples/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
twofactor_*.json
|
||||
@@ -56,9 +56,9 @@ async function main() {
|
||||
let codeAction = startResult.validActions.find(action => codeActionTypes.includes(action.type));
|
||||
if (codeAction) {
|
||||
if (codeAction.type == SteamSession.EAuthSessionGuardType.EmailCode) {
|
||||
// We wouldn't expect this to happen since mobile confirmations are only possible with 2FA enabled, but just in case...
|
||||
console.log(`A code has been sent to your email address at ${codeAction.detail}.`);
|
||||
} else {
|
||||
// We wouldn't expect this to happen since we're trying to enable 2FA, but just in case...
|
||||
console.log('You need to provide a Steam Guard Mobile Authenticator code.');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// If you aren't running this script inside of the repository, replace the following line with:
|
||||
// const SteamCommunity = require('steamcommunity');
|
||||
const SteamCommunity = require('../index.js');
|
||||
const SteamSession = require('steam-session');
|
||||
const SteamTotp = require('steam-totp');
|
||||
const ReadLine = require('readline');
|
||||
|
||||
let g_AbortPromptFunc = null;
|
||||
@@ -13,69 +13,32 @@ async function main() {
|
||||
let accountName = await promptAsync('Username: ');
|
||||
let password = await promptAsync('Password (hidden): ', true);
|
||||
|
||||
// Create a LoginSession for us to use to attempt to log into steam
|
||||
let session = new SteamSession.LoginSession(SteamSession.EAuthTokenPlatformType.MobileApp);
|
||||
attemptLogin(accountName, password);
|
||||
}
|
||||
|
||||
// Go ahead and attach our event handlers before we do anything else.
|
||||
session.on('authenticated', async () => {
|
||||
abortPrompt();
|
||||
function attemptLogin(accountName, password, twoFactorCode) {
|
||||
community.login({
|
||||
accountName,
|
||||
password,
|
||||
twoFactorCode,
|
||||
disableMobile: false
|
||||
}, async (err) => {
|
||||
if (err && err.message == 'SteamGuardMobile') {
|
||||
let code = await promptAsync('Steam Guard App Code OR Shared Secret: ');
|
||||
if (code.length > 5) {
|
||||
// If we were provided a shared secret, turn it into a code.
|
||||
code = SteamTotp.getAuthCode(code);
|
||||
}
|
||||
attemptLogin(accountName, password, code);
|
||||
return;
|
||||
}
|
||||
|
||||
let accessToken = session.accessToken;
|
||||
let cookies = await session.getWebCookies();
|
||||
|
||||
community.setCookies(cookies);
|
||||
community.setMobileAppAccessToken(accessToken);
|
||||
|
||||
// Enabling or disabling 2FA is presently the only action in node-steamcommunity which requires an access token.
|
||||
// In all other cases, using `community.setCookies(cookies)` is all you need to do in order to be logged in,
|
||||
// although there's never any harm in setting a mobile app access token.
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
doRevoke();
|
||||
});
|
||||
|
||||
session.on('timeout', () => {
|
||||
abortPrompt();
|
||||
console.log('This login attempt has timed out.');
|
||||
});
|
||||
|
||||
session.on('error', (err) => {
|
||||
abortPrompt();
|
||||
|
||||
// This should ordinarily not happen. This only happens in case there's some kind of unexpected error while
|
||||
// polling, e.g. the network connection goes down or Steam chokes on something.
|
||||
|
||||
console.log(`ERROR: This login attempt has failed! ${err.message}`);
|
||||
});
|
||||
|
||||
// Start our login attempt
|
||||
let startResult = await session.startWithCredentials({accountName, password});
|
||||
if (startResult.actionRequired) {
|
||||
// Some Steam Guard action is required. We only care about email and device codes; in theory an
|
||||
// EmailConfirmation and/or DeviceConfirmation action could be possible, but we're just going to ignore those.
|
||||
// If the user does receive a confirmation and accepts it, LoginSession will detect and handle that automatically.
|
||||
// The only consequence of ignoring it here is that we don't print a message to the user indicating that they
|
||||
// could accept an email or device confirmation.
|
||||
|
||||
let codeActionTypes = [SteamSession.EAuthSessionGuardType.EmailCode, SteamSession.EAuthSessionGuardType.DeviceCode];
|
||||
let codeAction = startResult.validActions.find(action => codeActionTypes.includes(action.type));
|
||||
if (codeAction) {
|
||||
if (codeAction.type == SteamSession.EAuthSessionGuardType.EmailCode) {
|
||||
// We wouldn't expect this to happen since we're trying to disable 2FA, but just in case...
|
||||
console.log(`A code has been sent to your email address at ${codeAction.detail}.`);
|
||||
} else {
|
||||
console.log('You need to provide a Steam Guard Mobile Authenticator code.');
|
||||
}
|
||||
|
||||
let code = await promptAsync('Code: ');
|
||||
if (code) {
|
||||
await session.submitSteamGuardCode(code);
|
||||
}
|
||||
|
||||
// If we fall through here without submitting a Steam Guard code, that means one of two things:
|
||||
// 1. The user pressed enter without providing a code, in which case the script will simply exit
|
||||
// 2. The user approved a device/email confirmation, in which case 'authenticated' was emitted and the prompt was canceled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function doRevoke() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// If you aren't running this script inside of the repository, replace the following line with:
|
||||
// const SteamCommunity = require('steamcommunity');
|
||||
const SteamCommunity = require('../index.js');
|
||||
const SteamSession = require('steam-session');
|
||||
const ReadLine = require('readline');
|
||||
const FS = require('fs');
|
||||
|
||||
@@ -16,69 +15,28 @@ async function main() {
|
||||
let accountName = await promptAsync('Username: ');
|
||||
let password = await promptAsync('Password (hidden): ', true);
|
||||
|
||||
// Create a LoginSession for us to use to attempt to log into steam
|
||||
let session = new SteamSession.LoginSession(SteamSession.EAuthTokenPlatformType.MobileApp);
|
||||
attemptLogin(accountName, password);
|
||||
}
|
||||
|
||||
// Go ahead and attach our event handlers before we do anything else.
|
||||
session.on('authenticated', async () => {
|
||||
abortPrompt();
|
||||
function attemptLogin(accountName, password, authCode) {
|
||||
community.login({
|
||||
accountName,
|
||||
password,
|
||||
authCode,
|
||||
disableMobile: false
|
||||
}, async (err) => {
|
||||
if (err && err.message == 'SteamGuard') {
|
||||
let code = await promptAsync('Steam Guard Email Code: ');
|
||||
attemptLogin(accountName, password, code);
|
||||
return;
|
||||
}
|
||||
|
||||
let accessToken = session.accessToken;
|
||||
let cookies = await session.getWebCookies();
|
||||
|
||||
community.setCookies(cookies);
|
||||
community.setMobileAppAccessToken(accessToken);
|
||||
|
||||
// Enabling or disabling 2FA is presently the only action in node-steamcommunity which requires an access token.
|
||||
// In all other cases, using `community.setCookies(cookies)` is all you need to do in order to be logged in,
|
||||
// although there's never any harm in setting a mobile app access token.
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
doSetup();
|
||||
});
|
||||
|
||||
session.on('timeout', () => {
|
||||
abortPrompt();
|
||||
console.log('This login attempt has timed out.');
|
||||
});
|
||||
|
||||
session.on('error', (err) => {
|
||||
abortPrompt();
|
||||
|
||||
// This should ordinarily not happen. This only happens in case there's some kind of unexpected error while
|
||||
// polling, e.g. the network connection goes down or Steam chokes on something.
|
||||
|
||||
console.log(`ERROR: This login attempt has failed! ${err.message}`);
|
||||
});
|
||||
|
||||
// Start our login attempt
|
||||
let startResult = await session.startWithCredentials({accountName, password});
|
||||
if (startResult.actionRequired) {
|
||||
// Some Steam Guard action is required. We only care about email and device codes; in theory an
|
||||
// EmailConfirmation and/or DeviceConfirmation action could be possible, but we're just going to ignore those.
|
||||
// If the user does receive a confirmation and accepts it, LoginSession will detect and handle that automatically.
|
||||
// The only consequence of ignoring it here is that we don't print a message to the user indicating that they
|
||||
// could accept an email or device confirmation.
|
||||
|
||||
let codeActionTypes = [SteamSession.EAuthSessionGuardType.EmailCode, SteamSession.EAuthSessionGuardType.DeviceCode];
|
||||
let codeAction = startResult.validActions.find(action => codeActionTypes.includes(action.type));
|
||||
if (codeAction) {
|
||||
if (codeAction.type == SteamSession.EAuthSessionGuardType.EmailCode) {
|
||||
console.log(`A code has been sent to your email address at ${codeAction.detail}.`);
|
||||
} else {
|
||||
// We wouldn't expect this to happen since we're trying to enable 2FA, but just in case...
|
||||
console.log('You need to provide a Steam Guard Mobile Authenticator code.');
|
||||
}
|
||||
|
||||
let code = await promptAsync('Code: ');
|
||||
if (code) {
|
||||
await session.submitSteamGuardCode(code);
|
||||
}
|
||||
|
||||
// If we fall through here without submitting a Steam Guard code, that means one of two things:
|
||||
// 1. The user pressed enter without providing a code, in which case the script will simply exit
|
||||
// 2. The user approved a device/email confirmation, in which case 'authenticated' was emitted and the prompt was canceled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doSetup() {
|
||||
@@ -118,10 +76,13 @@ function doSetup() {
|
||||
|
||||
async function promptActivationCode(response) {
|
||||
if (response.phone_number_hint) {
|
||||
console.log(`A code has been sent to your phone ending in ${response.phone_number_hint}.`);
|
||||
console.log(`An activation code has been sent to your phone ending in ${response.phone_number_hint}.`);
|
||||
} else if (response.confirm_type == 3) {
|
||||
// Exact meaning of confirm_type is unknown, but 3 appears to be email code
|
||||
console.log('An activation code has been sent to your email.');
|
||||
}
|
||||
|
||||
let smsCode = await promptAsync('SMS Code: ');
|
||||
let smsCode = await promptAsync('Activation Code: ');
|
||||
community.finalizeTwoFactor(response.shared_secret, smsCode, (err) => {
|
||||
if (err) {
|
||||
if (err.message == 'Invalid activation code') {
|
||||
|
||||
178
index.js
178
index.js
@@ -1,12 +1,9 @@
|
||||
const hex2b64 = require('node-bignumber').hex2b64;
|
||||
const {chrome} = require('@doctormckay/user-agents');
|
||||
const Request = require('request');
|
||||
const RSA = require('node-bignumber').Key;
|
||||
const SteamID = require('steamid');
|
||||
|
||||
const Helpers = require('./components/helpers.js');
|
||||
|
||||
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";
|
||||
|
||||
require('util').inherits(SteamCommunity, require('events').EventEmitter);
|
||||
|
||||
module.exports = SteamCommunity;
|
||||
@@ -31,7 +28,7 @@ function SteamCommunity(options) {
|
||||
"timeout": options.timeout || 50000,
|
||||
"gzip": true,
|
||||
"headers": {
|
||||
"User-Agent": options.userAgent || USER_AGENT
|
||||
"User-Agent": options.userAgent || chrome()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -61,158 +58,22 @@ SteamCommunity.prototype.login = function(details, callback) {
|
||||
throw new Error("Missing either accountName or password to login; both are needed");
|
||||
}
|
||||
|
||||
if (details.steamguard) {
|
||||
var parts = details.steamguard.split('||');
|
||||
this._setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), true);
|
||||
}
|
||||
|
||||
var disableMobile = typeof details.disableMobile == 'undefined' ? true : details.disableMobile;
|
||||
|
||||
var self = this;
|
||||
|
||||
// Delete the cache
|
||||
delete self._profileURL;
|
||||
delete this._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 = {};
|
||||
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, */*"
|
||||
};
|
||||
// default disableMobile to true
|
||||
let logOnOptions = Object.assign({}, details);
|
||||
logOnOptions.disableMobile = details.disableMobile !== false;
|
||||
|
||||
this._setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"));
|
||||
this._setCookie(Request.cookie("mobileClient=android"));
|
||||
} else {
|
||||
mobileHeaders = {"Referer": "https://steamcommunity.com/login"};
|
||||
}
|
||||
this._modernLogin(logOnOptions).then(({sessionID, cookies, steamguard, mobileAccessToken}) => {
|
||||
this.setCookies(cookies);
|
||||
|
||||
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);
|
||||
return;
|
||||
if (mobileAccessToken) {
|
||||
this.setMobileAppAccessToken(mobileAccessToken);
|
||||
}
|
||||
|
||||
if (!body.publickey_mod || !body.publickey_exp) {
|
||||
deleteMobileCookies();
|
||||
callback(new Error("Invalid RSA key received"));
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
"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({
|
||||
"uri": "https://steamcommunity.com/login/dologin/",
|
||||
"json": true,
|
||||
"form": formObj,
|
||||
"headers": mobileHeaders
|
||||
}, function(err, response, body) {
|
||||
deleteMobileCookies();
|
||||
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var error;
|
||||
if (!body.success && body.emailauth_needed) {
|
||||
// Steam Guard (email)
|
||||
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 && body.message.match(/Please verify your humanity/)) {
|
||||
error = new Error("CAPTCHA");
|
||||
error.captchaurl = "https://steamcommunity.com/login/rendercaptcha/?gid=" + body.captcha_gid;
|
||||
|
||||
self._captchaGid = body.captcha_gid;
|
||||
|
||||
callback(error);
|
||||
} else if (!body.success) {
|
||||
callback(new Error(body.message || "Unknown error"));
|
||||
} else {
|
||||
var sessionID = generateSessionID();
|
||||
var oAuth = {};
|
||||
self._setCookie(Request.cookie('sessionid=' + sessionID));
|
||||
|
||||
var cookies = self._jar.getCookieString("https://steamcommunity.com").split(';').map(function(cookie) {
|
||||
return cookie.trim();
|
||||
});
|
||||
|
||||
if (!disableMobile && body.oauth) {
|
||||
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
|
||||
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]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.setCookies(cookies);
|
||||
|
||||
callback(null, sessionID, cookies, steamguard, disableMobile ? null : oAuth.oauth_token);
|
||||
}
|
||||
}, "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);
|
||||
}
|
||||
callback(null, sessionID, cookies, steamguard, null);
|
||||
}).catch(err => callback(err));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -293,16 +154,20 @@ 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");
|
||||
if (cookie.domain) {
|
||||
this._jar.setCookie(cookie.clone(), protocol + '://' + cookie.domain);
|
||||
} else {
|
||||
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) {
|
||||
cookies.forEach((cookie) => {
|
||||
var cookieName = cookie.match(/(.+)=/)[1];
|
||||
var cookieName = cookie.trim().split('=')[0];
|
||||
if (cookieName == 'steamLogin' || cookieName == 'steamLoginSecure') {
|
||||
this.steamID = new SteamID(cookie.match(/=(\d+)/)[1]);
|
||||
this.steamID = new SteamID(cookie.match(/steamLogin(Secure)?=(\d+)/)[2]);
|
||||
}
|
||||
|
||||
this._setCookie(Request.cookie(cookie), !!(cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/)));
|
||||
@@ -576,6 +441,7 @@ SteamCommunity.prototype.getFriendsList = function(callback) {
|
||||
});
|
||||
};
|
||||
|
||||
require('./components/login.js');
|
||||
require('./components/http.js');
|
||||
require('./components/chat.js');
|
||||
require('./components/profile.js');
|
||||
|
||||
17
package.json
17
package.json
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"name": "steamcommunity",
|
||||
"version": "3.46.0",
|
||||
"version": "3.50.3",
|
||||
"description": "Provides an interface for logging into and interacting with the Steam Community website",
|
||||
"files": [
|
||||
"/classes",
|
||||
"/components",
|
||||
"/examples",
|
||||
"/resources",
|
||||
"/index.js"
|
||||
],
|
||||
"keywords": [
|
||||
"steam",
|
||||
"steam community"
|
||||
@@ -21,17 +28,15 @@
|
||||
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@doctormckay/user-agents": "^1.0.0",
|
||||
"async": "^2.6.3",
|
||||
"cheerio": "0.22.0",
|
||||
"image-size": "^0.8.2",
|
||||
"node-bignumber": "^1.2.1",
|
||||
"request": "^2.88.0",
|
||||
"steam-session": "^1.9.1",
|
||||
"steam-totp": "^1.5.0",
|
||||
"steamid": "^1.1.3",
|
||||
"xml2js": "^0.4.22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"steam-session": "^1.2.3"
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
|
||||
Reference in New Issue
Block a user