node-steamcommunity/components/confirmations.js

274 lines
8.0 KiB
JavaScript
Raw Normal View History

2019-02-14 13:29:21 +08:00
const Cheerio = require('cheerio');
const SteamTotp = require('steam-totp');
2015-12-03 10:31:17 +08:00
2019-02-14 13:29:21 +08:00
const SteamCommunity = require('../index.js');
const CConfirmation = require('../classes/CConfirmation.js');
2015-12-03 10:31:17 +08:00
2015-12-03 11:20:16 +08:00
/**
* Get a list of your account's currently outstanding confirmations.
* @param {int} time - The unix timestamp with which the following key was generated
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag "conf" (this key can be reused)
* @param {SteamCommunity~getConfirmations} callback - Called when the list of confirmations is received
*/
2015-12-03 10:31:17 +08:00
SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
2019-09-24 16:28:05 +08:00
request(this, "conf", key, time, "conf", null, false, (err, body) => {
if (err) {
if (err.message == "Invalid protocol: steammobile:") {
err.message = "Not Logged In";
2019-09-24 16:28:05 +08:00
this._notifySessionExpired(err);
}
2015-12-03 10:31:17 +08:00
callback(err);
return;
2015-12-03 10:31:17 +08:00
}
2019-09-24 16:28:05 +08:00
let $ = Cheerio.load(body);
let empty = $('#mobileconf_empty');
if (empty.length > 0) {
if (!$(empty).hasClass('mobileconf_done')) {
2015-12-03 10:31:17 +08:00
// An error occurred
callback(new Error(empty.find('div:nth-of-type(2)').text()));
} else {
callback(null, []);
}
return;
}
// We have something to confirm
2019-09-24 16:28:05 +08:00
let confirmations = $('#mobileconf_list');
if (!confirmations) {
2015-12-03 10:31:17 +08:00
callback(new Error("Malformed response"));
return;
}
2019-09-24 16:28:05 +08:00
let confs = [];
Array.prototype.forEach.call(confirmations.find('.mobileconf_list_entry'), (conf) => {
2015-12-03 10:31:17 +08:00
conf = $(conf);
2019-09-24 16:28:05 +08:00
let img = conf.find('.mobileconf_list_entry_icon img');
confs.push(new CConfirmation(this, {
2015-12-03 10:31:17 +08:00
"id": conf.data('confid'),
"type": conf.data('type'),
"creator": conf.data('creator'),
2015-12-03 10:31:17 +08:00
"key": conf.data('key'),
"title": conf.find('.mobileconf_list_entry_description>div:nth-of-type(1)').text().trim(),
"receiving": conf.find('.mobileconf_list_entry_description>div:nth-of-type(2)').text().trim(),
"time": conf.find('.mobileconf_list_entry_description>div:nth-of-type(3)').text().trim(),
"icon": img.length < 1 ? '' : $(img).attr('src')
}));
});
callback(null, confs);
});
};
2015-12-03 11:20:16 +08:00
/**
* @callback SteamCommunity~getConfirmations
* @param {Error|null} err - An Error object on failure, or null on success
2019-09-24 16:28:05 +08:00
* @param {CConfirmation[]} [confirmations] - An array of CConfirmation objects
2015-12-03 11:20:16 +08:00
*/
/**
* Get the trade offer ID associated with a particular confirmation
* @param {int} confID - The ID of the confirmation in question
* @param {int} time - The unix timestamp with which the following key was generated
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag "details" (this key can be reused)
* @param {SteamCommunity~getConfirmationOfferID} callback
*/
2015-12-03 10:31:17 +08:00
SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, callback) {
2019-09-24 16:28:05 +08:00
request(this, "details/" + confID, key, time, "details", null, true, (err, body) => {
if (err) {
2015-12-03 10:31:17 +08:00
callback(err);
return;
2015-12-03 10:31:17 +08:00
}
2019-09-24 16:28:05 +08:00
if (!body.success) {
2015-12-03 10:31:17 +08:00
callback(new Error("Cannot load confirmation details"));
return;
}
2019-09-24 16:28:05 +08:00
let $ = Cheerio.load(body.html);
let offer = $('.tradeoffer');
if (offer.length < 1) {
2015-12-03 10:31:17 +08:00
callback(null, null);
return;
}
callback(null, offer.attr('id').split('_')[1]);
});
};
2015-12-03 11:20:16 +08:00
/**
* @callback SteamCommunity~getConfirmationOfferID
* @param {Error|null} err - An Error object on failure, or null on success
* @param {string} offerID - The trade offer ID associated with the specified confirmation, or null if not for an offer
*/
/**
* Confirm or cancel a given confirmation.
2019-09-24 16:28:05 +08:00
* @param {int|int[]|string|string[]} confID - The ID of the confirmation in question, or an array of confirmation IDs
* @param {string|string[]} confKey - The confirmation key associated with the confirmation in question (or an array of them) (not a TOTP key, the `key` property of CConfirmation)
2015-12-03 11:20:16 +08:00
* @param {int} time - The unix timestamp with which the following key was generated
* @param {string} key - The confirmation key that was generated using the preceding time and the tag "allow" (if accepting) or "cancel" (if not accepting)
2015-12-03 11:20:16 +08:00
* @param {boolean} accept - true if you want to accept the confirmation, false if you want to cancel it
* @param {SteamCommunity~genericErrorCallback} callback - Called when the request is complete
*/
2015-12-03 10:31:17 +08:00
SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time, key, accept, callback) {
request(this, (confID instanceof Array) ? "multiajaxop" : "ajaxop", key, time, accept ? "allow" : "cancel", {
2015-12-03 10:31:17 +08:00
"op": accept ? "allow" : "cancel",
"cid": confID,
"ck": confKey
2019-09-24 16:28:05 +08:00
}, true, (err, body) => {
if (!callback) {
2015-12-03 10:31:17 +08:00
return;
}
2019-09-24 16:28:05 +08:00
if (err) {
2015-12-03 10:31:17 +08:00
callback(err);
return;
}
2019-09-24 16:28:05 +08:00
if (body.success) {
2015-12-03 10:31:17 +08:00
callback(null);
return;
}
2019-09-24 16:28:05 +08:00
if (body.message) {
2015-12-03 10:31:17 +08:00
callback(new Error(body.message));
return;
}
callback(new Error("Could not act on confirmation"));
});
};
/**
* Accept a confirmation for a given object (trade offer or market listing) automatically.
* @param {string} identitySecret
* @param {number|string} objectID
* @param {SteamCommunity~genericErrorCallback} callback
*/
SteamCommunity.prototype.acceptConfirmationForObject = function(identitySecret, objectID, callback) {
this._usedConfTimes = this._usedConfTimes || [];
2019-09-24 16:28:05 +08:00
let doConfirmation = () => {
let offset = this._timeOffset;
let time = SteamTotp.time(offset);
this.getConfirmations(time, SteamTotp.getConfirmationKey(identitySecret, time, "conf"), (err, confs) => {
if (err) {
callback(err);
return;
}
2019-09-24 16:28:05 +08:00
let conf = confs.find(conf => conf.creator == objectID);
if (!conf) {
callback(new Error("Could not find confirmation for object " + objectID));
return;
}
// make sure we don't reuse the same time
2019-09-24 16:28:05 +08:00
let localOffset = 0;
do {
time = SteamTotp.time(offset) + localOffset++;
2019-09-24 16:28:05 +08:00
} while (this._usedConfTimes.indexOf(time) != -1);
2019-09-24 16:28:05 +08:00
this._usedConfTimes.push(time);
if (this._usedConfTimes.length > 60) {
this._usedConfTimes.splice(0, this._usedConfTimes.length - 60); // we don't need to save more than 60 entries
}
conf.respond(time, SteamTotp.getConfirmationKey(identitySecret, time, "allow"), true, callback);
});
2019-09-24 16:28:05 +08:00
};
if (typeof this._timeOffset !== 'undefined') {
// time offset is already known and saved
doConfirmation();
} else {
SteamTotp.getTimeOffset((err, offset) => {
if (err) {
callback(err);
return;
}
this._timeOffset = offset;
doConfirmation();
setTimeout(() => {
// Delete the saved time offset after 12 hours because why not
delete this._timeOffset;
}, 1000 * 60 * 60 * 12).unref();
2019-09-24 16:28:05 +08:00
});
}
2019-09-24 16:28:05 +08:00
};
/**
* Send a single request to Steam to accept all outstanding confirmations (after loading the list). If one fails, the
* entire request will fail and there will be no way to know which failed without loading the list again.
* @param {number} time
* @param {string} confKey
* @param {string} allowKey
* @param {function} callback
*/
2016-07-14 01:11:44 +08:00
SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowKey, callback) {
2019-09-24 16:28:05 +08:00
this.getConfirmations(time, confKey, (err, confs) => {
2016-07-14 01:11:44 +08:00
if (err) {
callback(err);
return;
}
if (confs.length == 0) {
callback(null, []);
return;
}
2019-09-24 16:28:05 +08:00
this.respondToConfirmation(confs.map(conf => conf.id), confs.map(conf => conf.key), time, allowKey, true, (err) => {
2016-07-14 01:11:44 +08:00
if (err) {
callback(err);
return;
}
callback(err, confs);
});
});
};
2015-12-03 10:31:17 +08:00
function request(community, url, key, time, tag, params, json, callback) {
if (!community.steamID) {
throw new Error("Must be logged in before trying to do anything with confirmations");
}
2015-12-03 10:31:17 +08:00
params = params || {};
params.p = SteamTotp.getDeviceID(community.steamID);
2015-12-03 10:31:17 +08:00
params.a = community.steamID.getSteamID64();
params.k = key;
params.t = time;
params.m = "android";
params.tag = tag;
2019-09-24 16:28:05 +08:00
let req = {
"method": url == 'multiajaxop' ? 'POST' : 'GET',
2015-12-03 10:31:17 +08:00
"uri": "https://steamcommunity.com/mobileconf/" + url,
"json": !!json
};
if (req.method == "GET") {
req.qs = params;
} else {
req.form = params;
}
2019-09-24 16:28:05 +08:00
community.httpRequest(req, (err, response, body) => {
if (err) {
callback(err);
2015-12-03 10:31:17 +08:00
return;
}
callback(null, body);
2019-09-24 16:38:27 +08:00
}, 'steamcommunity');
2015-12-03 10:31:17 +08:00
}