steamcommunity 3.45.0 → 3.45.2

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