steamcommunity 3.46.1 → 3.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/classes/CConfirmation.js +37 -37
- package/classes/CEconItem.js +120 -120
- package/classes/CMarketItem.js +189 -189
- package/classes/CMarketSearchResult.js +89 -89
- package/classes/CSteamGroup.js +155 -155
- package/classes/CSteamUser.js +225 -217
- package/components/chat.js +283 -283
- package/components/confirmations.js +428 -428
- package/components/groups.js +798 -732
- package/components/help.js +64 -64
- package/components/helpers.js +128 -108
- package/components/http.js +150 -150
- package/components/inventoryhistory.js +173 -173
- package/components/market.js +387 -387
- package/components/profile.js +475 -475
- package/components/twofactor.js +152 -152
- package/components/users.js +831 -767
- package/components/webapi.js +118 -118
- package/examples/edit-group-announcement.js +118 -118
- package/package.json +9 -2
- package/resources/EChatState.js +14 -14
- package/resources/EConfirmationType.js +12 -12
- package/resources/EFriendRelationship.js +23 -23
- package/resources/EPersonaState.js +23 -23
- package/resources/EPersonaStateFlag.js +32 -32
- package/resources/EResult.js +254 -254
- package/resources/ESharedFileType.js +13 -13
- package/.editorconfig +0 -13
- package/.github/FUNDING.yml +0 -2
- package/.idea/.name +0 -1
- package/.idea/codeStyleSettings.xml +0 -13
- package/.idea/codeStyles/Project.xml +0 -15
- package/.idea/codeStyles/codeStyleConfig.xml +0 -6
- package/.idea/copyright/profiles_settings.xml +0 -3
- package/.idea/encodings.xml +0 -6
- package/.idea/inspectionProfiles/Project_Default.xml +0 -11
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -9
- package/.idea/node-steamcommunity.iml +0 -8
- package/.idea/steamcommunity.iml +0 -10
- package/.idea/vcs.xml +0 -7
- package/.idea/watcherTasks.xml +0 -4
- package/CONTRIBUTING.md +0 -36
|
@@ -1,428 +1,428 @@
|
|
|
1
|
-
var SteamCommunity = require('../index.js');
|
|
2
|
-
var Cheerio = require('cheerio');
|
|
3
|
-
var SteamTotp = require('steam-totp');
|
|
4
|
-
var Async = require('async');
|
|
5
|
-
|
|
6
|
-
var CConfirmation = require('../classes/CConfirmation.js');
|
|
7
|
-
var EConfirmationType = require('../resources/EConfirmationType.js');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Get a list of your account's currently outstanding confirmations.
|
|
11
|
-
* @param {int} time - The unix timestamp with which the following key was generated
|
|
12
|
-
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag 'conf' (this key can be reused)
|
|
13
|
-
* @param {SteamCommunity~getConfirmations} callback - Called when the list of confirmations is received
|
|
14
|
-
*/
|
|
15
|
-
SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
|
|
16
|
-
var self = this;
|
|
17
|
-
|
|
18
|
-
// Ugly hack to maintain backward compatibility
|
|
19
|
-
var tag = 'conf';
|
|
20
|
-
if (typeof key == 'object') {
|
|
21
|
-
tag = key.tag;
|
|
22
|
-
key = key.key;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// The official Steam app uses the tag 'list', but 'conf' still works so let's use that for backward compatibility.
|
|
26
|
-
request(this, 'getlist', key, time, tag, null, true, function(err, body) {
|
|
27
|
-
if (err) {
|
|
28
|
-
callback(err);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!body.success) {
|
|
33
|
-
if (body.needauth) {
|
|
34
|
-
var err = new Error('Not Logged In');
|
|
35
|
-
self._notifySessionExpired(err);
|
|
36
|
-
callback(err);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
callback(new Error(body.message || body.detail || 'Failed to get confirmation list'));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
var confs = (body.conf || []).map(conf => new CConfirmation(self, {
|
|
45
|
-
id: conf.id,
|
|
46
|
-
type: conf.type,
|
|
47
|
-
creator: conf.creator_id,
|
|
48
|
-
key: conf.nonce,
|
|
49
|
-
title: `${conf.type_name || 'Confirm'} - ${conf.headline || ''}`,
|
|
50
|
-
receiving: conf.type == EConfirmationType.Trade ? ((conf.summary || [])[1] || '') : '',
|
|
51
|
-
sending: (conf.summary || [])[0] || '',
|
|
52
|
-
time: (new Date(conf.creation_time * 1000)).toISOString(), // for backward compatibility
|
|
53
|
-
timestamp: new Date(conf.creation_time * 1000),
|
|
54
|
-
icon: conf.icon || ''
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
|
-
callback(null, confs);
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* @callback SteamCommunity~getConfirmations
|
|
63
|
-
* @param {Error|null} err - An Error object on failure, or null on success
|
|
64
|
-
* @param {CConfirmation[]} confirmations - An array of CConfirmation objects
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get the trade offer ID associated with a particular confirmation
|
|
69
|
-
* @param {int} confID - The ID of the confirmation in question
|
|
70
|
-
* @param {int} time - The unix timestamp with which the following key was generated
|
|
71
|
-
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag "detail" (this key can be reused)
|
|
72
|
-
* @param {SteamCommunity~getConfirmationOfferID} callback
|
|
73
|
-
*/
|
|
74
|
-
SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, callback) {
|
|
75
|
-
// The official Steam app uses the tag 'detail', but 'details' still works so let's use that for backward compatibility
|
|
76
|
-
request(this, 'detailspage/' + confID, key, time, 'details', null, false, function(err, body) {
|
|
77
|
-
if (err) {
|
|
78
|
-
callback(err);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (typeof body != 'string') {
|
|
83
|
-
callback(new Error("Cannot load confirmation details"));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
var $ = Cheerio.load(body);
|
|
88
|
-
var offer = $('.tradeoffer');
|
|
89
|
-
if(offer.length < 1) {
|
|
90
|
-
callback(null, null);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
callback(null, offer.attr('id').split('_')[1]);
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* @callback SteamCommunity~getConfirmationOfferID
|
|
100
|
-
* @param {Error|null} err - An Error object on failure, or null on success
|
|
101
|
-
* @param {string} offerID - The trade offer ID associated with the specified confirmation, or null if not for an offer
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Confirm or cancel a given confirmation.
|
|
106
|
-
* @param {int|int[]} confID - The ID of the confirmation in question, or an array of confirmation IDs
|
|
107
|
-
* @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)
|
|
108
|
-
* @param {int} time - The unix timestamp with which the following key was generated
|
|
109
|
-
* @param {string} key - The confirmation key that was generated using the preceding time and the tag "allow" (if accepting) or "cancel" (if not accepting)
|
|
110
|
-
* @param {boolean} accept - true if you want to accept the confirmation, false if you want to cancel it
|
|
111
|
-
* @param {SteamCommunity~genericErrorCallback} callback - Called when the request is complete
|
|
112
|
-
*/
|
|
113
|
-
SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time, key, accept, callback) {
|
|
114
|
-
// Ugly hack to maintain backward compatibility
|
|
115
|
-
var tag = accept ? 'allow' : 'cancel';
|
|
116
|
-
if (typeof key == 'object') {
|
|
117
|
-
tag = key.tag;
|
|
118
|
-
key = key.key;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// The official app uses tags reject/accept, but cancel/allow still works so use these for backward compatibility
|
|
122
|
-
request(this, (confID instanceof Array) ? 'multiajaxop' : 'ajaxop', key, time, tag, {
|
|
123
|
-
op: accept ? 'allow' : 'cancel',
|
|
124
|
-
cid: confID,
|
|
125
|
-
ck: confKey
|
|
126
|
-
}, true, function(err, body) {
|
|
127
|
-
if (!callback) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (err) {
|
|
132
|
-
callback(err);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (body.success) {
|
|
137
|
-
callback(null);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (body.message) {
|
|
142
|
-
callback(new Error(body.message));
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
callback(new Error('Could not act on confirmation'));
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Accept a confirmation for a given object (trade offer or market listing) automatically.
|
|
152
|
-
* @param {string} identitySecret
|
|
153
|
-
* @param {number|string} objectID
|
|
154
|
-
* @param {SteamCommunity~genericErrorCallback} callback
|
|
155
|
-
*/
|
|
156
|
-
SteamCommunity.prototype.acceptConfirmationForObject = function(identitySecret, objectID, callback) {
|
|
157
|
-
var self = this;
|
|
158
|
-
this._usedConfTimes = this._usedConfTimes || [];
|
|
159
|
-
|
|
160
|
-
if (typeof this._timeOffset !== 'undefined') {
|
|
161
|
-
// time offset is already known and saved
|
|
162
|
-
doConfirmation();
|
|
163
|
-
} else {
|
|
164
|
-
SteamTotp.getTimeOffset(function(err, offset) {
|
|
165
|
-
if (err) {
|
|
166
|
-
callback(err);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
self._timeOffset = offset;
|
|
171
|
-
doConfirmation();
|
|
172
|
-
|
|
173
|
-
setTimeout(function() {
|
|
174
|
-
// Delete the saved time offset after 12 hours because why not
|
|
175
|
-
delete self._timeOffset;
|
|
176
|
-
}, 1000 * 60 * 60 * 12).unref();
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function doConfirmation() {
|
|
181
|
-
var offset = self._timeOffset;
|
|
182
|
-
var time = SteamTotp.time(offset);
|
|
183
|
-
var confKey = SteamTotp.getConfirmationKey(identitySecret, time, 'list');
|
|
184
|
-
self.getConfirmations(time, {tag: 'list', key: confKey}, function(err, confs) {
|
|
185
|
-
if (err) {
|
|
186
|
-
callback(err);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
var conf = confs.filter(function(conf) { return conf.creator == objectID; });
|
|
191
|
-
if (conf.length == 0) {
|
|
192
|
-
callback(new Error('Could not find confirmation for object ' + objectID));
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
conf = conf[0];
|
|
197
|
-
|
|
198
|
-
// make sure we don't reuse the same time
|
|
199
|
-
var localOffset = 0;
|
|
200
|
-
do {
|
|
201
|
-
time = SteamTotp.time(offset) + localOffset++;
|
|
202
|
-
} while (self._usedConfTimes.indexOf(time) != -1);
|
|
203
|
-
|
|
204
|
-
self._usedConfTimes.push(time);
|
|
205
|
-
if (self._usedConfTimes.length > 60) {
|
|
206
|
-
self._usedConfTimes.splice(0, self._usedConfTimes.length - 60); // we don't need to save more than 60 entries
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
confKey = SteamTotp.getConfirmationKey(identitySecret, time, 'accept');
|
|
210
|
-
conf.respond(time, {tag: 'accept', key: confKey}, true, callback);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Send a single request to Steam to accept all outstanding confirmations (after loading the list). If one fails, the
|
|
217
|
-
* entire request will fail and there will be no way to know which failed without loading the list again.
|
|
218
|
-
* @param {number} time
|
|
219
|
-
* @param {string} confKey
|
|
220
|
-
* @param {string} allowKey
|
|
221
|
-
* @param {function} callback
|
|
222
|
-
*/
|
|
223
|
-
SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowKey, callback) {
|
|
224
|
-
var self = this;
|
|
225
|
-
|
|
226
|
-
this.getConfirmations(time, confKey, function(err, confs) {
|
|
227
|
-
if (err) {
|
|
228
|
-
callback(err);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (confs.length == 0) {
|
|
233
|
-
callback(null, []);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
self.respondToConfirmation(confs.map(function(conf) { return conf.id; }), confs.map(function(conf) { return conf.key; }), time, allowKey, true, function(err) {
|
|
238
|
-
if (err) {
|
|
239
|
-
callback(err);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
callback(err, confs);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
function request(community, url, key, time, tag, params, json, callback) {
|
|
249
|
-
if (!community.steamID) {
|
|
250
|
-
throw new Error('Must be logged in before trying to do anything with confirmations');
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
params = params || {};
|
|
254
|
-
params.p = SteamTotp.getDeviceID(community.steamID);
|
|
255
|
-
params.a = community.steamID.getSteamID64();
|
|
256
|
-
params.k = key;
|
|
257
|
-
params.t = time;
|
|
258
|
-
params.m = 'react';
|
|
259
|
-
params.tag = tag;
|
|
260
|
-
|
|
261
|
-
var req = {
|
|
262
|
-
method: url == 'multiajaxop' ? 'POST' : 'GET',
|
|
263
|
-
uri: 'https://steamcommunity.com/mobileconf/' + url,
|
|
264
|
-
json: !!json
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
if (req.method == 'GET') {
|
|
268
|
-
req.qs = params;
|
|
269
|
-
} else {
|
|
270
|
-
req.form = params;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
community.httpRequest(req, function(err, response, body) {
|
|
274
|
-
if (err) {
|
|
275
|
-
callback(err);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
callback(null, body);
|
|
280
|
-
}, 'steamcommunity');
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Confirmation checker
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* 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
|
|
287
|
-
* @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.
|
|
288
|
-
* @param {Buffer|string|null} [identitySecret=null] - Your identity_secret. If passed, all confirmations will be automatically accepted and nothing will be emitted.
|
|
289
|
-
*/
|
|
290
|
-
SteamCommunity.prototype.startConfirmationChecker = function(pollInterval, identitySecret) {
|
|
291
|
-
this._confirmationPollInterval = pollInterval;
|
|
292
|
-
this._knownConfirmations = this._knownConfirmations || {};
|
|
293
|
-
this._confirmationKeys = this._confirmationKeys || {};
|
|
294
|
-
this._identitySecret = identitySecret;
|
|
295
|
-
|
|
296
|
-
if(this._confirmationTimer) {
|
|
297
|
-
clearTimeout(this._confirmationTimer);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
setTimeout(this.checkConfirmations.bind(this), 500);
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Stop automatically polling our confirmations.
|
|
305
|
-
*/
|
|
306
|
-
SteamCommunity.prototype.stopConfirmationChecker = function() {
|
|
307
|
-
if(this._confirmationPollInterval) {
|
|
308
|
-
delete this._confirmationPollInterval;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if(this._identitySecret) {
|
|
312
|
-
delete this._identitySecret;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if(this._confirmationTimer) {
|
|
316
|
-
clearTimeout(this._confirmationTimer);
|
|
317
|
-
delete this._confirmationTimer;
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Run the confirmation checker right now instead of waiting for the next poll.
|
|
323
|
-
* Useful to call right after you send/accept an offer that needs confirmation.
|
|
324
|
-
*/
|
|
325
|
-
SteamCommunity.prototype.checkConfirmations = function() {
|
|
326
|
-
if(this._confirmationTimer) {
|
|
327
|
-
clearTimeout(this._confirmationTimer);
|
|
328
|
-
delete this._confirmationTimer;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
var self = this;
|
|
332
|
-
if(!this._confirmationQueue) {
|
|
333
|
-
this._confirmationQueue = Async.queue(function(conf, callback) {
|
|
334
|
-
// Worker to process new confirmations
|
|
335
|
-
if(self._identitySecret) {
|
|
336
|
-
// We should accept this
|
|
337
|
-
self.emit('debug', "Accepting confirmation #" + conf.id);
|
|
338
|
-
var time = Math.floor(Date.now() / 1000);
|
|
339
|
-
conf.respond(time, SteamTotp.getConfirmationKey(self._identitySecret, time, "allow"), true, function(err) {
|
|
340
|
-
// If there was an error and it wasn't actually accepted, we'll pick it up again
|
|
341
|
-
if (!err) self.emit('confirmationAccepted', conf);
|
|
342
|
-
delete self._knownConfirmations[conf.id];
|
|
343
|
-
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
|
|
344
|
-
});
|
|
345
|
-
} else {
|
|
346
|
-
self.emit('newConfirmation', conf);
|
|
347
|
-
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
|
|
348
|
-
}
|
|
349
|
-
}, 1);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
this.emit('debug', 'Checking confirmations');
|
|
353
|
-
|
|
354
|
-
this._confirmationCheckerGetKey('conf', function(err, key) {
|
|
355
|
-
if(err) {
|
|
356
|
-
resetTimer();
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
self.getConfirmations(key.time, key.key, function(err, confirmations) {
|
|
361
|
-
if(err) {
|
|
362
|
-
self.emit('debug', "Can't check confirmations: " + err.message);
|
|
363
|
-
resetTimer();
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
var known = self._knownConfirmations;
|
|
368
|
-
|
|
369
|
-
var newOnes = confirmations.filter(function(conf) {
|
|
370
|
-
return !known[conf.id];
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
if(newOnes.length < 1) {
|
|
374
|
-
resetTimer();
|
|
375
|
-
return; // No new ones
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// We have new confirmations!
|
|
379
|
-
newOnes.forEach(function(conf) {
|
|
380
|
-
self._knownConfirmations[conf.id] = conf; // Add it to our list of known confirmations
|
|
381
|
-
self._confirmationQueue.push(conf);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
resetTimer();
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
function resetTimer() {
|
|
389
|
-
if(self._confirmationPollInterval) {
|
|
390
|
-
self._confirmationTimer = setTimeout(self.checkConfirmations.bind(self), self._confirmationPollInterval);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
SteamCommunity.prototype._confirmationCheckerGetKey = function(tag, callback) {
|
|
396
|
-
if(this._identitySecret) {
|
|
397
|
-
if(tag == 'details') {
|
|
398
|
-
// We don't care about details
|
|
399
|
-
callback(new Error("Disabled"));
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
var time = Math.floor(Date.now() / 1000);
|
|
404
|
-
callback(null, {"time": time, "key": SteamTotp.getConfirmationKey(this._identitySecret, time, tag)});
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
var existing = this._confirmationKeys[tag];
|
|
409
|
-
var reusable = ['conf', 'details'];
|
|
410
|
-
|
|
411
|
-
// See if we already have a key that we can reuse.
|
|
412
|
-
if(reusable.indexOf(tag) != -1 && existing && (Date.now() - (existing.time * 1000) < (1000 * 60 * 5))) {
|
|
413
|
-
callback(null, existing);
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// We need a fresh one
|
|
418
|
-
var self = this;
|
|
419
|
-
this.emit('confKeyNeeded', tag, function(err, time, key) {
|
|
420
|
-
if(err) {
|
|
421
|
-
callback(err);
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
self._confirmationKeys[tag] = {"time": time, "key": key};
|
|
426
|
-
callback(null, {"time": time, "key": key});
|
|
427
|
-
});
|
|
428
|
-
};
|
|
1
|
+
var SteamCommunity = require('../index.js');
|
|
2
|
+
var Cheerio = require('cheerio');
|
|
3
|
+
var SteamTotp = require('steam-totp');
|
|
4
|
+
var Async = require('async');
|
|
5
|
+
|
|
6
|
+
var CConfirmation = require('../classes/CConfirmation.js');
|
|
7
|
+
var EConfirmationType = require('../resources/EConfirmationType.js');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get a list of your account's currently outstanding confirmations.
|
|
11
|
+
* @param {int} time - The unix timestamp with which the following key was generated
|
|
12
|
+
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag 'conf' (this key can be reused)
|
|
13
|
+
* @param {SteamCommunity~getConfirmations} callback - Called when the list of confirmations is received
|
|
14
|
+
*/
|
|
15
|
+
SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
|
|
16
|
+
var self = this;
|
|
17
|
+
|
|
18
|
+
// Ugly hack to maintain backward compatibility
|
|
19
|
+
var tag = 'conf';
|
|
20
|
+
if (typeof key == 'object') {
|
|
21
|
+
tag = key.tag;
|
|
22
|
+
key = key.key;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The official Steam app uses the tag 'list', but 'conf' still works so let's use that for backward compatibility.
|
|
26
|
+
request(this, 'getlist', key, time, tag, null, true, function(err, body) {
|
|
27
|
+
if (err) {
|
|
28
|
+
callback(err);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!body.success) {
|
|
33
|
+
if (body.needauth) {
|
|
34
|
+
var err = new Error('Not Logged In');
|
|
35
|
+
self._notifySessionExpired(err);
|
|
36
|
+
callback(err);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
callback(new Error(body.message || body.detail || 'Failed to get confirmation list'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var confs = (body.conf || []).map(conf => new CConfirmation(self, {
|
|
45
|
+
id: conf.id,
|
|
46
|
+
type: conf.type,
|
|
47
|
+
creator: conf.creator_id,
|
|
48
|
+
key: conf.nonce,
|
|
49
|
+
title: `${conf.type_name || 'Confirm'} - ${conf.headline || ''}`,
|
|
50
|
+
receiving: conf.type == EConfirmationType.Trade ? ((conf.summary || [])[1] || '') : '',
|
|
51
|
+
sending: (conf.summary || [])[0] || '',
|
|
52
|
+
time: (new Date(conf.creation_time * 1000)).toISOString(), // for backward compatibility
|
|
53
|
+
timestamp: new Date(conf.creation_time * 1000),
|
|
54
|
+
icon: conf.icon || ''
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
callback(null, confs);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @callback SteamCommunity~getConfirmations
|
|
63
|
+
* @param {Error|null} err - An Error object on failure, or null on success
|
|
64
|
+
* @param {CConfirmation[]} confirmations - An array of CConfirmation objects
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the trade offer ID associated with a particular confirmation
|
|
69
|
+
* @param {int} confID - The ID of the confirmation in question
|
|
70
|
+
* @param {int} time - The unix timestamp with which the following key was generated
|
|
71
|
+
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag "detail" (this key can be reused)
|
|
72
|
+
* @param {SteamCommunity~getConfirmationOfferID} callback
|
|
73
|
+
*/
|
|
74
|
+
SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, callback) {
|
|
75
|
+
// The official Steam app uses the tag 'detail', but 'details' still works so let's use that for backward compatibility
|
|
76
|
+
request(this, 'detailspage/' + confID, key, time, 'details', null, false, function(err, body) {
|
|
77
|
+
if (err) {
|
|
78
|
+
callback(err);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof body != 'string') {
|
|
83
|
+
callback(new Error("Cannot load confirmation details"));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
var $ = Cheerio.load(body);
|
|
88
|
+
var offer = $('.tradeoffer');
|
|
89
|
+
if(offer.length < 1) {
|
|
90
|
+
callback(null, null);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
callback(null, offer.attr('id').split('_')[1]);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @callback SteamCommunity~getConfirmationOfferID
|
|
100
|
+
* @param {Error|null} err - An Error object on failure, or null on success
|
|
101
|
+
* @param {string} offerID - The trade offer ID associated with the specified confirmation, or null if not for an offer
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Confirm or cancel a given confirmation.
|
|
106
|
+
* @param {int|int[]} confID - The ID of the confirmation in question, or an array of confirmation IDs
|
|
107
|
+
* @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)
|
|
108
|
+
* @param {int} time - The unix timestamp with which the following key was generated
|
|
109
|
+
* @param {string} key - The confirmation key that was generated using the preceding time and the tag "allow" (if accepting) or "cancel" (if not accepting)
|
|
110
|
+
* @param {boolean} accept - true if you want to accept the confirmation, false if you want to cancel it
|
|
111
|
+
* @param {SteamCommunity~genericErrorCallback} callback - Called when the request is complete
|
|
112
|
+
*/
|
|
113
|
+
SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time, key, accept, callback) {
|
|
114
|
+
// Ugly hack to maintain backward compatibility
|
|
115
|
+
var tag = accept ? 'allow' : 'cancel';
|
|
116
|
+
if (typeof key == 'object') {
|
|
117
|
+
tag = key.tag;
|
|
118
|
+
key = key.key;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The official app uses tags reject/accept, but cancel/allow still works so use these for backward compatibility
|
|
122
|
+
request(this, (confID instanceof Array) ? 'multiajaxop' : 'ajaxop', key, time, tag, {
|
|
123
|
+
op: accept ? 'allow' : 'cancel',
|
|
124
|
+
cid: confID,
|
|
125
|
+
ck: confKey
|
|
126
|
+
}, true, function(err, body) {
|
|
127
|
+
if (!callback) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (err) {
|
|
132
|
+
callback(err);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (body.success) {
|
|
137
|
+
callback(null);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (body.message) {
|
|
142
|
+
callback(new Error(body.message));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
callback(new Error('Could not act on confirmation'));
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Accept a confirmation for a given object (trade offer or market listing) automatically.
|
|
152
|
+
* @param {string} identitySecret
|
|
153
|
+
* @param {number|string} objectID
|
|
154
|
+
* @param {SteamCommunity~genericErrorCallback} callback
|
|
155
|
+
*/
|
|
156
|
+
SteamCommunity.prototype.acceptConfirmationForObject = function(identitySecret, objectID, callback) {
|
|
157
|
+
var self = this;
|
|
158
|
+
this._usedConfTimes = this._usedConfTimes || [];
|
|
159
|
+
|
|
160
|
+
if (typeof this._timeOffset !== 'undefined') {
|
|
161
|
+
// time offset is already known and saved
|
|
162
|
+
doConfirmation();
|
|
163
|
+
} else {
|
|
164
|
+
SteamTotp.getTimeOffset(function(err, offset) {
|
|
165
|
+
if (err) {
|
|
166
|
+
callback(err);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
self._timeOffset = offset;
|
|
171
|
+
doConfirmation();
|
|
172
|
+
|
|
173
|
+
setTimeout(function() {
|
|
174
|
+
// Delete the saved time offset after 12 hours because why not
|
|
175
|
+
delete self._timeOffset;
|
|
176
|
+
}, 1000 * 60 * 60 * 12).unref();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function doConfirmation() {
|
|
181
|
+
var offset = self._timeOffset;
|
|
182
|
+
var time = SteamTotp.time(offset);
|
|
183
|
+
var confKey = SteamTotp.getConfirmationKey(identitySecret, time, 'list');
|
|
184
|
+
self.getConfirmations(time, {tag: 'list', key: confKey}, function(err, confs) {
|
|
185
|
+
if (err) {
|
|
186
|
+
callback(err);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var conf = confs.filter(function(conf) { return conf.creator == objectID; });
|
|
191
|
+
if (conf.length == 0) {
|
|
192
|
+
callback(new Error('Could not find confirmation for object ' + objectID));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
conf = conf[0];
|
|
197
|
+
|
|
198
|
+
// make sure we don't reuse the same time
|
|
199
|
+
var localOffset = 0;
|
|
200
|
+
do {
|
|
201
|
+
time = SteamTotp.time(offset) + localOffset++;
|
|
202
|
+
} while (self._usedConfTimes.indexOf(time) != -1);
|
|
203
|
+
|
|
204
|
+
self._usedConfTimes.push(time);
|
|
205
|
+
if (self._usedConfTimes.length > 60) {
|
|
206
|
+
self._usedConfTimes.splice(0, self._usedConfTimes.length - 60); // we don't need to save more than 60 entries
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
confKey = SteamTotp.getConfirmationKey(identitySecret, time, 'accept');
|
|
210
|
+
conf.respond(time, {tag: 'accept', key: confKey}, true, callback);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Send a single request to Steam to accept all outstanding confirmations (after loading the list). If one fails, the
|
|
217
|
+
* entire request will fail and there will be no way to know which failed without loading the list again.
|
|
218
|
+
* @param {number} time
|
|
219
|
+
* @param {string} confKey
|
|
220
|
+
* @param {string} allowKey
|
|
221
|
+
* @param {function} callback
|
|
222
|
+
*/
|
|
223
|
+
SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowKey, callback) {
|
|
224
|
+
var self = this;
|
|
225
|
+
|
|
226
|
+
this.getConfirmations(time, confKey, function(err, confs) {
|
|
227
|
+
if (err) {
|
|
228
|
+
callback(err);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (confs.length == 0) {
|
|
233
|
+
callback(null, []);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
self.respondToConfirmation(confs.map(function(conf) { return conf.id; }), confs.map(function(conf) { return conf.key; }), time, allowKey, true, function(err) {
|
|
238
|
+
if (err) {
|
|
239
|
+
callback(err);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
callback(err, confs);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
function request(community, url, key, time, tag, params, json, callback) {
|
|
249
|
+
if (!community.steamID) {
|
|
250
|
+
throw new Error('Must be logged in before trying to do anything with confirmations');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
params = params || {};
|
|
254
|
+
params.p = SteamTotp.getDeviceID(community.steamID);
|
|
255
|
+
params.a = community.steamID.getSteamID64();
|
|
256
|
+
params.k = key;
|
|
257
|
+
params.t = time;
|
|
258
|
+
params.m = 'react';
|
|
259
|
+
params.tag = tag;
|
|
260
|
+
|
|
261
|
+
var req = {
|
|
262
|
+
method: url == 'multiajaxop' ? 'POST' : 'GET',
|
|
263
|
+
uri: 'https://steamcommunity.com/mobileconf/' + url,
|
|
264
|
+
json: !!json
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
if (req.method == 'GET') {
|
|
268
|
+
req.qs = params;
|
|
269
|
+
} else {
|
|
270
|
+
req.form = params;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
community.httpRequest(req, function(err, response, body) {
|
|
274
|
+
if (err) {
|
|
275
|
+
callback(err);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
callback(null, body);
|
|
280
|
+
}, 'steamcommunity');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Confirmation checker
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 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
|
|
287
|
+
* @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.
|
|
288
|
+
* @param {Buffer|string|null} [identitySecret=null] - Your identity_secret. If passed, all confirmations will be automatically accepted and nothing will be emitted.
|
|
289
|
+
*/
|
|
290
|
+
SteamCommunity.prototype.startConfirmationChecker = function(pollInterval, identitySecret) {
|
|
291
|
+
this._confirmationPollInterval = pollInterval;
|
|
292
|
+
this._knownConfirmations = this._knownConfirmations || {};
|
|
293
|
+
this._confirmationKeys = this._confirmationKeys || {};
|
|
294
|
+
this._identitySecret = identitySecret;
|
|
295
|
+
|
|
296
|
+
if(this._confirmationTimer) {
|
|
297
|
+
clearTimeout(this._confirmationTimer);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
setTimeout(this.checkConfirmations.bind(this), 500);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Stop automatically polling our confirmations.
|
|
305
|
+
*/
|
|
306
|
+
SteamCommunity.prototype.stopConfirmationChecker = function() {
|
|
307
|
+
if(this._confirmationPollInterval) {
|
|
308
|
+
delete this._confirmationPollInterval;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if(this._identitySecret) {
|
|
312
|
+
delete this._identitySecret;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if(this._confirmationTimer) {
|
|
316
|
+
clearTimeout(this._confirmationTimer);
|
|
317
|
+
delete this._confirmationTimer;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Run the confirmation checker right now instead of waiting for the next poll.
|
|
323
|
+
* Useful to call right after you send/accept an offer that needs confirmation.
|
|
324
|
+
*/
|
|
325
|
+
SteamCommunity.prototype.checkConfirmations = function() {
|
|
326
|
+
if(this._confirmationTimer) {
|
|
327
|
+
clearTimeout(this._confirmationTimer);
|
|
328
|
+
delete this._confirmationTimer;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
var self = this;
|
|
332
|
+
if(!this._confirmationQueue) {
|
|
333
|
+
this._confirmationQueue = Async.queue(function(conf, callback) {
|
|
334
|
+
// Worker to process new confirmations
|
|
335
|
+
if(self._identitySecret) {
|
|
336
|
+
// We should accept this
|
|
337
|
+
self.emit('debug', "Accepting confirmation #" + conf.id);
|
|
338
|
+
var time = Math.floor(Date.now() / 1000);
|
|
339
|
+
conf.respond(time, SteamTotp.getConfirmationKey(self._identitySecret, time, "allow"), true, function(err) {
|
|
340
|
+
// If there was an error and it wasn't actually accepted, we'll pick it up again
|
|
341
|
+
if (!err) self.emit('confirmationAccepted', conf);
|
|
342
|
+
delete self._knownConfirmations[conf.id];
|
|
343
|
+
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
self.emit('newConfirmation', conf);
|
|
347
|
+
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
|
|
348
|
+
}
|
|
349
|
+
}, 1);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
this.emit('debug', 'Checking confirmations');
|
|
353
|
+
|
|
354
|
+
this._confirmationCheckerGetKey('conf', function(err, key) {
|
|
355
|
+
if(err) {
|
|
356
|
+
resetTimer();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
self.getConfirmations(key.time, key.key, function(err, confirmations) {
|
|
361
|
+
if(err) {
|
|
362
|
+
self.emit('debug', "Can't check confirmations: " + err.message);
|
|
363
|
+
resetTimer();
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
var known = self._knownConfirmations;
|
|
368
|
+
|
|
369
|
+
var newOnes = confirmations.filter(function(conf) {
|
|
370
|
+
return !known[conf.id];
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if(newOnes.length < 1) {
|
|
374
|
+
resetTimer();
|
|
375
|
+
return; // No new ones
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// We have new confirmations!
|
|
379
|
+
newOnes.forEach(function(conf) {
|
|
380
|
+
self._knownConfirmations[conf.id] = conf; // Add it to our list of known confirmations
|
|
381
|
+
self._confirmationQueue.push(conf);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
resetTimer();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
function resetTimer() {
|
|
389
|
+
if(self._confirmationPollInterval) {
|
|
390
|
+
self._confirmationTimer = setTimeout(self.checkConfirmations.bind(self), self._confirmationPollInterval);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
SteamCommunity.prototype._confirmationCheckerGetKey = function(tag, callback) {
|
|
396
|
+
if(this._identitySecret) {
|
|
397
|
+
if(tag == 'details') {
|
|
398
|
+
// We don't care about details
|
|
399
|
+
callback(new Error("Disabled"));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
var time = Math.floor(Date.now() / 1000);
|
|
404
|
+
callback(null, {"time": time, "key": SteamTotp.getConfirmationKey(this._identitySecret, time, tag)});
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
var existing = this._confirmationKeys[tag];
|
|
409
|
+
var reusable = ['conf', 'details'];
|
|
410
|
+
|
|
411
|
+
// See if we already have a key that we can reuse.
|
|
412
|
+
if(reusable.indexOf(tag) != -1 && existing && (Date.now() - (existing.time * 1000) < (1000 * 60 * 5))) {
|
|
413
|
+
callback(null, existing);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// We need a fresh one
|
|
418
|
+
var self = this;
|
|
419
|
+
this.emit('confKeyNeeded', tag, function(err, time, key) {
|
|
420
|
+
if(err) {
|
|
421
|
+
callback(err);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
self._confirmationKeys[tag] = {"time": time, "key": key};
|
|
426
|
+
callback(null, {"time": time, "key": key});
|
|
427
|
+
});
|
|
428
|
+
};
|