steamcommunity 3.48.8 → 3.49.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,838 +1,838 @@
1
- const Cheerio = require('cheerio');
2
- const Crypto = require('crypto');
3
- const imageSize = require('image-size');
4
- const SteamID = require('steamid');
5
-
6
- const SteamCommunity = require('../index.js');
7
-
8
- const CEconItem = require('../classes/CEconItem.js');
9
- const Helpers = require('./helpers.js');
10
-
11
- SteamCommunity.prototype.addFriend = function(userID, callback) {
12
- if(typeof userID === 'string') {
13
- userID = new SteamID(userID);
14
- }
15
-
16
- var self = this;
17
- this.httpRequestPost({
18
- "uri": "https://steamcommunity.com/actions/AddFriendAjax",
19
- "form": {
20
- "accept_invite": 0,
21
- "sessionID": this.getSessionID(),
22
- "steamid": userID.toString()
23
- },
24
- "json": true
25
- }, function(err, response, body) {
26
- if(!callback) {
27
- return;
28
- }
29
-
30
- if (err) {
31
- callback(err);
32
- return;
33
- }
34
-
35
- if(body.success) {
36
- callback(null);
37
- } else {
38
- callback(new Error("Unknown error"));
39
- }
40
- }, "steamcommunity");
41
- };
42
-
43
- SteamCommunity.prototype.acceptFriendRequest = function(userID, callback) {
44
- if(typeof userID === 'string') {
45
- userID = new SteamID(userID);
46
- }
47
-
48
- var self = this;
49
- this.httpRequestPost({
50
- "uri": "https://steamcommunity.com/actions/AddFriendAjax",
51
- "form": {
52
- "accept_invite": 1,
53
- "sessionID": this.getSessionID(),
54
- "steamid": userID.toString()
55
- }
56
- }, function(err, response, body) {
57
- if(!callback) {
58
- return;
59
- }
60
-
61
- callback(err || null);
62
- }, "steamcommunity");
63
- };
64
-
65
- SteamCommunity.prototype.removeFriend = function(userID, callback) {
66
- if(typeof userID === 'string') {
67
- userID = new SteamID(userID);
68
- }
69
-
70
- var self = this;
71
- this.httpRequestPost({
72
- "uri": "https://steamcommunity.com/actions/RemoveFriendAjax",
73
- "form": {
74
- "sessionID": this.getSessionID(),
75
- "steamid": userID.toString()
76
- }
77
- }, function(err, response, body) {
78
- if(!callback) {
79
- return;
80
- }
81
-
82
- callback(err || null);
83
- }, "steamcommunity");
84
- };
85
-
86
- SteamCommunity.prototype.blockCommunication = function(userID, callback) {
87
- if(typeof userID === 'string') {
88
- userID = new SteamID(userID);
89
- }
90
-
91
- var self = this;
92
- this.httpRequestPost({
93
- "uri": "https://steamcommunity.com/actions/BlockUserAjax",
94
- "form": {
95
- "sessionID": this.getSessionID(),
96
- "steamid": userID.toString()
97
- }
98
- }, function(err, response, body) {
99
- if(!callback) {
100
- return;
101
- }
102
-
103
- callback(err || null);
104
- }, "steamcommunity");
105
- };
106
-
107
- SteamCommunity.prototype.unblockCommunication = function(userID, callback) {
108
- if(typeof userID === 'string') {
109
- userID = new SteamID(userID);
110
- }
111
-
112
- var form = {"action": "unignore"};
113
- form['friends[' + userID.toString() + ']'] = 1;
114
-
115
- this._myProfile('friends/blocked/', form, function(err, response, body) {
116
- if(!callback) {
117
- return;
118
- }
119
-
120
- if(err || response.statusCode >= 400) {
121
- callback(err || new Error("HTTP error " + response.statusCode));
122
- return;
123
- }
124
-
125
- callback(null);
126
- });
127
- };
128
-
129
- SteamCommunity.prototype.postUserComment = function(userID, message, callback) {
130
- if(typeof userID === 'string') {
131
- userID = new SteamID(userID);
132
- }
133
-
134
- var self = this;
135
- this.httpRequestPost({
136
- "uri": "https://steamcommunity.com/comment/Profile/post/" + userID.toString() + "/-1",
137
- "form": {
138
- "comment": message,
139
- "count": 1,
140
- "sessionid": this.getSessionID()
141
- },
142
- "json": true
143
- }, function(err, response, body) {
144
- if(!callback) {
145
- return;
146
- }
147
-
148
- if (err) {
149
- callback(err);
150
- return;
151
- }
152
-
153
- if(body.success) {
154
- const $ = Cheerio.load(body.comments_html);
155
- const commentID = $('.commentthread_comment').attr('id').split('_')[1];
156
-
157
- callback(null, commentID);
158
- } else if(body.error) {
159
- callback(new Error(body.error));
160
- } else {
161
- callback(new Error("Unknown error"));
162
- }
163
- }, "steamcommunity");
164
- };
165
-
166
- SteamCommunity.prototype.deleteUserComment = function(userID, commentID, callback) {
167
- if(typeof userID === 'string') {
168
- userID = new SteamID(userID);
169
- }
170
-
171
- var self = this;
172
- this.httpRequestPost({
173
- "uri": "https://steamcommunity.com/comment/Profile/delete/" + userID.toString() + "/-1",
174
- "form": {
175
- "gidcomment": commentID,
176
- "start": 0,
177
- "count": 1,
178
- "sessionid": this.getSessionID(),
179
- "feature2": -1
180
- },
181
- "json": true
182
- }, function(err, response, body) {
183
- if(!callback) {
184
- return;
185
- }
186
-
187
- if (err) {
188
- callback(err);
189
- return;
190
- }
191
-
192
- if(body.success && !body.comments_html.includes(commentID)) {
193
- callback(null);
194
- } else if(body.error) {
195
- callback(new Error(body.error));
196
- } else if(body.comments_html.includes(commentID)) {
197
- callback(new Error("Failed to delete comment"));
198
- } else {
199
- callback(new Error("Unknown error"));
200
- }
201
- }, "steamcommunity");
202
- };
203
-
204
- SteamCommunity.prototype.getUserComments = function(userID, options, callback) {
205
- if(typeof userID === 'string') {
206
- userID = new SteamID(userID);
207
- }
208
-
209
- if (typeof options === 'function') {
210
- callback = options;
211
- options = {};
212
- }
213
-
214
- var form = Object.assign({
215
- "start": 0,
216
- "count": 0,
217
- "feature2": -1,
218
- "sessionid": this.getSessionID()
219
- }, options);
220
-
221
- this.httpRequestPost({
222
- "uri": "https://steamcommunity.com/comment/Profile/render/" + userID.toString() + "/-1",
223
- "form": form,
224
- "json": true
225
- }, function(err, response, body) {
226
- if(!callback) {
227
- return;
228
- }
229
-
230
- if (err) {
231
- callback(err);
232
- return;
233
- }
234
-
235
- if(body.success) {
236
- const $ = Cheerio.load(body.comments_html);
237
- const comments = $(".commentthread_comment.responsive_body_text[id]").map((i, elem) => {
238
- var $elem = $(elem),
239
- $commentContent = $elem.find(".commentthread_comment_text");
240
- return {
241
- id: $elem.attr("id").split("_")[1],
242
- author: {
243
- steamID: new SteamID("[U:1:" + $elem.find("[data-miniprofile]").data("miniprofile") + "]"),
244
- name: $elem.find("bdi").text(),
245
- avatar: $elem.find(".playerAvatar img[src]").attr("src"),
246
- state: $elem.find(".playerAvatar").attr("class").split(" ").pop()
247
- },
248
- date: new Date($elem.find(".commentthread_comment_timestamp").data("timestamp") * 1000),
249
- text: $commentContent.text().trim(),
250
- html: $commentContent.html().trim()
251
- }
252
- }).get();
253
-
254
- callback(null, comments, body.total_count);
255
- } else if(body.error) {
256
- callback(new Error(body.error));
257
- } else {
258
- callback(new Error("Unknown error"));
259
- }
260
- }, "steamcommunity");
261
- };
262
-
263
- SteamCommunity.prototype.inviteUserToGroup = function(userID, groupID, callback) {
264
- if(typeof userID === 'string') {
265
- userID = new SteamID(userID);
266
- }
267
-
268
- var self = this;
269
- this.httpRequestPost({
270
- "uri": "https://steamcommunity.com/actions/GroupInvite",
271
- "form": {
272
- "group": groupID.toString(),
273
- "invitee": userID.toString(),
274
- "json": 1,
275
- "sessionID": this.getSessionID(),
276
- "type": "groupInvite"
277
- },
278
- "json": true
279
- }, function(err, response, body) {
280
- if(!callback) {
281
- return;
282
- }
283
-
284
- if (err) {
285
- callback(err);
286
- return;
287
- }
288
-
289
- if(body.results == 'OK') {
290
- callback(null);
291
- } else if(body.results) {
292
- callback(new Error(body.results));
293
- } else {
294
- callback(new Error("Unknown error"));
295
- }
296
- }, "steamcommunity");
297
- };
298
-
299
- SteamCommunity.prototype.followUser = function(userID, callback) {
300
- if(typeof userID === 'string') {
301
- userID = new SteamID(userID);
302
- }
303
-
304
- this.httpRequestPost({
305
- "uri": `https://steamcommunity.com/profiles/${userID.toString()}/followuser/`,
306
- "form": {
307
- "sessionid": this.getSessionID(),
308
- },
309
- "json": true
310
- }, function(err, response, body) {
311
- if (!callback) {
312
- return;
313
- }
314
-
315
- if (err) {
316
- callback(err);
317
- return;
318
- }
319
-
320
- if (body.success && body.success != SteamCommunity.EResult.OK) {
321
- callback(Helpers.eresultError(body.success));
322
- return;
323
- }
324
-
325
- callback(null);
326
- }, "steamcommunity");
327
- };
328
-
329
- SteamCommunity.prototype.unfollowUser = function(userID, callback) {
330
- if(typeof userID === 'string') {
331
- userID = new SteamID(userID);
332
- }
333
-
334
- this.httpRequestPost({
335
- "uri": `https://steamcommunity.com/profiles/${userID.toString()}/unfollowuser/`,
336
- "form": {
337
- "sessionid": this.getSessionID(),
338
- },
339
- "json": true
340
- }, function(err, response, body) {
341
- if (!callback) {
342
- return;
343
- }
344
-
345
- if (err) {
346
- callback(err);
347
- return;
348
- }
349
-
350
- if (body.success && body.success != SteamCommunity.EResult.OK) {
351
- callback(Helpers.eresultError(body.success));
352
- return;
353
- }
354
-
355
- callback(null);
356
- }, "steamcommunity");
357
- };
358
-
359
- SteamCommunity.prototype.getUserAliases = function(userID, callback) {
360
- if (typeof userID === 'string') {
361
- userID = new SteamID(userID);
362
- }
363
-
364
- this.httpRequestGet({
365
- "uri": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/ajaxaliases",
366
- "json": true
367
- }, function(err, response, body) {
368
- if (err) {
369
- callback(err);
370
- return;
371
- }
372
-
373
- if (typeof body !== 'object') {
374
- callback(new Error("Malformed response"));
375
- return;
376
- }
377
-
378
- callback(null, body.map(function(entry) {
379
- entry.timechanged = Helpers.decodeSteamTime(entry.timechanged);
380
- return entry;
381
- }));
382
- }, "steamcommunity");
383
- };
384
-
385
- /**
386
- * Get the background URL of user's profile.
387
- * @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
388
- * @param {function} callback
389
- */
390
- SteamCommunity.prototype.getUserProfileBackground = function(userID, callback) {
391
- if (typeof userID === 'string') {
392
- userID = new SteamID(userID);
393
- }
394
-
395
- this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64(), (err, response, body) => {
396
- if (err) {
397
- callback(err);
398
- return;
399
- }
400
-
401
- var $ = Cheerio.load(body);
402
-
403
- var $privateProfileInfo = $('.profile_private_info');
404
- if ($privateProfileInfo.length > 0) {
405
- callback(new Error($privateProfileInfo.text().trim()));
406
- return;
407
- }
408
-
409
- if ($('body').hasClass('has_profile_background')) {
410
- var backgroundUrl = $('div.profile_background_image_content').css('background-image');
411
- var matcher = backgroundUrl.match(/\(([^)]+)\)/);
412
-
413
- if (matcher.length != 2 || !matcher[1].length) {
414
- callback(new Error("Malformed response"));
415
- } else {
416
- callback(null, matcher[1]);
417
- }
418
- } else {
419
- callback(null, null);
420
- }
421
- }, "steamcommunity");
422
- };
423
-
424
- SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
425
- if (typeof userID === 'string') {
426
- userID = new SteamID(userID);
427
- }
428
-
429
- if (typeof userID === 'function') {
430
- callback = userID;
431
- userID = this.steamID;
432
- }
433
-
434
- if (!userID) {
435
- callback(new Error("No SteamID specified and not logged in"));
436
- return;
437
- }
438
-
439
- var self = this;
440
- this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory/", function(err, response, body) {
441
- if (err) {
442
- callback(err);
443
- return;
444
- }
445
-
446
- var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
447
- if (!match) {
448
- callback(new Error('Malformed response'));
449
- return;
450
- }
451
-
452
- var data;
453
- try {
454
- data = JSON.parse(match[1]);
455
- } catch(e) {
456
- callback(new Error("Malformed response"));
457
- return;
458
- }
459
-
460
- if (Object.keys(data).length == 0) {
461
- if (body.match(/inventory is currently private\./)) {
462
- callback(new Error('Private inventory'));
463
- return;
464
- }
465
-
466
- if (body.match(/profile_private_info/)) {
467
- callback(new Error('Private profile'));
468
- return;
469
- }
470
-
471
- // If they truly have no items in their inventory, Steam will send g_rgAppContextData as [] instead of {}.
472
- data = {};
473
- }
474
-
475
- callback(null, data);
476
- }, "steamcommunity");
477
- };
478
-
479
- /**
480
- * Get the contents of a user's inventory context.
481
- * @deprecated Use getUserInventoryContents instead
482
- * @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
483
- * @param {int} appID - The Steam application ID of the game for which you want an inventory
484
- * @param {int} contextID - The ID of the "context" within the game you want to retrieve
485
- * @param {boolean} tradableOnly - true to get only tradable items and currencies
486
- * @param {function} callback
487
- */
488
- SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, tradableOnly, callback) {
489
- var self = this;
490
-
491
- if (typeof userID === 'string') {
492
- userID = new SteamID(userID);
493
- }
494
-
495
- var endpoint = "/profiles/" + userID.getSteamID64();
496
- get([], []);
497
-
498
- function get(inventory, currency, start) {
499
- self.httpRequest({
500
- "uri": "https://steamcommunity.com" + endpoint + "/inventory/json/" + appID + "/" + contextID,
501
- "headers": {
502
- "Referer": "https://steamcommunity.com" + endpoint + "/inventory"
503
- },
504
- "qs": {
505
- "start": start,
506
- "trading": tradableOnly ? 1 : undefined
507
- },
508
- "json": true
509
- }, function(err, response, body) {
510
- if (err) {
511
- callback(err);
512
- return;
513
- }
514
-
515
- if (!body || !body.success || !body.rgInventory || !body.rgDescriptions || !body.rgCurrency) {
516
- if (body) {
517
- callback(new Error(body.Error || "Malformed response"));
518
- } else {
519
- callback(new Error("Malformed response"));
520
- }
521
-
522
- return;
523
- }
524
-
525
- var i;
526
- for (i in body.rgInventory) {
527
- if (!body.rgInventory.hasOwnProperty(i)) {
528
- continue;
529
- }
530
-
531
- inventory.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
532
- }
533
-
534
- for (i in body.rgCurrency) {
535
- if (!body.rgCurrency.hasOwnProperty(i)) {
536
- continue;
537
- }
538
-
539
- currency.push(new CEconItem(body.rgCurrency[i], body.rgDescriptions, contextID));
540
- }
541
-
542
- if (body.more) {
543
- var match = response.request.uri.href.match(/\/(profiles|id)\/([^\/]+)\//);
544
- if(match) {
545
- endpoint = "/" + match[1] + "/" + match[2];
546
- }
547
-
548
- get(inventory, currency, body.more_start);
549
- } else {
550
- callback(null, inventory, currency);
551
- }
552
- }, "steamcommunity");
553
- }
554
- };
555
-
556
- /**
557
- * Get the contents of a user's inventory context.
558
- * @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
559
- * @param {int} appID - The Steam application ID of the game for which you want an inventory
560
- * @param {int} contextID - The ID of the "context" within the game you want to retrieve
561
- * @param {boolean} tradableOnly - true to get only tradable items and currencies
562
- * @param {string} [language] - The language of item descriptions to return. Omit for default (which may either be English or your account's chosen language)
563
- * @param {function} callback
564
- */
565
- SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, language, callback) {
566
- if (typeof language === 'function') {
567
- callback = language;
568
- language = "english";
569
- }
570
-
571
- if (!userID) {
572
- callback(new Error("The user's SteamID is invalid or missing."));
573
- return;
574
- }
575
-
576
- var self = this;
577
-
578
- if (typeof userID === 'string') {
579
- userID = new SteamID(userID);
580
- }
581
-
582
- var pos = 1;
583
- get([], []);
584
-
585
- function get(inventory, currency, start) {
586
- self.httpRequest({
587
- "uri": "https://steamcommunity.com/inventory/" + userID.getSteamID64() + "/" + appID + "/" + contextID,
588
- "headers": {
589
- "Referer": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory"
590
- },
591
- "qs": {
592
- "l": language, // Default language
593
- "count": 1000, // Max items per 'page'
594
- "start_assetid": start
595
- },
596
- "json": true
597
- }, function(err, response, body) {
598
- if (err) {
599
- if (err.message == "HTTP error 403" && body === null) {
600
- // 403 with a body of "null" means the inventory/profile is private.
601
- if (self.steamID && userID.getSteamID64() == self.steamID.getSteamID64()) {
602
- // We can never get private profile error for our own inventory!
603
- self._notifySessionExpired(err);
604
- }
605
-
606
- callback(new Error("This profile is private."));
607
- return;
608
- }
609
-
610
- if (err.message == "HTTP error 500" && body && body.error) {
611
- err = new Error(body.error);
612
-
613
- var match = body.error.match(/^(.+) \((\d+)\)$/);
614
- if (match) {
615
- err.message = match[1];
616
- err.eresult = match[2];
617
- callback(err);
618
- return;
619
- }
620
- }
621
-
622
- callback(err);
623
- return;
624
- }
625
-
626
- if (body && body.success && body.total_inventory_count === 0) {
627
- // Empty inventory
628
- callback(null, [], [], 0);
629
- return;
630
- }
631
-
632
- if (appID == 730 && body && body.success && !body.assets) {
633
- // CS inventory has no visible items. We need a special case for this because Valve is incapable of
634
- // doing anything not dumb.
635
- callback(null, [], [], body.total_inventory_count);
636
- return;
637
- }
638
-
639
- if (!body || !body.success || !body.assets || !body.descriptions) {
640
- if (body) {
641
- // Dunno if the error/Error property even exists on this new endpoint
642
- callback(new Error(body.error || body.Error || "Malformed response"));
643
- } else {
644
- callback(new Error("Malformed response"));
645
- }
646
-
647
- return;
648
- }
649
-
650
- for (var i = 0; i < body.assets.length; i++) {
651
- var description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid);
652
-
653
- if (!tradableOnly || (description && description.tradable)) {
654
- body.assets[i].pos = pos++;
655
- (body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID));
656
- }
657
- }
658
-
659
- if (body.more_items) {
660
- get(inventory, currency, body.last_assetid);
661
- } else {
662
- callback(null, inventory, currency, body.total_inventory_count);
663
- }
664
- }, "steamcommunity");
665
- }
666
-
667
- // A bit of optimization; objects are hash tables so it's more efficient to look up by key than to iterate an array
668
- var quickDescriptionLookup = {};
669
-
670
- function getDescription(descriptions, classID, instanceID) {
671
- var key = classID + '_' + (instanceID || '0'); // instanceID can be undefined, in which case it's 0.
672
-
673
- if (quickDescriptionLookup[key]) {
674
- return quickDescriptionLookup[key];
675
- }
676
-
677
- for (var i = 0; i < descriptions.length; i++) {
678
- quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i];
679
- }
680
-
681
- return quickDescriptionLookup[key];
682
- }
683
- };
684
-
685
- /**
686
- * Upload an image to Steam and send it to another user over Steam chat.
687
- * @param {SteamID|string} userID - Either a SteamID object or a string that can parse into one
688
- * @param {Buffer} imageContentsBuffer - The image contents, as a Buffer
689
- * @param {{spoiler?: boolean}} [options]
690
- * @param {function} callback
691
- */
692
- SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer, options, callback) {
693
- if (typeof options == 'function') {
694
- callback = options;
695
- options = {};
696
- }
697
-
698
- options = options || {};
699
-
700
- if (!userID) {
701
- callback(new Error('The user\'s SteamID is invalid or missing'));
702
- return;
703
- }
704
-
705
- if (typeof userID == 'string') {
706
- userID = new SteamID(userID);
707
- }
708
-
709
- if (!Buffer.isBuffer(imageContentsBuffer)) {
710
- callback(new Error('The image contents must be a Buffer containing an image'));
711
- return;
712
- }
713
-
714
- var imageDetails = null;
715
- try {
716
- imageDetails = imageSize(imageContentsBuffer);
717
- } catch (ex) {
718
- callback(ex);
719
- return;
720
- }
721
-
722
- var imageHash = Crypto.createHash('sha1');
723
- imageHash.update(imageContentsBuffer);
724
- imageHash = imageHash.digest('hex');
725
-
726
- var filename = Date.now() + '_image.' + imageDetails.type;
727
-
728
- this.httpRequestPost({
729
- uri: 'https://steamcommunity.com/chat/beginfileupload/?l=english',
730
- headers: {
731
- referer: 'https://steamcommunity.com/chat/'
732
- },
733
- formData: { // it's multipart
734
- sessionid: this.getSessionID(),
735
- l: 'english',
736
- file_size: imageContentsBuffer.length,
737
- file_name: filename,
738
- file_sha: imageHash,
739
- file_image_width: imageDetails.width,
740
- file_image_height: imageDetails.height,
741
- file_type: 'image/' + (imageDetails.type == 'jpg' ? 'jpeg' : imageDetails.type)
742
- },
743
- json: true
744
- }, (err, res, body) => {
745
- if (err) {
746
- if (body && body.success) {
747
- var err2 = Helpers.eresultError(body.success);
748
- if (body.message) {
749
- err2.message = body.message;
750
- }
751
- callback(err2);
752
- } else {
753
- callback(err);
754
- }
755
- return;
756
- }
757
-
758
- if (body.success != 1) {
759
- callback(Helpers.eresultError(body.success));
760
- return;
761
- }
762
-
763
- var hmac = body.hmac;
764
- var timestamp = body.timestamp;
765
- var startResult = body.result;
766
-
767
- if (!startResult || !startResult.ugcid || !startResult.url_host || !startResult.request_headers) {
768
- callback(new Error('Malformed response'));
769
- return;
770
- }
771
-
772
- // Okay, now we need to PUT the file to the provided URL
773
- var uploadUrl = (startResult.use_https ? 'https' : 'http') + '://' + startResult.url_host + startResult.url_path;
774
- var headers = {};
775
- startResult.request_headers.forEach((header) => {
776
- headers[header.name.toLowerCase()] = header.value;
777
- });
778
-
779
- this.httpRequest({
780
- uri: uploadUrl,
781
- method: 'PUT',
782
- headers,
783
- body: imageContentsBuffer
784
- }, (err, res, body) => {
785
- if (err) {
786
- callback(err);
787
- return;
788
- }
789
-
790
- // Now we need to commit the upload
791
- this.httpRequestPost({
792
- uri: 'https://steamcommunity.com/chat/commitfileupload/',
793
- headers: {
794
- referer: 'https://steamcommunity.com/chat/'
795
- },
796
- formData: { // it's multipart again
797
- sessionid: this.getSessionID(),
798
- l: 'english',
799
- file_name: filename,
800
- file_sha: imageHash,
801
- success: '1',
802
- ugcid: startResult.ugcid,
803
- file_type: 'image/' + (imageDetails.type == 'jpg' ? 'jpeg' : imageDetails.type),
804
- file_image_width: imageDetails.width,
805
- file_image_height: imageDetails.height,
806
- timestamp,
807
- hmac,
808
- friend_steamid: userID.getSteamID64(),
809
- spoiler: options.spoiler ? '1' : '0'
810
- },
811
- json: true
812
- }, (err, res, body) => {
813
- if (err) {
814
- callback(err);
815
- return;
816
- }
817
-
818
- if (body.success != 1) {
819
- callback(Helpers.eresultError(body.success));
820
- return;
821
- }
822
-
823
- if (body.result.success != 1) {
824
- // lol valve
825
- callback(Helpers.eresultError(body.result.success));
826
- return;
827
- }
828
-
829
- if (!body.result.details || !body.result.details.url) {
830
- callback(new Error('Malformed response'));
831
- return;
832
- }
833
-
834
- callback(null, body.result.details.url);
835
- }, 'steamcommunity');
836
- }, 'steamcommunity');
837
- }, 'steamcommunity');
838
- };
1
+ const Cheerio = require('cheerio');
2
+ const Crypto = require('crypto');
3
+ const imageSize = require('image-size');
4
+ const SteamID = require('steamid');
5
+
6
+ const SteamCommunity = require('../index.js');
7
+
8
+ const CEconItem = require('../classes/CEconItem.js');
9
+ const Helpers = require('./helpers.js');
10
+
11
+ SteamCommunity.prototype.addFriend = function(userID, callback) {
12
+ if(typeof userID === 'string') {
13
+ userID = new SteamID(userID);
14
+ }
15
+
16
+ var self = this;
17
+ this.httpRequestPost({
18
+ "uri": "https://steamcommunity.com/actions/AddFriendAjax",
19
+ "form": {
20
+ "accept_invite": 0,
21
+ "sessionID": this.getSessionID(),
22
+ "steamid": userID.toString()
23
+ },
24
+ "json": true
25
+ }, function(err, response, body) {
26
+ if(!callback) {
27
+ return;
28
+ }
29
+
30
+ if (err) {
31
+ callback(err);
32
+ return;
33
+ }
34
+
35
+ if(body.success) {
36
+ callback(null);
37
+ } else {
38
+ callback(new Error("Unknown error"));
39
+ }
40
+ }, "steamcommunity");
41
+ };
42
+
43
+ SteamCommunity.prototype.acceptFriendRequest = function(userID, callback) {
44
+ if(typeof userID === 'string') {
45
+ userID = new SteamID(userID);
46
+ }
47
+
48
+ var self = this;
49
+ this.httpRequestPost({
50
+ "uri": "https://steamcommunity.com/actions/AddFriendAjax",
51
+ "form": {
52
+ "accept_invite": 1,
53
+ "sessionID": this.getSessionID(),
54
+ "steamid": userID.toString()
55
+ }
56
+ }, function(err, response, body) {
57
+ if(!callback) {
58
+ return;
59
+ }
60
+
61
+ callback(err || null);
62
+ }, "steamcommunity");
63
+ };
64
+
65
+ SteamCommunity.prototype.removeFriend = function(userID, callback) {
66
+ if(typeof userID === 'string') {
67
+ userID = new SteamID(userID);
68
+ }
69
+
70
+ var self = this;
71
+ this.httpRequestPost({
72
+ "uri": "https://steamcommunity.com/actions/RemoveFriendAjax",
73
+ "form": {
74
+ "sessionID": this.getSessionID(),
75
+ "steamid": userID.toString()
76
+ }
77
+ }, function(err, response, body) {
78
+ if(!callback) {
79
+ return;
80
+ }
81
+
82
+ callback(err || null);
83
+ }, "steamcommunity");
84
+ };
85
+
86
+ SteamCommunity.prototype.blockCommunication = function(userID, callback) {
87
+ if(typeof userID === 'string') {
88
+ userID = new SteamID(userID);
89
+ }
90
+
91
+ var self = this;
92
+ this.httpRequestPost({
93
+ "uri": "https://steamcommunity.com/actions/BlockUserAjax",
94
+ "form": {
95
+ "sessionID": this.getSessionID(),
96
+ "steamid": userID.toString()
97
+ }
98
+ }, function(err, response, body) {
99
+ if(!callback) {
100
+ return;
101
+ }
102
+
103
+ callback(err || null);
104
+ }, "steamcommunity");
105
+ };
106
+
107
+ SteamCommunity.prototype.unblockCommunication = function(userID, callback) {
108
+ if(typeof userID === 'string') {
109
+ userID = new SteamID(userID);
110
+ }
111
+
112
+ var form = {"action": "unignore"};
113
+ form['friends[' + userID.toString() + ']'] = 1;
114
+
115
+ this._myProfile('friends/blocked/', form, function(err, response, body) {
116
+ if(!callback) {
117
+ return;
118
+ }
119
+
120
+ if(err || response.statusCode >= 400) {
121
+ callback(err || new Error("HTTP error " + response.statusCode));
122
+ return;
123
+ }
124
+
125
+ callback(null);
126
+ });
127
+ };
128
+
129
+ SteamCommunity.prototype.postUserComment = function(userID, message, callback) {
130
+ if(typeof userID === 'string') {
131
+ userID = new SteamID(userID);
132
+ }
133
+
134
+ var self = this;
135
+ this.httpRequestPost({
136
+ "uri": "https://steamcommunity.com/comment/Profile/post/" + userID.toString() + "/-1",
137
+ "form": {
138
+ "comment": message,
139
+ "count": 1,
140
+ "sessionid": this.getSessionID()
141
+ },
142
+ "json": true
143
+ }, function(err, response, body) {
144
+ if(!callback) {
145
+ return;
146
+ }
147
+
148
+ if (err) {
149
+ callback(err);
150
+ return;
151
+ }
152
+
153
+ if(body.success) {
154
+ const $ = Cheerio.load(body.comments_html);
155
+ const commentID = $('.commentthread_comment').attr('id').split('_')[1];
156
+
157
+ callback(null, commentID);
158
+ } else if(body.error) {
159
+ callback(new Error(body.error));
160
+ } else {
161
+ callback(new Error("Unknown error"));
162
+ }
163
+ }, "steamcommunity");
164
+ };
165
+
166
+ SteamCommunity.prototype.deleteUserComment = function(userID, commentID, callback) {
167
+ if(typeof userID === 'string') {
168
+ userID = new SteamID(userID);
169
+ }
170
+
171
+ var self = this;
172
+ this.httpRequestPost({
173
+ "uri": "https://steamcommunity.com/comment/Profile/delete/" + userID.toString() + "/-1",
174
+ "form": {
175
+ "gidcomment": commentID,
176
+ "start": 0,
177
+ "count": 1,
178
+ "sessionid": this.getSessionID(),
179
+ "feature2": -1
180
+ },
181
+ "json": true
182
+ }, function(err, response, body) {
183
+ if(!callback) {
184
+ return;
185
+ }
186
+
187
+ if (err) {
188
+ callback(err);
189
+ return;
190
+ }
191
+
192
+ if(body.success && !body.comments_html.includes(commentID)) {
193
+ callback(null);
194
+ } else if(body.error) {
195
+ callback(new Error(body.error));
196
+ } else if(body.comments_html.includes(commentID)) {
197
+ callback(new Error("Failed to delete comment"));
198
+ } else {
199
+ callback(new Error("Unknown error"));
200
+ }
201
+ }, "steamcommunity");
202
+ };
203
+
204
+ SteamCommunity.prototype.getUserComments = function(userID, options, callback) {
205
+ if(typeof userID === 'string') {
206
+ userID = new SteamID(userID);
207
+ }
208
+
209
+ if (typeof options === 'function') {
210
+ callback = options;
211
+ options = {};
212
+ }
213
+
214
+ var form = Object.assign({
215
+ "start": 0,
216
+ "count": 0,
217
+ "feature2": -1,
218
+ "sessionid": this.getSessionID()
219
+ }, options);
220
+
221
+ this.httpRequestPost({
222
+ "uri": "https://steamcommunity.com/comment/Profile/render/" + userID.toString() + "/-1",
223
+ "form": form,
224
+ "json": true
225
+ }, function(err, response, body) {
226
+ if(!callback) {
227
+ return;
228
+ }
229
+
230
+ if (err) {
231
+ callback(err);
232
+ return;
233
+ }
234
+
235
+ if(body.success) {
236
+ const $ = Cheerio.load(body.comments_html);
237
+ const comments = $(".commentthread_comment.responsive_body_text[id]").map((i, elem) => {
238
+ var $elem = $(elem),
239
+ $commentContent = $elem.find(".commentthread_comment_text");
240
+ return {
241
+ id: $elem.attr("id").split("_")[1],
242
+ author: {
243
+ steamID: new SteamID("[U:1:" + $elem.find("[data-miniprofile]").data("miniprofile") + "]"),
244
+ name: $elem.find("bdi").text(),
245
+ avatar: $elem.find(".playerAvatar img[src]").attr("src"),
246
+ state: $elem.find(".playerAvatar").attr("class").split(" ").pop()
247
+ },
248
+ date: new Date($elem.find(".commentthread_comment_timestamp").data("timestamp") * 1000),
249
+ text: $commentContent.text().trim(),
250
+ html: $commentContent.html().trim()
251
+ }
252
+ }).get();
253
+
254
+ callback(null, comments, body.total_count);
255
+ } else if(body.error) {
256
+ callback(new Error(body.error));
257
+ } else {
258
+ callback(new Error("Unknown error"));
259
+ }
260
+ }, "steamcommunity");
261
+ };
262
+
263
+ SteamCommunity.prototype.inviteUserToGroup = function(userID, groupID, callback) {
264
+ if(typeof userID === 'string') {
265
+ userID = new SteamID(userID);
266
+ }
267
+
268
+ var self = this;
269
+ this.httpRequestPost({
270
+ "uri": "https://steamcommunity.com/actions/GroupInvite",
271
+ "form": {
272
+ "group": groupID.toString(),
273
+ "invitee": userID.toString(),
274
+ "json": 1,
275
+ "sessionID": this.getSessionID(),
276
+ "type": "groupInvite"
277
+ },
278
+ "json": true
279
+ }, function(err, response, body) {
280
+ if(!callback) {
281
+ return;
282
+ }
283
+
284
+ if (err) {
285
+ callback(err);
286
+ return;
287
+ }
288
+
289
+ if(body.results == 'OK') {
290
+ callback(null);
291
+ } else if(body.results) {
292
+ callback(new Error(body.results));
293
+ } else {
294
+ callback(new Error("Unknown error"));
295
+ }
296
+ }, "steamcommunity");
297
+ };
298
+
299
+ SteamCommunity.prototype.followUser = function(userID, callback) {
300
+ if(typeof userID === 'string') {
301
+ userID = new SteamID(userID);
302
+ }
303
+
304
+ this.httpRequestPost({
305
+ "uri": `https://steamcommunity.com/profiles/${userID.toString()}/followuser/`,
306
+ "form": {
307
+ "sessionid": this.getSessionID(),
308
+ },
309
+ "json": true
310
+ }, function(err, response, body) {
311
+ if (!callback) {
312
+ return;
313
+ }
314
+
315
+ if (err) {
316
+ callback(err);
317
+ return;
318
+ }
319
+
320
+ if (body.success && body.success != SteamCommunity.EResult.OK) {
321
+ callback(Helpers.eresultError(body.success));
322
+ return;
323
+ }
324
+
325
+ callback(null);
326
+ }, "steamcommunity");
327
+ };
328
+
329
+ SteamCommunity.prototype.unfollowUser = function(userID, callback) {
330
+ if(typeof userID === 'string') {
331
+ userID = new SteamID(userID);
332
+ }
333
+
334
+ this.httpRequestPost({
335
+ "uri": `https://steamcommunity.com/profiles/${userID.toString()}/unfollowuser/`,
336
+ "form": {
337
+ "sessionid": this.getSessionID(),
338
+ },
339
+ "json": true
340
+ }, function(err, response, body) {
341
+ if (!callback) {
342
+ return;
343
+ }
344
+
345
+ if (err) {
346
+ callback(err);
347
+ return;
348
+ }
349
+
350
+ if (body.success && body.success != SteamCommunity.EResult.OK) {
351
+ callback(Helpers.eresultError(body.success));
352
+ return;
353
+ }
354
+
355
+ callback(null);
356
+ }, "steamcommunity");
357
+ };
358
+
359
+ SteamCommunity.prototype.getUserAliases = function(userID, callback) {
360
+ if (typeof userID === 'string') {
361
+ userID = new SteamID(userID);
362
+ }
363
+
364
+ this.httpRequestGet({
365
+ "uri": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/ajaxaliases",
366
+ "json": true
367
+ }, function(err, response, body) {
368
+ if (err) {
369
+ callback(err);
370
+ return;
371
+ }
372
+
373
+ if (typeof body !== 'object') {
374
+ callback(new Error("Malformed response"));
375
+ return;
376
+ }
377
+
378
+ callback(null, body.map(function(entry) {
379
+ entry.timechanged = Helpers.decodeSteamTime(entry.timechanged);
380
+ return entry;
381
+ }));
382
+ }, "steamcommunity");
383
+ };
384
+
385
+ /**
386
+ * Get the background URL of user's profile.
387
+ * @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
388
+ * @param {function} callback
389
+ */
390
+ SteamCommunity.prototype.getUserProfileBackground = function(userID, callback) {
391
+ if (typeof userID === 'string') {
392
+ userID = new SteamID(userID);
393
+ }
394
+
395
+ this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64(), (err, response, body) => {
396
+ if (err) {
397
+ callback(err);
398
+ return;
399
+ }
400
+
401
+ var $ = Cheerio.load(body);
402
+
403
+ var $privateProfileInfo = $('.profile_private_info');
404
+ if ($privateProfileInfo.length > 0) {
405
+ callback(new Error($privateProfileInfo.text().trim()));
406
+ return;
407
+ }
408
+
409
+ if ($('body').hasClass('has_profile_background')) {
410
+ var backgroundUrl = $('div.profile_background_image_content').css('background-image');
411
+ var matcher = backgroundUrl.match(/\(([^)]+)\)/);
412
+
413
+ if (matcher.length != 2 || !matcher[1].length) {
414
+ callback(new Error("Malformed response"));
415
+ } else {
416
+ callback(null, matcher[1]);
417
+ }
418
+ } else {
419
+ callback(null, null);
420
+ }
421
+ }, "steamcommunity");
422
+ };
423
+
424
+ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
425
+ if (typeof userID === 'string') {
426
+ userID = new SteamID(userID);
427
+ }
428
+
429
+ if (typeof userID === 'function') {
430
+ callback = userID;
431
+ userID = this.steamID;
432
+ }
433
+
434
+ if (!userID) {
435
+ callback(new Error("No SteamID specified and not logged in"));
436
+ return;
437
+ }
438
+
439
+ var self = this;
440
+ this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory/", function(err, response, body) {
441
+ if (err) {
442
+ callback(err);
443
+ return;
444
+ }
445
+
446
+ var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
447
+ if (!match) {
448
+ callback(new Error('Malformed response'));
449
+ return;
450
+ }
451
+
452
+ var data;
453
+ try {
454
+ data = JSON.parse(match[1]);
455
+ } catch(e) {
456
+ callback(new Error("Malformed response"));
457
+ return;
458
+ }
459
+
460
+ if (Object.keys(data).length == 0) {
461
+ if (body.match(/inventory is currently private\./)) {
462
+ callback(new Error('Private inventory'));
463
+ return;
464
+ }
465
+
466
+ if (body.match(/profile_private_info/)) {
467
+ callback(new Error('Private profile'));
468
+ return;
469
+ }
470
+
471
+ // If they truly have no items in their inventory, Steam will send g_rgAppContextData as [] instead of {}.
472
+ data = {};
473
+ }
474
+
475
+ callback(null, data);
476
+ }, "steamcommunity");
477
+ };
478
+
479
+ /**
480
+ * Get the contents of a user's inventory context.
481
+ * @deprecated Use getUserInventoryContents instead
482
+ * @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
483
+ * @param {int} appID - The Steam application ID of the game for which you want an inventory
484
+ * @param {int} contextID - The ID of the "context" within the game you want to retrieve
485
+ * @param {boolean} tradableOnly - true to get only tradable items and currencies
486
+ * @param {function} callback
487
+ */
488
+ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, tradableOnly, callback) {
489
+ var self = this;
490
+
491
+ if (typeof userID === 'string') {
492
+ userID = new SteamID(userID);
493
+ }
494
+
495
+ var endpoint = "/profiles/" + userID.getSteamID64();
496
+ get([], []);
497
+
498
+ function get(inventory, currency, start) {
499
+ self.httpRequest({
500
+ "uri": "https://steamcommunity.com" + endpoint + "/inventory/json/" + appID + "/" + contextID,
501
+ "headers": {
502
+ "Referer": "https://steamcommunity.com" + endpoint + "/inventory"
503
+ },
504
+ "qs": {
505
+ "start": start,
506
+ "trading": tradableOnly ? 1 : undefined
507
+ },
508
+ "json": true
509
+ }, function(err, response, body) {
510
+ if (err) {
511
+ callback(err);
512
+ return;
513
+ }
514
+
515
+ if (!body || !body.success || !body.rgInventory || !body.rgDescriptions || !body.rgCurrency) {
516
+ if (body) {
517
+ callback(new Error(body.Error || "Malformed response"));
518
+ } else {
519
+ callback(new Error("Malformed response"));
520
+ }
521
+
522
+ return;
523
+ }
524
+
525
+ var i;
526
+ for (i in body.rgInventory) {
527
+ if (!body.rgInventory.hasOwnProperty(i)) {
528
+ continue;
529
+ }
530
+
531
+ inventory.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
532
+ }
533
+
534
+ for (i in body.rgCurrency) {
535
+ if (!body.rgCurrency.hasOwnProperty(i)) {
536
+ continue;
537
+ }
538
+
539
+ currency.push(new CEconItem(body.rgCurrency[i], body.rgDescriptions, contextID));
540
+ }
541
+
542
+ if (body.more) {
543
+ var match = response.request.uri.href.match(/\/(profiles|id)\/([^\/]+)\//);
544
+ if(match) {
545
+ endpoint = "/" + match[1] + "/" + match[2];
546
+ }
547
+
548
+ get(inventory, currency, body.more_start);
549
+ } else {
550
+ callback(null, inventory, currency);
551
+ }
552
+ }, "steamcommunity");
553
+ }
554
+ };
555
+
556
+ /**
557
+ * Get the contents of a user's inventory context.
558
+ * @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
559
+ * @param {int} appID - The Steam application ID of the game for which you want an inventory
560
+ * @param {int} contextID - The ID of the "context" within the game you want to retrieve
561
+ * @param {boolean} tradableOnly - true to get only tradable items and currencies
562
+ * @param {string} [language] - The language of item descriptions to return. Omit for default (which may either be English or your account's chosen language)
563
+ * @param {function} callback
564
+ */
565
+ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, language, callback) {
566
+ if (typeof language === 'function') {
567
+ callback = language;
568
+ language = "english";
569
+ }
570
+
571
+ if (!userID) {
572
+ callback(new Error("The user's SteamID is invalid or missing."));
573
+ return;
574
+ }
575
+
576
+ var self = this;
577
+
578
+ if (typeof userID === 'string') {
579
+ userID = new SteamID(userID);
580
+ }
581
+
582
+ var pos = 1;
583
+ get([], []);
584
+
585
+ function get(inventory, currency, start) {
586
+ self.httpRequest({
587
+ "uri": "https://steamcommunity.com/inventory/" + userID.getSteamID64() + "/" + appID + "/" + contextID,
588
+ "headers": {
589
+ "Referer": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory"
590
+ },
591
+ "qs": {
592
+ "l": language, // Default language
593
+ "count": 1000, // Max items per 'page'
594
+ "start_assetid": start
595
+ },
596
+ "json": true
597
+ }, function(err, response, body) {
598
+ if (err) {
599
+ if (err.message == "HTTP error 403" && body === null) {
600
+ // 403 with a body of "null" means the inventory/profile is private.
601
+ if (self.steamID && userID.getSteamID64() == self.steamID.getSteamID64()) {
602
+ // We can never get private profile error for our own inventory!
603
+ self._notifySessionExpired(err);
604
+ }
605
+
606
+ callback(new Error("This profile is private."));
607
+ return;
608
+ }
609
+
610
+ if (err.message == "HTTP error 500" && body && body.error) {
611
+ err = new Error(body.error);
612
+
613
+ var match = body.error.match(/^(.+) \((\d+)\)$/);
614
+ if (match) {
615
+ err.message = match[1];
616
+ err.eresult = match[2];
617
+ callback(err);
618
+ return;
619
+ }
620
+ }
621
+
622
+ callback(err);
623
+ return;
624
+ }
625
+
626
+ if (body && body.success && body.total_inventory_count === 0) {
627
+ // Empty inventory
628
+ callback(null, [], [], 0);
629
+ return;
630
+ }
631
+
632
+ if (appID == 730 && body && body.success && !body.assets) {
633
+ // CS inventory has no visible items. We need a special case for this because Valve is incapable of
634
+ // doing anything not dumb.
635
+ callback(null, [], [], body.total_inventory_count);
636
+ return;
637
+ }
638
+
639
+ if (!body || !body.success || !body.assets || !body.descriptions) {
640
+ if (body) {
641
+ // Dunno if the error/Error property even exists on this new endpoint
642
+ callback(new Error(body.error || body.Error || "Malformed response"));
643
+ } else {
644
+ callback(new Error("Malformed response"));
645
+ }
646
+
647
+ return;
648
+ }
649
+
650
+ for (var i = 0; i < body.assets.length; i++) {
651
+ var description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid);
652
+
653
+ if (!tradableOnly || (description && description.tradable)) {
654
+ body.assets[i].pos = pos++;
655
+ (body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID));
656
+ }
657
+ }
658
+
659
+ if (body.more_items) {
660
+ get(inventory, currency, body.last_assetid);
661
+ } else {
662
+ callback(null, inventory, currency, body.total_inventory_count);
663
+ }
664
+ }, "steamcommunity");
665
+ }
666
+
667
+ // A bit of optimization; objects are hash tables so it's more efficient to look up by key than to iterate an array
668
+ var quickDescriptionLookup = {};
669
+
670
+ function getDescription(descriptions, classID, instanceID) {
671
+ var key = classID + '_' + (instanceID || '0'); // instanceID can be undefined, in which case it's 0.
672
+
673
+ if (quickDescriptionLookup[key]) {
674
+ return quickDescriptionLookup[key];
675
+ }
676
+
677
+ for (var i = 0; i < descriptions.length; i++) {
678
+ quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i];
679
+ }
680
+
681
+ return quickDescriptionLookup[key];
682
+ }
683
+ };
684
+
685
+ /**
686
+ * Upload an image to Steam and send it to another user over Steam chat.
687
+ * @param {SteamID|string} userID - Either a SteamID object or a string that can parse into one
688
+ * @param {Buffer} imageContentsBuffer - The image contents, as a Buffer
689
+ * @param {{spoiler?: boolean}} [options]
690
+ * @param {function} callback
691
+ */
692
+ SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer, options, callback) {
693
+ if (typeof options == 'function') {
694
+ callback = options;
695
+ options = {};
696
+ }
697
+
698
+ options = options || {};
699
+
700
+ if (!userID) {
701
+ callback(new Error('The user\'s SteamID is invalid or missing'));
702
+ return;
703
+ }
704
+
705
+ if (typeof userID == 'string') {
706
+ userID = new SteamID(userID);
707
+ }
708
+
709
+ if (!Buffer.isBuffer(imageContentsBuffer)) {
710
+ callback(new Error('The image contents must be a Buffer containing an image'));
711
+ return;
712
+ }
713
+
714
+ var imageDetails = null;
715
+ try {
716
+ imageDetails = imageSize(imageContentsBuffer);
717
+ } catch (ex) {
718
+ callback(ex);
719
+ return;
720
+ }
721
+
722
+ var imageHash = Crypto.createHash('sha1');
723
+ imageHash.update(imageContentsBuffer);
724
+ imageHash = imageHash.digest('hex');
725
+
726
+ var filename = Date.now() + '_image.' + imageDetails.type;
727
+
728
+ this.httpRequestPost({
729
+ uri: 'https://steamcommunity.com/chat/beginfileupload/?l=english',
730
+ headers: {
731
+ referer: 'https://steamcommunity.com/chat/'
732
+ },
733
+ formData: { // it's multipart
734
+ sessionid: this.getSessionID(),
735
+ l: 'english',
736
+ file_size: imageContentsBuffer.length,
737
+ file_name: filename,
738
+ file_sha: imageHash,
739
+ file_image_width: imageDetails.width,
740
+ file_image_height: imageDetails.height,
741
+ file_type: 'image/' + (imageDetails.type == 'jpg' ? 'jpeg' : imageDetails.type)
742
+ },
743
+ json: true
744
+ }, (err, res, body) => {
745
+ if (err) {
746
+ if (body && body.success) {
747
+ var err2 = Helpers.eresultError(body.success);
748
+ if (body.message) {
749
+ err2.message = body.message;
750
+ }
751
+ callback(err2);
752
+ } else {
753
+ callback(err);
754
+ }
755
+ return;
756
+ }
757
+
758
+ if (body.success != 1) {
759
+ callback(Helpers.eresultError(body.success));
760
+ return;
761
+ }
762
+
763
+ var hmac = body.hmac;
764
+ var timestamp = body.timestamp;
765
+ var startResult = body.result;
766
+
767
+ if (!startResult || !startResult.ugcid || !startResult.url_host || !startResult.request_headers) {
768
+ callback(new Error('Malformed response'));
769
+ return;
770
+ }
771
+
772
+ // Okay, now we need to PUT the file to the provided URL
773
+ var uploadUrl = (startResult.use_https ? 'https' : 'http') + '://' + startResult.url_host + startResult.url_path;
774
+ var headers = {};
775
+ startResult.request_headers.forEach((header) => {
776
+ headers[header.name.toLowerCase()] = header.value;
777
+ });
778
+
779
+ this.httpRequest({
780
+ uri: uploadUrl,
781
+ method: 'PUT',
782
+ headers,
783
+ body: imageContentsBuffer
784
+ }, (err, res, body) => {
785
+ if (err) {
786
+ callback(err);
787
+ return;
788
+ }
789
+
790
+ // Now we need to commit the upload
791
+ this.httpRequestPost({
792
+ uri: 'https://steamcommunity.com/chat/commitfileupload/',
793
+ headers: {
794
+ referer: 'https://steamcommunity.com/chat/'
795
+ },
796
+ formData: { // it's multipart again
797
+ sessionid: this.getSessionID(),
798
+ l: 'english',
799
+ file_name: filename,
800
+ file_sha: imageHash,
801
+ success: '1',
802
+ ugcid: startResult.ugcid,
803
+ file_type: 'image/' + (imageDetails.type == 'jpg' ? 'jpeg' : imageDetails.type),
804
+ file_image_width: imageDetails.width,
805
+ file_image_height: imageDetails.height,
806
+ timestamp,
807
+ hmac,
808
+ friend_steamid: userID.getSteamID64(),
809
+ spoiler: options.spoiler ? '1' : '0'
810
+ },
811
+ json: true
812
+ }, (err, res, body) => {
813
+ if (err) {
814
+ callback(err);
815
+ return;
816
+ }
817
+
818
+ if (body.success != 1) {
819
+ callback(Helpers.eresultError(body.success));
820
+ return;
821
+ }
822
+
823
+ if (body.result.success != 1) {
824
+ // lol valve
825
+ callback(Helpers.eresultError(body.result.success));
826
+ return;
827
+ }
828
+
829
+ if (!body.result.details || !body.result.details.url) {
830
+ callback(new Error('Malformed response'));
831
+ return;
832
+ }
833
+
834
+ callback(null, body.result.details.url);
835
+ }, 'steamcommunity');
836
+ }, 'steamcommunity');
837
+ }, 'steamcommunity');
838
+ };