Updated confirmations.js methods to use new http interface

This commit is contained in:
Alex Corn 2023-06-27 03:38:56 -04:00
parent 028ee43bda
commit c1901d5f55
No known key found for this signature in database
GPG Key ID: E51989A3E7A27FDF
4 changed files with 148 additions and 205 deletions

View File

@ -1,3 +1,5 @@
const StdLib = require('@doctormckay/stdlib');
const SteamCommunity = require('../index.js'); const SteamCommunity = require('../index.js');
module.exports = CConfirmation; module.exports = CConfirmation;
@ -18,20 +20,33 @@ function CConfirmation(community, data) {
this.offerID = this.type == SteamCommunity.ConfirmationType.Trade ? this.creator : null; this.offerID = this.type == SteamCommunity.ConfirmationType.Trade ? this.creator : null;
} }
/**
* @param {number} time
* @param {string} key
* @param {function} [callback]
* @return Promise<{offerID: number}>
*/
CConfirmation.prototype.getOfferID = function(time, key, callback) { CConfirmation.prototype.getOfferID = function(time, key, callback) {
if (this.type && this.creator) { return StdLib.Promises.callbackPromise(['offerID'], null, false, async (resolve, reject) => {
if (this.type != SteamCommunity.ConfirmationType.Trade) { if (this.type && this.creator) {
callback(new Error('Not a trade confirmation')); if (this.type != SteamCommunity.ConfirmationType.Trade) {
return; return reject(new Error('Not a trade confirmation'));
}
return resolve({offerID: this.creator});
} }
callback(null, this.creator); return await this._community.getConfirmationOfferID(this.id, time, key, callback);
return; });
}
this._community.getConfirmationOfferID(this.id, time, key, callback);
}; };
/**
* @param {number} time
* @param {string} key
* @param {boolean} accept
* @param {function} [callback]
* @return Promise<void>
*/
CConfirmation.prototype.respond = function(time, key, accept, callback) { CConfirmation.prototype.respond = function(time, key, accept, callback) {
this._community.respondToConfirmation(this.id, this.key, time, key, accept, callback); return this._community.respondToConfirmation(this.id, this.key, time, key, accept, callback);
}; };

View File

