steamcommunity 3.43.1 → 3.44.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.
@@ -1,425 +1,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
-
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
+
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
+ };