mirror of
https://github.com/DoctorMcKay/node-steamcommunity.git
synced 2026-08-19 13:13:28 +08:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
574a3127cb | ||
|
|
55027f7b1f | ||
|
|
70ffc5559d | ||
|
|
f4be134f71 | ||
|
|
7792969cae | ||
|
|
57a3532860 | ||
|
|
facf335f0e | ||
|
|
1c3817b797 | ||
|
|
74efd50f72 | ||
|
|
87e3665b01 | ||
|
|
0b94419de4 | ||
|
|
defbd347cc | ||
|
|
7efc1f2fd0 | ||
|
|
05c2da89da | ||
|
|
b83c40419c | ||
|
|
fb0097499d | ||
|
|
c2983e3bcd | ||
|
|
80b3cc52d2 | ||
|
|
fa59b188a5 | ||
|
|
e446cc3cd8 | ||
|
|
722ca6689b | ||
|
|
1276c71e4e | ||
|
|
a51bd21153 | ||
|
|
f1bbfc4901 | ||
|
|
eb8d094bf1 | ||
|
|
b1cca95ec0 | ||
|
|
27ef78cc4b | ||
|
|
4223d88fd1 | ||
|
|
ac02f6cb49 | ||
|
|
d84601e256 | ||
|
|
4152865f5e | ||
|
|
7627e6a3a0 | ||
|
|
bbcf0eb72f | ||
|
|
d51b6582e2 | ||
|
|
bbd339b88b | ||
|
|
fcaaa38085 | ||
|
|
26f27e2698 | ||
|
|
6d8d0c5fbb | ||
|
|
08c9ab6809 | ||
|
|
c150425ee5 | ||
|
|
eae78aab93 | ||
|
|
5c2666e0f4 | ||
|
|
c64b6c7e99 | ||
|
|
318f9ab2b4 | ||
|
|
5c2894a7d4 | ||
|
|
3167034b97 | ||
|
|
3bfe8c3713 | ||
|
|
0c4e026701 | ||
|
|
c246011fe2 | ||
|
|
c290e16dc5 | ||
|
|
f8852ba6c4 | ||
|
|
52ee65d9da | ||
|
|
42e04b123a | ||
|
|
1f69d1fc04 | ||
|
|
e4b5e763eb | ||
|
|
6feb607366 | ||
|
|
cc64d9883a | ||
|
|
d4a92b4dea | ||
|
|
e2849719c1 | ||
|
|
c9541d093c | ||
|
|
339d5dc1e4 | ||
|
|
2a7a03d35e | ||
|
|
ff1c3279c8 | ||
|
|
9175a44828 | ||
|
|
deeeb633cf | ||
|
|
a50bd35a54 | ||
|
|
5bf51a2e3e | ||
|
|
448b32b9c6 | ||
|
|
01428e6007 | ||
|
|
43d1147fd7 | ||
|
|
479cc47bf5 |
@@ -25,11 +25,18 @@ function CEconItem(item, descriptions, contextID) {
|
||||
}
|
||||
}
|
||||
|
||||
this.is_currency = !!this.is_currency;
|
||||
this.tradable = !!this.tradable;
|
||||
this.marketable = !!this.marketable;
|
||||
this.commodity = !!this.commodity;
|
||||
this.market_tradable_restriction = (this.market_tradable_restriction ? parseInt(this.market_tradable_restriction, 10) : 0);
|
||||
this.market_marketable_restriction = (this.market_marketable_restriction ? parseInt(this.market_marketable_restriction, 10) : 0);
|
||||
this.fraudwarnings = this.fraudwarnings || [];
|
||||
this.descriptions = this.descriptions || [];
|
||||
|
||||
if(this.owner && JSON.stringify(this.owner) == '{}') {
|
||||
this.owner = null;
|
||||
}
|
||||
}
|
||||
|
||||
CEconItem.prototype.getImageURL = function() {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
var SteamCommunity = require('../index.js');
|
||||
var Cheerio = require('cheerio');
|
||||
|
||||
SteamCommunity.prototype.getMarketItem = function(appid, hashName, callback) {
|
||||
SteamCommunity.prototype.getMarketItem = function(appid, hashName, currency, callback) {
|
||||
if (typeof currency == "function") {
|
||||
callback = currency;
|
||||
currency = 1;
|
||||
}
|
||||
var self = this;
|
||||
this.request("https://steamcommunity.com/market/listings/" + appid + "/" + encodeURIComponent(hashName), function(err, response, body) {
|
||||
if(self._checkHttpError(err, response, callback)) {
|
||||
@@ -13,23 +17,21 @@ SteamCommunity.prototype.getMarketItem = function(appid, hashName, callback) {
|
||||
callback(new Error("There are no listings for this item."));
|
||||
return;
|
||||
}
|
||||
|
||||
var item = new CMarketItem(self, body, $);
|
||||
if(item.commodity) {
|
||||
item.updatePrice(function(err) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, item);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(null, item);
|
||||
}
|
||||
|
||||
var item = new CMarketItem(appid, hashName, self, body, $);
|
||||
item.updatePrice(currency, function(err) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, item);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function CMarketItem(community, body, $) {
|
||||
function CMarketItem(appid, hashName, community, body, $) {
|
||||
this._appid = appid;
|
||||
this._hashName = hashName;
|
||||
this._community = community;
|
||||
this._$ = $;
|
||||
|
||||
@@ -38,6 +40,12 @@ function CMarketItem(community, body, $) {
|
||||
if(match) {
|
||||
this._country = match[1];
|
||||
}
|
||||
|
||||
this._language = "english";
|
||||
match = body.match(/var g_strLanguage = "([^"]+)";/);
|
||||
if(match) {
|
||||
this._language = match[1];
|
||||
}
|
||||
|
||||
this.commodity = false;
|
||||
var match = body.match(/Market_LoadOrderSpread\(\s*(\d+)\s*\);/);
|
||||
@@ -62,34 +70,41 @@ function CMarketItem(community, body, $) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
this.quantity = 0;
|
||||
this.lowestPrice = 0;
|
||||
|
||||
if(!this.commodity) {
|
||||
var total = $('#searchResults_total');
|
||||
if(total) {
|
||||
this.quantity = parseInt(total.text().replace(/[^\d]/g, '').trim(), 10);
|
||||
}
|
||||
|
||||
var lowest = $('.market_listing_price.market_listing_price_with_fee');
|
||||
if(lowest[0]) {
|
||||
this.lowestPrice = parseInt($(lowest[0]).text().replace(/[^\d]/g, '').trim(), 10);
|
||||
|
||||
this.firstAsset = null;
|
||||
this.assets = null;
|
||||
match = body.match(/var g_rgAssets = (.*);/);
|
||||
if (match) {
|
||||
try {
|
||||
this.assets = JSON.parse(match[1]);
|
||||
this.assets = this.assets['730']['2'];
|
||||
this.firstAsset = this.assets[Object.keys(this.assets)[0]];
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.quantity = 0;
|
||||
this.lowestPrice = 0;
|
||||
// TODO: Buying listings and placing buy orders
|
||||
}
|
||||
|
||||
CMarketItem.prototype.updatePrice = function(callback) {
|
||||
CMarketItem.prototype.updatePrice = function (currency, callback) {
|
||||
if (this.commodity) {
|
||||
this.updatePriceForCommodity(currency, callback);
|
||||
} else {
|
||||
this.updatePriceForNonCommodity(currency, callback);
|
||||
}
|
||||
};
|
||||
|
||||
CMarketItem.prototype.updatePriceForCommodity = function(currency, callback) {
|
||||
if(!this.commodity) {
|
||||
throw new Error("Cannot update price for non-commodity item");
|
||||
}
|
||||
|
||||
// TODO: Currency option maybe?
|
||||
|
||||
var self = this;
|
||||
this._community.request({
|
||||
"uri": "https://steamcommunity.com/market/itemordershistogram?country=US&language=english¤cy=1&item_nameid=" + this.commodityID,
|
||||
"uri": "https://steamcommunity.com/market/itemordershistogram?country=US&language=english¤cy=" + currency + "&item_nameid=" + this.commodityID,
|
||||
"json": true,
|
||||
}, function(err, response, body) {
|
||||
if(self._community._checkHttpError(err, response, callback)) {
|
||||
@@ -124,3 +139,47 @@ CMarketItem.prototype.updatePrice = function(callback) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
CMarketItem.prototype.updatePriceForNonCommodity = function (currency, callback) {
|
||||
if(this.commodity) {
|
||||
throw new Error("Cannot update price for commodity item");
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this._community.request({
|
||||
"uri": "https://steamcommunity.com/market/listings/" +
|
||||
this._appid + "/" +
|
||||
encodeURIComponent(this._hashName) +
|
||||
"/render/?query=&start=0&count=10&country=US&language=english¤cy=" + currency,
|
||||
"json": true
|
||||
}, function(err, response, body) {
|
||||
if (self._community._checkHttpError(err, response, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.success != 1) {
|
||||
callback && callback(new Error("Error " + body.success));
|
||||
return;
|
||||
}
|
||||
|
||||
var match = body.total_count;
|
||||
if (match) {
|
||||
self.quantity = parseInt(match, 10);
|
||||
}
|
||||
|
||||
var lowestPrice;
|
||||
var $ = Cheerio.load(body.results_html);
|
||||
match = $(".market_listing_price.market_listing_price_with_fee");
|
||||
if (match) {
|
||||
for (var i = 0; i < match.length; i++) {
|
||||
lowestPrice = parseFloat($(match[i]).text().replace(",", ".").replace(/[^\d.]/g, ''));
|
||||
if (!isNaN(lowestPrice)) {
|
||||
self.lowestPrice = lowestPrice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback && callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ function performSearch(request, qs, results, callback) {
|
||||
callback(null, results);
|
||||
} else {
|
||||
qs.start += body.pagesize;
|
||||
performSearch(request, qs, results, callback);
|
||||
performSearch.call(self, request, qs, results, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ CSteamGroup.prototype.getMembers = function(addresses, callback) {
|
||||
callback = addresses;
|
||||
addresses = null;
|
||||
}
|
||||
|
||||
|
||||
this._community.getGroupMembers(this.steamID, callback, null, null, addresses, 0);
|
||||
};
|
||||
|
||||
@@ -76,10 +76,22 @@ CSteamGroup.prototype.leave = function(callback) {
|
||||
this._community.leaveGroup(this.steamID, callback);
|
||||
};
|
||||
|
||||
CSteamGroup.prototype.getAllAnnouncements = function(time, callback) {
|
||||
this._community.getAllGroupAnnouncements(this.steamID, time, callback);
|
||||
};
|
||||
|
||||
CSteamGroup.prototype.postAnnouncement = function(headline, content, callback) {
|
||||
this._community.postGroupAnnouncement(this.steamID, headline, content, callback);
|
||||
};
|
||||
|
||||
CSteamGroup.prototype.editAnnouncement = function(annoucementID, headline, content, callback) {
|
||||
this._community.editGroupAnnouncement(this.steamID, annoucementID, headline, content, callback)
|
||||
};
|
||||
|
||||
CSteamGroup.prototype.deleteAnnouncement = function(annoucementID, callback) {
|
||||
this._community.deleteGroupAnnouncement(this.steamID, annoucementID, callback)
|
||||
};
|
||||
|
||||
CSteamGroup.prototype.scheduleEvent = function(name, type, description, time, server, callback) {
|
||||
this._community.scheduleGroupEvent(this.steamID, name, type, description, time, server, callback);
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ function CSteamUser(community, userData, customurl) {
|
||||
var self = this;
|
||||
if(userData.groups && userData.groups[0] && userData.groups[0].group) {
|
||||
this.groups = userData.groups[0].group.map(function(group) {
|
||||
if(group['$'] && group['$'].isPrimary) {
|
||||
if(group['$'] && group['$'].isPrimary === "1") {
|
||||
self.primaryGroup = new SteamID(group.groupID64[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,8 @@ SteamCommunity.prototype.chatMessage = function(recipient, text, type, callback)
|
||||
}
|
||||
|
||||
type = type || 'saytext';
|
||||
|
||||
|
||||
var self = this;
|
||||
this.request.post({
|
||||
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Message/v1",
|
||||
"form": {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
var SteamCommunity = require('../index.js');
|
||||
var Cheerio = require('cheerio');
|
||||
var SteamTotp = require('steam-totp');
|
||||
var Async = require('async');
|
||||
|
||||
var CConfirmation = require('../classes/CConfirmation.js');
|
||||
|
||||
@@ -15,6 +17,7 @@ SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
|
||||
request(this, "conf", key, time, "conf", null, false, function(err, body) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var $ = Cheerio.load(body);
|
||||
@@ -73,6 +76,7 @@ SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, ca
|
||||
request(this, "details/" + confID, key, time, "details", null, true, function(err, body) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!body.success) {
|
||||
@@ -137,7 +141,7 @@ SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time,
|
||||
|
||||
function request(community, url, key, time, tag, params, json, callback) {
|
||||
params = params || {};
|
||||
params.p = "android:" + Date.now();
|
||||
params.p = SteamTotp.getDeviceID(community.steamID);
|
||||
params.a = community.steamID.getSteamID64();
|
||||
params.k = key;
|
||||
params.t = time;
|
||||
@@ -161,12 +165,14 @@ function request(community, url, key, time, tag, params, json, callback) {
|
||||
|
||||
/**
|
||||
* Start automatically polling our confirmations for new ones. The `confKeyNeeded` event will be emitted when we need a confirmation key, or `newConfirmation` when we get a new confirmation
|
||||
* @param {int} pollInterval - The interval, in milliseconds, at which we will poll for confirmations. This shouldn't be any less than 10,000 probably.
|
||||
* @param {int} pollInterval - The interval, in milliseconds, at which we will poll for confirmations. This should probably be at least 10,000 to avoid rate-limits.
|
||||
* @param {Buffer|string|null} [identitySecret=null] - Your identity_secret. If passed, all confirmations will be automatically accepted and nothing will be emitted.
|
||||
*/
|
||||
SteamCommunity.prototype.startConfirmationChecker = function(pollInterval) {
|
||||
SteamCommunity.prototype.startConfirmationChecker = function(pollInterval, identitySecret) {
|
||||
this._confirmationPollInterval = pollInterval;
|
||||
this._knownConfirmations = this._knownConfirmations || {};
|
||||
this._confirmationKeys = this._confirmationKeys || {};
|
||||
this._identitySecret = identitySecret;
|
||||
|
||||
if(this._confirmationTimer) {
|
||||
clearTimeout(this._confirmationTimer);
|
||||
@@ -183,6 +189,10 @@ SteamCommunity.prototype.stopConfirmationChecker = function() {
|
||||
delete this._confirmationPollInterval;
|
||||
}
|
||||
|
||||
if(this._identitySecret) {
|
||||
delete this._identitySecret;
|
||||
}
|
||||
|
||||
if(this._confirmationTimer) {
|
||||
clearTimeout(this._confirmationTimer);
|
||||
delete this._confirmationTimer;
|
||||
@@ -200,6 +210,27 @@ SteamCommunity.prototype.checkConfirmations = function() {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
if(!this._confirmationQueue) {
|
||||
this._confirmationQueue = Async.queue(function(conf, callback) {
|
||||
// Worker to process new confirmations
|
||||
if(self._identitySecret) {
|
||||
// We should accept this
|
||||
self.emit('debug', "Accepting confirmation #" + conf.id);
|
||||
var time = Math.floor(Date.now() / 1000);
|
||||
conf.respond(time, SteamTotp.getConfirmationKey(self._identitySecret, time, "allow"), true, function() {
|
||||
// If there was an error and it wasn't actually accepted, we'll pick it up again
|
||||
delete self._knownConfirmations[conf.id];
|
||||
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
|
||||
});
|
||||
} else {
|
||||
self.emit('newConfirmation', conf);
|
||||
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
this.emit('debug', 'Checking confirmations');
|
||||
|
||||
this._confirmationCheckerGetKey('conf', function(err, key) {
|
||||
if(err) {
|
||||
resetTimer();
|
||||
@@ -208,6 +239,7 @@ SteamCommunity.prototype.checkConfirmations = function() {
|
||||
|
||||
self.getConfirmations(key.time, key.key, function(err, confirmations) {
|
||||
if(err) {
|
||||
self.emit('debug', "Can't check confirmations: " + err.message);
|
||||
resetTimer();
|
||||
return;
|
||||
}
|
||||
@@ -225,16 +257,16 @@ SteamCommunity.prototype.checkConfirmations = function() {
|
||||
|
||||
// We have new confirmations! Grab a key to get details.
|
||||
self._confirmationCheckerGetKey('details', function(err, key) {
|
||||
var handled = 0;
|
||||
|
||||
newOnes.forEach(function(conf) {
|
||||
self._knownConfirmations[conf.id] = conf; // Add it to our list of known confirmations
|
||||
|
||||
if(err) {
|
||||
handleNewConfirmation(conf, handled++);
|
||||
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;
|
||||
handleNewConfirmation(conf, handled++);
|
||||
self._confirmationQueue.push(conf);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -255,12 +287,37 @@ SteamCommunity.prototype.checkConfirmations = function() {
|
||||
|
||||
// Delay them by 1 second per new confirmation that we see, so that keys won't be the same.
|
||||
setTimeout(function() {
|
||||
self.emit('newConfirmation', conf);
|
||||
if(self._identitySecret) {
|
||||
self.emit('debug', 'Accepting confirmation #' + conf.id);
|
||||
var time = Math.floor(Date.now() / 1000);
|
||||
conf.respond(time, SteamTotp.getConfirmationKey(self._identitySecret, time, "allow"), true, function(err) {
|
||||
if (err) {
|
||||
self.emit('debug', "Can't accept confirmation #" + conf.id + ": " + err.message);
|
||||
}
|
||||
|
||||
// We'll just retry next time we poll
|
||||
delete self._knownConfirmations[conf.id];
|
||||
});
|
||||
} else {
|
||||
self.emit('newConfirmation', conf);
|
||||
}
|
||||
}, handleNumber * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
SteamCommunity.prototype._confirmationCheckerGetKey = function(tag, callback) {
|
||||
if(this._identitySecret) {
|
||||
if(tag == 'details') {
|
||||
// We don't care about details
|
||||
callback(new Error("Disabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
var time = Math.floor(Date.now() / 1000);
|
||||
callback(null, {"time": time, "key": SteamTotp.getConfirmationKey(this._identitySecret, time, tag)});
|
||||
return;
|
||||
}
|
||||
|
||||
var existing = this._confirmationKeys[tag];
|
||||
var reusable = ['conf', 'details'];
|
||||
|
||||
|
||||
@@ -125,6 +125,56 @@ SteamCommunity.prototype.leaveGroup = function(gid, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback) {
|
||||
if(typeof gid === 'string') {
|
||||
gid = new SteamID(gid);
|
||||
}
|
||||
|
||||
if(typeof time === 'function') {
|
||||
callback = time;
|
||||
time = new Date(0); // The beginnig of time...
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.request({
|
||||
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/rss/"
|
||||
}, function(err, response, body) {
|
||||
|
||||
if(self._checkHttpError(err, response, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(self._checkCommunityError(body, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
xml2js.parseString(body, function(err, results) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(!results.rss.channel[0].item) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
var announcements = results.rss.channel[0].item.map(function(announcement) {
|
||||
var splitLink = announcement.link[0].split('/');
|
||||
return {
|
||||
headline: announcement.title[0],
|
||||
content: announcement.description[0],
|
||||
date: new Date(announcement.pubDate[0]),
|
||||
author: announcement.author[0],
|
||||
aid: splitLink[splitLink.length - 1]
|
||||
}
|
||||
}).filter(function(announcement) {
|
||||
return (announcement.date > time);
|
||||
});
|
||||
|
||||
return callback(null, announcements);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content, callback) {
|
||||
if(typeof gid === 'string') {
|
||||
gid = new SteamID(gid);
|
||||
@@ -137,15 +187,82 @@ SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content
|
||||
"sessionID": this.getSessionID(),
|
||||
"action": "post",
|
||||
"headline": headline,
|
||||
"body": content
|
||||
"body": content,
|
||||
"languages[0][headline]": headline,
|
||||
"languages[0][body]": content
|
||||
}
|
||||
}, function(err, response, body) {
|
||||
if(!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(err || response.statusCode >= 400) {
|
||||
callback(err || new Error("HTTP error " + response.statusCode));
|
||||
if(self._checkHttpError(err, response, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(self._checkCommunityError(body, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
})
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.editGroupAnnouncement = function(gid, aid, headline, content, callback) {
|
||||
if(typeof gid === 'string') {
|
||||
gid = new SteamID(gid);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
var submitData = {
|
||||
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements",
|
||||
"form": {
|
||||
"sessionID": this.getSessionID(),
|
||||
"gid": aid,
|
||||
"action": "update",
|
||||
"headline": headline,
|
||||
"body": content,
|
||||
"languages[0][headline]": headline,
|
||||
"languages[0][body]": content,
|
||||
"languages[0][updated]": 1
|
||||
}
|
||||
}
|
||||
|
||||
this.request.post(submitData, function(err, response, body) {
|
||||
if(!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(self._checkHttpError(err, response, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(self._checkCommunityError(body, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
})
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.deleteGroupAnnouncement = function(gid, aid, callback) {
|
||||
if(typeof gid === 'string') {
|
||||
gid = new SteamID(gid);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
var submitData = {
|
||||
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements/delete/" + aid + "?sessionID=" + this.getSessionID(),
|
||||
}
|
||||
|
||||
this.request.get(submitData, function(err, response, body) {
|
||||
if(!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(self._checkHttpError(err, response, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
|
||||
for(var 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') {
|
||||
@@ -81,7 +83,7 @@ SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
|
||||
|
||||
items = item.find('.history_item');
|
||||
for(j = 0; j < items.length; j++) {
|
||||
match = body.match(new RegExp("HistoryPageCreateItemHover\\( '" + $(items[j]).attr('id') + "', (\\d+), '(\\d+)', '(\\d+)', '(\\d+)' \\);"));
|
||||
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) {
|
||||
|
||||
@@ -190,10 +190,6 @@ SteamCommunity.prototype.profileSettings = function(settings, callback) {
|
||||
case 'inventoryGifts':
|
||||
values.inventoryGiftPrivacy = settings[i] ? 1 : 0;
|
||||
break;
|
||||
|
||||
case 'emailConfirmation':
|
||||
values.tradeConfirmationSetting = settings[i] ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,6 @@ SteamCommunity.prototype.enableTwoFactor = function(callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a random device ID hash
|
||||
var hash = require('crypto').createHash('sha1');
|
||||
hash.update(Math.random().toString());
|
||||
hash = hash.digest('hex');
|
||||
|
||||
self.request.post({
|
||||
"uri": "https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/",
|
||||
"form": {
|
||||
@@ -28,7 +23,7 @@ SteamCommunity.prototype.enableTwoFactor = function(callback) {
|
||||
"access_token": token,
|
||||
"authenticator_time": Math.floor(Date.now() / 1000),
|
||||
"authenticator_type": ETwoFactorTokenType.ValveMobileApp,
|
||||
"device_identifier": 'android:' + hash,
|
||||
"device_identifier": SteamTotp.getDeviceID(self.steamID),
|
||||
"sms_phone_id": "1"
|
||||
},
|
||||
"json": true
|
||||
|
||||
@@ -262,7 +262,12 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
|
||||
}
|
||||
|
||||
if(!body || !body.success || !body.rgInventory || !body.rgDescriptions || !body.rgCurrency) {
|
||||
callback(new Error(body.Error || "Malformed response"));
|
||||
if(body) {
|
||||
callback(new Error(body.Error || "Malformed response"));
|
||||
} else {
|
||||
callback(new Error("Malformed response"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
109
examples/edit-group-announcement.js
Normal file
109
examples/edit-group-announcement.js
Normal file
@@ -0,0 +1,109 @@
|
||||
var SteamCommunity = require('../index.js');
|
||||
var ReadLine = require('readline');
|
||||
var fs = require('fs');
|
||||
|
||||
var community = new SteamCommunity();
|
||||
var rl = ReadLine.createInterface({
|
||||
"input": process.stdin,
|
||||
"output": process.stdout
|
||||
});
|
||||
|
||||
rl.question("Username: ", function(accountName) {
|
||||
rl.question("Password: ", function(password) {
|
||||
doLogin(accountName, password);
|
||||
});
|
||||
});
|
||||
|
||||
function doLogin(accountName, password, authCode, twoFactorCode) {
|
||||
community.login({
|
||||
"accountName": accountName,
|
||||
"password": password,
|
||||
"authCode": authCode,
|
||||
"twoFactorCode": twoFactorCode
|
||||
}, function(err, sessionID, cookies, steamguard) {
|
||||
if(err) {
|
||||
if(err.message == 'SteamGuardMobile') {
|
||||
rl.question("Steam Authenticator Code: ", function(code) {
|
||||
doLogin(accountName, password, null, code);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(err.message == 'SteamGuard') {
|
||||
console.log("An email has been sent to your address at " + err.emaildomain);
|
||||
rl.question("Steam Guard Code: ", function(code) {
|
||||
doLogin(accountName, password, code);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(err);
|
||||
process.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Logged on!");
|
||||
|
||||
rl.question("Group ID: ", function(gid) {
|
||||
community.getSteamGroup(gid, function(err, group) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
group.getAllAnnouncements(function(err, announcements) {
|
||||
|
||||
if(announcements.length === 0) {
|
||||
return console.log("This group has no announcements");
|
||||
}
|
||||
|
||||
for (var i = announcements.length - 1; i >= 0; i--) {
|
||||
console.log("[%s] %s %s: %s", announcements[i].date, announcements[i].aid, announcements[i].author, announcements[i].content);
|
||||
};
|
||||
|
||||
rl.question("Would you like to delete delete or edit an annoucement? (Type edit/delete): ", function(choice) {
|
||||
rl.question("Annoucement ID: ", function(aid) {
|
||||
if(choice === 'edit') {
|
||||
rl.question("New title: ", function(header) {
|
||||
rl.question("New body: ", function(content) {
|
||||
// EW THE PYRAMID!
|
||||
// Try replace this with delete!
|
||||
editAnnouncement(group, aid, header, content);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
deleteAnnouncement(group, aid);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function editAnnouncement(group, aid, header, content) {
|
||||
// Actual community method.
|
||||
group.editAnnouncement(aid, header, content, function(error) {
|
||||
if(!error) {
|
||||
console.log("Annoucement edited!");
|
||||
} else {
|
||||
console.log("Unable to edit annoucement! %j", error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAnnouncement(group, aid) {
|
||||
// group.deleteAnnouncement(aid);
|
||||
// Or
|
||||
group.deleteAnnouncement(aid, function(err) {
|
||||
if(!err) {
|
||||
console.log("Deleted");
|
||||
} else {
|
||||
console.log("Error deleting announcement.");
|
||||
}
|
||||
})
|
||||
}
|
||||
97
examples/enable_twofactor.js
Normal file
97
examples/enable_twofactor.js
Normal file
@@ -0,0 +1,97 @@
|
||||
var SteamCommunity = require('../index.js');
|
||||
var ReadLine = require('readline');
|
||||
var fs = require('fs');
|
||||
|
||||
var community = new SteamCommunity();
|
||||
var rl = ReadLine.createInterface({
|
||||
"input": process.stdin,
|
||||
"output": process.stdout
|
||||
});
|
||||
|
||||
rl.question("Username: ", function(accountName) {
|
||||
rl.question("Password: ", function(password) {
|
||||
doLogin(accountName, password);
|
||||
});
|
||||
});
|
||||
|
||||
function doLogin(accountName, password, authCode) {
|
||||
community.login({
|
||||
"accountName": accountName,
|
||||
"password": password,
|
||||
"authCode": authCode
|
||||
}, function(err, sessionID, cookies, steamguard) {
|
||||
if(err) {
|
||||
if(err.message == 'SteamGuardMobile') {
|
||||
console.log("This account already has two-factor authentication enabled.");
|
||||
process.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
if(err.message == 'SteamGuard') {
|
||||
console.log("An email has been sent to your address at " + err.emaildomain);
|
||||
rl.question("Steam Guard Code: ", function(code) {
|
||||
doLogin(accountName, password, code);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(err);
|
||||
process.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Logged on!");
|
||||
community.enableTwoFactor(function(err, response) {
|
||||
if(err) {
|
||||
if(err.eresult == 2) {
|
||||
console.log("Error: Failed to enable two-factor authentication. Do you have a phone number attached to your account?");
|
||||
process.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
if(err.eresult == 84) {
|
||||
console.log("Error: RateLimitExceeded. Try again later.");
|
||||
process.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(err);
|
||||
process.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
if(response.status != 1) {
|
||||
console.log("Error: Status " + response.status);
|
||||
process.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Writing secrets to twofactor_" + community.steamID.getSteamID64() + ".json");
|
||||
console.log("Revocation code: " + response.revocation_code);
|
||||
fs.writeFile("twofactor_" + community.steamID.getSteamID64() + ".json", JSON.stringify(response, null, "\t"));
|
||||
|
||||
promptActivationCode(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function promptActivationCode(response) {
|
||||
rl.question("SMS Code: ", function(smsCode) {
|
||||
community.finalizeTwoFactor(response.shared_secret, smsCode, function(err) {
|
||||
if(err) {
|
||||
if(err.message == "Invalid activation code") {
|
||||
console.log(err);
|
||||
promptActivationCode(response);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log("Two-factor authentication enabled!");
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
}
|
||||
157
index.js
157
index.js
@@ -3,36 +3,49 @@ var RSA = require('node-bignumber').Key;
|
||||
var hex2b64 = require('node-bignumber').hex2b64;
|
||||
var SteamID = require('steamid');
|
||||
|
||||
const USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36";
|
||||
|
||||
require('util').inherits(SteamCommunity, require('events').EventEmitter);
|
||||
|
||||
module.exports = SteamCommunity;
|
||||
|
||||
SteamCommunity.SteamID = SteamID;
|
||||
|
||||
function SteamCommunity(localAddress) {
|
||||
function SteamCommunity(options) {
|
||||
options = options || {};
|
||||
|
||||
this._jar = Request.jar();
|
||||
this._captchaGid = -1;
|
||||
this.chatState = SteamCommunity.ChatState.Offline;
|
||||
|
||||
var defaults = {
|
||||
"jar": this._jar,
|
||||
"timeout": 50000
|
||||
"timeout": options.timeout || 50000,
|
||||
"gzip": true,
|
||||
"headers": {
|
||||
"User-Agent": options.userAgent || USER_AGENT
|
||||
}
|
||||
};
|
||||
|
||||
if(localAddress) {
|
||||
defaults.localAddress = localAddress;
|
||||
if (typeof options == "string") {
|
||||
options = {
|
||||
localAddress: options
|
||||
};
|
||||
}
|
||||
this._options = options;
|
||||
|
||||
if (options.localAddress) {
|
||||
defaults.localAddress = options.localAddress;
|
||||
}
|
||||
|
||||
this.request = Request.defaults(defaults);
|
||||
|
||||
this.request = options.request || Request.defaults();
|
||||
this.request = this.request.defaults(defaults);
|
||||
|
||||
// English
|
||||
this._jar.setCookie(Request.cookie('Steam_Language=english'), 'https://steamcommunity.com');
|
||||
|
||||
// UTC
|
||||
this._jar.setCookie(Request.cookie('timezoneOffset=0,0'), 'https://steamcommunity.com');
|
||||
|
||||
this._jar.setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"), "https://steamcommunity.com");
|
||||
this._jar.setCookie(Request.cookie("mobileClient=android"), "https://steamcommunity.com");
|
||||
}
|
||||
|
||||
SteamCommunity.prototype.login = function(details, callback) {
|
||||
@@ -46,70 +59,73 @@ SteamCommunity.prototype.login = function(details, callback) {
|
||||
// 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, */*"
|
||||
"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
|
||||
"form": {"username": details.accountName},
|
||||
"headers": mobileHeaders,
|
||||
"json": true
|
||||
}, function(err, response, body) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
// Remove the mobile cookies
|
||||
if (self._checkHttpError(err, response, callback)) {
|
||||
deleteMobileCookies();
|
||||
return;
|
||||
}
|
||||
|
||||
var json;
|
||||
try {
|
||||
json = JSON.parse(body);
|
||||
} catch(e) {
|
||||
callback(e);
|
||||
|
||||
if(!body.publickey_mod || !body.publickey_exp) {
|
||||
deleteMobileCookies();
|
||||
callback(new Error("Invalid RSA key received"));
|
||||
return;
|
||||
}
|
||||
|
||||
var key = new RSA();
|
||||
key.setPublic(json.publickey_mod, json.publickey_exp);
|
||||
|
||||
var form = {
|
||||
"captcha_text": details.captcha || "",
|
||||
"captchagid": self._captchaGid,
|
||||
"emailauth": details.authCode || "",
|
||||
"emailsteamid": "",
|
||||
"password": hex2b64(key.encrypt(details.password)),
|
||||
"remember_login": "true",
|
||||
"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"
|
||||
};
|
||||
key.setPublic(body.publickey_mod, body.publickey_exp);
|
||||
|
||||
self.request.post({
|
||||
"uri": "https://steamcommunity.com/login/dologin/",
|
||||
"json": true,
|
||||
"form": form,
|
||||
"form": {
|
||||
"captcha_text": details.captcha || "",
|
||||
"captchagid": self._captchaGid,
|
||||
"emailauth": details.authCode || "",
|
||||
"emailsteamid": "",
|
||||
"password": hex2b64(key.encrypt(details.password)),
|
||||
"remember_login": "true",
|
||||
"rsatimestamp": body.timestamp,
|
||||
"twofactorcode": details.twoFactorCode || "",
|
||||
"username": details.accountName,
|
||||
"oauth_client_id": "DE45CD61",
|
||||
"oauth_scope": "read_profile write_profile read_client write_client",
|
||||
"loginfriendlyname": "#login_emailauth_friendlyname_mobile",
|
||||
"donotcache": Date.now()
|
||||
},
|
||||
"headers": mobileHeaders
|
||||
}, function(err, response, body) {
|
||||
deleteMobileCookies();
|
||||
|
||||
if(self._checkHttpError(err, response, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var error;
|
||||
if(!body.success && body.emailauth_needed) {
|
||||
// Steam Guard (email)
|
||||
var error = new Error("SteamGuard");
|
||||
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;
|
||||
} 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;
|
||||
|
||||
@@ -138,10 +154,53 @@ SteamCommunity.prototype.login = function(details, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, sessionID, cookies, steamguard);
|
||||
callback(null, sessionID, cookies, steamguard, oAuth.oauth_token);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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");
|
||||
}
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.oAuthLogin = function(steamguard, token, callback) {
|
||||
steamguard = steamguard.split('||');
|
||||
var steamID = new SteamID(steamguard[0]);
|
||||
|
||||
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=' + encodeURIComponent(steamID.getSteamID64() + '||' + body.response.token),
|
||||
'steamLoginSecure=' + encodeURIComponent(steamID.getSteamID64() + '||' + body.response.token_secure),
|
||||
'steamMachineAuth' + steamID.getSteamID64() + '=' + steamguard[1],
|
||||
'sessionid=' + self.getSessionID()
|
||||
];
|
||||
|
||||
self.setCookies(cookies);
|
||||
callback(null, self.getSessionID(), cookies);
|
||||
});
|
||||
};
|
||||
|
||||
SteamCommunity.prototype.setCookies = function(cookies) {
|
||||
@@ -171,7 +230,7 @@ SteamCommunity.prototype.getSessionID = function() {
|
||||
};
|
||||
|
||||
function generateSessionID() {
|
||||
return Math.floor(Math.random() * 1000000000);
|
||||
return require('crypto').randomBytes(12).toString('hex');
|
||||
}
|
||||
|
||||
SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
|
||||
@@ -288,7 +347,7 @@ SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
|
||||
|
||||
var match = response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/);
|
||||
if(!match) {
|
||||
callback("Can't get profile URL");
|
||||
callback(new Error("Can't get profile URL"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "steamcommunity",
|
||||
"version": "3.12.2",
|
||||
"version": "3.18.7",
|
||||
"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",
|
||||
@@ -13,12 +13,12 @@
|
||||
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"request": "^2.61.0",
|
||||
"node-bignumber": "^1.2.1",
|
||||
"steamid": "^0.3.1",
|
||||
"xml2js": "^0.4.11",
|
||||
"cheerio": "^0.19.0",
|
||||
"async": "^1.4.2",
|
||||
"steam-totp": "^1.0.0"
|
||||
"request": "^2.61.0",
|
||||
"node-bignumber": "^1.2.1",
|
||||
"steamid": "^0.3.1",
|
||||
"xml2js": "^0.4.11",
|
||||
"cheerio": "^0.19.0",
|
||||
"async": "^1.4.2",
|
||||
"steam-totp": "^1.3.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user