@ -1,47 +1,34 @@
const Cheerio = require('cheerio'); const Cheerio = require('cheerio');
const StdLib = require('@doctormckay/stdlib');
const SteamTotp = require('steam-totp'); const SteamTotp = require('steam-totp');
const SteamCommunity = require('../index.js'); const SteamCommunity = require('../index.js');
const CConfirmation = require('../classes/CConfirmation.js'); const CConfirmation = require('../classes/CConfirmation.js');
var EConfirmationType = require('../resources/EConfirmationType.js'); const EConfirmationType = SteamCommunity.EConfirmationType;
/** /**
* Get a list of your account's currently outstanding confirmations. * Get a list of your account's currently outstanding confirmations.
* @param {int} time - The unix timestamp with which the following key was generated * @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 {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 * @param {SteamCommunity~getConfirmations} [callback] - Called when the list of confirmations is received
* @return Promise<{confirmations: CConfirmation[]}>
*/ */
SteamCommunity.prototype.getConfirmations = function(time, key, callback) { SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
var self = this; return StdLib.Promises.callbackPromise(['confirmations'], callback, false, async (resolve, reject) => {
let body = await request(this, 'getlist', key, time, 'list', null);
// Ugly hack to maintain backward compatibility
var tag = 'conf';
if (typeof key == 'object') {
tag = key.tag;
key = key.key;
}
// The official Steam app uses the tag 'list', but 'conf' still works so let's use that for backward compatibility.
request(this, 'getlist', key, time, tag, null, true, function(err, body) {
if (err) {
callback(err);
return;
}
if (!body.success) { if (!body.success) {
if (body.needauth) { if (body.needauth) {
var err = new Error('Not Logged In'); let err = new Error('Not Logged In');
self._notifySessionExpired(err); this._notifySessionExpired(err);
callback(err); return reject(err);
return;
} }
callback(new Error(body.message || body.detail || 'Failed to get confirmation list')); return reject(new Error(body.message || body.detail || 'Failed to get confirmation list'));
return;
} }
var confs = (body.conf || []).map(conf => new CConfirmation(self, { let confs = (body.conf || []).map(conf => new CConfirmation(this, {
id: conf.id, id: conf.id,
type: conf.type, type: conf.type,
creator: conf.creator_id, creator: conf.creator_id,
@ -54,7 +41,7 @@ SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
icon: conf.icon || '' icon: conf.icon || ''
})); }));
callback(null, confs); resolve({confirmations: confs});
}); });
}; };
@ -69,29 +56,24 @@ SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
* @param {int} confID - The ID of the confirmation in question * @param {int} confID - The ID of the confirmation in question
* @param {int} time - The unix timestamp with which the following key was generated * @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 "detail" (this key can be reused) * @param {string} key - The confirmation key that was generated using the preceeding time and the tag "detail" (this key can be reused)
* @param {SteamCommunity~getConfirmationOfferID} callback * @param {SteamCommunity~getConfirmationOfferID} [callback]
* @return Promise<{offerID: string|null}>
*/ */
SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, callback) { SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, callback) {
// The official Steam app uses the tag 'detail', but 'details' still works so let's use that for backward compatibility return StdLib.Promises.callbackPromise(['offerID'], callback, false, async (resolve, reject) => {
request(this, 'detailspage/' + confID, key, time, 'details', null, false, function(err, body) { let body = await request(this, 'detailspage/' + confID, key, time, 'detail', null);
if (err) {
callback(err);
return;
}
if (typeof body != 'string') { if (typeof body != 'string') {
callback(new Error('Cannot load confirmation details')); return reject(new Error('Cannot load confirmation details'));
return;
} }
let $ = Cheerio.load(body); let $ = Cheerio.load(body);
let offer = $('.tradeoffer'); let offer = $('.tradeoffer');
if (offer.length < 1) { if (offer.length < 1) {
callback(null, null); return resolve({offerID: null});
return;
} }
callback(null, offer.attr('id').split('_')[1]); resolve({offerID: offer.attr('id').split('_')[1]});
}); });
}; };
@ -108,42 +90,25 @@ SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, ca
* @param {int} time - The unix timestamp with which the following key was generated * @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) * @param {string} key - The confirmation key that was generated using the preceding time and the tag "allow" (if accepting) or "cancel" (if not accepting)
* @param {boolean} accept - true if you want to accept the confirmation, false if you want to cancel it * @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 * @param {SteamCommunity~genericErrorCallback} [callback] - Called when the request is complete
* @return Promise<void>
*/ */
SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time, key, accept, callback) { SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time, key, accept, callback) {
// Ugly hack to maintain backward compatibility return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
var tag = accept ? 'allow' : 'cancel'; let tag = accept ? 'accept' : 'reject';
if (typeof key == 'object') {
tag = key.tag;
key = key.key;
}
// The official app uses tags reject/accept, but cancel/allow still works so use these for backward compatibility // The official app uses tags reject/accept, but cancel/allow still works so use these for backward compatibility
request(this, (confID instanceof Array) ? 'multiajaxop' : 'ajaxop', key, time, tag, { let body = await request(this, (confID instanceof Array) ? 'multiajaxop' : 'ajaxop', key, time, tag, {
op: accept ? 'allow' : 'cancel', op: accept ? 'allow' : 'cancel',
cid: confID, cid: confID,
ck: confKey ck: confKey
}, true, function(err, body) { });
if (!callback) {
return;
}
if (err) {
callback(err);
return;
}
if (body.success) { if (body.success) {
callback(null); return resolve();
return;
} }
if (body.message) { reject(new Error(body.message || body.detail || 'Could not act on confirmation'));
callback(new Error(body.message));
return;
}
callback(new Error('Could not act on confirmation'));
}); });
}; };
@ -151,94 +116,79 @@ SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time,
* Accept a confirmation for a given object (trade offer or market listing) automatically. * Accept a confirmation for a given object (trade offer or market listing) automatically.
* @param {string} identitySecret * @param {string} identitySecret
* @param {number|string} objectID * @param {number|string} objectID
* @param {SteamCommunity~genericErrorCallback} callback * @param {SteamCommunity~genericErrorCallback} [callback]
* @return Promise<void>
*/ */
SteamCommunity.prototype.acceptConfirmationForObject = function(identitySecret, objectID, callback) { SteamCommunity.prototype.acceptConfirmationForObject = function(identitySecret, objectID, callback) {
this._usedConfTimes = this._usedConfTimes || []; this._usedConfTimes = this._usedConfTimes || [];
let doConfirmation = () => { return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
// Figure out our time offset
if (typeof this._timeOffset == 'undefined') {
await new Promise((resolve) => {
SteamTotp.getTimeOffset((err, offset) => {
if (err) {
// not critical that this succeeds
return resolve();
}
this._timeOffset = offset;
resolve();
});
});
}
let offset = this._timeOffset; let offset = this._timeOffset;
let time = SteamTotp.time(offset); let time = SteamTotp.time(offset);
this.getConfirmations(time, SteamTotp.getConfirmationKey(identitySecret, time, 'conf'), (err, confs) => { let key = SteamTotp.getConfirmationKey(identitySecret, time, 'list');
if (err) { let {confirmations} = await this.getConfirmations(time, key);
callback(err);
return;
}
let conf = confs.find(conf => conf.creator == objectID); let conf = confirmations.find(conf => conf.creator == objectID);
if (!conf) { if (!conf) {
callback(new Error('Could not find confirmation for object ' + objectID)); return reject(new Error(`Could not find confirmation for object ${objectID}`));
return; }
}
// make sure we don't reuse the same time // make sure we don't reuse the same time
let localOffset = 0; let localOffset = 0;
do { do {
time = SteamTotp.time(offset) + localOffset++; time = SteamTotp.time(offset) + localOffset++;
} while (this._usedConfTimes.indexOf(time) != -1); } while (this._usedConfTimes.includes(time));
this._usedConfTimes.push(time); this._usedConfTimes.push(time);
if (this._usedConfTimes.length > 60) { if (this._usedConfTimes.length > 60) {
this._usedConfTimes.splice(0, this._usedConfTimes.length - 60); // we don't need to save more than 60 entries 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); await conf.respond(time, SteamTotp.getConfirmationKey(identitySecret, time, 'accept'), true);
}); });
};
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();
});
}
}; };
/** /**
* Send a single request to Steam to accept all outstanding confirmations (after loading the list). If one fails, the * 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. * entire request will fail and there will be no way to know which failed without loading the list again.
* @param {number} time * @param {number} time
* @param {string} confKey * @param {string} listKey
* @param {string} allowKey * @param {string} acceptKey
* @param {function} callback * @param {function} [callback]
* @return Promise<{confirmations: CConfirmation[]}>
*/ */
SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowKey, callback) { SteamCommunity.prototype.acceptAllConfirmations = function(time, listKey, acceptKey, callback) {
this.getConfirmations(time, confKey, (err, confs) => { return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
if (err) { let {confirmations} = await this.getConfirmations(time, listKey);
callback(err);
return; if (confirmations.length == 0) {
return resolve({confirmations: []});
} }
if (confs.length == 0) { let confIds = confirmations.map(conf => conf.id);
callback(null, []); let confKeys = confirmations.map(conf => conf.key);
return; await this.respondToConfirmation(confIds, confKeys, time, acceptKey, true);
}
this.respondToConfirmation(confs.map(conf => conf.id), confs.map(conf => conf.key), time, allowKey, true, (err) => { resolve({confirmations});
if (err) {
callback(err);
return;
}
callback(err, confs);
});
}); });
}; };
function request(community, url, key, time, tag, params, json, callback) { async function request(community, url, key, time, tag, params) {
if (!community.steamID) { if (!community.steamID) {
throw new Error('Must be logged in before trying to do anything with confirmations'); throw new Error('Must be logged in before trying to do anything with confirmations');
} }
@ -253,8 +203,8 @@ function request(community, url, key, time, tag, params, json, callback) {
let req = { let req = {
method: url == 'multiajaxop' ? 'POST' : 'GET', method: url == 'multiajaxop' ? 'POST' : 'GET',
uri: 'https://steamcommunity.com/mobileconf/' + url, url: `https://steamcommunity.com/mobileconf/${url}`,
json: !!json source: 'steamcommunity'
}; };
if (req.method == 'GET') { if (req.method == 'GET') {
@ -263,12 +213,6 @@ function request(community, url, key, time, tag, params, json, callback) {
req.form = params; req.form = params;
} }
community.httpRequest(req, (err, response, body) => { let result = await community.httpRequest(req);
if (err) { return result.jsonBody || result.textBody;
callback(err);
return;
}
callback(null, body);
}, 'steamcommunity');
} }

View File

@ -1,4 +1,5 @@
const {HttpResponse} = require('@doctormckay/stdlib/http'); // eslint-disable-line const {HttpResponse} = require('@doctormckay/stdlib/http'); // eslint-disable-line
const {betterPromise} = require('@doctormckay/stdlib/promises');
const SteamCommunity = require('../index.js'); const SteamCommunity = require('../index.js');
@ -20,67 +21,50 @@ const SteamCommunity = require('../index.js');
* @return {Promise<HttpResponse>} * @return {Promise<HttpResponse>}
*/ */
SteamCommunity.prototype.httpRequest = function(options) { SteamCommunity.prototype.httpRequest = function(options) {
return new Promise((resolve, reject) => { return betterPromise(async (resolve, reject) => {
let requestID = ++this._httpRequestID; let requestID = ++this._httpRequestID;
let source = options.source || ''; let source = options.source || '';
let continued = false; await betterPromise((resolve, reject) => {
if (!this.onPreHttpRequest || !this.onPreHttpRequest(requestID, source, options, (err) => {
let continueRequest = async (err) => { err ? reject(err) : resolve();
if (continued) { })) {
return; // No pre-hook, or the pre-hook doesn't want to delay the request.
resolve();
} }
});
continued = true; let result = await this._httpClient.request({
method: options.method,
url: options.url,
queryString: options.qs,
headers: options.headers,
body: options.body,
urlEncodedForm: options.form,
multipartForm: options.multipartForm,
json: options.json,
followRedirects: options.followRedirect
});
if (err) { let httpError = options.checkHttpError !== false && this._checkHttpError(result);
return reject(err); let communityError = !options.json && options.checkCommunityError !== false && this._checkCommunityError(result);
} let tradeError = !options.json && options.checkTradeError !== false && this._checkTradeError(result);
let jsonError = options.json && options.checkJsonError !== false && !result.jsonBody ? new Error('Malformed JSON response') : null;
/** @var {HttpResponse} result */ this.emit('postHttpRequest', {
let result; requestID,
source,
options,
response: result,
body: result.textBody,
error: httpError || communityError || tradeError || jsonError || null,
httpError,
communityError,
tradeError,
jsonError
});
try { resolve(result);
result = await this._httpClient.request({
method: options.method,
url: options.url,
queryString: options.qs,
headers: options.headers,
body: options.body,
urlEncodedForm: options.form,
multipartForm: options.multipartForm,
json: options.json,
followRedirects: options.followRedirect
});
} catch (ex) {
return reject(ex);
}
let httpError = options.checkHttpError !== false && this._checkHttpError(result);
let communityError = !options.json && options.checkCommunityError !== false && this._checkCommunityError(result);
let tradeError = !options.json && options.checkTradeError !== false && this._checkTradeError(result);
let jsonError = options.json && options.checkJsonError !== false && !result.jsonBody ? new Error('Malformed JSON response') : null;
this.emit('postHttpRequest', {
requestID,
source,
options,
response: result,
body: result.textBody,
error: httpError || communityError || tradeError || jsonError || null,
httpError,
communityError,
tradeError,
jsonError
});
resolve(result);
};
if (!this.onPreHttpRequest || !this.onPreHttpRequest(requestID, source, options, continueRequest)) {
// No pre-hook, or the pre-hook doesn't want to delay the request.
continueRequest(null);
}
}); });
}; };

View File

@ -22,7 +22,7 @@
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git" "url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
}, },
"dependencies": { "dependencies": {
"@doctormckay/stdlib": "^2.5.0", "@doctormckay/stdlib": "^2.6.0",
"cheerio": "0.22.0", "cheerio": "0.22.0",
"image-size": "^0.8.2", "image-size": "^0.8.2",
"request": "^2.88.0", "request": "^2.88.0",