Compare commits

...

486 Commits

Author SHA1 Message Date
Alex Corn
fd872490c8 3.44.4 2023-06-14 20:10:14 -04:00
Alex Corn
be528d1a19 Change default value for disableMobile to true 2023-06-14 20:10:00 -04:00
Alex Corn
3bcaf40850 3.44.3 2022-10-28 04:31:42 -04:00
DoctorMcKay
0ccac69bea Merge pull request #300 from misterdudu/patch-1
Fix: New rate limit rule from steam
2022-10-28 04:30:41 -04:00
Eduardo
48d63857e2 Fix: New rate limit rule from steam
Steam added a new rule for inventories. Requesting with a "count" higher than 2000 will ban the proxy after four requests. All requests made with less than 2001 will have a rate limit of around 60/min per proxy.
2022-10-28 04:34:16 -03:00
Alex Corn
b11734cd9b 3.44.2 2022-08-24 21:47:03 -04:00
DoctorMcKay
5c37c38dda Merge pull request #291 from Heartz66/patch-2
Fix Not Logged In
2022-08-24 21:46:46 -04:00
Heartz66
260bfaa0ad Fix Not Logged In
Steam recently changed their login pages. This change should correctly identify the login state.
2022-08-24 23:59:51 +02:00
Alex Corn
1031a4dbd7 3.44.1 2022-05-13 22:49:06 -04:00
Alex Corn
e28fe5ca05 Merge pull request #286 from Heartz66/patch-1
Fix Not Logged In
2022-05-13 22:44:56 -04:00
Heartz66
90a9ee1aa9 Fix Not Logged In
Current version doesn't correctly identify login state. This change should correctly identify the state.
2022-05-13 17:10:52 +02:00
Alex Corn
9a069e80fb 3.44.0 2022-02-02 03:36:23 -05:00
Alex Corn
640c47b933 Updated comment 2022-02-02 03:35:44 -05:00
Alex Corn
f5d65a9ad4 Added getBoosterPackCatalog and createBoosterPack 2022-02-02 03:15:50 -05:00
Alex Corn
7ae8a97a0f Slight refactor for help.js component 2021-07-22 23:58:47 -04:00
Alex Corn
ec1284e961 Make callback optional in removePackage/restorePackage 2021-07-22 03:30:42 -04:00
Alex Corn
dc4bb1b554 3.43.1 2021-07-22 03:25:57 -04:00
Alex Corn
cd90d7a563 Trim comments before returning them 2021-07-22 03:25:47 -04:00
Alex Corn
851c14bd93 3.43.0 2021-07-22 03:09:56 -04:00
Alex Corn
24a42e9658 Export EFriendRelationship with the E prefix 2021-07-22 03:05:23 -04:00
Alex Corn
8ec671d952 Reformat 2021-07-22 03:04:38 -04:00
Alex Corn
2821a5900c Merge pull request #267 from Revadike/feature-get-friends-list
Added getFriendsList
2021-07-22 03:04:16 -04:00
Alex Corn
2da6725283 Merge branch 'master' into feature-get-friends-list 2021-07-22 03:04:06 -04:00
Alex Corn
23a7940064 Merge branch 'master' into feature-get-friends-list 2021-07-22 03:03:24 -04:00
Alex Corn
165c963b1a Create new help component and move restore/removePackage to it 2021-07-22 03:01:57 -04:00
Alex Corn
429a76edcc Reformat 2021-07-22 02:56:03 -04:00
Alex Corn
3a224ff45f Merge pull request #262 from Revadike/feature-package-control
Added restorePackage & removePackage
2021-07-22 02:55:01 -04:00
Alex Corn
dfad525e4e Merge pull request #220 from Revadike/feature/profile-comments
Added deleteUserComment and getUserComments
2021-07-22 02:34:32 -04:00
Revadike
81877f4bb7 Use object.assign instead 2021-07-22 07:48:46 +02:00
Alex Corn
f6a3186865 Merge pull request #268 from Revadike/enums-update
Updated Enums
2021-06-05 02:41:02 -04:00
Revadike
e0687f8828 Updated Enums 2021-06-04 04:24:11 +02:00
Revadike
d54dd2c892 Added getFriendsList 2021-06-04 04:08:07 +02:00
Revadike
b4818d6d67 Bugfix 2021-06-04 03:26:24 +02:00
Revadike
d8eb07f046 re-use variable
Co-authored-by: Mr-VIT <vit@v1t.su>
2021-05-13 01:14:52 +02:00
Revadike
e6995e337e Added restorePackage & removePackage 2021-03-19 15:22:41 +01:00
Alex Corn
2bbdbf36ad 3.42.0 2020-11-05 02:55:53 -05:00
Alex Corn
e6311c0fcb Removed attempt to get oauth token from webchat 2020-11-05 02:42:31 -05:00
Alex Corn
2fc8d1f4aa Refactored gem sack code 2020-11-05 02:19:31 -05:00
Jack Nolddor
b00b871a5b Added gems/sack transformations (#260)
Added Sack of Gems <---> Gems transformations
2020-11-05 01:31:35 -05:00
Alex Corn
15079621d2 3.41.8 2020-10-03 04:54:58 -04:00
Alex Corn
d91c600eae Merge pull request #258 from theanht1/master
Fix parentalUnlock
2020-10-02 02:17:35 -04:00
Anh Mai
0f98403c16 feat: Add sessionid to form of parentalUnlock 2020-09-30 17:43:00 +07:00
Alex Corn
d9b6b55efc 3.41.7 2020-09-23 18:11:33 -04:00
Alex Corn
0fc62d2966 Merge pull request #257 from timi-owo/master
Fixed 'language' was missing from the 'CSteamUser.getInventoryContents'
2020-09-23 17:46:59 -04:00
timi-owo
5daf5f712a Fixed param 'language' was missing when calling 'CSteamUser.getInventoryContents' method 2020-09-24 01:44:32 +08:00
Alex Corn
737aa9dc41 Added comment 2020-07-01 00:52:15 -04:00
Alex Corn
07fb26047b 3.41.6 2020-06-30 22:36:48 -04:00
Alex Corn
2f9b24c447 Only attempt to delete this.currency if it actually exists 2020-06-30 22:24:04 -04:00
Alex Corn
211c625da0 3.41.5 2020-06-26 03:45:17 -04:00
Alex Corn
2e61a6ed70 Fixed editProfile and profileSettings for Steam update 2020-06-26 03:44:58 -04:00
Alex Corn
f0a3cb27f8 Merge pull request #250 from itsjfx/open-booster-pack-patch
Add openBoosterPack
2020-05-28 22:30:35 -04:00
Alex Corn
6ed33a100e 3.41.4 2020-05-28 00:15:39 -04:00
Alex Corn
d41a52638b Prevent timeOffset delete timeout from holding app open 2020-05-28 00:15:20 -04:00
jfx
58516d4ed1 Add openBoosterPack 2020-05-23 16:24:20 +10:00
Alex Corn
3f99c718d4 Modernized disable_twofactor example script 2020-04-04 21:53:34 -04:00
Alex Corn
b92c5d852d Modernized enable_twofactor example script 2020-04-04 21:53:32 -04:00
Alex Corn
f5da011cde Create FUNDING.yml 2020-03-10 03:51:16 -04:00
Alex Corn
3633ab032c 3.41.3 2019-11-08 03:33:06 -05:00
Alex Corn
76df74d724 Added hidden parameter to CSteamGroup#postAnnouncement 2019-11-08 03:33:00 -05:00
Alex Corn
8b5cd59021 v3.41.2 2019-11-08 03:27:57 -05:00
Alex Corn
25adcb101b 3.41.1 2019-11-08 03:24:42 -05:00
Alex Corn
d7ecad670d Merge pull request #237 from Heartz66/patch-1
Fix wrong date
2019-11-08 03:23:27 -05:00
Heartz66
8225ea5127 Fix wrong date
https://github.com/DoctorMcKay/node-steamcommunity/issues/236
2019-11-06 19:43:45 +01:00
Alex Corn
59bf4f1e92 Merge pull request #233 from Revadike/patch-1
Optional hidden parameter to postGroupAnnouncement
2019-10-15 01:13:03 +02:00
Revadike
a3468bf99b Optional hidden parameter to postGroupAnnouncement 2019-10-14 04:28:53 +02:00
Alex Corn
e299623452 Use different filenames for different images, similar to how Steam does it 2019-09-25 05:33:14 -04:00
Alex Corn
29b31009ce Merge remote-tracking branch 'origin/master' 2019-09-25 05:30:14 -04:00
Alex Corn
d4d0d7da26 3.41.0 2019-09-24 04:07:34 -04:00
Alex Corn
3c8488d441 Added method sendImageToUser 2019-09-24 04:06:37 -04:00
Alex Corn
3ea0995415 3.40.2 2019-09-21 04:27:55 -04:00
Alex Corn
21c4ba3e27 Restore cache_expiration from game-specific data for backwards compatibility
(Fixes #229)
2019-09-21 04:27:39 -04:00
Alex Corn
4d4df7c2a5 3.40.1 2019-09-07 05:06:06 -04:00
Alex Corn
aa0d4d32b7 Removed stats-reporter 2019-09-07 05:05:46 -04:00
Alex Corn
444a6d238d Merge pull request #225 from Revadike/patch-1
Added userID guard
2019-06-17 03:18:06 -04:00
Revadike
98373708e5 Added userID guard
Because I got this:
```
 2019-06-12 | 01:22:30.151 | ERROR | crashBot                       | Fatal error: TypeError: Cannot read property 'getSteamID64' of null
    at get (node_modules\steamcommunity\components\users.js:416:60)
    at SteamCommunity.getUserInventoryContents (node_modules\steamcommunity\components\users.js:412:2)
    at unpackSteamBoosterPacks (index.js:3588:21)
    at cleanupSteamInventory (index.js:3583:5)
    at SteamUser.refreshSteamWebSession (index.js:857:38)
    at Object.onceWrapper (events.js:273:13)
    at SteamUser.emit (events.js:182:13)
    at SteamUser.gotSteamWebSession (index.js:1754:16)
    at SteamUser.emit (events.js:182:13)
    at _apiRequest (node_modules\steam-user\components\web.js:61:9)
    at IncomingMessage.<anonymous> (node_modules\steam-user\components\webapi.js:82:4)
    at IncomingMessage.emit (events.js:187:15)
    at endReadableNT (_stream_readable.js:1094:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)
```
2019-06-17 02:43:07 +02:00
Alex Corn
0df2662053 3.40.0 2019-04-19 01:28:43 -04:00
Alex Corn
a46ed17415 Added clearPersonaNameHistory 2019-04-19 01:20:01 -04:00
Revadike
9ed377e843 Oopsy 2019-04-05 00:13:02 +02:00
Revadike
868ce9cdb4 Added missing functions 2019-04-05 00:10:00 +02:00
Revadike
8edc5020e1 Added getUserComments(userID, options, callback)
https://github.com/DoctorMcKay/node-steamcommunity/issues/208
2019-04-05 00:06:48 +02:00
Revadike
f2be027a63 Updated postUserComment and added deleteUserComment
https://github.com/DoctorMcKay/node-steamcommunity/issues/219
2019-04-04 23:30:28 +02:00
Alex Corn
fbfff1398b Merge remote-tracking branch 'origin/master'
# Conflicts:
#	.idea/encodings.xml
2019-02-14 00:29:44 -05:00
Alex Corn
224b7b85e0 3.39.0 2019-02-13 17:59:41 -05:00
Alex Corn
22d34b1faf Added Steam ID to output of getClientLogonToken 2019-02-13 17:59:33 -05:00
Alex Corn
cfd932dc72 Changed output format of getClientLogonToken 2019-02-13 17:42:28 -05:00
Alex Corn
4710afa28b Added getClientLogonToken 2019-02-13 17:40:45 -05:00
Alex Corn
1180eb9d3f Deprecated webchat methods 2019-02-13 17:35:39 -05:00
Alex Corn
3ca85bedc2 Merge branch 'master' of ssh://github.com/DoctorMcKay/node-steamcommunity 2019-02-13 17:28:57 -05:00
Alex Corn
e05efabcb9 Merge pull request #211 from Hongarc/master
Add comment group
2019-02-12 21:35:58 -05:00
Alex Corn
6cd06c3adf Updated module type 2019-02-06 19:54:04 -05:00
Alex Corn
d35e3c107c Make sure CConfirmation.{id, creator} are always strings 2019-02-06 19:36:01 -05:00
Alex Corn
4e90811008 Merge pull request #213 from Royalgamer06/patch-2
Package 'fs' never used
2018-11-19 14:54:33 -05:00
Royalgamer06
018d3de5a2 Package 'fs' never used 2018-11-19 20:48:57 +01:00
Alex Corn
3949abe09f 3.38.0 2018-11-01 01:20:31 -04:00
Alex Corn
62394b9ffe Added ability to change friends list privacy 2018-11-01 01:20:19 -04:00
Alex Corn
ebac6c97ad Added IDE config to repo 2018-10-31 03:47:55 -04:00
Hongarc
a0ad33d5eb Add comment group 2018-10-20 13:29:52 +07:00
Alex Corn
b7a3e51bfc Merge pull request #205 from waylaidwanderer/master
Add the ability to set custom mobile user-agent
2018-07-11 02:31:04 -04:00
Joel
bf13224cbf Add the ability to set custom mobile user-agent 2018-07-10 23:30:12 -07:00
Alex Corn
117374a4c5 Updated Chrome user-agent 2018-07-11 02:22:39 -04:00
Alex Corn
aa84505132 3.37.1 2018-07-11 02:12:45 -04:00
Alex Corn
bb97565f2a Fixed steamID not being properly set 2018-07-11 02:12:37 -04:00
Alex Corn
7462e4b767 3.37.0 2018-07-11 00:00:16 -04:00
Alex Corn
adbf1d47a7 Added postProfileStatus and deleteProfileStatus (closes #200) 2018-07-10 23:59:57 -04:00
Alex Corn
8c4fde720a Updated EResult 2018-07-10 23:53:05 -04:00
Alex Corn
458d7b6db1 Renamed getProfileBackground to getUserProfileBackground and added it to CSteamUser 2018-07-10 23:28:26 -04:00
Alex Corn
411d74036f Updated getProfileBackground output a bit 2018-07-10 23:24:41 -04:00
Mert
1b3117b8dc Add getProfileBackground method
Adds ability to get URL of Steamcommunity profile background.
2018-07-10 23:18:03 -04:00
Alex Corn
6eec88d196 3.36.2 2018-05-18 17:32:48 -04:00
Alex Corn
3921ce17d0 Follow redirects when posting to own profile (fixes #178) 2018-05-18 17:32:33 -04:00
Alex Corn
4441d6f6e0 Bumped required request version 2018-05-18 17:29:14 -04:00
Alex Corn
870de6817e 3.36.1 2018-04-15 10:43:14 -04:00
Alex Corn
d5dbb230df Removed extra import 2018-04-15 10:43:05 -04:00
Alex Corn
5e9c06bdc4 3.36.0 2018-04-11 00:27:57 -04:00
Alex Corn
8d163d0d85 Added privacy controls for gameDetails and playtime 2018-04-11 00:27:10 -04:00
Alex Corn
051d376508 3.35.2 2018-04-10 01:08:14 -04:00
Alex Corn
913dff68ef Fixed crashes if somehow market items are really broken (fixes #197) 2018-04-10 01:06:52 -04:00
Alex Corn
754847ee88 Make CMarketItem not broken for non-CSGO apps (fixes #196) 2018-04-10 01:01:03 -04:00
Alex Corn
645e03b89c 3.35.1 2018-04-01 23:36:04 -04:00
Alex Corn
aa302bc09e Fixed various profile url requests 2018-04-01 23:35:56 -04:00
Alex Corn
1c0f8d4bca 3.35.0 2018-04-01 03:39:00 -04:00
Alex Corn
a7bd694f4b Added inventory gift redemption 2018-04-01 03:38:45 -04:00
Alex Corn
9e380e71f5 jsdoc 2018-04-01 03:31:07 -04:00
Alex Corn
e97e63fe6d Added ability to get gem value for an item and to turn items into gems 2018-04-01 03:22:00 -04:00
Alex Corn
f6c7c74ab6 Fixed Node.js process being kept running unnecessarily 2018-04-01 03:15:13 -04:00
Alex Corn
8846082f60 Fixed getMarketApps not returning all apps 2018-04-01 02:51:46 -04:00
Alexander Corn
eb609b5c42 Added some more explicit error handling if we try to do things while logged out 2018-02-27 01:40:59 -05:00
Alex Corn
238a36de0f v3.34.1 2018-01-22 02:02:56 -05:00
Alex Corn
c38111b61a Merge pull request #189 from Aareksio/patch-1
Fix infinite loop in getWebApiKey
2018-01-22 01:48:17 -05:00
Arkadiusz Sygulski
0eb27a15d2 Fix infinite loop in getWebApiKey
Fix infinite loop in getWebApiKey when trying to get web api key on account without email address confirmed
2018-01-22 03:48:45 +01:00
Alexander Corn
0ab3a94493 Use writeFileSync instead of writeFile without callback 2018-01-16 14:40:07 -05:00
Alexander Corn
7c2ec65b8c Add .editorconfig 2017-12-30 02:25:07 -05:00
Alexander Corn
8e761ffe6c 3.34.0 2017-12-28 00:34:23 -05:00
Alexander Corn
06f04887fd Updated CONTRIBUTING.md 2017-12-28 00:34:15 -05:00
Alexander Corn
e5f986a8bf Added author info 2017-12-28 00:33:01 -05:00
Alexander Corn
5da6e40ebb Fixed some code style issues 2017-12-28 00:32:06 -05:00
Alex Corn
0b8dd57a40 Merge pull request #186 from hell00world/master
Added changeTradeURL method
2017-12-28 00:21:00 -05:00
hell00world
6f8456882a Added changeTradeURL method 2017-12-28 00:19:57 +01:00
Alex Corn
0b57b47ecb Minor tweaks 2017-12-26 16:39:23 -05:00
Alex Corn
b5f50a6614 Style update 2017-12-26 16:34:19 -05:00
Alex Corn
4257140c15 Merge remote-tracking branch 'origin/master' 2017-12-26 16:34:02 -05:00
Alex Corn
724badd175 Merge pull request #163 from potato20/disable-mobile-flag
Flag to disable mobile login
2017-12-26 16:33:56 -05:00
Alex Corn
a9455b0ff6 Style updates, use HTTPS, coerce steamids to SteamId objects 2017-12-26 16:32:21 -05:00
Alex Corn
b3dfdd47d8 Fixed missing brace 2017-12-26 16:32:04 -05:00
Alex Corn
1efac6bc70 Merge pull request #159 from Jarzon/groupcomments
Steam group comments
2017-12-26 16:28:16 -05:00
Alex Corn
eacb85c711 3.33.2 2017-11-30 08:59:58 -05:00
Alex Corn
8eab34bf18 Fixed errors not being handled in getTradeURL 2017-11-30 08:59:49 -05:00
Alexander Corn
b45e8d97ed 3.33.1 2017-10-21 16:52:11 -04:00
Alex Corn
6abb14a9bc Merge pull request #183 from borianm/patch-1
Fix for issue #182
2017-10-21 16:48:23 -04:00
Alexander Corn
6049d7a259 Added deprecation notice to getInventoryHistory 2017-10-20 19:28:26 -04:00
AirboN
e0320b27d7 Fixed issue with undefined author in events 2017-10-17 00:48:06 +02:00
AirboN
7986a87b3f Fixed issue with undefined author in events 2017-10-17 00:42:26 +02:00
Alexander Corn
cf7e5f8995 3.33.0 2017-10-06 12:01:10 -04:00
Alexander Corn
a03dad9507 Added getTradeURL method 2017-10-02 14:55:29 -04:00
Alexander Corn
7da7ecfc56 3.32.3 2017-09-29 00:13:17 -04:00
Alex Corn
6bf3f26f2f Merge pull request #175 from Visagalis/patch-1
Typo/bug in GetGroupHistory code.
2017-09-29 00:13:00 -04:00
Jason Vaillancourt
dc2e5d09a7 Merge branch 'master' into groupcomments 2017-08-20 20:13:41 -04:00
Alexander Corn
d716e8a494 3.32.2 2017-08-17 13:12:44 -04:00
Alexander Corn
458a179852 Fixed reference to undefined self 2017-08-17 13:12:36 -04:00
Alexander Corn
f5d76527ba 3.32.1 2017-08-15 21:43:14 -04:00
Alexander Corn
5875f47ebc Ignore chat friendstate responses if we no longer care 2017-08-15 21:43:10 -04:00
Julius
d7095aa7c5 Typo/bug in GetGroupHistory code.
Typo which makes group history objects to contain incorrect actor (it contains same actor as user).
2017-08-08 21:52:12 +03:00
Alexander Corn
03e0a1bdac 3.32.0 2017-08-06 19:18:43 -04:00
Alexander Corn
a51973c9b9 Added CSteamGroup methods for group-join requests 2017-08-06 19:11:21 -04:00
Alexander Corn
96d87ee8e6 Added methods to deal with Steam group join requests 2017-08-06 19:08:45 -04:00
Alexander Corn
7beef548dc Merge pull request #174 from Unsigno/master
Updated getUserInventoryContexts
2017-07-11 12:32:27 -04:00
Unsigno
6d9ebabb17 Updated getUserInventoryContexts to return 0 inventories and handle private profile error 2017-07-11 14:02:01 +02:00
Alexander Corn
5d5d8548af 3.31.0 2017-06-15 10:06:54 -04:00
Alexander Corn
b5142ad0cc Added language argument to getUserInventoryContents 2017-06-15 10:04:37 -04:00
Alexander Corn
3045a5d0cc Added stats-reporter integration 2017-06-15 10:03:14 -04:00
Alexander Corn
2dcb123761 Update request dependency version
(make david-dm happy)
2017-06-11 13:26:52 -04:00
Alexander Corn
bd29d598b1 Throw Error if using confirmations without being logged in 2017-06-02 23:35:23 -04:00
Alexander Corn
7e39715427 Merge pull request #168 from morethanwords/patch-1
Update getUserInventoryContents
2017-04-21 14:23:25 -04:00
Eduard Kuzmenko
a97edfd492 Update users.js 2017-04-21 15:46:20 +03:00
Eduard Kuzmenko
68b1f21655 Update getUserInventoryContents
There will be a re-loop if description of the item not the last one in descriptions (and quickDescriptionLookup will rewrite everytime)
2017-04-21 15:26:45 +03:00
Alexander Corn
b4772baf39 3.30.7 2017-03-27 14:42:46 -04:00
Alexander Corn
47dc22ca0d Handle cases where inventories are private in getUserInventoryContexts 2017-03-27 14:39:41 -04:00
Jarzon
3bba7025d4 Changes for code consistency 2017-03-15 13:08:22 -04:00
Jarzon
e2f94cb1e7 Convert the number to a string 2017-03-15 12:55:48 -04:00
Alexander Corn
acbd71666b Merge pull request #165 from Aareksio/patch-1
Check if instance is logged in before comparing steamids (fixes #164)
2017-03-06 11:35:23 -05:00
Arkadiusz Sygulski
3c35026a32 Check if instance is logged in before comparing steamids (fixes #164) 2017-03-06 16:02:20 +01:00
Alexander Corn
aca375d9da Give different error message if family view unlock failed for rate-limit 2017-03-04 22:30:25 -05:00
Jarzon
91285ba1c7 Merge branch 'master' into groupcomments 2017-03-04 09:04:24 -05:00
Alexander Corn
758b749487 3.30.6 2017-02-25 19:12:42 -05:00
Alexander Corn
a9b16cef1e Fixed crash no non-string 403 responses
https://dev.doctormckay.com/topic/845-responsebodymatch-is-not-a-function/
2017-02-25 19:12:34 -05:00
Potato 2.0
c6ab392c37 Add disableMobile flag to login() so user has option to not pretend we're logging in from a phone 2017-02-18 14:13:15 -05:00
Alexander Corn
4542793175 3.30.5 2017-02-12 02:22:43 -05:00
Alexander Corn
d18a0244ec Updated default user-agent to latest Chrome on Win10 x64 2017-02-12 02:22:17 -05:00
Alexander Corn
b1c772d9b9 Keep HTTP connections open between requests 2017-02-11 22:28:22 -05:00
Jarzon
530bbcb9eb Pass a Error obj to the callback 2017-02-08 15:22:50 -05:00
Jarzon
f7ebe68bd8 Adding .deleteComment() 2017-02-08 15:08:34 -05:00
Jarzon
e2dae00af2 Simplifying the request's options 2017-02-07 18:34:12 -05:00
Jarzon
bbd80d4154 Fixing not getting the html in the comment 2017-02-02 07:53:18 -05:00
Jarzon
c180557f52 Using decodeSteamTime() to parse date
Adding to .decodeSteamTime() the ability to parse steam relative time
2017-02-01 13:30:38 -05:00
Jarzon
b3174b9f0b Revert using chrono to parse the date 2017-02-01 12:03:34 -05:00
Jarzon
12f1ddf6a0 Missing header Content-Type 2017-02-01 11:27:58 -05:00
Jarzon
db3aa4ace8 Catch the community id and the custom url of the author 2017-01-31 08:48:38 -05:00
Jarzon
f1b742dff4 Adding Chrono to parse relative date 2017-01-31 06:26:11 -05:00
Jarzon
444649ee8c Adding .getAllComments() 2017-01-31 06:16:38 -05:00
Alexander Corn
b9ec1ccfb5 3.30.4 2017-01-18 15:51:44 -05:00
Alexander Corn
0c7c030f78 Fixed string in callback instead of Error (fixes #153) 2017-01-18 15:50:11 -05:00
Alexander Corn
e5f1f14aad Fixed empty inventory resulting in "Malformed response" (fixes #156)
Display Steam error message when available instead of "HTTP error 500"
2017-01-18 15:49:27 -05:00
Alexander Corn
0f72ea7d93 3.30.3 2017-01-04 00:10:40 -05:00
Alexander Corn
d1906a2c83 Fallback to the default "?" avatar if we don't have a hash (fixes #151) 2017-01-04 00:10:11 -05:00
Alexander Corn
3964af7ade Throw a descriptive error if accountName or password is missing in login
Closes #119
2017-01-04 00:07:33 -05:00
Alexander Corn
5a5c5373aa Bump dependency versions to make david happy 2017-01-04 00:03:18 -05:00
Alexander Corn
ec60e1b73e 3.30.2 2016-12-30 17:11:12 -05:00
Alexander Corn
a22a75d4e1 Merge pull request #150 from Vankxr/master
Check if requested inventory is ours.
2016-12-30 17:06:21 -05:00
Vankxr
313753bb05 Check if requested inventory is ours.
Should fix https://github.com/DoctorMcKay/node-steamcommunity/issues/149
2016-12-30 21:24:53 +00:00
Alexander Corn
459fe4d8b1 3.30.1 2016-12-15 00:53:46 -05:00
Alexander Corn
489c0441a5 Restore market_fee_app property to CEconItem for Steam Community items 2016-12-15 00:53:26 -05:00
Alexander Corn
d91e74d02a 3.30.0 2016-12-13 01:53:57 -05:00
Alexander Corn
d120d09970 Fixed tag data to be identical to old endpoint 2016-12-13 01:53:30 -05:00
Alexander Corn
84ce979a78 Fixed new inventory loading not working properly 2016-12-13 01:52:52 -05:00
Alexander Corn
88b0495386 Renamed getUserInventory, added back old one and marked it as deprecated 2016-12-13 01:32:17 -05:00
Alexander Corn
d367e8eae4 Added better paging controls for new inventory endpoint 2016-12-13 01:29:06 -05:00
Alexander Corn
9a3cf927c0 Fixed some more currency detection 2016-12-13 01:28:48 -05:00
Alexander Corn
56cdfff672 3.29.1 2016-12-13 01:07:12 -05:00
Alexander Corn
35e6dd16e7 Re-add "pos" property to inventory items for backwards compatibility 2016-12-13 01:07:00 -05:00
Alexander Corn
d0af8584c1 Send referer when fetching inventories 2016-12-13 00:50:29 -05:00
Alexander Corn
f1afcd1962 3.29.0 2016-12-13 00:46:23 -05:00
Alexander Corn
513b0bff1c Added some optimizations and backwards-compatibility for inv fetching 2016-12-13 00:46:02 -05:00
Alexander Corn
ab037b1931 Pass response body back to caller for inventory fetching 2016-12-13 00:45:38 -05:00
Alexander Corn
06df0ecd42 Handle currencies with new endpoint's data in CEconItem 2016-12-13 00:45:23 -05:00
João Silva
b789e13a70 Update inventory loading endpoint (#148)
* Update inventory loading endpoint

Should fix 429s :)

* Fix missing contextID parameter at CEconItem class

* Revert "Fix missing contextID parameter at CEconItem class"

This reverts commit 416321105b.

* Revert "Revert "Fix missing contextID parameter at CEconItem class""

This reverts commit 49ecd46954.

* Update #1
2016-12-13 00:23:02 -05:00
Alexander Corn
95a36b00c1 Remember time offset for purposes of acceptConfirmationForObject
Closes #147
2016-12-08 17:57:01 -05:00
Alexander Corn
41c87501c4 3.28.0 2016-10-28 19:24:05 -04:00
Alexander Corn
4f3ddcc787 Merge pull request #134 from Aareksio/confirmations
Emit `confirmationAccepted` after accepting the confirmation using `confirmationChecker`
2016-10-27 17:07:41 -04:00
Arkadiusz Sygulski
d10fa9983d Emit confirmationAccepted after accepting the confirmation 2016-10-27 22:30:58 +02:00
Arkadiusz Sygulski
666226731e Remove unused function: handleNewConfirmation 2016-10-27 22:18:31 +02:00
Alexander Corn
d096f47cac 3.27.2 2016-10-10 00:55:36 -04:00
Alexander Corn
a8eedb3067 Make it possible to skip json error check 2016-10-10 00:55:19 -04:00
Alexander Corn
ed12a54e81 3.27.1 2016-10-04 09:16:08 -04:00
Alexander Corn
eb341314ef Merge pull request #130 from Pavelas/master
Delete the profile URL cache after log in.
2016-10-04 09:15:03 -04:00
Pavelas
33cd9474ab Delete the profile URL cache after log in. 2016-10-04 10:26:20 +02:00
Alexander Corn
e8a43734be 3.27.0 2016-09-16 19:30:32 -04:00
Alexander Corn
8a27147f5c Added accountAlerts to getNotifications 2016-09-16 19:30:19 -04:00
Alexander Corn
d00d07f828 Added acceptConfirmationForObject convenience method 2016-09-16 19:03:49 -04:00
Alexander Corn
471c11d406 Merge pull request #128 from SzymonLisowiec/patch-1
Problem when search "Key:" in polish language.
2016-09-16 18:21:17 -04:00
Alexander Corn
dd3a9254aa 3.26.1 2016-09-12 11:22:29 -04:00
Alexander Corn
71a3893636 Fixed cookies not being set for non-community domains after logging in
Also fixed secure flag not working for cookies
2016-09-12 11:22:18 -04:00
Alexander Corn
5928c3b76a 3.26.0 2016-09-12 01:33:30 -04:00
Alexander Corn
1b60712b8b Updated getNotifications to use the new JSON endpoint 2016-09-12 01:33:11 -04:00
Alexander Corn
335a955abd Fixed cookies not being set 2016-09-12 01:24:39 -04:00
Alexander Corn
6d534df8ba Set cookies on all Steam domains, and properly flag as secure 2016-09-12 01:17:00 -04:00
Alexander Corn
9022a9a45e 3.25.0 2016-09-08 19:45:05 -04:00
Alexander Corn
ce4d7ae026 Pull trade offer ID directly from listing page 2016-09-08 19:23:15 -04:00
Kysune
b863377c87 Problem when search "Key:" in polish language. 2016-09-01 21:05:46 +02:00
Alexander Corn
20785ee92b 3.24.0 2016-08-31 22:20:49 -04:00
Alexander Corn
3afb393f58 Updated getInventoryHistory for new pagination scheme 2016-08-31 22:20:36 -04:00
Alexander Corn
79543187f0 Fixed timezones being incorrect in trade history entries 2016-08-31 22:20:24 -04:00
Alexander Corn
3139aec463 Reformat inventoryhistory.js 2016-08-31 21:48:17 -04:00
Alexander Corn
bb917af86e 3.23.3 2016-08-10 18:55:36 -04:00
Alexander Corn
17f51b9caa Fixed double callbacks on some json errors (fixes #123) 2016-08-10 18:55:23 -04:00
Alexander Corn
dd638df118 3.23.2 2016-08-09 15:45:32 -04:00
Alexander Corn
b937a05442 Make sure we actually get json back when we want it (fixes #122) 2016-08-09 15:45:21 -04:00
Alexander Corn
9c34d18cd9 3.23.1 2016-08-07 00:04:56 -04:00
Alexander Corn
02e3798124 Handle some cases where the session expires while we're using webchat 2016-08-07 00:04:30 -04:00
Alexander Corn
5d9922cc33 3.23.0 2016-07-13 13:12:39 -04:00
Alexander Corn
153011e1c9 Make _community non-enumerable on CConfirmation 2016-07-13 13:11:56 -04:00
Alexander Corn
575c196cb7 Added acceptAllConfirmations method 2016-07-13 13:11:44 -04:00
Alexander Corn
47a24d19cf Added ability to act on multiple confirmations 2016-07-13 13:11:36 -04:00
Alexander Corn
0eac19b71c 3.22.0 2016-07-10 01:02:48 -04:00
Alexander Corn
ca73ecc786 Parse times in getUserAliases into Date objects 2016-07-10 01:02:36 -04:00
Alexander Corn
e8733613d7 Merge pull request #117 from andrewda/master
Add ability to get user aliases
2016-07-10 00:52:13 -04:00
Andrew Dassonville
89a1df67ec Add ability to get user aliases 2016-07-06 16:20:42 -07:00
Alexander Corn
3ba0a8592e 3.21.2 2016-07-01 18:30:32 -04:00
Alexander Corn
57fa7a679f Fixed market search results including query string (fixes #113) 2016-07-01 18:29:56 -04:00
Alexander Corn
446e315183 Fixed market search result prices being horribly wrong 2016-07-01 18:27:39 -04:00
Alexander Corn
c6a9b74471 3.21.1 2016-06-29 23:08:54 -04:00
Alexander Corn
018e18cb5d Give "Family View Restricted" error if access denied due to Family View 2016-06-29 23:08:40 -04:00
Alexander Corn
303ce10e73 3.21.0 2016-06-27 16:57:12 -04:00
Alexander Corn
60e0e63a92 Fixed marketSearch (fixes #112) 2016-06-27 16:56:16 -04:00
Luke
17e5b94782 Added deleting events in CSteamGroup (#109)
* Added deleteEvent()

* Added deleteGroupEvent()
2016-06-21 22:39:23 -04:00
Alexander Corn
beaf012846 3.20.0 2016-06-15 15:07:45 -04:00
Alexander Corn
38c20a342c Merge pull request #105 from luop90/master
Added editing events to CSteamGroup
2016-06-15 14:17:41 -04:00
Luke
a406cfbfe2 Fixed a valid server object being overwritten 2016-06-15 11:25:44 -06:00
Luke Henke
c970eee9d3 Added editing events to CSteamGroup 2016-06-14 20:29:11 -06:00
Alexander Corn
38e3eefb33 3.19.10 2016-06-14 16:08:03 -04:00
Alexander Corn
a2e26459d9 Emit chatLogOnFailed on error, and handle fatal errors (fixes #104) 2016-06-14 16:07:46 -04:00
Alexander Corn
c1b04ee743 Fixed crash if chat poll returns no response
https://dev.doctormckay.com/topic/383-error-in-node-steamcommunity/
2016-06-14 16:02:04 -04:00
Alexander Corn
542eb8d47e 3.19.9 2016-06-03 20:11:56 -04:00
Alexander Corn
9081528f72 Fixed "Error: Error:" in trade errors 2016-06-03 20:10:33 -04:00
Alexander Corn
8be8e395b6 3.19.8 2016-05-05 16:12:35 -04:00
Alexander Corn
4b7ea80fcf Fixed profile URL cache always being deleted when editing profile 2016-05-05 16:12:27 -04:00
Alexander Corn
7f945afbca 3.19.7 2016-05-05 16:00:38 -04:00
Alexander Corn
3b241ba24f Cache profile URL so we don't need to request it all the time 2016-05-05 16:00:28 -04:00
Alexander Corn
fddd74cf24 Fixed crash if response to login is malformed 2016-04-04 21:42:46 -04:00
Alexander Corn
9a3138aa35 3.19.6 2016-04-02 16:15:35 -04:00
Alexander Corn
839d8b51e3 Automatically handle time offset when enabling 2FA (fixes #98) 2016-04-02 16:14:34 -04:00
Alexander Corn
ddf0221489 Added disable_twofactor example 2016-04-02 16:14:17 -04:00
Alexander Corn
64aaca66be 3.19.5 2016-03-31 14:34:27 -04:00
Alexander Corn
aac26212d7 Fixed sessionExpired event always being emitted for confirmation errors 2016-03-23 19:03:55 -04:00
Alexander Corn
d8d99a804f 3.19.4 2016-03-23 14:33:28 -04:00
Alexander Corn
f47afae1ea Pin cheerio version to 0.19.0
https://github.com/DoctorMcKay/node-steam-tradeoffer-manager/issues/120#issuecomment-200179648
2016-03-23 14:33:08 -04:00
Alexander Corn
8d90aac203 Use SteamID v1.0.0 2016-03-22 01:20:02 -04:00
Alexander Corn
f1bfd8b3af 3.19.3 2016-03-18 23:33:41 -04:00
Alexander Corn
579fc04c1c Prompt for CAPTCHA input in examples if needed (fixes #88) 2016-03-18 23:33:30 -04:00
Alexander Corn
58927dc937 Fixed fake error when removing 2FA (fixes #96) 2016-03-18 23:29:17 -04:00
Alexander Corn
018cacac59 Added CONTRIBUTING.md 2016-03-15 15:06:08 -04:00
Alexander Corn
26effb27fe 3.19.2 2016-03-14 21:39:55 -04:00
Alexander Corn
5880e922ab Fixed crashes when checking non-string HTTP responses (fixes #94) 2016-03-14 21:39:45 -04:00
Alexander Corn
fe007811a5 Bumped cheerio version 2016-03-08 01:56:31 -05:00
Alexander Corn
7b882c8439 Updated readme 2016-03-08 01:53:45 -05:00
Alexander Corn
6c4cb9e62f 3.19.1 2016-03-08 01:37:29 -05:00
Alexander Corn
2300081d43 Only fire the callback for blocked requests if one was provided 2016-03-08 01:37:20 -05:00
Alexander Corn
0f20f918be 3.19.0 2016-03-08 01:23:46 -05:00
Alexander Corn
b761b8108b Check properties instead of SteamID proto (fixes #90) 2016-03-08 01:23:31 -05:00
Alexander Corn
46e16cc0b6 Fixed loggedIn method 2016-03-07 01:32:27 -05:00
Alexander Corn
237d3786a2 Properly handle steammobile: protocol error meaning we're not logged in 2016-03-07 01:32:20 -05:00
Alexander Corn
1884ceb7d9 Emit sessionExpired when we detect that we aren't logged in 2016-03-07 01:32:02 -05:00
Alexander Corn
893c4df06b 3.19.0-beta6 2016-03-05 19:38:44 -05:00
Alexander Corn
593ed6b9ef Detect when we aren't logged in and should be 2016-03-05 18:58:39 -05:00
Alexander Corn
840e9548ed 3.19.0-beta5 2016-03-05 00:40:56 -05:00
Alexander Corn
dee6b7964b Fixed crash under specific conditions 2016-03-05 00:40:45 -05:00
Alexander Corn
2fc434d475 3.19.0-beta4 2016-03-05 00:27:13 -05:00
Alexander Corn
42ebbda00d Fixed typo 2016-03-05 00:27:04 -05:00
Alexander Corn
eed791acbd Added ability to delay HTTP requests 2016-03-05 00:14:36 -05:00
Alexander Corn
54b83016af 3.19.0-beta3 2016-03-05 00:01:47 -05:00
Alexander Corn
1032b1f832 Removed now-unnecessary _checkHttpError and _checkCommunityError calls 2016-03-04 23:59:49 -05:00
Alexander Corn
fce2560abe Handle calls to httpRequest with URI and no options 2016-03-04 23:50:49 -05:00
Alexander Corn
0069bb36ef 3.19.0-beta2 2016-03-04 19:36:42 -05:00
Alexander Corn
1028c4193f Added HTTP source 2016-03-04 19:35:04 -05:00
Alexander Corn
991b11e0f4 3.19.0-beta1 2016-03-04 18:51:39 -05:00
Alexander Corn
72c01468da Want node v4.0.0 or later 2016-03-04 18:27:50 -05:00
Alexander Corn
eecd682033 Added unified interface for HTTP requests with pre/post hooks 2016-03-04 18:26:47 -05:00
Alexander Corn
574a3127cb 3.18.7 2016-03-04 00:19:31 -05:00
Alexander Corn
55027f7b1f Merge pull request #87 from andrewda/patch-1
Fixed crash when handling an inventory issue
2016-03-04 00:18:17 -05:00
Andrew Dassonville
70ffc5559d Renamed body.error to body.Error 2016-03-03 19:22:05 -08:00
Andrew Dassonville
f4be134f71 Fixed crash when handling an inventory issue
```
/bots/node_modules/steamcommunity/components/users.js:265
                                callback(new Error(body.Error || "Malformed response"));
                                                       ^

TypeError: Cannot read property 'Error' of undefined
    at Request._callback (/bots/node_modules/steamcommunity/components/users.js:265:28)
    at Request.self.callback (/bots/node_modules/steamcommunity/node_modules/request/request.js:199:22)
    at emitTwo (events.js:100:13)
    at Request.emit (events.js:185:7)
    at Request.<anonymous> (/bots/node_modules/steamcommunity/node_modules/request/request.js:1036:10)
    at emitOne (events.js:95:20)
    at Request.emit (events.js:182:7)
    at IncomingMessage.<anonymous> (/bots/node_modules/steamcommunity/node_modules/request/request.js:963:12)
    at emitNone (events.js:85:20)
    at IncomingMessage.emit (events.js:179:7)
```
2016-03-03 13:42:05 -08:00
Alexander Corn
7792969cae Merge pull request #54 from Mikxail/bugfix-market-item-price
bugfix market-item price
2016-02-29 16:44:17 -05:00
Alexander Corn
57a3532860 3.18.6 2016-02-29 16:42:00 -05:00
Alexander Corn
facf335f0e Fixed captcha overriding more important errors (fixes #56) 2016-02-29 16:41:37 -05:00
Alexander Corn
1c3817b797 Fixed captcha URL (fixes #83) 2016-02-29 16:24:29 -05:00
Alexander Corn
74efd50f72 Added more informative error messages for RSA key retrieval failure 2016-02-06 23:48:26 -05:00
Alexander Corn
87e3665b01 Revised JSDoc language (#80) 2016-02-04 21:49:54 -05:00
Alexander Corn
0b94419de4 Merge pull request #76 from Aareksio/patch-1
Fix for issue #75
2016-01-25 11:47:34 -05:00
Arkadiusz Sygulski
defbd347cc Fix for issue #75
In received XML there's `isPrimary="0"`, which mostly likely causes the problem, which results the last group in xml to be set as primary. 
This should fix it.
2016-01-25 11:35:22 +01:00
Alexander Corn
7efc1f2fd0 3.18.5 2016-01-21 01:15:00 -05:00
Alexander Corn
05c2da89da Fixed crash when unable to check confirmations 2016-01-21 01:14:52 -05:00
Alexander Corn
b83c40419c 3.18.4 2016-01-20 23:05:07 -05:00
Alexander Corn
fb0097499d Added some more debug output to confirmation checker 2016-01-20 23:04:54 -05:00
Alexander Corn
c2983e3bcd RIP market and trade confirmation opt-out 2016-01-20 13:47:14 -05:00
Alexander Corn
80b3cc52d2 3.18.3 2016-01-19 11:30:02 -05:00
Alexander Corn
fa59b188a5 Use the standard device ID when enabling 2FA 2016-01-19 11:29:26 -05:00
Alexander Corn
e446cc3cd8 3.18.2 2016-01-18 18:47:02 -05:00
Alexander Corn
722ca6689b Removed excess whitespace 2016-01-18 18:46:45 -05:00
Alexander Corn
1276c71e4e Use same device ID for all trade confirmation requests 2016-01-18 18:38:50 -05:00
Alexander Corn
a51bd21153 3.18.1 2016-01-17 19:19:14 -05:00
Alexander Corn
f1bbfc4901 Removed unused arguments from CSteamGroup#deleteAnnouncement 2016-01-17 19:19:04 -05:00
Alexander Corn
eb8d094bf1 3.18.0 2016-01-17 19:10:02 -05:00
Alexander Corn
b1cca95ec0 Merge pull request #70 from LeCoffee/master
Adding ability to edit an annoucement.
2016-01-17 19:08:20 -05:00
Shaun Barratt
27ef78cc4b Added ability to deleteAnnoucement 2016-01-17 02:27:11 +00:00
Shaun Barratt
4223d88fd1 Catching HTTP errors properly
Specifying the Group in getAllGroupAnnouncements
Removing unnecessary comments
Made the callback for getAllGroupAnnouncements mandatory
2016-01-16 18:39:04 +00:00
Shaun Barratt
ac02f6cb49 Added getAllAnnouncements
Added example on how to use it
2016-01-16 00:06:42 +00:00
Shaun Barratt
d84601e256 Adding the whitespace back (blame atom.io!)
Converting spaces to tabs.
2016-01-15 21:28:43 +00:00
Shaun Barratt
4152865f5e Adding ability to edit an annoucement.
Added an example.
2016-01-15 17:48:57 +00:00
Alexander Corn
7627e6a3a0 3.17.2 2016-01-13 01:02:52 -05:00
Alexander Corn
bbcf0eb72f Merge pull request #65 from spddl/master
Error: You must fill out all fields in at least one language - fix
2016-01-13 01:01:58 -05:00
spddl
d51b6582e2 Error: You must fill out all fields in at least one language fix 2016-01-13 06:16:51 +01:00
Alexander Corn
bbd339b88b 3.17.1 2016-01-05 22:46:05 -05:00
Alexander Corn
fcaaa38085 Merge pull request #59 from Aareksio/master
Fixed chatMessage function
2016-01-05 22:44:54 -05:00
mikxail
26f27e2698 remove unused price calculating block
and fix lowestPrice value
2016-01-05 21:58:49 +03:00
Mole
6d8d0c5fbb Fixed missing 'var self = this;' 2016-01-05 15:33:54 +01:00
Alexander Corn
08c9ab6809 3.17.0 2016-01-04 19:17:46 -05:00
Alexander Corn
c150425ee5 Use gzip for HTTP requests 2016-01-03 14:38:02 -05:00
Mikhail Konovalov
eae78aab93 remove options argument for updatePrice method 2015-12-28 15:58:31 +03:00
Alexander Corn
5c2666e0f4 Merge pull request #55 from Mikxail/constructor-additional-options
additional options for SteamCommunity constructor
2015-12-28 00:24:11 -05:00
Alexander Corn
c64b6c7e99 Merge pull request #53 from Mikxail/bugfix-market-search
bugfix call performSearch
2015-12-28 00:17:26 -05:00
Mikhail Konovalov
318f9ab2b4 bugfix call performSearch 2015-12-27 16:53:15 +03:00
Mikhail Konovalov
5c2894a7d4 bugfix market-item price
and add currency option for .getMarketItem(appid, hashName, currency, cb)
2015-12-27 16:51:11 +03:00
Mikhail Konovalov
3167034b97 additional options for SteamCommunity constructor 2015-12-27 16:08:06 +03:00
Alexander Corn
3bfe8c3713 3.16.0 2015-12-26 18:38:53 -05:00
Alexander Corn
0c4e026701 Require steamguard value in oAuthLogin method 2015-12-26 18:34:22 -05:00
Alexander Corn
c246011fe2 Added oauth token to login callback 2015-12-26 18:34:11 -05:00
Alexander Corn
c290e16dc5 Generate sessionids that follow the same pattern as Valve's 2015-12-26 18:19:35 -05:00
Alexander Corn
f8852ba6c4 Pretend to be Chrome 2015-12-26 18:17:01 -05:00
Alexander Corn
52ee65d9da Added oAuthLogin 2015-12-26 18:15:09 -05:00
Alexander Corn
42e04b123a 3.15.0 2015-12-21 14:22:27 -05:00
Alexander Corn
1f69d1fc04 CEconItem: is_currency is bool, fraudwarnings/descriptions are arrays 2015-12-21 14:22:07 -05:00
Alexander Corn
e4b5e763eb Added support for escrow to getInventoryHistory 2015-12-21 14:21:47 -05:00
Alexander Corn
6feb607366 3.14.1593 2015-12-15 00:29:44 -05:00
Alexander Corn
cc64d9883a Fixed some errors not being Error objects 2015-12-15 00:29:00 -05:00
Alexander Corn
d4a92b4dea Added enable_twofactor example 2015-12-12 18:41:10 -05:00
Alexander Corn
e2849719c1 3.14.159 2015-12-10 18:43:32 -05:00
Alexander Corn
c9541d093c Delete mobile-client cookies after we login so we don't get mobile pages 2015-12-10 18:42:16 -05:00
Alexander Corn
339d5dc1e4 3.14.1 2015-12-07 16:36:20 -05:00
Alexander Corn
2a7a03d35e Use SHA-1 hash of our SteamID when enabling 2FA 2015-12-07 16:34:49 -05:00
Alexander Corn
ff1c3279c8 Use random hex value instead of current time for deviceid 2015-12-07 11:42:10 -05:00
Alexander Corn
9175a44828 3.14.0 2015-12-05 01:41:39 -05:00
Alexander Corn
deeeb633cf Delete _identitySecret if we have it when we stop polling 2015-12-05 01:41:35 -05:00
Alexander Corn
a50bd35a54 Added proper queue for confirmations 2015-12-05 01:37:41 -05:00
Alexander Corn
5bf51a2e3e Added automatic confirmation accepting 2015-12-05 00:41:27 -05:00
Alexander Corn
448b32b9c6 callback with an error if we don't get a valid RSA key (resolves #29) 2015-12-05 00:24:52 -05:00
Alexander Corn
01428e6007 3.13.0 2015-12-03 14:03:59 -05:00
Alexander Corn
43d1147fd7 Add tradeConfirmation alias for emailConfirmation and marketConfirmation
Resolves #24
2015-12-03 13:59:54 -05:00
Alexander Corn
479cc47bf5 Fixed crashes on 2FA errors (fixes #22) 2015-12-03 13:57:49 -05:00
Alexander Corn
01217a3659 3.12.2 2015-12-02 22:38:59 -05:00
Alexander Corn
0e34bceb1f Only reset the polling timer if we have an interval set 2015-12-02 22:38:51 -05:00
Alexander Corn
c4ea8eeb41 3.12.1
I screwed up 3.12.0 and npm won't let me publish over it, so 3.12.0 just
doesn't exist.
2015-12-02 22:25:00 -05:00
Alexander Corn
0f83fe8c64 Added confirmation polling 2015-12-02 22:20:16 -05:00
Alexander Corn
5624611421 Moved getWebApiKey to webapi.js component 2015-12-02 21:35:35 -05:00
Alexander Corn
49d2bb4252 Fixed finalizeTwoFactor checking success before want_more (fixes #16) 2015-12-02 21:33:33 -05:00
Alexander Corn
b54ccff7c8 Added confirmations 2015-12-02 21:31:17 -05:00
Alexander Corn
b82f7ca045 Merge pull request #17 from s992/master
Updates login code to use mobile login headers and cookies
2015-12-02 19:19:50 -05:00
Sean Walsh
2526539266 removes extra loginfriendlyname 2015-12-01 21:41:30 -08:00
Sean Walsh
fc75036974 moves headers away from being default on everything. only use them on login. 2015-12-01 17:57:26 -08:00
Sean Walsh
ee1e819812 removes extra steam_language cookie 2015-12-01 17:40:51 -08:00
Sean Walsh
fceea0737d updates login code to use mobile login so that we can grab the oauth token. 2015-11-30 17:39:55 -08:00
Alexander Corn
f3ac556922 Added check for community error when getting webapi oauth token 2015-11-30 19:40:10 -05:00
Alexander Corn
2e02c14455 3.11.1 2015-11-27 22:09:47 -05:00
Alexander Corn
8f580fc231 Fixed enableTwoFactor returning success when it wasn't successful 2015-11-27 22:09:36 -05:00
Alexander Corn
6f596b5b16 3.11.0 2015-11-27 17:57:57 -05:00
Alexander Corn
1249e28d83 Added enableTwoFactor and finalizeTwoFactor methods 2015-11-27 17:57:39 -05:00
Alexander Corn
85bcaa70be 3.10.0 2015-11-27 01:46:10 -05:00
Alexander Corn
2a5419513c Added disableTwoFactor method 2015-11-27 01:45:58 -05:00
Alexander Corn
deec5c52a4 Fixed isLimitedAccount and vacBanned (fixes #12) 2015-11-27 01:32:12 -05:00
Alexander Corn
2399a5b783 Added getWebApiOauthToken 2015-11-27 01:28:31 -05:00
Alexander Corn
efea8c6265 3.9.10 2015-11-11 00:52:18 -05:00
Alexander Corn
2be414b20c Merge pull request #10 from charredgrass/master
Fixed error on getNotifications
2015-11-11 00:36:54 -05:00
Maxwell Chow
25ebb4c5a3 Fixed error on getNotifications 2015-11-10 20:46:17 -08:00
Alexander Corn
ac87fd3a40 3.9.9 2015-11-09 23:30:39 -05:00
Alexander Corn
c67e2e04f1 Merge pull request #9 from shaunidiot/patch-1
Spelling error
2015-11-09 23:30:02 -05:00
Shaun
83eeb0f190 Spelling error 2015-11-10 12:29:23 +08:00
Alexander Corn
e09cf24fbf 3.9.8 2015-10-26 21:30:46 -04:00
Alexander Corn
9d2b5ade67 Fixed scheduleGroupEvent method
Fixes #7
2015-10-26 21:30:27 -04:00
Alexander Corn
be4e5c9449 3.9.7 2015-10-14 00:37:36 -04:00
Alexander Corn
5926197f23 Fixed primary group setting not working 2015-10-14 00:37:27 -04:00
Alexander Corn
2d86f81142 3.9.6 2015-10-06 18:53:33 -04:00
Alexander Corn
f37bff4a57 Make the error in getWebApiKey an Error object 2015-10-06 18:53:25 -04:00
Alexander Corn
c700b7663b 3.9.5 2015-10-04 19:47:06 -04:00
Alexander Corn
c26e548c64 Fixed some dumb code 2015-10-04 19:46:38 -04:00
Alexander Corn
658d8fb708 Merge pull request #6 from JorisD33/patch-get-inventory
Patch getInventory
2015-10-04 19:43:59 -04:00
Joris
cd68841d1d Fix getInventory issue 2015-10-04 23:28:40 +02:00
Joris
5853968661 Fix CSteamUser constructor 2015-10-04 23:20:30 +02:00
Alexander Corn
10d899f7a7 3.9.4 2015-09-20 00:49:51 -04:00
Alexander Corn
4e1453e702 Better handling for missing items in user XML 2015-09-20 00:49:42 -04:00
Alexander Corn
f3cf2a4361 3.9.3 2015-09-20 00:33:32 -04:00
Alexander Corn
2890b70add Actually fixed the previous issue 2015-09-20 00:33:24 -04:00
Alexander Corn
1810727a2f 3.9.2 2015-09-19 23:35:10 -04:00
Alexander Corn
3d566ed0fb Return an error if we get invalid XML when getting a CSteamUser 2015-09-19 23:35:01 -04:00
Alexander Corn
80979bc387 3.9.1 2015-09-09 12:08:36 -04:00
Alexander Corn
4e326e3295 Always use Error objects in callbacks 2015-09-09 12:08:18 -04:00
Alexander Corn
cff35ba863 3.9.0 2015-09-09 12:04:52 -04:00
Alexander Corn
d975fdfba8 Added getInventoryHistory method 2015-09-09 12:04:28 -04:00
Alexander Corn
4e3c319ee1 Added async dependency 2015-09-09 11:52:31 -04:00
Alexander Corn
e5cbd23110 3.8.0 2015-09-07 00:10:38 -04:00
Alexander Corn
82f1d490f9 Added contextid property to CEconItem objects 2015-09-07 00:10:32 -04:00
Alexander Corn
c3f5b492b3 Added inventory methods to CSteamUser 2015-09-07 00:01:35 -04:00
Alexander Corn
09cd81b607 Added CEconItem and getUserInventory 2015-09-07 00:00:19 -04:00
Alexander Corn
8a6d912374 Added getUserInventoryContexts 2015-09-06 23:22:10 -04:00
Alexander Corn
c392f2ac74 3.7.2 2015-09-02 17:28:27 -04:00
Alexander Corn
8e6f859269 Properly handle all instances of access being denied because not logged in 2015-09-02 17:28:13 -04:00
Alexander Corn
84dce9872a 3.7.1 2015-09-02 17:22:17 -04:00
Alexander Corn
c2ad457e87 Fixed getWebApiKey not failing if not logged in 2015-09-02 17:19:12 -04:00
Alexander Corn
0116842deb 3.7.0 2015-08-14 00:04:56 -04:00
Alexander Corn
3d0522a560 Added CSteamUser#inviteToGroup and SteamCommunity#inviteUserToGroup 2015-08-14 00:04:43 -04:00
Alexander Corn
3db3d47e92 Made CSteamUser methods available directly on SteamCommunity 2015-08-13 23:57:08 -04:00
Alexander Corn
f341dbe72a 3.6.0 2015-08-13 00:16:01 -04:00
Alexander Corn
22f388e110 Added uploadAvatar method 2015-08-12 19:32:16 -04:00
Alexander Corn
115f13f686 Added readme shields 2015-08-11 02:00:49 -04:00
Alexander Corn
a30739067e Added LICENSE 2015-08-11 02:00:43 -04:00
Alexander Corn
bd87f27c9c 3.5.2 2015-08-07 00:28:33 -04:00
Alexander Corn
07c8c724e7 Use UTC times 2015-08-07 00:28:24 -04:00
Alexander Corn
ea88c1cda4 3.5.1 2015-08-05 00:53:01 -04:00
Alexander Corn
c291f0fab3 Fixed group member retrieval paging not working 2015-08-05 00:52:54 -04:00
Alexander Corn
88063ebf27 3.5.0 2015-08-05 00:37:08 -04:00
Alexander Corn
8485b447d8 Added getGroupHistory and CSteamGroup#getHistory 2015-08-05 00:13:32 -04:00
Alexander Corn
df20c1ac25 Added getGroupMembersEx and addresses param to CSteamGroup#getMembers 2015-08-04 23:51:09 -04:00
Alexander Corn
49b038d3d2 Added class-less group methods 2015-08-04 23:37:16 -04:00
Alexander Corn
d1efe5f203 Ignore IDE stuff 2015-08-04 23:37:08 -04:00
Alexander Corn
b16a8144ff 3.4.0 2015-07-30 16:50:36 -04:00
Alexander Corn
019e33d360 Merge pull request #3 from rannmann/master
Add getMarketApps component
2015-07-30 16:49:39 -04:00
Jake Forrester
01093f38fa Fixed callback error type consistency 2015-07-30 13:45:07 -07:00
Jake Forrester
62c2f1b965 Moved non-class file to components 2015-07-30 13:28:28 -07:00
Jake Forrester
21895a221c Added getMarketApps
Returns an object in format of {appid: appName, ...}
2015-07-30 11:42:47 -07:00
Alexander Corn
f3d88d6b83 3.3.3 2015-07-29 22:31:15 -04:00
Alexander Corn
09327af220 Fixed one more crash 2015-07-29 22:31:05 -04:00
Alexander Corn
0bb2cb6137 3.3.2 2015-07-29 22:29:23 -04:00
Alexander Corn
c9538137d5 Fixed crashes when using various CSteamUser methods 2015-07-29 22:29:14 -04:00
Alexander Corn
df05fd04c4 3.3.1 2015-07-29 22:22:57 -04:00
Alexander Corn
dee52bf7e2 Fixed crashes when using various class methods 2015-07-29 22:22:43 -04:00
Alexander Corn
6d455e8797 3.3.0 2015-07-27 22:56:42 -04:00
Alexander Corn
a0b776ca8b Added setupProfile method 2015-07-27 22:55:25 -04:00
Alexander Corn
3f0db21e02 3.2.1 2015-07-25 01:58:00 -04:00
Alexander Corn
6798b73a17 Made profile callbacks optional 2015-07-25 01:57:53 -04:00
50 changed files with 5398 additions and 827 deletions

13
.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: DoctorMcKay
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=N36YVAT42CZ4G&item_name=node%2dsteamcommunity&currency_code=USD'

51
.gitignore vendored
View File

@@ -1,2 +1,53 @@
node_modules/*
test.js
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
.idea/httpRequests/
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
cmake-build-release/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
node-steamcommunity

13
.idea/codeStyleSettings.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
</value>
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
</component>
</project>

15
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,15 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="JSON">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

6
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

3
.idea/copyright/profiles_settings.xml generated Normal file
View File

@@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

6
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@@ -0,0 +1,11 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ES6ConvertRequireIntoImport" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ES6ConvertToForOf" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ES6ConvertVarToLetConst" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSEqualityComparisonWithCoercion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSFunctionExpressionToArrowFunction" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="JSStringConcatenationToES6Template" enabled="false" level="INFORMATION" enabled_by_default="false" />
</profile>
</component>

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

9
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/../node-steamcommunity Wiki/.idea/node-steamcommunity Wiki.iml" filepath="$PROJECT_DIR$/../node-steamcommunity Wiki/.idea/node-steamcommunity Wiki.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/steamcommunity.iml" filepath="$PROJECT_DIR$/.idea/steamcommunity.iml" />
</modules>
</component>
</project>

8
.idea/node-steamcommunity.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

10
.idea/steamcommunity.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="node-steamcommunity Wiki" />
</component>
</module>

7
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/../node-steamcommunity Wiki" vcs="Git" />
</component>
</project>

4
.idea/watcherTasks.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="Babel" />
</project>

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
package-lock=false

36
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,36 @@
# Contributing to SteamCommunity
Thanks for your interest in making `SteamCommunity` better! I'd appreciate it if you read over this document quickly
before you submit your issue or pull request. It'll make things go much smoother.
# Issues
Submitting an issue?
- If you're **reporting a bug**, please include all relevant details.
- A descriptive title helps for one. Titles of just "Error" or "It doesn't work" really don't help.
- Please describe what you're trying to do, what actually happens, and what you can do to reproduce the problem.
- If you have an error message or a crash, please include the full text of the error message and the stack trace.
- Include the relevant snippet of your code. Wrap it in \`\`\`js /* code */ \`\`\` and GitHub [will format it nicely for you](https://help.github.com/articles/github-flavored-markdown/#syntax-highlighting).
- If you're **requesting a feature**, please be descriptive and understanding.
- A good title makes a difference. Please briefly describe what you're requesting in the title.
- Be descriptive in the issue body, too. Say what you want to do, and ideally what the method should be named.
- Be understanding if I don't think that your feature request falls within the scope of this module.
- If you're **asking a question** or **requesting support**, please don't submit a GitHub issue.
- Issues are only for problems directly relating to the module's code.
- Please [post a thread in the dedicated forum](https://dev.doctormckay.com/forum/8-node-steamcommunity/) instead.
# Pull Requests
Submitting a pull request? Great! Thanks for contributing your time and code! Please keep the following in mind.
- Please follow the existing code style.
- Tabs for indentation
- camelCase for variables and functions
- Opening braces on the same line as the if/for/while statement
- No omitting unnecessary braces
- etc.
- Please avoid breaking changes. If you make a breaking change that can be done in a backwards-compatible manner, I won't accept it.
- Please don't increment the version number in `package.json`. I'll do that myself when I publish it to npm.
- Please include a brief description of your change in the pull request if it's not immediately apparent from the code.
- Be understanding if I don't think that your change falls within the scope of this module.

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Alex Corn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,15 +1,23 @@
# Steam Community for Node.js
[![npm version](https://img.shields.io/npm/v/steamcommunity.svg)](https://npmjs.com/package/steamcommunity)
[![npm downloads](https://img.shields.io/npm/dm/steamcommunity.svg)](https://npmjs.com/package/steamcommunity)
[![dependencies](https://img.shields.io/david/DoctorMcKay/node-steamcommunity.svg)](https://david-dm.org/DoctorMcKay/node-steamcommunity)
[![license](https://img.shields.io/npm/l/steamcommunity.svg)](https://github.com/DoctorMcKay/node-steamcommunity/blob/master/LICENSE)
[![paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=N36YVAT42CZ4G&item_name=node%2dsteamcommunity&currency_code=USD)
This lightweight module provides an easy interface for the Steam Community website. This module can be used to simply login to steamcommunity.com for use with other libraries, or to interact with steamcommunity.com.
This module provides an easy interface for the Steam Community website. This module can be used to simply login to steamcommunity.com for use with other libraries, or to interact with steamcommunity.com.
It supports Steam Guard but doesn't support CAPTCHAs, so make sure you don't provide Steam with an incorrect password.
It supports Steam Guard and CAPTCHAs.
# Installation
Install it from npm:
$ npm install steamcommunity
**Have a question about the module or coding in general? *Do not create a GitHub issue.* GitHub issues are for feature
requests and bug reports. Instead, post in the [dedicated forum](https://dev.doctormckay.com/forum/8-node-steamcommunity/).
Such issues may be ignored!**
# Documentation
As of version 1.0.0, documentation is on the [GitHub wiki](https://github.com/DoctorMcKay/node-steamcommunity/wiki).
Documentation is available on the [GitHub wiki](https://github.com/DoctorMcKay/node-steamcommunity/wiki).
# Support
Report bugs on the [issue tracker](https://github.com/DoctorMcKay/node-steamcommunity/issues) or ask questions
on the [dedicated forum](https://dev.doctormckay.com/forum/8-node-steamcommunity/).

35
classes/CConfirmation.js Normal file
View File

@@ -0,0 +1,35 @@
var SteamCommunity = require('../index.js');
module.exports = CConfirmation;
function CConfirmation(community, data) {
Object.defineProperty(this, "_community", {"value": community});
this.id = data.id.toString();
this.type = data.type;
this.creator = data.creator.toString();
this.key = data.key;
this.title = data.title;
this.receiving = data.receiving;
this.time = data.time;
this.icon = data.icon;
this.offerID = this.type == SteamCommunity.ConfirmationType.Trade ? this.creator : null;
}
CConfirmation.prototype.getOfferID = function(time, key, callback) {
if (this.type && this.creator) {
if (this.type != SteamCommunity.ConfirmationType.Trade) {
callback(new Error("Not a trade confirmation"));
return;
}
callback(null, this.creator);
return;
}
this._community.getConfirmationOfferID(this.id, time, key, callback);
};
CConfirmation.prototype.respond = function(time, key, accept, callback) {
this._community.respondToConfirmation(this.id, this.key, time, key, accept, callback);
};

120
classes/CEconItem.js Normal file
View File

@@ -0,0 +1,120 @@
module.exports = CEconItem;
function CEconItem(item, description, contextID) {
var thing;
for (thing in item) {
if (item.hasOwnProperty(thing)) {
this[thing] = item[thing];
}
}
var isCurrency = !!(this.is_currency || this.currency) || typeof this.currencyid !== 'undefined'; // I don't want to put this on the object yet; it's nice to have the ids at the top of printed output
if (isCurrency) {
this.currencyid = this.id = (this.id || this.currencyid);
} else {
this.assetid = this.id = (this.id || this.assetid);
}
this.instanceid = this.instanceid || '0';
this.amount = parseInt(this.amount, 10);
this.contextid = this.contextid || contextID.toString();
// Merge the description
if (description) {
// Is this a listing of descriptions?
if (description[this.classid + '_' + this.instanceid]) {
description = description[this.classid + '_' + this.instanceid];
}
for (thing in description) {
if (description.hasOwnProperty(thing)) {
this[thing] = description[thing];
}
}
}
this.is_currency = isCurrency;
this.tradable = !!this.tradable;
this.marketable = !!this.marketable;
this.commodity = !!this.commodity;
this.market_tradable_restriction = (this.market_tradable_restriction ? parseInt(this.market_tradable_restriction, 10) : 0);
this.market_marketable_restriction = (this.market_marketable_restriction ? parseInt(this.market_marketable_restriction, 10) : 0);
this.fraudwarnings = this.fraudwarnings || [];
this.descriptions = this.descriptions || [];
if (this.owner && JSON.stringify(this.owner) == '{}') {
this.owner = null;
}
// Restore old property names of tags
if (this.tags) {
this.tags = this.tags.map(function(tag) {
return {
"internal_name": tag.internal_name,
"name": tag.localized_tag_name || tag.name,
"category": tag.category,
"color": tag.color || "",
"category_name": tag.localized_category_name || tag.category_name
};
});
}
// Restore market_fee_app, if applicable
var match;
if (this.appid == 753 && this.contextid == 6 && this.market_hash_name && (match = this.market_hash_name.match(/^(\d+)\-/))) {
this.market_fee_app = parseInt(match[1], 10);
}
// Restore cache_expiration, if we can (for CS:GO items)
if (this.appid == 730 && this.contextid == 2 && this.owner_descriptions) {
let description = this.owner_descriptions.find(d => d.value && d.value.indexOf('Tradable After ') == 0);
if (description) {
let date = new Date(description.value.substring(15).replace(/[,()]/g, ''));
if (date) {
this.cache_expiration = date.toISOString();
}
}
}
// If we have item_expiration, also set cache_expiration to the same value
if (this.item_expiration) {
this.cache_expiration = this.item_expiration;
}
if (this.actions === "") {
this.actions = [];
}
// One wouldn't think that we need this if statement, but apparently v8 has a weird bug/quirk where deleting a
// property results in greatly increased memory usage. Because that makes sense.
if (this.currency) {
delete this.currency;
}
}
CEconItem.prototype.getImageURL = function() {
return "https://steamcommunity-a.akamaihd.net/economy/image/" + this.icon_url + "/";
};
CEconItem.prototype.getLargeImageURL = function() {
if(!this.icon_url_large) {
return this.getImageURL();
}
return "https://steamcommunity-a.akamaihd.net/economy/image/" + this.icon_url_large + "/";
};
CEconItem.prototype.getTag = function(category) {
if (!this.tags) {
return null;
}
for (var i = 0; i < this.tags.length; i++) {
if (this.tags[i].category == category) {
return this.tags[i];
}
}
return null;
};

View File

@@ -1,51 +1,60 @@
var SteamCommunity = require('../index.js');
var Cheerio = require('cheerio');
SteamCommunity.prototype.getMarketItem = function(appid, hashName, callback) {
SteamCommunity.prototype.getMarketItem = function(appid, hashName, currency, callback) {
if (typeof currency == "function") {
callback = currency;
currency = 1;
}
var self = this;
this.request("https://steamcommunity.com/market/listings/" + appid + "/" + encodeURIComponent(hashName), function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
this.httpRequest("https://steamcommunity.com/market/listings/" + appid + "/" + encodeURIComponent(hashName), function(err, response, body) {
if (err) {
callback(err);
return;
}
var $ = Cheerio.load(body);
if($('.market_listing_table_message') && $('.market_listing_table_message').text().trim() == 'There are no listings for this item.') {
callback(new Error("There are no listings for this item."));
return;
}
var item = new CMarketItem(self, body, $);
if(item.commodity) {
item.updatePrice(function(err) {
if(err) {
callback(err);
} else {
callback(null, item);
}
});
} else {
callback(null, item);
}
});
var item = new CMarketItem(appid, hashName, self, body, $);
item.updatePrice(currency, function(err) {
if(err) {
callback(err);
} else {
callback(null, item);
}
});
}, "steamcommunity");
};
function CMarketItem(community, body, $) {
function CMarketItem(appid, hashName, community, body, $) {
this._appid = appid;
this._hashName = hashName;
this._community = community;
this._$ = $;
this._country = "US";
var match = body.match(/var g_strCountryCode = "([^"]+)";/);
if(match) {
this._country = match[1];
}
this._language = "english";
match = body.match(/var g_strLanguage = "([^"]+)";/);
if(match) {
this._language = match[1];
}
this.commodity = false;
var match = body.match(/Market_LoadOrderSpread\(\s*(\d+)\s*\);/);
match = body.match(/Market_LoadOrderSpread\(\s*(\d+)\s*\);/);
if(match) {
this.commodity = true;
this.commodityID = parseInt(match[1], 10);
}
this.medianSalePrices = null;
match = body.match(/var line1=([^;]+);/);
if(match) {
@@ -62,65 +71,119 @@ function CMarketItem(community, body, $) {
// ignore
}
}
this.quantity = 0;
this.lowestPrice = 0;
if(!this.commodity) {
var total = $('#searchResults_total');
if(total) {
this.quantity = parseInt(total.text().replace(/[^\d]/g, '').trim(), 10);
}
var lowest = $('.market_listing_price.market_listing_price_with_fee');
if(lowest[0]) {
this.lowestPrice = parseInt($(lowest[0]).text().replace(/[^\d]/g, '').trim(), 10);
this.firstAsset = null;
this.assets = null;
match = body.match(/var g_rgAssets = (.*);/);
if (match) {
try {
this.assets = JSON.parse(match[1]);
this.assets = this.assets[appid];
this.assets = this.assets[Object.keys(this.assets)[0]];
this.firstAsset = this.assets[Object.keys(this.assets)[0]];
} catch (e) {
// ignore
}
}
this.quantity = 0;
this.lowestPrice = 0;
// TODO: Buying listings and placing buy orders
}
CMarketItem.prototype.updatePrice = function(callback) {
CMarketItem.prototype.updatePrice = function (currency, callback) {
if (this.commodity) {
this.updatePriceForCommodity(currency, callback);
} else {
this.updatePriceForNonCommodity(currency, callback);
}
};
CMarketItem.prototype.updatePriceForCommodity = function(currency, callback) {
if(!this.commodity) {
throw new Error("Cannot update price for non-commodity item");
}
// TODO: Currency option maybe?
var self = this;
this._community.request({
"uri": "https://steamcommunity.com/market/itemordershistogram?country=US&language=english&currency=1&item_nameid=" + this.commodityID,
"json": true,
this._community.httpRequest({
"uri": "https://steamcommunity.com/market/itemordershistogram?country=US&language=english&currency=" + currency + "&item_nameid=" + this.commodityID,
"json": true
}, function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
if (err) {
callback(err);
return;
}
if(body.success != 1) {
if(callback) {
callback(new Error("Error " + body.success));
}
return;
}
var match = (body.sell_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/);
if(match) {
self.quantity = parseInt(match[1], 10);
}
self.buyQuantity = 0;
match = (body.buy_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/);
if(match) {
self.buyQuantity = parseInt(match[1], 10);
}
self.lowestPrice = parseInt(body.lowest_sell_order, 10);
self.highestBuyOrder = parseInt(body.highest_buy_order, 10);
// TODO: The tables?
if(callback) {
callback(null);
}
});
}, "steamcommunity");
};
CMarketItem.prototype.updatePriceForNonCommodity = function (currency, callback) {
if(this.commodity) {
throw new Error("Cannot update price for commodity item");
}
var self = this;
this._community.httpRequest({
"uri": "https://steamcommunity.com/market/listings/" +
this._appid + "/" +
encodeURIComponent(this._hashName) +
"/render/?query=&start=0&count=10&country=US&language=english&currency=" + currency,
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if (body.success != 1) {
callback && callback(new Error("Error " + body.success));
return;
}
var match = body.total_count;
if (match) {
self.quantity = parseInt(match, 10);
}
var lowestPrice;
var $ = Cheerio.load(body.results_html);
match = $(".market_listing_price.market_listing_price_with_fee");
if (match) {
for (var i = 0; i < match.length; i++) {
lowestPrice = parseFloat($(match[i]).text().replace(",", ".").replace(/[^\d.]/g, ''));
if (!isNaN(lowestPrice)) {
self.lowestPrice = lowestPrice;
break;
}
}
}
callback && callback(null);
}, "steamcommunity");
};

View File

@@ -3,82 +3,87 @@ var Cheerio = require('cheerio');
SteamCommunity.prototype.marketSearch = function(options, callback) {
var qs = {};
if(typeof options === 'string') {
qs.query = options;
} else {
qs.query = options.query || '';
qs.appid = options.appid;
qs.search_descriptions = options.searchDescriptions ? 1 : 0;
if(qs.appid) {
for(var i in options) {
if(['query', 'appid', 'searchDescriptions'].indexOf(i) != -1) {
continue;
}
// This is a tag
qs['category_' + qs.appid + '_' + i + '[]'] = 'tag_' + options[i];
}
}
}
qs.start = 0;
qs.count = 100;
qs.sort_column = 'price';
qs.sort_dir = 'asc';
performSearch(this.request, qs, [], callback);
var self = this;
var results = [];
performSearch();
function performSearch() {
self.httpRequest({
"uri": "https://steamcommunity.com/market/search/render/",
"qs": qs,
"headers": {
"referer": "https://steamcommunity.com/market/search"
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if(!body.success) {
callback(new Error("Success is not true"));
return;
}
if(!body.results_html) {
callback(new Error("No results_html in response"));
return;
}
var $ = Cheerio.load(body.results_html);
var $errorMsg = $('.market_listing_table_message');
if($errorMsg.length > 0) {
callback(new Error($errorMsg.text()));
return;
}
var rows = $('.market_listing_row_link');
for(var i = 0; i < rows.length; i++) {
results.push(new CMarketSearchResult($(rows[i])));
}
if(body.start + body.pagesize >= body.total_count) {
callback(null, results);
} else {
qs.start += body.pagesize;
performSearch();
}
}, "steamcommunity");
}
};
function performSearch(request, qs, results, callback) {
request({
"uri": "https://steamcommunity.com/market/search/render/",
"qs": qs,
"headers": {
"referer": "https://steamcommunity.com/market/search"
},
"json": true
}, function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
return;
}
if(!body.success) {
callback(new Error("Success is not true"));
return;
}
if(!body.results_html) {
callback(new Error("No results_html in response"));
return;
}
var $ = Cheerio.load(body.results_html);
if($('.market_listing_table_message').length > 0) {
callback(new Error($('.market_listing_table_message').text()));
return;
}
var rows = $('.market_listing_row_link');
for(var i = 0; i < rows.length; i++) {
results.push(new CMarketSearchResult($(rows[i])));
}
if(body.start + body.pagesize >= body.total_count) {
callback(null, results);
} else {
qs.start += body.pagesize;
performSearch(request, qs, results, callback);
}
});
}
function CMarketSearchResult(row) {
var match = row.attr('href').match(/\/market\/listings\/(\d+)\/(.+)/);
var match = row.attr('href').match(/\/market\/listings\/(\d+)\/([^\?\/]+)/);
this.appid = parseInt(match[1], 10);
this.market_hash_name = decodeURIComponent(match[2]);
this.image = row.find('.market_listing_item_img').attr('src').match(/^https?:\/\/[^\/]+\/economy\/image\/[^\/]+\//)[0];
this.price = parseInt(row.find('.market_listing_their_price .market_table_value span').text().replace(/[^\d]+/g, ''), 10);
this.image = ((row.find('.market_listing_item_img').attr('src') || "").match(/^https?:\/\/[^\/]+\/economy\/image\/[^\/]+\//) || [])[0];
this.price = parseInt(row.find('.market_listing_their_price .market_table_value span.normal_price').text().replace(/[^\d]+/g, ''), 10);
this.quantity = parseInt(row.find('.market_listing_num_listings_qty').text().replace(/[^\d]+/g, ''), 10);
}

View File

@@ -1,40 +1,38 @@
var SteamCommunity = require('../index.js');
var Helpers = require('../components/helpers.js');
var SteamID = require('steamid');
var xml2js = require('xml2js');
SteamCommunity.prototype.getSteamGroup = function(id, callback) {
if(typeof id !== 'string' && !(typeof id === 'object' && id.__proto__ === SteamID.prototype)) {
if(typeof id !== 'string' && !Helpers.isSteamID(id)) {
throw new Error("id parameter should be a group URL string or a SteamID object");
}
if(typeof id === 'object' && (id.universe != SteamID.Universe.PUBLIC || id.type != SteamID.Type.CLAN)) {
throw new Error("SteamID must stand for a clan account in the public universe");
}
var self = this;
this.request("https://steamcommunity.com/" + (typeof id === 'string' ? "groups/" + id : "gid/" + id.toString()) + "/memberslistxml/?xml=1", function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
this.httpRequest("https://steamcommunity.com/" + (typeof id === 'string' ? "groups/" + id : "gid/" + id.toString()) + "/memberslistxml/?xml=1", function(err, response, body) {
if (err) {
callback(err);
return;
}
if(self._checkCommunityError(body, callback)) {
return;
}
xml2js.parseString(body, function(err, result) {
if(err) {
callback(err);
return;
}
callback(null, new CSteamGroup(self, result.memberList));
});
});
}, "steamcommunity");
};
function CSteamGroup(community, groupData) {
this._community = community;
this.steamID = new SteamID(groupData.groupID64[0]);
this.name = groupData.groupDetails[0].groupName[0];
this.url = groupData.groupDetails[0].groupURL[0];
@@ -50,7 +48,7 @@ function CSteamGroup(community, groupData) {
CSteamGroup.prototype.getAvatarURL = function(size, protocol) {
size = size || '';
protocol = protocol || 'http://';
var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + this.avatarHash.substring(0, 2) + "/" + this.avatarHash;
if(size == 'full' || size == 'medium') {
return url + "_" + size + ".jpg";
@@ -59,228 +57,99 @@ CSteamGroup.prototype.getAvatarURL = function(size, protocol) {
}
};
CSteamGroup.prototype.getMembers = function(callback, members, link) {
members = members || [];
link = link || "http://steamcommunity.com/gid/" + this.steamID.toString() + "/memberslistxml/?xml=1";
var self = this;
this._community.request(link, function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
return;
}
xml2js.parseString(body, function(err, result) {
if(err) {
callback(err);
return;
}
members = members.concat(result.memberList.members[0].steamID64.map(function(steamID) {
return new SteamID(steamID);
}));
if(result.memberList.nextPageLink) {
self.getMembers(callback, members, result.memberList.nextPageLink[0]);
} else {
callback(null, members);
}
});
});
CSteamGroup.prototype.getMembers = function(addresses, callback) {
if(typeof addresses === 'function') {
callback = addresses;
addresses = null;
}
this._community.getGroupMembers(this.steamID, callback, null, null, addresses, 0);
};
CSteamGroup.prototype.join = function(callback) {
var form = {
"action": "join",
"sessionID": this._community.getSessionID()
};
var self = this;
this._community.request.post("https://steamcommunity.com/gid/" + this.steamID.toString(), {"form": form}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(self._community._checkCommunityError(body, callback)) {
return;
}
callback(null);
});
this._community.joinGroup(this.steamID, callback);
};
CSteamGroup.prototype.leave = function(callback) {
var form = {
"sessionID": this._community.getSessionID(),
"action": "leaveGroup",
"groupId": this.steamID.toString()
};
var self = this;
this._community._myProfile("home_process", form, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(self._community._checkCommunityError(body, callback)) {
return;
}
callback(null);
});
this._community.leaveGroup(this.steamID, callback);
};
CSteamGroup.prototype.postAnnouncement = function(headline, content, callback) {
var form = {
"sessionID": this._community.getSessionID(),
"action": "post",
"headline": headline,
"body": content
};
this._community.request.post("https://steamcommunity.com/gid/" + this.steamID.toString() + "/announcements", {"form": form}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(self._community._checkCommunityError(body, callback)) {
return;
}
callback(null);
});
CSteamGroup.prototype.getAllAnnouncements = function(time, callback) {
this._community.getAllGroupAnnouncements(this.steamID, time, callback);
};
CSteamGroup.prototype.postAnnouncement = function(headline, content, hidden, callback) {
this._community.postGroupAnnouncement(this.steamID, headline, content, hidden, callback);
};
CSteamGroup.prototype.editAnnouncement = function(annoucementID, headline, content, callback) {
this._community.editGroupAnnouncement(this.steamID, annoucementID, headline, content, callback)
};
CSteamGroup.prototype.deleteAnnouncement = function(annoucementID, callback) {
this._community.deleteGroupAnnouncement(this.steamID, annoucementID, callback)
};
CSteamGroup.prototype.scheduleEvent = function(name, type, description, time, server, callback) {
// Event types: ChatEvent - Chat, OtherEvent - A lil somethin somethin, PartyEvent - Party!, MeetingEvent - Important meeting, SpecialCauseEvent - Special cause (charity ball?), MusicAndArtsEvent - Music or Art type thing, SportsEvent - Sporting endeavor, TripEvent - Out of town excursion
// Passing a number for type will make it a game event for that appid
if(typeof server === 'function') {
callback = server;
server = {"ip": "", "password": ""};
} else if(typeof server === 'string') {
server = {"ip": server, "password": ""};
} else {
server = {"ip": "", "password": ""};
}
var form = {
"sessionid": this._community.getSessionID(),
"action": "newEvent",
"tzOffset": new Date().getTimezoneOffset() * -60,
"name": name,
"type": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? "GameEvent" : type),
"appID": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? type : ''),
"serverIP": server.ip,
"serverPassword": server.password,
"notes": description,
"eventQuickTime": "now"
};
if(time === null) {
form.startDate = 'MM/DD/YY';
form.startHour = '12';
form.startMinute = '00';
form.startAMPM = 'PM';
form.timeChoice = 'quick';
} else {
form.startDate = (time.getMonth() + 1 < 10 ? '0' : '') + (time.getMonth() + 1) + '/' + (time.getDate() < 10 ? '0' : '') + time.getDate() + '/' + time.getFullYear().toString().substring(2);
form.startHour = (time.getHours() === 0 ? '12' : (time.getHours() > 12 ? time.getHours() - 12 : time.getHours()));
form.startMinute = (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
form.startAMPM = (time.getHours() <= 12 ? 'AM' : 'PM');
form.timeChoice = 'specific';
}
var self = this;
this._community.request.post("https://steamcommunity.com/gid/" + this.steamID.toString() + "/eventEdit", {"form": form}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(self._community._checkCommunityError(body, callback)) {
return;
}
callback(null);
});
this._community.scheduleGroupEvent(this.steamID, name, type, description, time, server, callback);
};
CSteamGroup.prototype.editEvent = function(id, name, type, description, time, server, callback) {
this._community.editGroupEvent(this.steamID, id, name, type, description, time, server, callback);
};
CSteamGroup.prototype.deleteEvent = function (id, callback) {
this._community.deleteGroupEvent(this.steamID, id, callback);
};
CSteamGroup.prototype.setPlayerOfTheWeek = function(steamID, callback) {
var form = {
"xml": 1,
"action": "potw",
"memberId": steamID.getSteam3RenderedID(),
"sessionid": this._community.getSessionID()
};
var self = this;
this._community.request.post("https://steamcommunity.com/gid/" + this.steamID.toString() + "/potwEdit", {"form": form}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
xml2js.parseString(body, function(err, results) {
if(err) {
callback(err);
return;
}
if(results.response.results[0] == 'OK') {
callback(null, new SteamID(results.response.oldPOTW[0]), new SteamID(results.response.newPOTW[0]));
} else {
callback(new Error(results.response.results[0]));
}
});
});
this._community.setGroupPlayerOfTheWeek(this.steamID, steamID, callback);
};
CSteamGroup.prototype.kick = function(steamID, callback) {
var form = {
"sessionID": this._community.getSessionID(),
"action": "kick",
"memberId": steamID.toString(),
"queryString": ""
};
var self = this;
this._community.request.post("https://steamcommunity.com/gid/" + this.steamID.toString() + "/membersManage", {"form": form}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(self._community._checkCommunityError(body, callback)) {
return;
}
callback(null);
});
this._community.kickGroupMember(this.steamID, steamID, callback);
};
CSteamGroup.prototype.getHistory = function(page, callback) {
this._community.getGroupHistory(this.steamID, page, callback);
};
CSteamGroup.prototype.getAllComments = function(from, count, callback) {
this._community.getAllGroupComments(this.steamID, from, count, callback);
};
CSteamGroup.prototype.deleteComment = function(cid, callback) {
this._community.deleteGroupComment(this.steamID, cid, callback);
};
CSteamGroup.prototype.comment = function(message, callback) {
this._community.postGroupComment(this.steamID, message, callback);
};
/**
* Get requests to join this restricted group.
* @param {function} callback - First argument is null/Error, second is array of SteamID objects
*/
CSteamGroup.prototype.getJoinRequests = function(callback) {
this._community.getGroupJoinRequests(this.steamID, callback);
};
/**
* Respond to one or more join requests to this restricted group.
* @param {SteamID|string|SteamID[]|string[]} steamIDs - The SteamIDs of the users you want to approve or deny membership for (or a single value)
* @param {boolean} approve - True to put them in the group, false to deny their membership
* @param {function} callback - Takes only an Error object/null as the first argument
*/
CSteamGroup.prototype.respondToJoinRequests = function(steamIDs, approve, callback) {
this._community.respondToGroupJoinRequests(this.steamID, steamIDs, approve, callback);
};
/**
* Respond to *ALL* pending group-join requests for this group.
* @param {boolean} approve - True to allow everyone who requested into the group, false to not
* @param {function} callback - Takes only an Error object/null as the first argument
*/
CSteamGroup.prototype.respondToAllJoinRequests = function(approve, callback) {
this._community.respondToAllGroupJoinRequests(this.steamID, approve, callback);
};

View File

@@ -1,37 +1,35 @@
var SteamCommunity = require('../index.js');
var Helpers = require('../components/helpers.js');
var SteamID = require('steamid');
var xml2js = require('xml2js');
SteamCommunity.prototype.getSteamUser = function(id, callback) {
if(typeof id !== 'string' && !(typeof id === 'object' && id.__proto__ === SteamID.prototype)) {
if(typeof id !== 'string' && !Helpers.isSteamID(id)) {
throw new Error("id parameter should be a user URL string or a SteamID object");
}
if(typeof id === 'object' && (id.universe != SteamID.Universe.PUBLIC || id.type != SteamID.Type.INDIVIDUAL)) {
throw new Error("SteamID must stand for an individual account in the public universe");
}
var self = this;
this.request("http://steamcommunity.com/" + (typeof id === 'string' ? "id/" + id : "profiles/" + id.toString()) + "/?xml=1", function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
this.httpRequest("http://steamcommunity.com/" + (typeof id === 'string' ? "id/" + id : "profiles/" + id.toString()) + "/?xml=1", function(err, response, body) {
if (err) {
callback(err);
return;
}
if(self._checkCommunityError(body, callback)) {
return;
}
xml2js.parseString(body, function(err, result) {
if(err || (!result.response && !result.profile)) {
callback(err || new Error("No valid response"));
return;
}
if(result.response && result.response.error && result.response.error.length) {
callback(new Error(result.response.error[0]));
return;
}
// Try and find custom URL from redirect
var customurl = null;
if(response.request.redirects && response.request.redirects.length) {
@@ -40,60 +38,85 @@ SteamCommunity.prototype.getSteamUser = function(id, callback) {
customurl = match[1];
}
}
if(!result.profile.steamID64) {
callback(new Error("No valid response"));
return;
}
callback(null, new CSteamUser(self, result.profile, customurl));
});
});
}, "steamcommunity");
};
function CSteamUser(community, userData, customurl) {
this._community = community;
this.steamID = new SteamID(userData.steamID64[0]);
this.name = userData.steamID[0];
this.onlineState = userData.onlineState[0];
this.stateMessage = userData.stateMessage[0];
this.privacyState = userData.privacyState[0];
this.visibilityState = userData.visibilityState[0];
this.avatarHash = userData.avatarIcon[0].match(/([0-9a-f]+)\.[a-z]+$/)[1];
this.vacBanned = !!userData.vacBanned[0];
this.tradeBanState = userData.tradeBanState[0];
this.isLimitedAccount = !!userData.isLimitedAccount[0];
this.customURL = userData.customURL ? userData.customURL[0] : customurl;
this.name = processItem('steamID');
this.onlineState = processItem('onlineState');
this.stateMessage = processItem('stateMessage');
this.privacyState = processItem('privacyState', 'uncreated');
this.visibilityState = processItem('visibilityState');
this.avatarHash = processItem('avatarIcon', '').match(/([0-9a-f]+)\.[a-z]+$/);
if(this.avatarHash) {
this.avatarHash = this.avatarHash[1];
}
this.vacBanned = processItem('vacBanned', false) == 1;
this.tradeBanState = processItem('tradeBanState', 'None');
this.isLimitedAccount = processItem('isLimitedAccount') == 1;
this.customURL = processItem('customURL', customurl);
if(this.visibilityState == 3) {
this.memberSince = new Date(userData.memberSince[0].replace(/(\d{1,2})(st|nd|th)/, "$1"));
this.location = userData.location[0] || null;
this.realName = userData.realname[0] || null;
this.summary = userData.summary[0] || null;
let memberSinceValue = processItem('memberSince', '0').replace(/(\d{1,2})(st|nd|th)/, "$1");
if (memberSinceValue.indexOf(',') === -1) {
memberSinceValue += ', ' + new Date().getFullYear();
}
this.memberSince = new Date(memberSinceValue);
this.location = processItem('location');
this.realName = processItem('realname');
this.summary = processItem('summary');
} else {
this.memberSince = null;
this.location = null;
this.realName = null;
this.summary = null;
}
// Maybe handle mostPlayedGames?
this.groups = null;
this.primaryGroup = null;
var self = this;
if(userData.groups && userData.groups[0] && userData.groups[0].group) {
this.groups = userData.groups[0].group.map(function(group) {
if(group['$'] && group['$'].isPrimary) {
if(group['$'] && group['$'].isPrimary === "1") {
self.primaryGroup = new SteamID(group.groupID64[0]);
}
return new SteamID(group.groupID64[0]);
});
}
function processItem(name, defaultVal) {
if(!userData[name]) {
return defaultVal;
}
return userData[name][0];
}
}
CSteamUser.getAvatarURL = function(hash, size, protocol) {
size = size || '';
protocol = protocol || 'http://';
hash = hash || "72f78b4c8cc1f62323f8a33f6d53e27db57c2252"; // The default "?" avatar
var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + hash.substring(0, 2) + "/" + hash;
if(size == 'full' || size == 'medium') {
return url + "_" + size + ".jpg";
@@ -107,120 +130,88 @@ CSteamUser.prototype.getAvatarURL = function(size, protocol) {
};
CSteamUser.prototype.addFriend = function(callback) {
this._community.request.post('https://steamcommunity.com/actions/AddFriendAjax', {"form": {"accept_invite": 0, "sessionID": this._community.getSessionID(), "steamid": this.steamID.toString()}}, function(err, response, body) {
if(!callback) {
return;
}
if(self._checkHttpError(err, response, callback)) {
return;
}
var json;
try {
json = JSON.parse(body);
} catch(e) {
callback(e);
return;
}
if(json.success) {
callback(null);
} else {
callback(new Error("Unknown error"));
}
});
this._community.addFriend(this.steamID, callback);
};
CSteamUser.prototype.acceptFriendRequest = function(callback) {
this._community.request.post('https://steamcommunity.com/actions/AddFriendAjax', {"form": {"accept_invite": 1, "sessionID": this._community.getSessionID(), "steamid": this.steamID.toString()}}, function(err, response, body) {
if(!callback) {
return;
}
if(self._checkHttpError(err, response, callback)) {
return;
}
callback(null);
});
this._community.acceptFriendRequest(this.steamID, callback);
};
CSteamUser.prototype.removeFriend = function(callback) {
this._community.request.post('https://steamcommunity.com/actions/RemoveFriendAjax', {"form": {"sessionID": this._community.getSessionID(), "steamid": this.steamID.toString()}}, function(err, response, body) {
if(!callback) {
return;
}
if(self._checkHttpError(err, response, callback)) {
return;
}
callback(null);
});
this._community.removeFriend(this.steamID, callback);
};
CSteamUser.prototype.blockCommunication = function(callback) {
this._community.request.post('https://steamcommunity.com/actions/BlockUserAjax', {"form": {"sessionID": this._community.getSessionID(), "steamid": this.steamID.toString()}}, function(err, response, body) {
if(!callback) {
return;
}
if(self._checkHttpError(err, response, callback)) {
return;
}
callback(null);
});
this._community.blockCommunication(this.steamID, callback);
};
CSteamUser.prototype.unblockCommunication = function(callback) {
var form = {"action": "unignore"};
form['friends[' + this.steamID.toString() + ']'] = 1;
this._community._myProfile('friends/blocked/', form, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
callback(null);
});
this._community.unblockCommunication(this.steamID, callback);
};
CSteamUser.prototype.comment = function(message, callback) {
this._community.request.post('https://steamcommunity.com/comment/Profile/post/' + this.steamID.toString() + '/-1/', {"form": {
"comment": message,
"count": 6,
"sessionid": this._community.getSessionID()
}}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
var json;
try {
json = JSON.parse(body);
} catch(e) {
callback(e);
return;
}
if(json.success) {
callback(null);
} else if(json.error) {
callback(new Error(json.error));
} else {
callback(new Error("Unknown error"));
}
});
this._community.postUserComment(this.steamID, message, callback);
};
CSteamUser.prototype.deleteComment = function(commentID, callback) {
this._community.deleteUserComment(this.steamID, commentID, callback);
};
CSteamUser.prototype.getComments = function(options, callback) {
this._community.getUserComments(this.steamID, options, callback);
};
CSteamUser.prototype.inviteToGroup = function(groupID, callback) {
this._community.inviteUserToGroup(this.steamID, groupID, callback);
};
CSteamUser.prototype.getAliases = function(callback) {
this._community.getUserAliases(this.steamID, callback);
};
CSteamUser.prototype.getInventoryContexts = function(callback) {
this._community.getUserInventoryContexts(this.steamID, callback);
};
/**
* Get the contents of a user's inventory context.
* @deprecated Use CSteamUser#getInventoryContents instead
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param callback
*/
CSteamUser.prototype.getInventory = function(appID, contextID, tradableOnly, callback) {
this._community.getUserInventory(this.steamID, appID, contextID, tradableOnly, callback);
};
/**
* Get the contents of a user's inventory context.
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param {string} [language] - The language of item descriptions to return. Omit for default (which may either be English or your account's chosen language)
* @param callback
*/
CSteamUser.prototype.getInventoryContents = function(appID, contextID, tradableOnly, language, callback) {
this._community.getUserInventoryContents(this.steamID, appID, contextID, tradableOnly, language, callback);
};
/**
* Get the background URL of user's profile.
* @param {function} callback
*/
CSteamUser.prototype.getProfileBackground = function(callback) {
this._community.getUserProfileBackground(this.steamID, callback);
};
/**
* Upload an image to Steam and send it to the target user over chat.
* @param {Buffer} imageContentsBuffer - The image contents, as a Buffer
* @param {{spoiler?: boolean}} [options]
* @param {function} callback
*/
CSteamUser.prototype.sendImage = function(imageContentsBuffer, options, callback) {
this._community.sendImageToUser(this.steamID, imageContentsBuffer, options, callback);
};

View File

@@ -1,116 +1,109 @@
var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
SteamCommunity.ChatState = {
"Offline": 0,
"LoggingOn": 1,
"LogOnFailed": 2,
"LoggedOn": 3
};
SteamCommunity.PersonaState = {
"Offline": 0,
"Online": 1,
"Busy": 2,
"Away": 3,
"Snooze": 4,
"LookingToTrade": 5,
"LookingToPlay": 6,
"Max": 7
};
SteamCommunity.PersonaStateFlag = {
"HasRichPresence": 1,
"InJoinableGame": 2,
"OnlineUsingWeb": 256,
"OnlineUsingMobile": 512,
"OnlineUsingBigPicture": 1024
};
SteamCommunity.ChatState = require('../resources/EChatState.js');
SteamCommunity.PersonaState = require('../resources/EPersonaState.js');
SteamCommunity.PersonaStateFlag = require('../resources/EPersonaStateFlag.js');
/**
* @deprecated No support for new Steam chat. Use steam-user instead.
* @param {int} interval
* @param {string} uiMode
*/
SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
if(this.chatState == SteamCommunity.ChatState.LoggingOn || this.chatState == SteamCommunity.ChatState.LoggedOn) {
return;
}
interval = interval || 500;
uiMode = uiMode || "web";
this.emit('debug', 'Requesting chat WebAPI token');
this.chatState = SteamCommunity.ChatState.LoggingOn;
var self = this;
this.request("https://steamcommunity.com/chat", function(err, response, body) {
if(err || response.statusCode != 200) {
self.emit('debug', 'Error requesting chat WebAPI token: ' + (err ? err.message : "HTTP error " + response.statusCode));
self.chatState = SteamCommunity.ChatState.LogOnFailed;
setTimeout(self.chatLogon.bind(self), 5000);
this.getWebApiOauthToken(function(err, token) {
if(err) {
var fatal = err.message.indexOf('not authorized') != -1;
if (!fatal) {
self.chatState = SteamCommunity.ChatState.LogOnFailed;
setTimeout(self.chatLogon.bind(self), 5000);
} else {
self.chatState = SteamCommunity.ChatState.Offline;
}
self.emit('chatLogOnFailed', err, fatal);
self.emit('debug', "Cannot get oauth token: " + err.message);
return;
}
var match = body.match(/[0-9a-f]{32}/);
if(!match) {
self.emit('debug', 'Couldn\'t find a WebAPI chat token in the response.');
self.chatState = SteamCommunity.ChatState.LogOnFailed;
setTimeout(self.chatLogon.bind(self), 5000);
return;
}
self.request.post({
self.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Logon/v1",
"form": {
"ui_mode": uiMode,
"access_token": match[0]
"access_token": token
},
"json": true
}, function(err, response, body) {
if(err || response.statusCode != 200) {
self.chatState = SteamCommunity.ChatState.LogOnFailed;
self.emit('chatLogOnFailed', err ? err : new Error("HTTP error " + response.statusCode), false);
self.emit('debug', 'Error logging into webchat: ' + (err ? err.message : "HTTP error " + response.statusCode));
self.chatState = SteamCommunity.ChatState.LogOnFailed;
setTimeout(self.chatLogon.bind(self), 5000);
return;
}
if(body.error != 'OK') {
self.emit('debug', 'Error logging into webchat: ' + body.error);
self.chatState = SteamCommunity.ChatState.LogOnFailed;
self.emit('chatLogOnFailed', new Error(body.error), false);
self.emit('debug', 'Error logging into webchat: ' + body.error);
setTimeout(self.chatLogon.bind(self), 5000);
return;
}
self._chat = {
"umqid": body.umqid,
"message": body.message,
"accessToken": match[0],
"interval": interval
"accessToken": token,
"interval": interval,
"uiMode": uiMode
};
self.chatFriends = {};
self.chatState = SteamCommunity.ChatState.LoggedOn;
self.emit('chatLoggedOn');
self._chatPoll();
});
}, "steamcommunity");
});
};
/**
* @deprecated No support for new Steam chat. Use steam-user instead.
* @param {string|SteamID} recipient
* @param {string} text
* @param {string} [type]
* @param {function} [callback]
*/
SteamCommunity.prototype.chatMessage = function(recipient, text, type, callback) {
if(this.chatState != SteamCommunity.ChatState.LoggedOn) {
throw new Error("Chat must be logged on before messages can be sent");
}
if(typeof recipient === 'string') {
recipient = new SteamID(recipient);
}
if(typeof type === 'function') {
callback = type;
type = 'saytext';
}
type = type || 'saytext';
this.request.post({
var self = this;
this.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Message/v1",
"form": {
"access_token": this._chat.accessToken,
@@ -124,22 +117,26 @@ SteamCommunity.prototype.chatMessage = function(recipient, text, type, callback)
if(!callback) {
return;
}
if(self._checkHttpError(err, response, callback)) {
if (err) {
callback(err);
return;
}
if(body.error != 'OK') {
callback(new Error(body.error));
} else {
callback(null);
}
});
}, "steamcommunity");
};
/**
* @deprecated No support for new Steam chat. Use steam-user instead.
*/
SteamCommunity.prototype.chatLogoff = function() {
var self = this;
this.request.post({
this.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Logoff/v1",
"form": {
"access_token": this._chat.accessToken,
@@ -156,14 +153,17 @@ SteamCommunity.prototype.chatLogoff = function() {
delete self.chatFriends;
self.chatState = SteamCommunity.ChatState.Offline;
}
});
}, "steamcommunity");
};
/**
* @private
*/
SteamCommunity.prototype._chatPoll = function() {
this.emit('debug', 'Doing chat poll');
var self = this;
this.request.post({
this.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Poll/v1",
"form": {
"umqid": self._chat.umqid,
@@ -176,58 +176,88 @@ SteamCommunity.prototype._chatPoll = function() {
},
"json": true
}, function(err, response, body) {
if(self.chatState == SteamCommunity.ChatState.Offline) {
if (self.chatState == SteamCommunity.ChatState.Offline) {
return;
}
self._chat.timer = setTimeout(self._chatPoll.bind(self), self._chat.interval);
if(err || response.statusCode != 200) {
self.emit('debug', 'Error in chat poll: ' + (err ? err.message : "HTTP error " + response.statusCode));
if (err.message == "Not Logged On") {
self._relogWebChat();
}
return;
}
if(body.error != 'OK') {
self.emit('debug', 'Error in chat poll: ' + body.error);
if(!body || body.error != 'OK') {
self.emit('debug', 'Error in chat poll: ' + (body && body.error ? body.error : "Malformed response"));
if (body && body.error && body.error == "Not Logged On") {
self._relogWebChat();
}
return;
}
self._chat.message = body.messagelast;
(body.messages || []).forEach(function(message) {
var sender = new SteamID();
sender.universe = SteamID.Universe.PUBLIC;
sender.type = SteamID.Type.INDIVIDUAL;
sender.instance = SteamID.Instance.DESKTOP;
sender.accountid = message.accountid_from;
switch(message.type) {
case 'personastate':
self._chatUpdatePersona(sender);
break;
case 'saytext':
self.emit('chatMessage', sender, message.text);
break;
case 'typing':
self.emit('chatTyping', sender);
break;
default:
self.emit('debug', 'Unhandled chat message type: ' + message.type);
}
});
});
}, "steamcommunity");
};
/**
* @private
*/
SteamCommunity.prototype._relogWebChat = function() {
this.emit('debug', "Relogging web chat");
clearTimeout(this._chat.timer);
this.chatState = SteamCommunity.ChatState.Offline;
this.chatLogon(this._chat.interval, this._chat.uiMode);
};
/**
* @param {SteamID} steamID
* @private
*/
SteamCommunity.prototype._chatUpdatePersona = function(steamID) {
if (!this.chatFriends || this.chatState == SteamCommunity.ChatState.Offline) {
return; // we no longer care
}
this.emit('debug', 'Updating persona data for ' + steamID);
var self = this;
this.request({
this.httpRequest({
"uri": "https://steamcommunity.com/chat/friendstate/" + steamID.accountid,
"json": true
}, function(err, response, body) {
if (!self.chatFriends || self.chatState == SteamCommunity.ChatState.Offline) {
return; // welp
}
if(err || response.statusCode != 200) {
self.emit('debug', 'Chat update persona error: ' + (err ? err.message : "HTTP error " + response.statusCode));
setTimeout(function() {
@@ -235,7 +265,7 @@ SteamCommunity.prototype._chatUpdatePersona = function(steamID) {
}, 2000);
return;
}
var persona = {
"steamID": steamID,
"personaName": body.m_strName,
@@ -249,5 +279,5 @@ SteamCommunity.prototype._chatUpdatePersona = function(steamID) {
self.emit('chatPersonaState', steamID, persona);
self.chatFriends[steamID.getSteamID64()] = persona;
});
};
}, "steamcommunity");
};

425
components/confirmations.js Normal file
View File

@@ -0,0 +1,425 @@
var SteamCommunity = require('../index.js');
var Cheerio = require('cheerio');
var SteamTotp = require('steam-totp');
var Async = require('async');
var CConfirmation = require('../classes/CConfirmation.js');
/**
* Get a list of your account's currently outstanding confirmations.
* @param {int} time - The unix timestamp with which the following key was generated
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag "conf" (this key can be reused)
* @param {SteamCommunity~getConfirmations} callback - Called when the list of confirmations is received
*/
SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
var self = this;
request(this, "conf", key, time, "conf", null, false, function(err, body) {
if(err) {
if (err.message == "Invalid protocol: steammobile:") {
err.message = "Not Logged In";
self._notifySessionExpired(err);
}
callback(err);
return;
}
var $ = Cheerio.load(body);
var empty = $('#mobileconf_empty');
if(empty.length > 0) {
if(!$(empty).hasClass('mobileconf_done')) {
// An error occurred
callback(new Error(empty.find('div:nth-of-type(2)').text()));
} else {
callback(null, []);
}
return;
}
// We have something to confirm
var confirmations = $('#mobileconf_list');
if(!confirmations) {
callback(new Error("Malformed response"));
return;
}
var confs = [];
Array.prototype.forEach.call(confirmations.find('.mobileconf_list_entry'), function(conf) {
conf = $(conf);
var img = conf.find('.mobileconf_list_entry_icon img');
confs.push(new CConfirmation(self, {
"id": conf.data('confid'),
"type": conf.data('type'),
"creator": conf.data('creator'),
"key": conf.data('key'),
"title": conf.find('.mobileconf_list_entry_description>div:nth-of-type(1)').text().trim(),
"receiving": conf.find('.mobileconf_list_entry_description>div:nth-of-type(2)').text().trim(),
"time": conf.find('.mobileconf_list_entry_description>div:nth-of-type(3)').text().trim(),
"icon": img.length < 1 ? '' : $(img).attr('src')
}));
});
callback(null, confs);
});
};
/**
* @callback SteamCommunity~getConfirmations
* @param {Error|null} err - An Error object on failure, or null on success
* @param {CConfirmation[]} confirmations - An array of CConfirmation objects
*/
/**
* Get the trade offer ID associated with a particular confirmation
* @param {int} confID - The ID of the confirmation in question
* @param {int} time - The unix timestamp with which the following key was generated
* @param {string} key - The confirmation key that was generated using the preceeding time and the tag "details" (this key can be reused)
* @param {SteamCommunity~getConfirmationOfferID} callback
*/
SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, callback) {
request(this, "details/" + confID, key, time, "details", null, true, function(err, body) {
if(err) {
callback(err);
return;
}
if(!body.success) {
callback(new Error("Cannot load confirmation details"));
return;
}
var $ = Cheerio.load(body.html);
var offer = $('.tradeoffer');
if(offer.length < 1) {
callback(null, null);
return;
}
callback(null, offer.attr('id').split('_')[1]);
});
};
/**
* @callback SteamCommunity~getConfirmationOfferID
* @param {Error|null} err - An Error object on failure, or null on success
* @param {string} offerID - The trade offer ID associated with the specified confirmation, or null if not for an offer
*/
/**
* Confirm or cancel a given confirmation.
* @param {int|int[]} confID - The ID of the confirmation in question, or an array of confirmation IDs
* @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)
* @param {int} time - The unix timestamp with which the following key was generated
* @param {string} key - The confirmation key that was generated using the preceding time and the tag "allow" (if accepting) or "cancel" (if not accepting)
* @param {boolean} accept - true if you want to accept the confirmation, false if you want to cancel it
* @param {SteamCommunity~genericErrorCallback} callback - Called when the request is complete
*/
SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time, key, accept, callback) {
request(this, (confID instanceof Array) ? "multiajaxop" : "ajaxop", key, time, accept ? "allow" : "cancel", {
"op": accept ? "allow" : "cancel",
"cid": confID,
"ck": confKey
}, true, function(err, body) {
if(!callback) {
return;
}
if(err) {
callback(err);
return;
}
if(body.success) {
callback(null);
return;
}
if(body.message) {
callback(new Error(body.message));
return;
}
callback(new Error("Could not act on confirmation"));
});
};
/**
* Accept a confirmation for a given object (trade offer or market listing) automatically.
* @param {string} identitySecret
* @param {number|string} objectID
* @param {SteamCommunity~genericErrorCallback} callback
*/
SteamCommunity.prototype.acceptConfirmationForObject = function(identitySecret, objectID, callback) {
var self = this;
this._usedConfTimes = this._usedConfTimes || [];
if (typeof this._timeOffset !== 'undefined') {
// time offset is already known and saved
doConfirmation();
} else {
SteamTotp.getTimeOffset(function(err, offset) {
if (err) {
callback(err);
return;
}
self._timeOffset = offset;
doConfirmation();
setTimeout(function() {
// Delete the saved time offset after 12 hours because why not
delete self._timeOffset;
}, 1000 * 60 * 60 * 12).unref();
});
}
function doConfirmation() {
var offset = self._timeOffset;
var time = SteamTotp.time(offset);
self.getConfirmations(time, SteamTotp.getConfirmationKey(identitySecret, time, "conf"), function(err, confs) {
if (err) {
callback(err);
return;
}
var conf = confs.filter(function(conf) { return conf.creator == objectID; });
if (conf.length == 0) {
callback(new Error("Could not find confirmation for object " + objectID));
return;
}
conf = conf[0];
// make sure we don't reuse the same time
var localOffset = 0;
do {
time = SteamTotp.time(offset) + localOffset++;
} while (self._usedConfTimes.indexOf(time) != -1);
self._usedConfTimes.push(time);
if (self._usedConfTimes.length > 60) {
self._usedConfTimes.splice(0, self._usedConfTimes.length - 60); // we don't need to save more than 60 entries
}
conf.respond(time, SteamTotp.getConfirmationKey(identitySecret, time, "allow"), true, callback);
});
}
};
/**
* Send a single request to Steam to accept all outstanding confirmations (after loading the list). If one fails, the
* entire request will fail and there will be no way to know which failed without loading the list again.
* @param {number} time
* @param {string} confKey
* @param {string} allowKey
* @param {function} callback
*/
SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowKey, callback) {
var self = this;
this.getConfirmations(time, confKey, function(err, confs) {
if (err) {
callback(err);
return;
}
if (confs.length == 0) {
callback(null, []);
return;
}
self.respondToConfirmation(confs.map(function(conf) { return conf.id; }), confs.map(function(conf) { return conf.key; }), time, allowKey, true, function(err) {
if (err) {
callback(err);
return;
}
callback(err, confs);
});
});
};
function request(community, url, key, time, tag, params, json, callback) {
if (!community.steamID) {
throw new Error("Must be logged in before trying to do anything with confirmations");
}
params = params || {};
params.p = SteamTotp.getDeviceID(community.steamID);
params.a = community.steamID.getSteamID64();
params.k = key;
params.t = time;
params.m = "android";
params.tag = tag;
var req = {
"method": url == 'multiajaxop' ? 'POST' : 'GET',
"uri": "https://steamcommunity.com/mobileconf/" + url,
"json": !!json
};
if (req.method == "GET") {
req.qs = params;
} else {
req.form = params;
}
community.httpRequest(req, function(err, response, body) {
if (err) {
callback(err);
return;
}
callback(null, body);
}, "steamcommunity");
}
// Confirmation checker
/**
* 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
* @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.
* @param {Buffer|string|null} [identitySecret=null] - Your identity_secret. If passed, all confirmations will be automatically accepted and nothing will be emitted.
*/
SteamCommunity.prototype.startConfirmationChecker = function(pollInterval, identitySecret) {
this._confirmationPollInterval = pollInterval;
this._knownConfirmations = this._knownConfirmations || {};
this._confirmationKeys = this._confirmationKeys || {};
this._identitySecret = identitySecret;
if(this._confirmationTimer) {
clearTimeout(this._confirmationTimer);
}
setTimeout(this.checkConfirmations.bind(this), 500);
};
/**
* Stop automatically polling our confirmations.
*/
SteamCommunity.prototype.stopConfirmationChecker = function() {
if(this._confirmationPollInterval) {
delete this._confirmationPollInterval;
}
if(this._identitySecret) {
delete this._identitySecret;
}
if(this._confirmationTimer) {
clearTimeout(this._confirmationTimer);
delete this._confirmationTimer;
}
};
/**
* Run the confirmation checker right now instead of waiting for the next poll.
* Useful to call right after you send/accept an offer that needs confirmation.
*/
SteamCommunity.prototype.checkConfirmations = function() {
if(this._confirmationTimer) {
clearTimeout(this._confirmationTimer);
delete this._confirmationTimer;
}
var self = this;
if(!this._confirmationQueue) {
this._confirmationQueue = Async.queue(function(conf, callback) {
// Worker to process new confirmations
if(self._identitySecret) {
// We should accept this
self.emit('debug', "Accepting confirmation #" + conf.id);
var time = Math.floor(Date.now() / 1000);
conf.respond(time, SteamTotp.getConfirmationKey(self._identitySecret, time, "allow"), true, function(err) {
// If there was an error and it wasn't actually accepted, we'll pick it up again
if (!err) self.emit('confirmationAccepted', conf);
delete self._knownConfirmations[conf.id];
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
});
} else {
self.emit('newConfirmation', conf);
setTimeout(callback, 1000); // Call the callback in 1 second, to make sure the time changes
}
}, 1);
}
this.emit('debug', 'Checking confirmations');
this._confirmationCheckerGetKey('conf', function(err, key) {
if(err) {
resetTimer();
return;
}
self.getConfirmations(key.time, key.key, function(err, confirmations) {
if(err) {
self.emit('debug', "Can't check confirmations: " + err.message);
resetTimer();
return;
}
var known = self._knownConfirmations;
var newOnes = confirmations.filter(function(conf) {
return !known[conf.id];
});
if(newOnes.length < 1) {
resetTimer();
return; // No new ones
}
// We have new confirmations!
newOnes.forEach(function(conf) {
self._knownConfirmations[conf.id] = conf; // Add it to our list of known confirmations
self._confirmationQueue.push(conf);
});
resetTimer();
});
});
function resetTimer() {
if(self._confirmationPollInterval) {
self._confirmationTimer = setTimeout(self.checkConfirmations.bind(self), self._confirmationPollInterval);
}
}
};
SteamCommunity.prototype._confirmationCheckerGetKey = function(tag, callback) {
if(this._identitySecret) {
if(tag == 'details') {
// We don't care about details
callback(new Error("Disabled"));
return;
}
var time = Math.floor(Date.now() / 1000);
callback(null, {"time": time, "key": SteamTotp.getConfirmationKey(this._identitySecret, time, tag)});
return;
}
var existing = this._confirmationKeys[tag];
var reusable = ['conf', 'details'];
// See if we already have a key that we can reuse.
if(reusable.indexOf(tag) != -1 && existing && (Date.now() - (existing.time * 1000) < (1000 * 60 * 5))) {
callback(null, existing);
return;
}
// We need a fresh one
var self = this;
this.emit('confKeyNeeded', tag, function(err, time, key) {
if(err) {
callback(err);
return;
}
self._confirmationKeys[tag] = {"time": time, "key": key};
callback(null, {"time": time, "key": key});
});
};

732
components/groups.js Normal file
View File

@@ -0,0 +1,732 @@
var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
var xml2js = require('xml2js');
var Cheerio = require('cheerio');
var Helpers = require('./helpers.js');
var EResult = SteamCommunity.EResult;
SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link, addresses, addressIdx) {
members = members || [];
if (!link) {
if (typeof gid !== 'string') {
// It's a SteamID object
link = "http://steamcommunity.com/gid/" + gid.toString() + "/memberslistxml/?xml=1";
} else {
try {
var sid = new SteamID(gid);
if (sid.type == SteamID.Type.CLAN && sid.isValid()) {
link = "http://steamcommunity.com/gid/" + sid.getSteamID64() + "/memberslistxml/?xml=1";
} else {
throw new Error("Doesn't particularly matter what this message is");
}
} catch (e) {
link = "http://steamcommunity.com/groups/" + gid + "/memberslistxml/?xml=1";
}
}
}
addressIdx = addressIdx || 0;
var options = {};
options.uri = link;
if(addresses) {
if(addressIdx >= addresses.length) {
addressIdx = 0;
}
options.localAddress = addresses[addressIdx];
}
var self = this;
this.httpRequest(options, function(err, response, body) {
if (err) {
callback(err);
return;
}
xml2js.parseString(body, function(err, result) {
if (err) {
callback(err);
return;
}
members = members.concat(result.memberList.members[0].steamID64.map(function(steamID) {
return new SteamID(steamID);
}));
if (result.memberList.nextPageLink) {
addressIdx++;
self.getGroupMembers(gid, callback, members, result.memberList.nextPageLink[0], addresses, addressIdx);
} else {
callback(null, members);
}
});
}, "steamcommunity");
};
SteamCommunity.prototype.getGroupMembersEx = function(gid, addresses, callback) {
this.getGroupMembers(gid, callback, null, null, addresses, 0);
};
SteamCommunity.prototype.joinGroup = function(gid, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64(),
"form": {
"action": "join",
"sessionID": this.getSessionID()
}
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.leaveGroup = function(gid, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
var self = this;
this._myProfile("home_process", {
"sessionID": this.getSessionID(),
"action": "leaveGroup",
"groupId": gid.getSteamID64()
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
});
};
SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
if(typeof time === 'function') {
callback = time;
time = new Date(0); // The beginnig of time...
}
var self = this;
this.httpRequest({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/rss/"
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
xml2js.parseString(body, function(err, results) {
if(err) {
return callback(err);
}
if(!results.rss.channel[0].item) {
return callback(null, []);
}
var announcements = results.rss.channel[0].item.map(function(announcement) {
var splitLink = announcement.link[0].split('/');
return {
headline: announcement.title[0],
content: announcement.description[0],
date: new Date(announcement.pubDate[0]),
author: (typeof announcement.author === 'undefined') ? null : announcement.author[0],
aid: splitLink[splitLink.length - 1]
}
}).filter(function(announcement) {
return (announcement.date > time);
});
return callback(null, announcements);
});
}, "steamcommunity");
};
SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content, hidden, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
if(typeof hidden === 'function') {
callback = hidden;
hidden = false;
}
var self = this;
var form = {
"sessionID": this.getSessionID(),
"action": "post",
"headline": headline,
"body": content,
"languages[0][headline]": headline,
"languages[0][body]": content
};
if(hidden) {
form.is_hidden = "is_hidden"
}
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements",
form
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.editGroupAnnouncement = function(gid, aid, headline, content, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
var self = this;
var submitData = {
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements",
"form": {
"sessionID": this.getSessionID(),
"gid": aid,
"action": "update",
"headline": headline,
"body": content,
"languages[0][headline]": headline,
"languages[0][body]": content,
"languages[0][updated]": 1
}
};
this.httpRequestPost(submitData, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.deleteGroupAnnouncement = function(gid, aid, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
var self = this;
var submitData = {
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements/delete/" + aid + "?sessionID=" + this.getSessionID()
};
this.httpRequestGet(submitData, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.scheduleGroupEvent = function(gid, name, type, description, time, server, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
// Event types: ChatEvent - Chat, OtherEvent - A lil somethin somethin, PartyEvent - Party!, MeetingEvent - Important meeting, SpecialCauseEvent - Special cause (charity ball?), MusicAndArtsEvent - Music or Art type thing, SportsEvent - Sporting endeavor, TripEvent - Out of town excursion
// Passing a number for type will make it a game event for that appid
if(typeof server === 'function') {
callback = server;
server = {"ip": "", "password": ""};
} else if(typeof server === 'string') {
server = {"ip": server, "password": ""};
} else if(typeof server !== 'object') {
server = {"ip": "", "password": ""};
}
var form = {
"sessionid": this.getSessionID(),
"action": "newEvent",
"tzOffset": new Date().getTimezoneOffset() * -60,
"name": name,
"type": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? "GameEvent" : type),
"appID": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? type : ''),
"serverIP": server.ip,
"serverPassword": server.password,
"notes": description,
"eventQuickTime": "now"
};
if(time === null) {
form.startDate = 'MM/DD/YY';
form.startHour = '12';
form.startMinute = '00';
form.startAMPM = 'PM';
form.timeChoice = 'quick';
} else {
form.startDate = (time.getMonth() + 1 < 10 ? '0' : '') + (time.getMonth() + 1) + '/' + (time.getDate() < 10 ? '0' : '') + time.getDate() + '/' + time.getFullYear().toString().substring(2);
form.startHour = (time.getHours() === 0 ? '12' : (time.getHours() > 12 ? time.getHours() - 12 : time.getHours()));
form.startMinute = (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
form.startAMPM = (time.getHours() <= 12 ? 'AM' : 'PM');
form.timeChoice = 'specific';
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.toString() + "/eventEdit",
"form": form
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.editGroupEvent = function (gid, id, name, type, description, time, server, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
// Event types: ChatEvent - Chat, OtherEvent - A lil somethin somethin, PartyEvent - Party!, MeetingEvent - Important meeting, SpecialCauseEvent - Special cause (charity ball?), MusicAndArtsEvent - Music or Art type thing, SportsEvent - Sporting endeavor, TripEvent - Out of town excursion
// Passing a number for type will make it a game event for that appid
if (typeof server === 'function') {
callback = server;
server = {"ip": "", "password": ""};
} else if (typeof server === 'string') {
server = {"ip": server, "password": ""};
} else if (typeof server !== 'object') {
server = {"ip": "", "password": ""};
}
var form = {
"sessionid": this.getSessionID(),
"action": "updateEvent",
"eventID": id,
"tzOffset": new Date().getTimezoneOffset() * -60,
"name": name,
"type": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? "GameEvent" : type),
"appID": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? type : ''),
"serverIP": server.ip,
"serverPassword": server.password,
"notes": description,
"eventQuickTime": "now"
};
if (time === null) {
form.startDate = 'MM/DD/YY';
form.startHour = '12';
form.startMinute = '00';
form.startAMPM = 'PM';
form.timeChoice = 'quick';
} else {
form.startDate = (time.getMonth() + 1 < 10 ? '0' : '') + (time.getMonth() + 1) + '/' + (time.getDate() < 10 ? '0' : '') + time.getDate() + '/' + time.getFullYear().toString().substring(2);
form.startHour = (time.getHours() === 0 ? '12' : (time.getHours() > 12 ? time.getHours() - 12 : time.getHours()));
form.startMinute = (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
form.startAMPM = (time.getHours() <= 12 ? 'AM' : 'PM');
form.timeChoice = 'specific';
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.toString() + "/eventEdit",
"form": form
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.deleteGroupEvent = function(gid, id, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var form = {
"sessionid": this.getSessionID(),
"action": "deleteEvent",
"eventID": id
};
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.toString() + "/eventEdit",
"form": form
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.setGroupPlayerOfTheWeek = function(gid, steamID, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
if(typeof steamID === 'string') {
steamID = new SteamID(steamID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/potwEdit",
"form": {
"xml": 1,
"action": "potw",
"memberId": steamID.getSteam3RenderedID(),
"sessionid": this.getSessionID()
}
}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
xml2js.parseString(body, function(err, results) {
if(err) {
callback(err);
return;
}
if(results.response.results[0] == 'OK') {
callback(null, new SteamID(results.response.oldPOTW[0]), new SteamID(results.response.newPOTW[0]));
} else {
callback(new Error(results.response.results[0]));
}
});
}, "steamcommunity");
};
SteamCommunity.prototype.kickGroupMember = function(gid, steamID, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
if(typeof steamID === 'string') {
steamID = new SteamID(steamID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/membersManage",
"form": {
"sessionID": this.getSessionID(),
"action": "kick",
"memberId": steamID.getSteamID64(),
"queryString": ""
}
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
if(typeof page === 'function') {
callback = page;
page = 1;
}
var self = this;
this.httpRequest("https://steamcommunity.com/gid/" + gid.getSteamID64() + "/history?p=" + page, function(err, response, body) {
if (err) {
callback(err);
return;
}
var $ = Cheerio.load(body);
var output = {};
var paging = $('.group_paging p').text();
var match = paging.match(/(\d+) - (\d+) of (\d+)/);
if(match) {
output.first = parseInt(match[1], 10);
output.last = parseInt(match[2], 10);
output.total = parseInt(match[3], 10);
}
output.items = [];
var currentYear = (new Date()).getFullYear();
var lastDate = Date.now();
Array.prototype.forEach.call($('.historyItem, .historyItemb'), function(item) {
var data = {};
var $item = $(item);
data.type = $item.find('.historyShort').text().replace(/ /g, '');
var users = $item.find('.whiteLink[data-miniprofile]');
var sid;
if(users[0]) {
sid = new SteamID();
sid.universe = SteamID.Universe.PUBLIC;
sid.type = SteamID.Type.INDIVIDUAL;
sid.instance = SteamID.Instance.DESKTOP;
sid.accountid = $(users[0]).data('miniprofile');
data.user = sid;
}
if(users[1]) {
sid = new SteamID();
sid.universe = SteamID.Universe.PUBLIC;
sid.type = SteamID.Type.INDIVIDUAL;
sid.instance = SteamID.Instance.DESKTOP;
sid.accountid = $(users[1]).data('miniprofile');
data.actor = sid;
}
// Figure out the date. Of course there's no year, because Valve
var dateParts = $item.find('.historyDate').text().split('@');
var date = dateParts[0].trim().replace(/(st|nd|th)$/, '').trim() + ', ' + currentYear;
var time = dateParts[1].trim().replace(/(am|pm)/, ' $1');
date = new Date(date + ' ' + time + ' UTC');
// If this date is in the future, or it's later than the previous one, decrement the year
if(date.getTime() > lastDate) {
date.setFullYear(date.getFullYear() - 1);
}
data.date = date;
output.items.push(data);
});
callback(null, output);
}, "steamcommunity");
};
SteamCommunity.prototype.getAllGroupComments = function(gid, from, count, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var options = {
"uri": "https://steamcommunity.com/comment/Clan/render/" + gid.getSteamID64() + "/-1/",
"form": {
"start": from,
"count": count
}
};
var self = this;
this.httpRequestPost(options, function(err, response, body) {
if (err) {
callback(err);
return;
}
var comments = [];
var $ = Cheerio.load(JSON.parse(body).comments_html);
$(".commentthread_comment_content").each(function () {
var comment = {};
var cachedSelector;
var $selector = $(this).find(".commentthread_author_link");
comment.authorName = $($selector).find("bdi").text();
comment.authorId = $($selector).attr("href").replace(/https?:\/\/steamcommunity.com\/(id|profiles)\//, "");
comment.date = Helpers.decodeSteamTime($(this).find(".commentthread_comment_timestamp").text().trim());
$selector = $(this).find(".commentthread_comment_text");
comment.commentId = $($selector).attr("id").replace("comment_content_", "");
comment.text = $($selector).html().trim();
comments.push(comment);
});
callback(null, comments);
}, "steamcommunity");
};
SteamCommunity.prototype.deleteGroupComment = function(gid, cid, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
if (typeof cid !== 'string') {
cid = cid.toString();
}
var options = {
"uri": "https://steamcommunity.com/comment/Clan/delete/" + gid.getSteamID64() + "/-1/",
"form": {
"sessionid": this.getSessionID(),
"gidcomment": cid
}
};
var self = this;
this.httpRequestPost(options, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.postGroupComment = function(gid, message, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var options = {
"uri": "https://steamcommunity.com/comment/Clan/post/" + gid.getSteamID64() + "/-1/",
"form": {
"comment": message,
"count": 6,
"sessionid": this.getSessionID()
}
};
var self = this;
this.httpRequestPost(options, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
/**
* Get requests to join a restricted group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {function} callback - First argument is null/Error, second is array of SteamID objects
*/
SteamCommunity.prototype.getGroupJoinRequests = function(gid, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
this.httpRequestGet("https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage", (err, res, body) => {
if (!body) {
callback(new Error("Malformed response"));
return;
}
var matches = body.match(/JoinRequests_ApproveDenyUser\(\W*['"](\d+)['"],\W0\W\)/g);
if (!matches) {
// no pending requests
callback(null, []);
return;
}
var requests = [];
for (var i = 0; i < matches.length; i++) {
requests.push(new SteamID("[U:1:" + matches[i].match(/JoinRequests_ApproveDenyUser\(\W*['"](\d+)['"],\W0\W\)/)[1] + "]"));
}
callback(null, requests);
}, "steamcommunity");
};
/**
* Respond to one or more join requests to a restricted group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {SteamID|string|SteamID[]|string[]} steamIDs - The SteamIDs of the users you want to approve or deny membership for (or a single value)
* @param {boolean} approve - True to put them in the group, false to deny their membership
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.respondToGroupJoinRequests = function(gid, steamIDs, approve, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var rgAccounts = (!Array.isArray(steamIDs) ? [steamIDs] : steamIDs).map(sid => sid.toString());
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage",
"form": {
"rgAccounts": rgAccounts,
"bapprove": approve ? "1" : "0",
"json": "1",
"sessionID": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (!callback) {
return;
}
if (body != EResult.OK) {
var err = new Error(EResult[body] || ("Error " + body));
err.eresult = body;
callback(err);
} else {
callback(null);
}
}, "steamcommunity");
};
/**
* Respond to *ALL* pending group-join requests for a particular group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {boolean} approve - True to allow everyone who requested into the group, false to not
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.respondToAllGroupJoinRequests = function(gid, approve, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage",
"form": {
"bapprove": approve ? "1" : "0",
"json": "1",
"action": "bulkrespond",
"sessionID": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (!callback) {
return;
}
if (body != EResult.OK) {
var err = new Error(EResult[body] || ("Error " + body));
err.eresult = body;
callback(err);
} else {
callback(null);
}
}, "steamcommunity");
};

64
components/help.js Normal file
View File

@@ -0,0 +1,64 @@
const SteamCommunity = require('../index.js');
const Helpers = require('./helpers.js');
const HELP_SITE_DOMAIN = 'https://help.steampowered.com';
/**
* Restore a previously removed steam package from your steam account.
* @param {int|string} packageID
* @param {function} callback
*/
SteamCommunity.prototype.restorePackage = function(packageID, callback) {
this.httpRequestPost({
uri: HELP_SITE_DOMAIN + '/wizard/AjaxDoPackageRestore',
form: {
packageid: packageID,
sessionid: this.getSessionID(HELP_SITE_DOMAIN),
wizard_ajax: 1
},
json: true
}, wizardAjaxHandler(callback));
};
/**
* Remove a steam package from your steam account.
* @param {int|string} packageID
* @param {function} callback
*/
SteamCommunity.prototype.removePackage = function(packageID, callback) {
this.httpRequestPost({
uri: HELP_SITE_DOMAIN + '/wizard/AjaxDoPackageRemove',
form: {
packageid: packageID,
sessionid: this.getSessionID(HELP_SITE_DOMAIN),
wizard_ajax: 1
},
json: true
}, wizardAjaxHandler(callback));
};
/**
* Returns a handler for wizard ajax HTTP requests.
* @param {function} callback
* @returns {(function(*=, *, *): void)|*}
*/
function wizardAjaxHandler(callback) {
return (err, res, body) => {
if (!callback) {
return;
}
if (err) {
callback(err);
return;
}
if (!body.success) {
callback(body.errorMsg ? new Error(body.errorMsg) : Helpers.eresultError(body.success));
return;
}
callback(null);
};
}

56
components/helpers.js Normal file
View File

@@ -0,0 +1,56 @@
const EResult = require('../resources/EResult.js');
exports.isSteamID = function(input) {
var keys = Object.keys(input);
if (keys.length != 4) {
return false;
}
// Make sure it has the keys we expect
keys = keys.filter(function(item) {
return ['universe', 'type', 'instance', 'accountid'].indexOf(item) != -1;
});
return keys.length == 4;
};
exports.decodeSteamTime = function(time) {
var date = new Date();
if (time.includes("@")) {
var parts = time.split('@');
if (!parts[0].includes(",")) {
// no year, assume current year
parts[0] += ", " + date.getFullYear();
}
date = new Date(parts.join('@').replace(/(am|pm)/, ' $1') + " UTC"); // add a space so JS can decode it
} else {
// Relative date
var amount = time.replace(/(\d) (minutes|hour|hours) ago/, "$1");
if(time.includes("minutes")) {
date.setMinutes(date.getMinutes() - amount);
} else if(time.match(/hour|hours/)) {
date.setHours(date.getHours() - amount);
}
}
return date;
};
/**
* Get an Error object for a particular EResult
* @param {int} eresult
* @returns {null|Error}
*/
exports.eresultError = function(eresult) {
if (eresult == EResult.OK) {
// no error
return null;
}
var err = new Error(EResult[eresult] || ("Error " + eresult));
err.eresult = eresult;
return err;
};

150
components/http.js Normal file
View File

@@ -0,0 +1,150 @@
var SteamCommunity = require('../index.js');
SteamCommunity.prototype.httpRequest = function(uri, options, callback, source) {
if (typeof uri === 'object') {
source = callback;
callback = options;
options = uri;
uri = options.url || options.uri;
} else if (typeof options === 'function') {
source = callback;
callback = options;
options = {};
}
options.url = options.uri = uri;
if (this._httpRequestConvenienceMethod) {
options.method = this._httpRequestConvenienceMethod;
delete this._httpRequestConvenienceMethod;
}
var requestID = ++this._httpRequestID;
source = source || "";
var self = this;
var continued = false;
if (!this.onPreHttpRequest || !this.onPreHttpRequest(requestID, source, options, continueRequest)) {
// No pre-hook, or the pre-hook doesn't want to delay the request.
continueRequest(null);
}
function continueRequest(err) {
if (continued) {
return;
}
continued = true;
if (err) {
if (callback) {
callback(err);
}
return;
}
self.request(options, function (err, response, body) {
var hasCallback = !!callback;
var httpError = options.checkHttpError !== false && self._checkHttpError(err, response, callback, body);
var communityError = !options.json && options.checkCommunityError !== false && self._checkCommunityError(body, httpError ? function () {} : callback); // don't fire the callback if hasHttpError did it already
var tradeError = !options.json && options.checkTradeError !== false && self._checkTradeError(body, httpError || communityError ? function () {} : callback); // don't fire the callback if either of the previous already did
var jsonError = options.json && options.checkJsonError !== false && !body ? new Error("Malformed JSON response") : null;
self.emit('postHttpRequest', requestID, source, options, httpError || communityError || tradeError || jsonError || null, response, body, {
"hasCallback": hasCallback,
"httpError": httpError,
"communityError": communityError,
"tradeError": tradeError,
"jsonError": jsonError
});
if (hasCallback && !(httpError || communityError || tradeError)) {
if (jsonError) {
callback.call(self, jsonError, response);
} else {
callback.apply(self, arguments);
}
}
});
}
};
SteamCommunity.prototype.httpRequestGet = function() {
this._httpRequestConvenienceMethod = "GET";
return this.httpRequest.apply(this, arguments);
};
SteamCommunity.prototype.httpRequestPost = function() {
this._httpRequestConvenienceMethod = "POST";
return this.httpRequest.apply(this, arguments);
};
SteamCommunity.prototype._notifySessionExpired = function(err) {
this.emit('sessionExpired', err);
};
SteamCommunity.prototype._checkHttpError = function(err, response, callback, body) {
if (err) {
callback(err, response, body);
return err;
}
if (response.statusCode >= 300 && response.statusCode <= 399 && response.headers.location.indexOf('/login') != -1) {
err = new Error("Not Logged In");
callback(err, response, body);
this._notifySessionExpired(err);
return err;
}
if (response.statusCode == 403 && typeof response.body === 'string' && response.body.match(/<div id="parental_notice_instructions">Enter your PIN below to exit Family View.<\/div>/)) {
err = new Error("Family View Restricted");
callback(err, response, body);
return err;
}
if (response.statusCode >= 400) {
err = new Error("HTTP error " + response.statusCode);
err.code = response.statusCode;
callback(err, response, body);
return err;
}
return false;
};
SteamCommunity.prototype._checkCommunityError = function(html, callback) {
var err;
if(typeof html === 'string' && html.match(/<h1>Sorry!<\/h1>/)) {
var match = html.match(/<h3>(.+)<\/h3>/);
err = new Error(match ? match[1] : "Unknown error occurred");
callback(err);
return err;
}
if (typeof html === 'string' && html.indexOf('g_steamID = false;') > -1 && html.indexOf('<title>Sign In</title>') > -1) {
err = new Error("Not Logged In");
callback(err);
this._notifySessionExpired(err);
return err;
}
return false;
};
SteamCommunity.prototype._checkTradeError = function(html, callback) {
if (typeof html !== 'string') {
return false;
}
var match = html.match(/<div id="error_msg">\s*([^<]+)\s*<\/div>/);
if (match) {
var err = new Error(match[1].trim());
callback(err);
return err;
}
return false;
};

View File

@@ -0,0 +1,188 @@
var SteamCommunity = require('../index.js');
var CEconItem = require('../classes/CEconItem.js');
var SteamID = require('steamid');
var request = require('request');
var Cheerio = require('cheerio');
var Async = require('async');
/*
* Inventory history in a nutshell.
*
* There are no more page numbers. Now you have to request after_time and optionally after_trade.
* Without "prev" set, you will request 30 trades that were completed FURTHER IN THE PAST than after_time (and optionally after_trade)
* With "prev" set, you will request 30 trades that were completed MORE RECENTLY than after_time (and optionally after_trade)
*/
/**
* @deprecated Use GetTradeHistory instead: https://lab.xpaw.me/steam_api_documentation.html#IEconService_GetTradeHistory_v1
* @param {object} options
* @param {function} callback
*/
SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
options.direction = options.direction || "past";
var qs = "?l=english";
if (options.startTime) {
if (options.startTime instanceof Date) {
options.startTime = Math.floor(options.startTime.getTime() / 1000);
}
qs += "&after_time=" + options.startTime;
if (options.startTrade) {
qs += "&after_trade=" + options.startTrade;
}
}
if (options.direction == "future") {
qs += "&prev=1";
}
this._myProfile("inventoryhistory" + qs, null, function(err, response, body) {
if (err) {
callback(err);
return;
}
var output = {};
var vanityURLs = [];
var $ = Cheerio.load(body);
if (!$('.inventory_history_pagingrow').html()) {
callback(new Error("Malformed page: no paging row found"));
return;
}
// Load the inventory item data
var match2 = body.match(/var g_rgHistoryInventory = (.*);/);
if (!match2) {
callback(new Error("Malformed page: no trade found"));
return;
}
try {
var historyInventory = JSON.parse(match2[1]);
} catch (ex) {
callback(new Error("Malformed page: no well-formed trade data found"));
return;
}
var i;
// See if we've got paging buttons
var $paging = $('.inventory_history_nextbtn .pagebtn:not(.disabled)');
var href;
for (i = 0; i < $paging.length; i++) {
href = $paging[i].attribs.href;
if (href.match(/prev=1/)) {
output.firstTradeTime = new Date(href.match(/after_time=(\d+)/)[1] * 1000);
output.firstTradeID = href.match(/after_trade=(\d+)/)[1];
} else {
output.lastTradeTime = new Date(href.match(/after_time=(\d+)/)[1] * 1000);
output.lastTradeID = href.match(/after_trade=(\d+)/)[1];
}
}
output.trades = [];
var trades = $('.tradehistoryrow');
var item, trade, profileLink, items, j, econItem, timeMatch, time;
for (i = 0; i < trades.length; i++) {
item = $(trades[i]);
trade = {};
trade.onHold = !!item.find('span:nth-of-type(2)').text().match(/Trade on Hold/i);
timeMatch = item.find('.tradehistory_timestamp').html().match(/(\d+):(\d+)(am|pm)/);
if (timeMatch[1] == 12 && timeMatch[3] == 'am') {
timeMatch[1] = 0;
}
if (timeMatch[1] < 12 && timeMatch[3] == 'pm') {
timeMatch[1] = parseInt(timeMatch[1], 10) + 12;
}
time = (timeMatch[1] < 10 ? '0' : '') + timeMatch[1] + ':' + timeMatch[2] + ':00';
trade.date = new Date(item.find('.tradehistory_date').html() + ' ' + time + ' UTC');
trade.partnerName = item.find('.tradehistory_event_description a').html();
trade.partnerSteamID = null;
trade.partnerVanityURL = null;
trade.itemsReceived = [];
trade.itemsGiven = [];
profileLink = item.find('.tradehistory_event_description a').attr('href');
if (profileLink.indexOf('/profiles/') != -1) {
trade.partnerSteamID = new SteamID(profileLink.match(/(\d+)$/)[1]);
} else {
trade.partnerVanityURL = profileLink.match(/\/([^\/]+)$/)[1];
if (options.resolveVanityURLs && vanityURLs.indexOf(trade.partnerVanityURL) == -1) {
vanityURLs.push(trade.partnerVanityURL);
}
}
items = item.find('.history_item');
for (j = 0; j < items.length; j++) {
match = body.match(new RegExp("HistoryPageCreateItemHover\\( '" + $(items[j]).attr('id') + "', (\\d+), '(\\d+)', '(\\d+|class_\\d+_instance_\\d+|class_\\d+)', '(\\d+)' \\);"));
econItem = historyInventory[match[1]][match[2]][match[3]];
if ($(items[j]).attr('id').indexOf('received') != -1) {
trade.itemsReceived.push(new CEconItem(econItem));
} else {
trade.itemsGiven.push(new CEconItem(econItem));
}
}
output.trades.push(trade);
}
if (options.resolveVanityURLs) {
Async.map(vanityURLs, resolveVanityURL, function(err, results) {
if (err) {
callback(err);
return;
}
for (i = 0; i < output.trades.length; i++) {
if (output.trades[i].partnerSteamID || !output.trades[i].partnerVanityURL) {
continue;
}
// Find the vanity URL
for (j = 0; j < results.length; j++) {
if (results[j].vanityURL == output.trades[i].partnerVanityURL) {
output.trades[i].partnerSteamID = new SteamID(results[j].steamID);
break;
}
}
}
callback(null, output);
});
} else {
callback(null, output);
}
}, "steamcommunity");
};
function resolveVanityURL(vanityURL, callback) {
request("https://steamcommunity.com/id/" + vanityURL + "/?xml=1", function(err, response, body) {
if (err) {
callback(err);
return;
}
var match = body.match(/<steamID64>(\d+)<\/steamID64>/);
if (!match || !match[1]) {
callback(new Error("Couldn't find Steam ID"));
return;
}
callback(null, {"vanityURL": vanityURL, "steamID": match[1]});
});
}

387
components/market.js Normal file
View File

@@ -0,0 +1,387 @@
const SteamCommunity = require('../index.js');
const Cheerio = require('cheerio');
const Helpers = require('./helpers.js');
/**
* Get a list of all apps on the market
* @param {function} callback - First argument is null|Error, second is an object of appid => name
*/
SteamCommunity.prototype.getMarketApps = function(callback) {
var self = this;
this.httpRequest('https://steamcommunity.com/market/', function (err, response, body) {
if (err) {
callback(err);
return;
}
var $ = Cheerio.load(body);
if ($('.market_search_game_button_group')) {
let apps = {};
$('.market_search_game_button_group a.game_button').each(function (i, element) {
var e = Cheerio.load(element);
var name = e('.game_button_game_name').text().trim();
var url = element.attribs.href;
var appid = url.substr(url.indexOf('=') + 1);
apps[appid] = name;
});
callback(null, apps);
} else {
callback(new Error("Malformed response"));
}
}, "steamcommunity");
};
/**
* Check if an item is eligible to be turned into gems and if so, get its gem value
* @param {int} appid
* @param {int|string} assetid
* @param {function} callback
*/
SteamCommunity.prototype.getGemValue = function(appid, assetid, callback) {
this._myProfile({
"endpoint": "ajaxgetgoovalue/",
"qs": {
"sessionid": this.getSessionID(),
"appid": appid,
"contextid": 6,
"assetid": assetid
},
"checkHttpError": false,
"json": true
}, null, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body.goo_value || !body.strTitle) {
callback(new Error("Malformed response"));
return;
}
callback(null, {"promptTitle": body.strTitle, "gemValue": parseInt(body.goo_value, 10)});
});
};
/**
* Turn an eligible item into gems.
* @param {int} appid
* @param {int|string} assetid
* @param {int} expectedGemsValue
* @param {function} callback
*/
SteamCommunity.prototype.turnItemIntoGems = function(appid, assetid, expectedGemsValue, callback) {
this._myProfile({
"endpoint": "ajaxgrindintogoo/",
"json": true,
"checkHttpError": false
}, {
"appid": appid,
"contextid": 6,
"assetid": assetid,
"goo_value_expected": expectedGemsValue,
"sessionid": this.getSessionID()
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body['goo_value_received '] || !body.goo_value_total) { // lol valve
callback(new Error("Malformed response"));
return;
}
callback(null, {"gemsReceived": parseInt(body['goo_value_received '], 10), "totalGems": parseInt(body.goo_value_total, 10)});
})
};
/**
* Open a booster pack.
* @param {int} appid
* @param {int|string} assetid
* @param {function} callback
*/
SteamCommunity.prototype.openBoosterPack = function(appid, assetid, callback) {
this._myProfile({
"endpoint": "ajaxunpackbooster/",
"json": true,
"checkHttpError": false
}, {
"appid": appid,
"communityitemid": assetid,
"sessionid": this.getSessionID()
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body.rgItems) {
callback(new Error("Malformed response"));
return;
}
callback(null, body.rgItems);
})
};
/**
* Get the booster pack catalog to see what booster packs you can create
* @param {function} callback
*/
SteamCommunity.prototype.getBoosterPackCatalog = function(callback) {
this.httpRequestGet('https://steamcommunity.com/tradingcards/boostercreator/', (err, res, body) => {
if (err) {
callback(err);
return;
}
let idx = body.indexOf('CBoosterCreatorPage.Init(');
if (idx == -1) {
callback(new Error('Malformed response'));
return;
}
let lines = body.slice(idx).split('\n').map(l => l.trim());
for (let i = 1; i <= 4; i++) {
if (typeof lines[i] != 'string' || !lines[i].match(/,$/)) {
let err = new Error('Malformed response');
err.line = i;
callback(err);
return;
}
lines[i] = lines[i].replace(/,$/, '');
}
let boosterPackCatalog, totalGems, tradableGems, untradableGems;
try {
boosterPackCatalog = JSON.parse(lines[1]);
totalGems = parseInt(lines[2].match(/\d+/)[0], 10);
tradableGems = parseInt(lines[3].match(/\d+/)[0], 10);
untradableGems = parseInt(lines[4].match(/\d+/)[0], 10);
} catch (ex) {
let err = new Error('Malformed response');
err.inner = ex;
callback(err);
return;
}
let keyedCatalog = {};
boosterPackCatalog.forEach((app) => {
app.price = parseInt(app.price, 10);
app.unavailable = app.unavailable || false;
app.availableAtTime = app.available_at_time || null;
if (typeof app.availableAtTime == 'string') {
app.availableAtTime = Helpers.decodeSteamTime(app.availableAtTime);
}
delete app.available_at_time;
keyedCatalog[app.appid] = app;
});
callback(null, {
totalGems,
tradableGems,
untradableGems,
catalog: keyedCatalog
});
});
};
/**
* Create a booster pack using gems.
* @param {int} appid
* @param {boolean} [useUntradableGems=false]
* @param callback
*/
SteamCommunity.prototype.createBoosterPack = function(appid, useUntradableGems, callback) {
if (typeof useUntradableGems == 'function') {
callback = useUntradableGems;
useUntradableGems = false;
}
this.httpRequestPost({
uri: 'https://steamcommunity.com/tradingcards/ajaxcreatebooster/',
form: {
sessionid: this.getSessionID(),
appid,
series: 1,
// tradability_preference can be a value 1-3
// 1: Prefer using tradable gems, but use untradable if necessary
// 2: Only use tradable gems
// 3: Prefer using untradable gems, but use tradable if necessary
tradability_preference: useUntradableGems ? 3 : 2
},
json: true,
checkHttpError: false
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.purchase_eresult && body.purchase_eresult != 1) {
callback(Helpers.eresultError(body.purchase_eresult));
return;
}
// We can now check HTTP status codes
if (this._checkHttpError(err, res, callback, body)) {
return;
}
callback(null, {
totalGems: parseInt(body.goo_amount, 10),
tradableGems: parseInt(body.tradable_goo_amount, 10),
untradableGems: parseInt(body.untradable_goo_amount, 10),
resultItem: body.purchase_result
});
});
};
/**
* Get details about a gift in your inventory.
* @param {string} giftID
* @param {function} callback
*/
SteamCommunity.prototype.getGiftDetails = function(giftID, callback) {
this.httpRequestPost({
"uri": "https://steamcommunity.com/gifts/" + giftID + "/validateunpack",
"form": {
"sessionid": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body.packageid || !body.gift_name) {
callback(new Error("Malformed response"));
return;
}
callback(null, {
"giftName": body.gift_name,
"packageID": parseInt(body.packageid, 10),
"owned": body.owned
});
});
};
/**
* Unpack a gift in your inventory to your library.
* @param {string} giftID
* @param {function} callback
*/
SteamCommunity.prototype.redeemGift = function(giftID, callback) {
this.httpRequestPost({
"uri": "https://steamcommunity.com/gifts/" + giftID + "/unpack",
"form": {
"sessionid": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
callback(null);
});
};
/**
* @param {int|string} assetid
* @param {int} denominationIn
* @param {int} denominationOut
* @param {int} quantityIn
* @param {int} quantityOut
* @param {function} callback
* @private
*/
SteamCommunity.prototype._gemExchange = function(assetid, denominationIn, denominationOut, quantityIn, quantityOut, callback) {
this._myProfile({
endpoint: 'ajaxexchangegoo/',
json: true,
checkHttpError: false
}, {
appid: 753,
assetid,
goo_denomination_in: denominationIn,
goo_amount_in: quantityIn,
goo_denomination_out: denominationOut,
goo_amount_out_expected: quantityOut,
sessionid: this.getSessionID()
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
callback(Helpers.eresultError(body.success));
});
};
/**
* Pack gems into sack of gems.
* @param {int|string} assetid - ID of gem stack you want to pack into sacks
* @param {int} desiredSackCount - How many sacks you want. You must have at least this amount * 1000 gems in the stack you're packing
* @param {function} callback
*/
SteamCommunity.prototype.packGemSacks = function(assetid, desiredSackCount, callback) {
this._gemExchange(assetid, 1, 1000, desiredSackCount * 1000, desiredSackCount, callback);
};
/**
* Unpack sack of gems into gems.
* @param {int|string} assetid - ID of sack stack you want to unpack (say that 5 times fast)
* @param {int} sacksToUnpack
* @param {function} callback
*/
SteamCommunity.prototype.unpackGemSacks = function(assetid, sacksToUnpack, callback) {
this._gemExchange(assetid, 1000, 1, sacksToUnpack, sacksToUnpack * 1000, callback);
};

View File

@@ -1,6 +1,9 @@
var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
var Cheerio = require('cheerio');
const Cheerio = require('cheerio');
const FS = require('fs');
const SteamID = require('steamid');
const Helpers = require('./helpers.js');
const SteamCommunity = require('../index.js');
SteamCommunity.PrivacyState = {
"Private": 1,
@@ -9,165 +12,464 @@ SteamCommunity.PrivacyState = {
};
var CommentPrivacyState = {
"1": "commentselfonly",
"2": "commentfriendsonly",
"3": "commentanyone"
"1": 2, // private
"2": 0, // friends only
"3": 1 // anyone
};
SteamCommunity.prototype.setupProfile = function(callback) {
var self = this;
this._myProfile("edit?welcomed=1", null, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
} else {
callback(null);
}
});
};
SteamCommunity.prototype.editProfile = function(settings, callback) {
var self = this;
this._myProfile("edit", null, function(err, response, body) {
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
this._myProfile('edit/info', null, function(err, response, body) {
if (err || response.statusCode != 200) {
if (callback) {
callback(err || new Error('HTTP error ' + response.statusCode));
}
return;
}
var $ = Cheerio.load(body);
var form = $('#editForm');
if(!form) {
callback(new Error("Malformed response"));
var existingSettings = $('#profile_edit_config').data('profile-edit');
if (!existingSettings || !existingSettings.strPersonaName) {
if (callback) {
callback(new Error('Malformed response'));
}
return;
}
var values = {};
form.serializeArray().forEach(function(item) {
values[item.name] = item.value;
});
for(var i in settings) {
var values = {
sessionID: self.getSessionID(),
type: 'profileSave',
weblink_1_title: '',
weblink_1_url: '',
weblink_2_title: '',
weblink_2_url: '',
weblink_3_title: '',
weblink_3_url: '',
personaName: existingSettings.strPersonaName,
real_name: existingSettings.strRealName,
summary: existingSettings.strSummary,
country: existingSettings.LocationData.locCountryCode,
state: existingSettings.LocationData.locStateCode,
city: existingSettings.LocationData.locCityCode,
customURL: existingSettings.strCustomURL,
json: 1
};
for (var i in settings) {
if(!settings.hasOwnProperty(i)) {
continue;
}
switch(i) {
case 'name':
values.personaName = settings[i];
break;
case 'realName':
values.real_name = settings[i];
break;
case 'summary':
values.summary = settings[i];
break;
case 'country':
values.country = settings[i];
break;
case 'state':
values.state = settings[i];
break;
case 'city':
values.city = settings[i];
break;
case 'customURL':
values.customURL = settings[i];
break;
// These don't work right now
/*
case 'background':
// The assetid of our desired profile background
values.profile_background = settings[i];
break;
case 'featuredBadge':
// Currently, game badges aren't supported
values.favorite_badge_badgeid = settings[i];
break;
case 'primaryGroup':
if(typeof settings[i] === 'object' && settings[i].accountid) {
values.primary_group_steamid = settings[i].accountid;
if(typeof settings[i] === 'object' && settings[i].getSteamID64) {
values.primary_group_steamid = settings[i].getSteamID64();
} else {
values.primary_group_steamid = new SteamID(settings[i]).accountid;
values.primary_group_steamid = new SteamID(settings[i]).getSteamID64();
}
break;
*/
// TODO: profile showcases
}
}
self._myProfile("edit", values, function(err, response, body) {
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
self._myProfile('edit', values, function(err, response, body) {
if (settings.customURL) {
delete self._profileURL;
}
if (!callback) {
return;
}
// Check for an error
var $ = Cheerio.load(body);
var error = $('#errorText .formRowFields');
if(error) {
error = error.text().trim();
if(error) {
callback(new Error(error));
if (err || response.statusCode != 200) {
callback(err || new Error('HTTP error ' + response.statusCode));
return;
}
try {
var json = JSON.parse(body);
if (!json.success || json.success != 1) {
callback(new Error(json.errmsg || 'Request was not successful'));
return;
}
callback(null);
} catch (ex) {
callback(ex);
}
callback(null);
});
});
};
SteamCommunity.prototype.profileSettings = function(settings, callback) {
var self = this;
this._myProfile("edit/settings", null, function(err, response, body) {
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
this._myProfile('edit/settings', null, (err, response, body) => {
if (err || response.statusCode != 200) {
if (callback) {
callback(err || new Error('HTTP error ' + response.statusCode));
}
return;
}
var $ = Cheerio.load(body);
var form = $('#editForm');
if(!form) {
callback(new Error("Malformed response"));
var existingSettings = $('#profile_edit_config').data('profile-edit');
if (!existingSettings || !existingSettings.Privacy) {
if (callback) {
callback(new Error('Malformed response'));
}
return;
}
var values = {};
form.serializeArray().forEach(function(item) {
values[item.name] = item.value;
});
for(var i in settings) {
if(!settings.hasOwnProperty(i)) {
// PrivacySettings => {PrivacyProfile, PrivacyInventory, PrivacyInventoryGifts, PrivacyOwnedGames, PrivacyPlaytime}
// eCommentPermission
var privacy = existingSettings.Privacy.PrivacySettings;
var commentPermission = existingSettings.Privacy.eCommentPermission;
for (var i in settings) {
if (!settings.hasOwnProperty(i)) {
continue;
}
switch(i) {
switch (i) {
case 'profile':
values.privacySetting = settings[i];
privacy.PrivacyProfile = settings[i];
break;
case 'comments':
values.commentSetting = CommentPrivacyState[settings[i]];
commentPermission = CommentPrivacyState[settings[i]];
break;
case 'inventory':
values.inventoryPrivacySetting = settings[i];
privacy.PrivacyInventory = settings[i];
break;
case 'inventoryGifts':
values.inventoryGiftPrivacy = settings[i] ? 1 : 0;
privacy.PrivacyInventoryGifts = settings[i] ? SteamCommunity.PrivacyState.Private : SteamCommunity.PrivacyState.Public;
break;
case 'emailConfirmation':
values.tradeConfirmationSetting = settings[i] ? 1 : 0;
case 'gameDetails':
privacy.PrivacyOwnedGames = settings[i];
break;
case 'playtime':
privacy.PrivacyPlaytime = settings[i] ? SteamCommunity.PrivacyState.Private : SteamCommunity.PrivacyState.Public;
break;
case 'friendsList':
privacy.PrivacyFriendsList = settings[i];
break;
}
}
self._myProfile("edit/settings", values, function(err, response, body) {
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
this._myProfile({
method: 'POST',
endpoint: 'ajaxsetprivacy/',
json: true,
formData: { // it's multipart because lolvalve
sessionid: this.getSessionID(),
Privacy: JSON.stringify(privacy),
eCommentPermission: commentPermission
}
}, null, function(err, response, body) {
if (err || response.statusCode != 200) {
if (callback) {
callback(err || new Error('HTTP error ' + response.statusCode));
}
return;
}
callback(null);
if (body.success != 1) {
if (callback) {
callback(new Error(body.success ? 'Error ' + body.success : 'Request was not successful'));
}
return;
}
if (callback) {
callback(null, body.Privacy);
}
});
});
};
SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
if(typeof format === 'function') {
callback = format;
format = null;
}
// are we logged in?
if (!this.steamID) {
callback(new Error("Not Logged In"));
return;
}
var self = this;
if(image instanceof Buffer) {
doUpload(image);
} else if(image.match(/^https?:\/\//)) {
this.httpRequestGet({
"uri": image,
"encoding": null
}, function(err, response, body) {
if(err || response.statusCode != 200) {
if(callback) {
callback(err ? new Error(err.message + " downloading image") : new Error("HTTP error " + response.statusCode + " downloading image"));
}
return;
}
if(!format) {
format = response.headers['content-type'];
}
doUpload(body);
}, "steamcommunity");
} else {
if(!format) {
format = image.match(/\.([^\.]+)$/);
if(format) {
format = format[1];
}
}
FS.readFile(image, function(err, file) {
if(err) {
if(callback) {
callback(err);
}
return;
}
doUpload(file);
})
}
function doUpload(buffer) {
if(!format) {
if(callback) {
callback(new Error("Unknown image format"));
}
return;
}
if(format.match(/^image\//)) {
format = format.substring(6);
}
var filename = '';
var contentType = '';
switch(format.toLowerCase()) {
case 'jpg':
case 'jpeg':
filename = 'avatar.jpg';
contentType = 'image/jpeg';
break;
case 'png':
filename = 'avatar.png';
contentType = 'image/png';
break;
case 'gif':
filename = 'avatar.gif';
contentType = 'image/gif';
break;
default:
if(callback) {
callback(new Error("Unknown or invalid image format"));
}
return;
}
self.httpRequestPost({
"uri": "https://steamcommunity.com/actions/FileUploader",
"formData": {
"MAX_FILE_SIZE": buffer.length,
"type": "player_avatar_image",
"sId": self.steamID.getSteamID64(),
"sessionid": self.getSessionID(),
"doSub": 1,
"json": 1,
"avatar": {
"value": buffer,
"options": {
"filename": filename,
"contentType": contentType
}
}
},
"json": true
}, function(err, response, body) {
if(err) {
if(callback) {
callback(err);
}
return;
}
if(body && !body.success && body.message) {
if(callback) {
callback(new Error(body.message));
}
return;
}
if(response.statusCode != 200) {
if(callback) {
callback(new Error("HTTP error " + response.statusCode));
}
return;
}
if(!body || !body.success) {
if(callback) {
callback(new Error("Malformed response"));
}
return;
}
if(callback) {
callback(null, body.images.full);
}
}, "steamcommunity");
}
};
/**
* Post a new status to your profile activity feed.
* @param {string} statusText - The text of this status update
* @param {{appID: int}} [options] - Options for this status update. All are optional. If you don't pass any options, this can be omitted.
* @param {function} callback - err, postID
*/
SteamCommunity.prototype.postProfileStatus = function(statusText, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
this._myProfile("ajaxpostuserstatus/", {
"appid": options.appID || 0,
"sessionid": this.getSessionID(),
"status_text": statusText
}, (err, res, body) => {
try {
body = JSON.parse(body);
if (body.message) {
callback(new Error(body.message));
return;
}
var match = body.blotter_html.match(/id="userstatus_(\d+)_/);
if (!match) {
callback(new Error("Malformed response"));
return;
}
callback(null, parseInt(match[1], 10));
} catch (ex) {
callback(ex);
}
});
};
/**
* Delete a previously-posted profile status update.
* @param {int} postID
* @param {function} [callback]
*/
SteamCommunity.prototype.deleteProfileStatus = function(postID, callback) {
this._myProfile("ajaxdeleteuserstatus/", {
"sessionid": this.getSessionID(),
"postid": postID
}, (err, res, body) => {
if (!callback) {
return;
}
try {
body = JSON.parse(body);
if (!body.success) {
callback(new Error("Malformed response"));
return;
}
callback(Helpers.eresultError(body.success));
} catch (ex) {
callback(ex);
}
});
};

159
components/twofactor.js Normal file
View File

@@ -0,0 +1,159 @@
var SteamTotp = require('steam-totp');
var SteamCommunity = require('../index.js');
var ETwoFactorTokenType = {
"None": 0, // No token-based two-factor authentication
"ValveMobileApp": 1, // Tokens generated using Valve's special charset (5 digits, alphanumeric)
"ThirdParty": 2 // Tokens generated using literally everyone else's standard charset (6 digits, numeric). This is disabled.
};
SteamCommunity.prototype.enableTwoFactor = function(callback) {
var self = this;
this.getWebApiOauthToken(function(err, token) {
if(err) {
callback(err);
return;
}
self.httpRequestPost({
"uri": "https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/",
"form": {
"steamid": self.steamID.getSteamID64(),
"access_token": token,
"authenticator_time": Math.floor(Date.now() / 1000),
"authenticator_type": ETwoFactorTokenType.ValveMobileApp,
"device_identifier": SteamTotp.getDeviceID(self.steamID),
"sms_phone_id": "1"
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if(!body.response) {
callback(new Error("Malformed response"));
return;
}
if(body.response.status != 1) {
var error = new Error("Error " + body.response.status);
error.eresult = body.response.status;
callback(error);
return;
}
callback(null, body.response);
}, "steamcommunity");
});
};
SteamCommunity.prototype.finalizeTwoFactor = function(secret, activationCode, callback) {
var attemptsLeft = 30;
var diff = 0;
var self = this;
this.getWebApiOauthToken(function(err, token) {
if(err) {
callback(err);
return;
}
SteamTotp.getTimeOffset(function(err, offset, latency) {
if (err) {
callback(err);
return;
}
diff = offset;
finalize(token);
});
});
function finalize(token) {
var code = SteamTotp.generateAuthCode(secret, diff);
self.httpRequestPost({
"uri": "https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/",
"form": {
"steamid": self.steamID.getSteamID64(),
"access_token": token,
"authenticator_code": code,
"authenticator_time": Math.floor(Date.now() / 1000),
"activation_code": activationCode
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if(!body.response) {
callback(new Error("Malformed response"));
return;
}
body = body.response;
if(body.server_time) {
diff = body.server_time - Math.floor(Date.now() / 1000);
}
if(body.status == 89) {
callback(new Error("Invalid activation code"));
} else if(body.want_more) {
attemptsLeft--;
diff += 30;
finalize(token);
} else if(!body.success) {
callback(new Error("Error " + body.status));
} else {
callback(null);
}
}, "steamcommunity");
}
};
SteamCommunity.prototype.disableTwoFactor = function(revocationCode, callback) {
var self = this;
this.getWebApiOauthToken(function(err, token) {
if(err) {
callback(err);
return;
}
self.httpRequestPost({
"uri": "https://api.steampowered.com/ITwoFactorService/RemoveAuthenticator/v1/",
"form": {
"steamid": self.steamID.getSteamID64(),
"access_token": token,
"revocation_code": revocationCode,
"steamguard_scheme": 1
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if(!body.response) {
callback(new Error("Malformed response"));
return;
}
if(!body.response.success) {
callback(new Error("Request failed"));
return;
}
// success = true means it worked
callback(null);
}, "steamcommunity");
});
};

767
components/users.js Normal file
View File

@@ -0,0 +1,767 @@
const Cheerio = require('cheerio');
const Crypto = require('crypto');
const imageSize = require('image-size');
const SteamID = require('steamid');
const SteamCommunity = require('../index.js');
const CEconItem = require('../classes/CEconItem.js');
const Helpers = require('./helpers.js');
SteamCommunity.prototype.addFriend = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/AddFriendAjax",
"form": {
"accept_invite": 0,
"sessionID": this.getSessionID(),
"steamid": userID.toString()
},
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
}
if(body.success) {
callback(null);
} else {
callback(new Error("Unknown error"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.acceptFriendRequest = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/AddFriendAjax",
"form": {
"accept_invite": 1,
"sessionID": this.getSessionID(),
"steamid": userID.toString()
}
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.removeFriend = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/RemoveFriendAjax",
"form": {
"sessionID": this.getSessionID(),
"steamid": userID.toString()
}
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.blockCommunication = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/BlockUserAjax",
"form": {
"sessionID": this.getSessionID(),
"steamid": userID.toString()
}
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.unblockCommunication = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var form = {"action": "unignore"};
form['friends[' + userID.toString() + ']'] = 1;
this._myProfile('friends/blocked/', form, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
callback(null);
});
};
SteamCommunity.prototype.postUserComment = function(userID, message, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/comment/Profile/post/" + userID.toString() + "/-1",
"form": {
"comment": message,
"count": 1,
"sessionid": this.getSessionID()
},
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
}
if(body.success) {
const $ = Cheerio.load(body.comments_html);
const commentID = $('.commentthread_comment').attr('id').split('_')[1];
callback(null, commentID);
} else if(body.error) {
callback(new Error(body.error));
} else {
callback(new Error("Unknown error"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.deleteUserComment = function(userID, commentID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/comment/Profile/delete/" + userID.toString() + "/-1",
"form": {
"gidcomment": commentID,
"start": 0,
"count": 1,
"sessionid": this.getSessionID(),
"feature2": -1
},
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
}
if(body.success && !body.comments_html.includes(commentID)) {
callback(null);
} else if(body.error) {
callback(new Error(body.error));
} else if(body.comments_html.includes(commentID)) {
callback(new Error("Failed to delete comment"));
} else {
callback(new Error("Unknown error"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.getUserComments = function(userID, options, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
if (typeof options === 'function') {
callback = options;
options = {};
}
var form = Object.assign({
"start": 0,
"count": 0,
"feature2": -1,
"sessionid": this.getSessionID()
}, options);
this.httpRequestPost({
"uri": "https://steamcommunity.com/comment/Profile/render/" + userID.toString() + "/-1",
"form": form,
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
}
if(body.success) {
const $ = Cheerio.load(body.comments_html);
const comments = $(".commentthread_comment.responsive_body_text[id]").map((i, elem) => {
var $elem = $(elem),
$commentContent = $elem.find(".commentthread_comment_text");
return {
id: $elem.attr("id").split("_")[1],
author: {
steamID: new SteamID("[U:1:" + $elem.find("[data-miniprofile]").data("miniprofile") + "]"),
name: $elem.find("bdi").text(),
avatar: $elem.find(".playerAvatar img[src]").attr("src"),
state: $elem.find(".playerAvatar").attr("class").split(" ").pop()
},
date: new Date($elem.find(".commentthread_comment_timestamp").data("timestamp") * 1000),
text: $commentContent.text().trim(),
html: $commentContent.html().trim()
}
}).get();
callback(null, comments, body.total_count);
} else if(body.error) {
callback(new Error(body.error));
} else {
callback(new Error("Unknown error"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.inviteUserToGroup = function(userID, groupID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/GroupInvite",
"form": {
"group": groupID.toString(),
"invitee": userID.toString(),
"json": 1,
"sessionID": this.getSessionID(),
"type": "groupInvite"
},
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
}
if(body.results == 'OK') {
callback(null);
} else if(body.results) {
callback(new Error(body.results));
} else {
callback(new Error("Unknown error"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.getUserAliases = function(userID, callback) {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
this.httpRequestGet({
"uri": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/ajaxaliases",
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if (typeof body !== 'object') {
callback(new Error("Malformed response"));
return;
}
callback(null, body.map(function(entry) {
entry.timechanged = Helpers.decodeSteamTime(entry.timechanged);
return entry;
}));
}, "steamcommunity");
};
/**
* Get the background URL of user's profile.
* @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
* @param {function} callback
*/
SteamCommunity.prototype.getUserProfileBackground = function(userID, callback) {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64(), (err, response, body) => {
if (err) {
callback(err);
return;
}
var $ = Cheerio.load(body);
var $privateProfileInfo = $('.profile_private_info');
if ($privateProfileInfo.length > 0) {
callback(new Error($privateProfileInfo.text().trim()));
return;
}
if ($('body').hasClass('has_profile_background')) {
var backgroundUrl = $('div.profile_background_image_content').css('background-image');
var matcher = backgroundUrl.match(/\(([^)]+)\)/);
if (matcher.length != 2 || !matcher[1].length) {
callback(new Error("Malformed response"));
} else {
callback(null, matcher[1]);
}
} else {
callback(null, null);
}
}, "steamcommunity");
};
SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
if (typeof userID === 'function') {
callback = userID;
userID = this.steamID;
}
if (!userID) {
callback(new Error("No SteamID specified and not logged in"));
return;
}
var self = this;
this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory/", function(err, response, body) {
if (err) {
callback(err);
return;
}
var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
if (!match) {
var errorMessage = "Malformed response";
if(body.match(/0 items in their inventory\./)){
callback(null, {});
return;
}else if(body.match(/inventory is currently private\./)){
errorMessage = "Private inventory";
}else if(body.match(/profile\_private\_info/)){
errorMessage = "Private profile";
}
callback(new Error(errorMessage));
return;
}
var data;
try {
data = JSON.parse(match[1]);
} catch(e) {
callback(new Error("Malformed response"));
return;
}
callback(null, data);
}, "steamcommunity");
};
/**
* Get the contents of a user's inventory context.
* @deprecated Use getUserInventoryContents instead
* @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param {function} callback
*/
SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, tradableOnly, callback) {
var self = this;
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
var endpoint = "/profiles/" + userID.getSteamID64();
get([], []);
function get(inventory, currency, start) {
self.httpRequest({
"uri": "https://steamcommunity.com" + endpoint + "/inventory/json/" + appID + "/" + contextID,
"headers": {
"Referer": "https://steamcommunity.com" + endpoint + "/inventory"
},
"qs": {
"start": start,
"trading": tradableOnly ? 1 : undefined
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if (!body || !body.success || !body.rgInventory || !body.rgDescriptions || !body.rgCurrency) {
if (body) {
callback(new Error(body.Error || "Malformed response"));
} else {
callback(new Error("Malformed response"));
}
return;
}
var i;
for (i in body.rgInventory) {
if (!body.rgInventory.hasOwnProperty(i)) {
continue;
}
inventory.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
}
for (i in body.rgCurrency) {
if (!body.rgCurrency.hasOwnProperty(i)) {
continue;
}
currency.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
}
if (body.more) {
var match = response.request.uri.href.match(/\/(profiles|id)\/([^\/]+)\//);
if(match) {
endpoint = "/" + match[1] + "/" + match[2];
}
get(inventory, currency, body.more_start);
} else {
callback(null, inventory, currency);
}
}, "steamcommunity");
}
};
/**
* Get the contents of a user's inventory context.
* @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param {string} [language] - The language of item descriptions to return. Omit for default (which may either be English or your account's chosen language)
* @param {function} callback
*/
SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, language, callback) {
if (typeof language === 'function') {
callback = language;
language = "english";
}
if (!userID) {
callback(new Error("The user's SteamID is invalid or missing."));
return;
}
var self = this;
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
var pos = 1;
get([], []);
function get(inventory, currency, start) {
self.httpRequest({
"uri": "https://steamcommunity.com/inventory/" + userID.getSteamID64() + "/" + appID + "/" + contextID,
"headers": {
"Referer": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory"
},
"qs": {
"l": language, // Default language
"count": 2000, // Max items per 'page'
"start_assetid": start
},
"json": true
}, function(err, response, body) {
if (err) {
if (err.message == "HTTP error 403" && body === null) {
// 403 with a body of "null" means the inventory/profile is private.
if (self.steamID && userID.getSteamID64() == self.steamID.getSteamID64()) {
// We can never get private profile error for our own inventory!
self._notifySessionExpired(err);
}
callback(new Error("This profile is private."));
return;
}
if (err.message == "HTTP error 500" && body && body.error) {
err = new Error(body.error);
var match = body.error.match(/^(.+) \((\d+)\)$/);
if (match) {
err.message = match[1];
err.eresult = match[2];
callback(err);
return;
}
}
callback(err);
return;
}
if (body && body.success && body.total_inventory_count === 0) {
// Empty inventory
callback(null, [], [], 0);
return;
}
if (!body || !body.success || !body.assets || !body.descriptions) {
if (body) {
// Dunno if the error/Error property even exists on this new endpoint
callback(new Error(body.error || body.Error || "Malformed response"));
} else {
callback(new Error("Malformed response"));
}
return;
}
for (var i = 0; i < body.assets.length; i++) {
var description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid);
if (!tradableOnly || (description && description.tradable)) {
body.assets[i].pos = pos++;
(body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID));
}
}
if (body.more_items) {
get(inventory, currency, body.last_assetid);
} else {
callback(null, inventory, currency, body.total_inventory_count);
}
}, "steamcommunity");
}
// A bit of optimization; objects are hash tables so it's more efficient to look up by key than to iterate an array
var quickDescriptionLookup = {};
function getDescription(descriptions, classID, instanceID) {
var key = classID + '_' + (instanceID || '0'); // instanceID can be undefined, in which case it's 0.
if (quickDescriptionLookup[key]) {
return quickDescriptionLookup[key];
}
for (var i = 0; i < descriptions.length; i++) {
quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i];
}
return quickDescriptionLookup[key];
}
};
/**
* Upload an image to Steam and send it to another user over Steam chat.
* @param {SteamID|string} userID - Either a SteamID object or a string that can parse into one
* @param {Buffer} imageContentsBuffer - The image contents, as a Buffer
* @param {{spoiler?: boolean}} [options]
* @param {function} callback
*/
SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer, options, callback) {
if (typeof options == 'function') {
callback = options;
options = {};
}
options = options || {};
if (!userID) {
callback(new Error('The user\'s SteamID is invalid or missing'));
return;
}
if (typeof userID == 'string') {
userID = new SteamID(userID);
}
if (!Buffer.isBuffer(imageContentsBuffer)) {
callback(new Error('The image contents must be a Buffer containing an image'));
return;
}
var imageDetails = null;
try {
imageDetails = imageSize(imageContentsBuffer);
} catch (ex) {
callback(ex);
return;
}
var imageHash = Crypto.createHash('sha1');
imageHash.update(imageContentsBuffer);
imageHash = imageHash.digest('hex');
var filename = Date.now() + '_image.' + imageDetails.type;
this.httpRequestPost({
uri: 'https://steamcommunity.com/chat/beginfileupload/?l=english',
headers: {
referer: 'https://steamcommunity.com/chat/'
},
formData: { // it's multipart
sessionid: this.getSessionID(),
l: 'english',
file_size: imageContentsBuffer.length,
file_name: filename,
file_sha: imageHash,
file_image_width: imageDetails.width,
file_image_height: imageDetails.height,
file_type: 'image/' + (imageDetails.type == 'jpg' ? 'jpeg' : imageDetails.type)
},
json: true
}, (err, res, body) => {
if (err) {
if (body && body.success) {
var err2 = Helpers.eresultError(body.success);
if (body.message) {
err2.message = body.message;
}
callback(err2);
} else {
callback(err);
}
return;
}
if (body.success != 1) {
callback(Helpers.eresultError(body.success));
return;
}
var hmac = body.hmac;
var timestamp = body.timestamp;
var startResult = body.result;
if (!startResult || !startResult.ugcid || !startResult.url_host || !startResult.request_headers) {
callback(new Error('Malformed response'));
return;
}
// Okay, now we need to PUT the file to the provided URL
var uploadUrl = (startResult.use_https ? 'https' : 'http') + '://' + startResult.url_host + startResult.url_path;
var headers = {};
startResult.request_headers.forEach((header) => {
headers[header.name.toLowerCase()] = header.value;
});
this.httpRequest({
uri: uploadUrl,
method: 'PUT',
headers,
body: imageContentsBuffer
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
// Now we need to commit the upload
this.httpRequestPost({
uri: 'https://steamcommunity.com/chat/commitfileupload/',
headers: {
referer: 'https://steamcommunity.com/chat/'
},
formData: { // it's multipart again
sessionid: this.getSessionID(),
l: 'english',
file_name: filename,
file_sha: imageHash,
success: '1',
ugcid: startResult.ugcid,
file_type: 'image/' + (imageDetails.type == 'jpg' ? 'jpeg' : imageDetails.type),
file_image_width: imageDetails.width,
file_image_height: imageDetails.height,
timestamp,
hmac,
friend_steamid: userID.getSteamID64(),
spoiler: options.spoiler ? '1' : '0'
},
json: true
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success != 1) {
callback(Helpers.eresultError(body.success));
return;
}
if (body.result.success != 1) {
// lol valve
callback(Helpers.eresultError(body.result.success));
return;
}
if (!body.result.details || !body.result.details.url) {
callback(new Error('Malformed response'));
return;
}
callback(null, body.result.details.url);
}, 'steamcommunity');
}, 'steamcommunity');
}, 'steamcommunity');
};

57
components/webapi.js Normal file
View File

@@ -0,0 +1,57 @@
var SteamCommunity = require('../index.js');
SteamCommunity.prototype.getWebApiKey = function(domain, callback) {
var self = this;
this.httpRequest({
"uri": "https://steamcommunity.com/dev/apikey?l=english",
"followRedirect": false
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if(body.match(/<h2>Access Denied<\/h2>/)) {
return callback(new Error("Access Denied"));
}
if(body.match(/You must have a validated email address to create a Steam Web API key./)) {
return callback(new Error("You must have a validated email address to create a Steam Web API key."));
}
var match = body.match(/<p>Key: ([0-9A-F]+)<\/p>/);
if(match) {
// We already have an API key registered
callback(null, match[1]);
} else {
// We need to register a new API key
self.httpRequestPost('https://steamcommunity.com/dev/registerkey?l=english', {
"form": {
"domain": domain,
"agreeToTerms": "agreed",
"sessionid": self.getSessionID(),
"Submit": "Register"
}
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
self.getWebApiKey(domain, callback);
}, "steamcommunity");
}
}, "steamcommunity");
};
/**
* @deprecated No longer works if not logged in via mobile login. Will be removed in a future release.
* @param {function} callback
*/
SteamCommunity.prototype.getWebApiOauthToken = function(callback) {
if (this.oAuthToken) {
return callback(null, this.oAuthToken);
}
callback(new Error('This operation requires an OAuth token, which can only be obtained from node-steamcommunity\'s `login` method.'));
};

View File

@@ -0,0 +1,62 @@
// If you aren't running this script inside of the repository, replace the following line with:
// const SteamCommunity = require('steamcommunity');
const SteamCommunity = require('../index.js');
const ReadLine = require('readline');
let community = new SteamCommunity();
let rl = ReadLine.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Username: ', (accountName) => {
rl.question('Password: ', (password) => {
rl.question('Two-Factor Auth Code: ', (authCode) =>{
rl.question('Revocation Code: R', (rCode) => {
doLogin(accountName, password, authCode, '', rCode);
});
});
});
});
function doLogin(accountName, password, authCode, captcha, rCode) {
community.login({
accountName: accountName,
password: password,
twoFactorCode: authCode,
captcha: captcha
}, (err, sessionID, cookies, steamguard) => {
if (err) {
if (err.message == 'SteamGuard') {
console.log('This account does not have two-factor authentication enabled.');
process.exit();
return;
}
if (err.message == 'CAPTCHA') {
console.log(err.captchaurl);
rl.question('CAPTCHA: ', (captchaInput) => {
doLogin(accountName, password, authCode, captchaInput);
});
return;
}
console.log(err);
process.exit();
return;
}
console.log('Logged on!');
community.disableTwoFactor('R' + rCode, (err) => {
if (err) {
console.log(err);
process.exit();
return;
}
console.log('Two-factor authentication disabled!');
process.exit();
});
});
}

View File

@@ -0,0 +1,118 @@
var SteamCommunity = require('../index.js');
var ReadLine = require('readline');
var community = new SteamCommunity();
var rl = ReadLine.createInterface({
"input": process.stdin,
"output": process.stdout
});
rl.question("Username: ", function(accountName) {
rl.question("Password: ", function(password) {
doLogin(accountName, password);
});
});
function doLogin(accountName, password, authCode, twoFactorCode, captcha) {
community.login({
"accountName": accountName,
"password": password,
"authCode": authCode,
"twoFactorCode": twoFactorCode,
"captcha": captcha
}, function(err, sessionID, cookies, steamguard) {
if(err) {
if(err.message == 'SteamGuardMobile') {
rl.question("Steam Authenticator Code: ", function(code) {
doLogin(accountName, password, null, code);
});
return;
}
if(err.message == 'SteamGuard') {
console.log("An email has been sent to your address at " + err.emaildomain);
rl.question("Steam Guard Code: ", function(code) {
doLogin(accountName, password, code);
});
return;
}
if(err.message == 'CAPTCHA') {
console.log(err.captchaurl);
rl.question("CAPTCHA: ", function(captchaInput) {
doLogin(accountName, password, authCode, twoFactorCode, captchaInput);
});
return;
}
console.log(err);
process.exit();
return;
}
console.log("Logged on!");
rl.question("Group ID: ", function(gid) {
community.getSteamGroup(gid, function(err, group) {
if (err) {
console.log(err);
process.exit(1);
}
group.getAllAnnouncements(function(err, announcements) {
if(announcements.length === 0) {
return console.log("This group has no announcements");
}
for (var i = announcements.length - 1; i >= 0; i--) {
console.log("[%s] %s %s: %s", announcements[i].date, announcements[i].aid, announcements[i].author, announcements[i].content);
};
rl.question("Would you like to delete delete or edit an annoucement? (Type edit/delete): ", function(choice) {
rl.question("Annoucement ID: ", function(aid) {
if(choice === 'edit') {
rl.question("New title: ", function(header) {
rl.question("New body: ", function(content) {
// EW THE PYRAMID!
// Try replace this with delete!
editAnnouncement(group, aid, header, content);
});
});
} else {
deleteAnnouncement(group, aid);
}
});
});
});
});
});
});
}
function editAnnouncement(group, aid, header, content) {
// Actual community method.
group.editAnnouncement(aid, header, content, function(error) {
if(!error) {
console.log("Annoucement edited!");
} else {
console.log("Unable to edit annoucement! %j", error);
process.exit(1);
}
});
}
function deleteAnnouncement(group, aid) {
// group.deleteAnnouncement(aid);
// Or
group.deleteAnnouncement(aid, function(err) {
if(!err) {
console.log("Deleted");
} else {
console.log("Error deleting announcement.");
}
})
}

View File

@@ -0,0 +1,112 @@
// If you aren't running this script inside of the repository, replace the following line with:
// const SteamCommunity = require('steamcommunity');
const SteamCommunity = require('../index.js');
const ReadLine = require('readline');
const FS = require('fs');
const EResult = SteamCommunity.EResult;
let community = new SteamCommunity();
let rl = ReadLine.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Username: ', (accountName) => {
rl.question('Password: ', (password) => {
doLogin(accountName, password);
});
});
function doLogin(accountName, password, authCode, captcha) {
community.login({
accountName: accountName,
password: password,
authCode: authCode,
captcha: captcha
}, (err, sessionID, cookies, steamguard) => {
if (err) {
if (err.message == 'SteamGuardMobile') {
console.log('This account already has two-factor authentication enabled.');
process.exit();
return;
}
if (err.message == 'SteamGuard') {
console.log(`An email has been sent to your address at ${err.emaildomain}`);
rl.question('Steam Guard Code: ', (code) => {
doLogin(accountName, password, code);
});
return;
}
if (err.message == 'CAPTCHA') {
console.log(err.captchaurl);
rl.question('CAPTCHA: ', (captchaInput) => {
doLogin(accountName, password, authCode, captchaInput);
});
return;
}
console.log(err);
process.exit();
return;
}
console.log('Logged on!');
community.enableTwoFactor((err, response) => {
if (err) {
if (err.eresult == EResult.Fail) {
console.log('Error: Failed to enable two-factor authentication. Do you have a phone number attached to your account?');
process.exit();
return;
}
if (err.eresult == EResult.RateLimitExceeded) {
console.log('Error: RateLimitExceeded. Try again later.');
process.exit();
return;
}
console.log(err);
process.exit();
return;
}
if (response.status != EResult.OK) {
console.log(`Error: Status ${response.status}`);
process.exit();
return;
}
let filename = `twofactor_${community.steamID.getSteamID64()}.json`;
console.log(`Writing secrets to ${filename}`);
console.log(`Revocation code: ${response.revocation_code}`);
FS.writeFileSync(filename, JSON.stringify(response, null, '\t'));
promptActivationCode(response);
});
});
}
function promptActivationCode(response) {
rl.question('SMS Code: ', (smsCode) => {
community.finalizeTwoFactor(response.shared_secret, smsCode, (err) => {
if (err) {
if (err.message == 'Invalid activation code') {
console.log(err);
promptActivationCode(response);
return;
}
console.log(err);
} else {
console.log('Two-factor authentication enabled!');
}
process.exit();
});
});
}

636
index.js
View File

@@ -1,317 +1,587 @@
var Request = require('request');
var RSA = require('node-bignumber').Key;
var hex2b64 = require('node-bignumber').hex2b64;
var SteamID = require('steamid');
const hex2b64 = require('node-bignumber').hex2b64;
const Request = require('request');
const RSA = require('node-bignumber').Key;
const SteamID = require('steamid');
const Helpers = require('./components/helpers.js');
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";
require('util').inherits(SteamCommunity, require('events').EventEmitter);
module.exports = SteamCommunity;
SteamCommunity.SteamID = SteamID;
SteamCommunity.ConfirmationType = require('./resources/EConfirmationType.js');
SteamCommunity.EResult = require('./resources/EResult.js');
SteamCommunity.EFriendRelationship = require('./resources/EFriendRelationship.js');
function SteamCommunity(options) {
options = options || {};
function SteamCommunity() {
this._jar = Request.jar();
this._captchaGid = -1;
this.request = Request.defaults({"jar": this._jar, "timeout": 50000});
this._httpRequestID = 0;
this.chatState = SteamCommunity.ChatState.Offline;
var defaults = {
"jar": this._jar,
"timeout": options.timeout || 50000,
"gzip": true,
"headers": {
"User-Agent": options.userAgent || USER_AGENT
}
};
if (typeof options == "string") {
options = {
localAddress: options
};
}
this._options = options;
if (options.localAddress) {
defaults.localAddress = options.localAddress;
}
this.request = options.request || Request.defaults({"forever": true}); // "forever" indicates that we want a keep-alive agent
this.request = this.request.defaults(defaults);
// English
this._jar.setCookie(Request.cookie('Steam_Language=english'), 'https://steamcommunity.com');
this._setCookie(Request.cookie('Steam_Language=english'));
// UTC
this._setCookie(Request.cookie('timezoneOffset=0,0'));
}
SteamCommunity.prototype.login = function(details, callback) {
if(details.steamguard) {
var parts = details.steamguard.split('||');
this._jar.setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), 'https://steamcommunity.com');
if (!details.accountName || !details.password) {
throw new Error("Missing either accountName or password to login; both are needed");
}
if (details.steamguard) {
var parts = details.steamguard.split('||');
this._setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), true);
}
var disableMobile = typeof details.disableMobile == 'undefined' ? true : details.disableMobile;
var self = this;
this.request.post("https://steamcommunity.com/login/getrsakey/", {"form": {"username": details.accountName}}, function(err, response, body) {
if(err) {
// Delete the cache
delete self._profileURL;
// headers required to convince steam that we're logging in from a mobile device so that we can get the oAuth data
var mobileHeaders = {};
if (!disableMobile) {
mobileHeaders = {
"X-Requested-With": "com.valvesoftware.android.steam.community",
"Referer": "https://steamcommunity.com/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client",
"User-Agent": this._options.mobileUserAgent || details.mobileUserAgent || "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
"Accept": "text/javascript, text/html, application/xml, text/xml, */*"
};
this._setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"));
this._setCookie(Request.cookie("mobileClient=android"));
} else {
mobileHeaders = {"Referer": "https://steamcommunity.com/login"};
}
this.httpRequestPost("https://steamcommunity.com/login/getrsakey/", {
"form": {"username": details.accountName},
"headers": mobileHeaders,
"json": true
}, function(err, response, body) {
// Remove the mobile cookies
if (err) {
deleteMobileCookies();
callback(err);
return;
}
var json;
try {
json = JSON.parse(body);
} catch(e) {
callback(e);
if (!body.publickey_mod || !body.publickey_exp) {
deleteMobileCookies();
callback(new Error("Invalid RSA key received"));
return;
}
var key = new RSA();
key.setPublic(json.publickey_mod, json.publickey_exp);
var form = {
key.setPublic(body.publickey_mod, body.publickey_exp);
var formObj = {
"captcha_text": details.captcha || "",
"captchagid": self._captchaGid,
"emailauth": details.authCode || "",
"emailsteamid": "",
"loginfriendlyname": "",
"password": hex2b64(key.encrypt(details.password)),
"remember_login": "true",
"rsatimestamp": json.timestamp,
"rsatimestamp": body.timestamp,
"twofactorcode": details.twoFactorCode || "",
"username": details.accountName
"username": details.accountName,
"loginfriendlyname": "",
"donotcache": Date.now()
};
self.request.post({
if (!disableMobile) {
formObj.oauth_client_id = "DE45CD61";
formObj.oauth_scope = "read_profile write_profile read_client write_client";
formObj.loginfriendlyname = "#login_emailauth_friendlyname_mobile";
}
self.httpRequestPost({
"uri": "https://steamcommunity.com/login/dologin/",
"json": true,
"form": form
"form": formObj,
"headers": mobileHeaders
}, function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
deleteMobileCookies();
if (err) {
callback(err);
return;
}
if(!body.success && body.emailauth_needed) {
var error;
if (!body.success && body.emailauth_needed) {
// Steam Guard (email)
var error = new Error("SteamGuard");
error = new Error("SteamGuard");
error.emaildomain = body.emaildomain;
callback(error);
} else if(!body.success && body.requires_twofactor) {
} else if (!body.success && body.requires_twofactor) {
// Steam Guard (app)
callback(new Error("SteamGuardMobile"));
} else if(!body.success && body.captcha_needed) {
var error = new Error("CAPTCHA");
error.captchaurl = "https://steamcommunity.com/public/captcha.php?gid=" + body.captcha_gid;
} else if (!body.success && body.captcha_needed && body.message.match(/Please verify your humanity/)) {
error = new Error("CAPTCHA");
error.captchaurl = "https://steamcommunity.com/login/rendercaptcha/?gid=" + body.captcha_gid;
self._captchaGid = body.captcha_gid;
callback(error);
} else if(!body.success) {
} else if (!body.success) {
callback(new Error(body.message || "Unknown error"));
} else {
var sessionID = generateSessionID();
self._jar.setCookie(Request.cookie('sessionid=' + sessionID), 'http://steamcommunity.com');
self.steamID = new SteamID(body.transfer_parameters.steamid);
var oAuth = {};
self._setCookie(Request.cookie('sessionid=' + sessionID));
var cookies = self._jar.getCookieString("https://steamcommunity.com").split(';').map(function(cookie) {
return cookie.trim();
});
if (!disableMobile && body.oauth) {
oAuth = JSON.parse(body.oauth);
self.steamID = new SteamID(oAuth.steamid);
self.oAuthToken = oAuth.oauth_token;
} else {
for(var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split('=');
if(parts[0] == 'steamLogin') {
self.steamID = new SteamID(decodeURIComponent(parts[1]).split('||')[0])
break;
}
}
self.oAuthToken = null;
}
// Find the Steam Guard cookie
var steamguard = null;
for(var i = 0; i < cookies.length; i++) {
for (var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split('=');
if(parts[0] == 'steamMachineAuth' + self.steamID) {
steamguard = self.steamID.toString() + '||' + decodeURIComponent(parts[1]);
break;
}
}
callback(null, sessionID, cookies, steamguard);
self.setCookies(cookies);
callback(null, sessionID, cookies, steamguard, disableMobile ? null : oAuth.oauth_token);
}
}, "steamcommunity");
}, "steamcommunity");
function deleteMobileCookies() {
var cookie = Request.cookie('mobileClientVersion=');
cookie.expires = new Date(0);
self._setCookie(cookie);
cookie = Request.cookie('mobileClient=');
cookie.expires = new Date(0);
self._setCookie(cookie);
}
};
SteamCommunity.prototype.oAuthLogin = function(steamguard, token, callback) {
steamguard = steamguard.split('||');
var steamID = new SteamID(steamguard[0]);
var self = this;
this.httpRequestPost({
"uri": "https://api.steampowered.com/IMobileAuthService/GetWGToken/v1/",
"form": {
"access_token": token
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if(!body.response || !body.response.token || !body.response.token_secure) {
callback(new Error("Malformed response"));
return;
}
var cookies = [
'steamLogin=' + encodeURIComponent(steamID.getSteamID64() + '||' + body.response.token),
'steamLoginSecure=' + encodeURIComponent(steamID.getSteamID64() + '||' + body.response.token_secure),
'steamMachineAuth' + steamID.getSteamID64() + '=' + steamguard[1],
'sessionid=' + self.getSessionID()
];
self.setCookies(cookies);
callback(null, self.getSessionID(), cookies);
}, "steamcommunity");
};
/**
* Get a token that can be used to log onto Steam using steam-user.
* @param {function} callback
*/
SteamCommunity.prototype.getClientLogonToken = function(callback) {
this.httpRequestGet({
"uri": "https://steamcommunity.com/chat/clientjstoken",
"json": true
}, (err, res, body) => {
if (err || res.statusCode != 200) {
callback(err ? err : new Error('HTTP error ' + res.statusCode));
return;
}
if (!body.logged_in) {
let e = new Error('Not Logged In');
callback(e);
this._notifySessionExpired(e);
return;
}
if (!body.steamid || !body.account_name || !body.token) {
callback(new Error('Malformed response'));
return;
}
callback(null, {
"steamID": new SteamID(body.steamid),
"accountName": body.account_name,
"webLogonToken": body.token
});
});
};
SteamCommunity.prototype._setCookie = function(cookie, secure) {
var protocol = secure ? "https" : "http";
cookie.secure = !!secure;
this._jar.setCookie(cookie.clone(), protocol + "://steamcommunity.com");
this._jar.setCookie(cookie.clone(), protocol + "://store.steampowered.com");
this._jar.setCookie(cookie.clone(), protocol + "://help.steampowered.com");
};
SteamCommunity.prototype.setCookies = function(cookies) {
var self = this;
cookies.forEach(function(cookie) {
cookies.forEach((cookie) => {
var cookieName = cookie.match(/(.+)=/)[1];
if(cookieName == 'steamLogin') {
self.steamID = new SteamID(cookie.match(/=(\d+)/)[1]);
if (cookieName == 'steamLogin' || cookieName == 'steamLoginSecure') {
this.steamID = new SteamID(cookie.match(/=(\d+)/)[1]);
}
self._jar.setCookie(Request.cookie(cookie), (cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/) ? "https://" : "http://") + "steamcommunity.com");
this._setCookie(Request.cookie(cookie), !!(cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/)));
});
};
SteamCommunity.prototype.getSessionID = function() {
var cookies = this._jar.getCookieString("http://steamcommunity.com").split(';');
SteamCommunity.prototype.getSessionID = function(host = "http://steamcommunity.com") {
var cookies = this._jar.getCookieString(host).split(';');
for(var i = 0; i < cookies.length; i++) {
var match = cookies[i].trim().match(/([^=]+)=(.+)/);
if(match[1] == 'sessionid') {
return decodeURIComponent(match[2]);
}
}
var sessionID = generateSessionID();
this._jar.setCookie(Request.cookie('sessionid=' + sessionID), "http://steamcommunity.com");
this._setCookie(Request.cookie('sessionid=' + sessionID));
return sessionID;
};
function generateSessionID() {
return Math.floor(Math.random() * 1000000000);
};
SteamCommunity.prototype.getWebApiKey = function(domain, callback) {
var self = this;
this.request("https://steamcommunity.com/dev/apikey", function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
return;
}
if(body.match(/<h2>Access Denied<\/h2>/)) {
return callback("Access Denied");
}
var match = body.match(/<p>Key: ([0-9A-F]+)<\/p>/);
if(match) {
// We already have an API key registered
callback(null, match[1]);
} else {
// We need to register a new API key
self.request.post('https://steamcommunity.com/dev/registerkey', {
"form": {
"domain": domain,
"agreeToTerms": "agreed",
"sessionid": self.getSessionID(),
"Submit": "Register"
}
}, function(err, response, body) {
if(err || response.statusCode >= 400) {
return callback(err.message || "HTTP error " + response.statusCode);
}
self.getWebApiKey(domain, callback);
});
}
});
};
return require('crypto').randomBytes(12).toString('hex');
}
SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
this.request.post("https://steamcommunity.com/parental/ajaxunlock", {
var self = this;
var sessionID = self.getSessionID();
this.httpRequestPost("https://steamcommunity.com/parental/ajaxunlock", {
"json": true,
"form": {
"pin": pin
"pin": pin,
"sessionid": sessionID
}
}, function(err, response, body) {
if(!callback) {
return;
}
if(self._checkHttpError(err, response, callback)) {
if (err) {
callback(err);
return;
}
if(!body || typeof body.success !== 'boolean') {
return callback("Invalid response");
if (!body || typeof body.success !== 'boolean') {
callback("Invalid response");
return;
}
if(!body.success) {
return callback("Incorrect PIN");
if (!body.success) {
switch (body.eresult) {
case 15:
callback("Incorrect PIN");
break;
case 25:
callback("Too many invalid PIN attempts");
break;
default:
callback("Error " + body.eresult);
}
return;
}
callback();
}.bind(this));
}.bind(this), "steamcommunity");
};
SteamCommunity.prototype.getNotifications = function(callback) {
this.request.get("https://steamcommunity.com/actions/RefreshNotificationArea", function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
var self = this;
this.httpRequestGet({
"uri": "https://steamcommunity.com/actions/GetNotificationCounts",
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
var notifications = {
"comments": 0,
"items": 0,
"invites": 0,
"gifts": 0,
"chat": 0,
"trades": 0
};
var items = {
"comments": /(\d+) new comments?/,
"items": /(\d+) new items? in your inventory/,
"invites": /(\d+) new invites?/,
"gifts": /(\d+) new gifts?/,
"chat": /(\d+) unread chat messages?/,
"trades": /(\d+) new trade notifications?/
};
var match;
for(var i in items) {
if(match = body.match(items[i])) {
notifications[i] = parseInt(match[1], 10);
}
if (!body || !body.notifications) {
callback(new Error("Malformed response"));
return;
}
var notifications = {
"trades": body.notifications[1] || 0,
"gameTurns": body.notifications[2] || 0,
"moderatorMessages": body.notifications[3] || 0,
"comments": body.notifications[4] || 0,
"items": body.notifications[5] || 0,
"invites": body.notifications[6] || 0,
// dunno about 7
"gifts": body.notifications[8] || 0,
"chat": body.notifications[9] || 0,
"helpRequestReplies": body.notifications[10] || 0,
"accountAlerts": body.notifications[11] || 0
};
callback(null, notifications);
});
}, "steamcommunity");
};
SteamCommunity.prototype.resetItemNotifications = function(callback) {
this.request.get("https://steamcommunity.com/my/inventory", function(err, response, body) {
var self = this;
this.httpRequestGet("https://steamcommunity.com/my/inventory", function(err, response, body) {
if(!callback) {
return;
}
if(self._checkHttpError(err, response, callback)) {
return;
}
callback(null);
});
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.loggedIn = function(callback) {
this.request("https://steamcommunity.com/my", {"followRedirect": false}, function(err, response, body) {
this.httpRequestGet({
"uri": "https://steamcommunity.com/my",
"followRedirect": false,
"checkHttpError": false
}, function(err, response, body) {
if(err || (response.statusCode != 302 && response.statusCode != 403)) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(response.statusCode == 403) {
callback(null, true, true);
return;
}
callback(null, !!response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/), false);
});
}, "steamcommunity");
};
SteamCommunity.prototype._checkCommunityError = function(html, callback) {
if(html.match(/<h1>Sorry!<\/h1>/)) {
var match = html.match(/<h3>(.+)<\/h3>/);
callback(new Error(match ? match[1] : "Unknown error occurred"));
return true;
}
return false;
SteamCommunity.prototype.getTradeURL = function(callback) {
this._myProfile("tradeoffers/privacy", null, (err, response, body) => {
if (err) {
callback(err);
return;
}
var match = body.match(/https?:\/\/(www.)?steamcommunity.com\/tradeoffer\/new\/?\?partner=\d+(&|&amp;)token=([a-zA-Z0-9-_]+)/);
if (match) {
var token = match[3];
callback(null, match[0], token);
} else {
callback(new Error("Malformed response"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.changeTradeURL = function(callback) {
this._myProfile("tradeoffers/newtradeurl", {"sessionid": this.getSessionID()}, (err, response, body) => {
if (!callback) {
return;
}
if (!body || typeof body !== "string" || body.length < 3 || body.indexOf('"') !== 0) {
callback(new Error("Malformed response"));
return;
}
var newToken = body.replace(/"/g, ''); //"t1o2k3e4n" => t1o2k3e4n
callback(null, "https://steamcommunity.com/tradeoffer/new/?partner=" + this.steamID.accountid + "&token=" + newToken, newToken);
}, "steamcommunity");
};
/**
* Clear your profile name (alias) history.
* @param {function} callback
*/
SteamCommunity.prototype.clearPersonaNameHistory = function(callback) {
this._myProfile("ajaxclearaliashistory/", {"sessionid": this.getSessionID()}, (err, res, body) => {
if (!callback) {
return;
}
if (err) {
return callback(err);
}
if (res.statusCode != 200) {
return callback(new Error("HTTP error " + res.statusCode));
}
try {
body = JSON.parse(body);
callback(Helpers.eresultError(body.success));
} catch (ex) {
return callback(new Error("Malformed response"));
}
});
};
SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
var self = this;
this.request("https://steamcommunity.com/my", {"followRedirect": false}, function(err, response, body) {
if(err || response.statusCode != 302) {
callback(err || "HTTP error " + response.statusCode);
if (this._profileURL) {
completeRequest(this._profileURL);
} else {
this.httpRequest("https://steamcommunity.com/my", {"followRedirect": false}, function(err, response, body) {
if(err || response.statusCode != 302) {
callback(err || "HTTP error " + response.statusCode);
return;
}
var match = response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/);
if(!match) {
callback(new Error("Can't get profile URL"));
return;
}
self._profileURL = match[1];
setTimeout(function () {
delete self._profileURL; // delete the cache
}, 60000).unref();
completeRequest(match[1]);
}, "steamcommunity");
}
function completeRequest(url) {
var options = endpoint.endpoint ? endpoint : {};
options.uri = "https://steamcommunity.com" + url + "/" + (endpoint.endpoint || endpoint);
if (form) {
options.method = "POST";
options.form = form;
options.followAllRedirects = true;
} else if (!options.method) {
options.method = "GET";
}
self.httpRequest(options, callback, "steamcommunity");
}
};
/**
* Returns an object whose keys are 64-bit SteamIDs, and whose values are values from the EFriendRelationship enum.
* Therefore, you can deduce your friends or blocked list from this object.
* @param {function} callback
*/
SteamCommunity.prototype.getFriendsList = function(callback) {
this.httpRequestGet({
"uri": "https://steamcommunity.com/textfilter/ajaxgetfriendslist",
"json": true
}, (err, res, body) => {
if (err) {
callback(err ? err : new Error('HTTP error ' + res.statusCode));
return;
}
var match = response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/);
if(!match) {
callback("Can't get profile URL");
if (body.success != 1) {
callback(Helpers.eresultError(body.success));
return;
}
(form ? self.request.post : self.request)("https://steamcommunity.com" + match[1] + "/" + endpoint, form ? {"form": form} : {}, callback);
if (!body.friendslist || !body.friendslist.friends) {
callback(new Error('Malformed response'));
return;
}
const friends = {};
body.friendslist.friends.forEach(friend => (friends[friend.ulfriendid] = friend.efriendrelationship));
callback(null, friends);
});
};
SteamCommunity.prototype._checkHttpError = function(err, response, callback) {
if(err) {
callback(err);
return true;
}
if(response.statusCode != 200) {
var error = new Error("HTTP error " + response.statusCode);
error.code = response.statusCode;
callback(error);
return true;
}
return false;
};
require('./components/http.js');
require('./components/chat.js');
require('./components/profile.js');
require('./components/market.js');
require('./components/groups.js');
require('./components/users.js');
require('./components/inventoryhistory.js');
require('./components/webapi.js');
require('./components/twofactor.js');
require('./components/confirmations.js');
require('./components/help.js');
require('./classes/CMarketItem.js');
require('./classes/CMarketSearchResult.js');
require('./classes/CSteamGroup.js');
require('./classes/CSteamUser.js');
require('./components/chat.js');
require('./components/profile.js');
/**
@callback SteamCommunity~genericErrorCallback
@param {Error|null} err - An Error object on failure, or null on success
*/

View File

@@ -1,22 +1,36 @@
{
"name": "steamcommunity",
"version": "3.2.0",
"version": "3.44.4",
"description": "Provides an interface for logging into and interacting with the Steam Community website",
"keywords": ["steam", "steam community"],
"keywords": [
"steam",
"steam community"
],
"homepage": "https://github.com/DoctorMcKay/node-steamcommunity",
"bugs": {
"url": "https://github.com/DoctorMcKay/node-steamcommunity/issues"
},
"license": "MIT",
"author": {
"name": "Alex Corn",
"email": "mckay@doctormckay.com",
"url": "https://www.doctormckay.com"
},
"repository": {
"type": "git",
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
},
"dependencies": {
"request": "^2.58.0",
"node-bignumber": "^1.2.1",
"steamid": "^0.3.0",
"xml2js": "^0.4.9",
"cheerio": "^0.19.0"
"async": "^2.6.3",
"cheerio": "0.22.0",
"image-size": "^0.8.2",
"node-bignumber": "^1.2.1",
"request": "^2.88.0",
"steam-totp": "^1.5.0",
"steamid": "^1.1.3",
"xml2js": "^0.4.22"
},
"engines": {
"node": ">=4.0.0"
}
}
}

14
resources/EChatState.js Normal file
View File

@@ -0,0 +1,14 @@
/**
* @enum EChatState
*/
module.exports = {
"Offline": 0,
"LoggingOn": 1,
"LogOnFailed": 2,
"LoggedOn": 3,
"0": "Offline",
"1": "LoggingOn",
"2": "LogOnFailed",
"3": "LoggedOn"
};

View File

@@ -0,0 +1,12 @@
/**
* @enum EConfirmationType
*/
module.exports = {
// 1 is unknown, possibly "Invalid"
"Trade": 2,
"MarketListing": 3,
// 4 is opt-out or other like account confirmation?
"2": "Trade",
"3": "MarketListing"
};

View File

@@ -0,0 +1,23 @@
/**
* @enum EFriendRelationship
*/
module.exports = {
"None": 0,
"Blocked": 1,
"RequestRecipient": 2,
"Friend": 3,
"RequestInitiator": 4,
"Ignored": 5,
"IgnoredFriend": 6,
"SuggestedFriend": 7, // removed "was used by the original implementation of the facebook linking feature; but now unused."
// Value-to-name mapping for convenience
"0": "None",
"1": "Blocked",
"2": "RequestRecipient",
"3": "Friend",
"4": "RequestInitiator",
"5": "Ignored",
"6": "IgnoredFriend",
"7": "SuggestedFriend",
};

View File

@@ -0,0 +1,23 @@
/**
* @enum EPersonaState
*/
module.exports = {
"Offline": 0,
"Online": 1,
"Busy": 2,
"Away": 3,
"Snooze": 4,
"LookingToTrade": 5,
"LookingToPlay": 6,
"Invisible": 7,
// Value-to-name mapping for convenience
"0": "Offline",
"1": "Online",
"2": "Busy",
"3": "Away",
"4": "Snooze",
"5": "LookingToTrade",
"6": "LookingToPlay",
"7": "Invisible",
};

View File

@@ -0,0 +1,32 @@
/**
* @enum EPersonaStateFlag
*/
module.exports = {
"HasRichPresence": 1,
"InJoinableGame": 2,
"Golden": 4,
"RemotePlayTogether": 8,
"OnlineUsingWeb": 256, // removed "renamed to ClientTypeWeb"
"ClientTypeWeb": 256,
"OnlineUsingMobile": 512, // removed "renamed to ClientTypeMobile"
"ClientTypeMobile": 512,
"OnlineUsingBigPicture": 1024, // removed "renamed to ClientTypeTenfoot"
"ClientTypeTenfoot": 1024,
"OnlineUsingVR": 2048, // removed "renamed to ClientTypeVR"
"ClientTypeVR": 2048,
"LaunchTypeGamepad": 4096,
"LaunchTypeCompatTool": 8192,
// Value-to-name mapping for convenience
"1": "HasRichPresence",
"2": "InJoinableGame",
"4": "Golden",
"8": "RemotePlayTogether",
"256": "ClientTypeWeb",
"512": "ClientTypeMobile",
"1024": "ClientTypeTenfoot",
"2048": "ClientTypeVR",
"4096": "LaunchTypeGamepad",
"8192": "LaunchTypeCompatTool",
};

254
resources/EResult.js Normal file
View File

@@ -0,0 +1,254 @@
/**
* @enum EResult
*/
module.exports = {
"Invalid": 0,
"OK": 1,
"Fail": 2,
"NoConnection": 3,
"InvalidPassword": 5,
"LoggedInElsewhere": 6,
"InvalidProtocolVer": 7,
"InvalidParam": 8,
"FileNotFound": 9,
"Busy": 10,
"InvalidState": 11,
"InvalidName": 12,
"InvalidEmail": 13,
"DuplicateName": 14,
"AccessDenied": 15,
"Timeout": 16,
"Banned": 17,
"AccountNotFound": 18,
"InvalidSteamID": 19,
"ServiceUnavailable": 20,
"NotLoggedOn": 21,
"Pending": 22,
"EncryptionFailure": 23,
"InsufficientPrivilege": 24,
"LimitExceeded": 25,
"Revoked": 26,
"Expired": 27,
"AlreadyRedeemed": 28,
"DuplicateRequest": 29,
"AlreadyOwned": 30,
"IPNotFound": 31,
"PersistFailed": 32,
"LockingFailed": 33,
"LogonSessionReplaced": 34,
"ConnectFailed": 35,
"HandshakeFailed": 36,
"IOFailure": 37,
"RemoteDisconnect": 38,
"ShoppingCartNotFound": 39,
"Blocked": 40,
"Ignored": 41,
"NoMatch": 42,
"AccountDisabled": 43,
"ServiceReadOnly": 44,
"AccountNotFeatured": 45,
"AdministratorOK": 46,
"ContentVersion": 47,
"TryAnotherCM": 48,
"PasswordRequiredToKickSession": 49,
"AlreadyLoggedInElsewhere": 50,
"Suspended": 51,
"Cancelled": 52,
"DataCorruption": 53,
"DiskFull": 54,
"RemoteCallFailed": 55,
"PasswordNotSet": 56, // removed "renamed to PasswordUnset"
"PasswordUnset": 56,
"ExternalAccountUnlinked": 57,
"PSNTicketInvalid": 58,
"ExternalAccountAlreadyLinked": 59,
"RemoteFileConflict": 60,
"IllegalPassword": 61,
"SameAsPreviousValue": 62,
"AccountLogonDenied": 63,
"CannotUseOldPassword": 64,
"InvalidLoginAuthCode": 65,
"AccountLogonDeniedNoMailSent": 66, // removed "renamed to AccountLogonDeniedNoMail"
"AccountLogonDeniedNoMail": 66,
"HardwareNotCapableOfIPT": 67,
"IPTInitError": 68,
"ParentalControlRestricted": 69,
"FacebookQueryError": 70,
"ExpiredLoginAuthCode": 71,
"IPLoginRestrictionFailed": 72,
"AccountLocked": 73, // removed "renamed to AccountLockedDown"
"AccountLockedDown": 73,
"AccountLogonDeniedVerifiedEmailRequired": 74,
"NoMatchingURL": 75,
"BadResponse": 76,
"RequirePasswordReEntry": 77,
"ValueOutOfRange": 78,
"UnexpectedError": 79,
"Disabled": 80,
"InvalidCEGSubmission": 81,
"RestrictedDevice": 82,
"RegionLocked": 83,
"RateLimitExceeded": 84,
"AccountLogonDeniedNeedTwoFactorCode": 85, // removed "renamed to AccountLoginDeniedNeedTwoFactor"
"AccountLoginDeniedNeedTwoFactor": 85,
"ItemOrEntryHasBeenDeleted": 86, // removed "renamed to ItemDeleted"
"ItemDeleted": 86,
"AccountLoginDeniedThrottle": 87,
"TwoFactorCodeMismatch": 88,
"TwoFactorActivationCodeMismatch": 89,
"AccountAssociatedToMultiplePlayers": 90, // removed "renamed to AccountAssociatedToMultiplePartners"
"AccountAssociatedToMultiplePartners": 90,
"NotModified": 91,
"NoMobileDeviceAvailable": 92, // removed "renamed to NoMobileDevice"
"NoMobileDevice": 92,
"TimeIsOutOfSync": 93, // removed "renamed to TimeNotSynced"
"TimeNotSynced": 93,
"SMSCodeFailed": 94,
"TooManyAccountsAccessThisResource": 95, // removed "renamed to AccountLimitExceeded"
"AccountLimitExceeded": 95,
"AccountActivityLimitExceeded": 96,
"PhoneActivityLimitExceeded": 97,
"RefundToWallet": 98,
"EmailSendFailure": 99,
"NotSettled": 100,
"NeedCaptcha": 101,
"GSLTDenied": 102,
"GSOwnerDenied": 103,
"InvalidItemType": 104,
"IPBanned": 105,
"GSLTExpired": 106,
"InsufficientFunds": 107,
"TooManyPending": 108,
"NoSiteLicensesFound": 109,
"WGNetworkSendExceeded": 110,
"AccountNotFriends": 111,
"LimitedUserAccount": 112,
"CantRemoveItem": 113,
"AccountHasBeenDeleted": 114,
"AccountHasAnExistingUserCancelledLicense": 115,
"DeniedDueToCommunityCooldown": 116,
"NoLauncherSpecified": 117,
"MustAgreeToSSA": 118,
"ClientNoLongerSupported": 119,
// Value-to-name mapping for convenience
"0": "Invalid",
"1": "OK",
"2": "Fail",
"3": "NoConnection",
"5": "InvalidPassword",
"6": "LoggedInElsewhere",
"7": "InvalidProtocolVer",
"8": "InvalidParam",
"9": "FileNotFound",
"10": "Busy",
"11": "InvalidState",
"12": "InvalidName",
"13": "InvalidEmail",
"14": "DuplicateName",
"15": "AccessDenied",
"16": "Timeout",
"17": "Banned",
"18": "AccountNotFound",
"19": "InvalidSteamID",
"20": "ServiceUnavailable",
"21": "NotLoggedOn",
"22": "Pending",
"23": "EncryptionFailure",
"24": "InsufficientPrivilege",
"25": "LimitExceeded",
"26": "Revoked",
"27": "Expired",
"28": "AlreadyRedeemed",
"29": "DuplicateRequest",
"30": "AlreadyOwned",
"31": "IPNotFound",
"32": "PersistFailed",
"33": "LockingFailed",
"34": "LogonSessionReplaced",
"35": "ConnectFailed",
"36": "HandshakeFailed",
"37": "IOFailure",
"38": "RemoteDisconnect",
"39": "ShoppingCartNotFound",
"40": "Blocked",
"41": "Ignored",
"42": "NoMatch",
"43": "AccountDisabled",
"44": "ServiceReadOnly",
"45": "AccountNotFeatured",
"46": "AdministratorOK",
"47": "ContentVersion",
"48": "TryAnotherCM",
"49": "PasswordRequiredToKickSession",
"50": "AlreadyLoggedInElsewhere",
"51": "Suspended",
"52": "Cancelled",
"53": "DataCorruption",
"54": "DiskFull",
"55": "RemoteCallFailed",
"56": "PasswordUnset",
"57": "ExternalAccountUnlinked",
"58": "PSNTicketInvalid",
"59": "ExternalAccountAlreadyLinked",
"60": "RemoteFileConflict",
"61": "IllegalPassword",
"62": "SameAsPreviousValue",
"63": "AccountLogonDenied",
"64": "CannotUseOldPassword",
"65": "InvalidLoginAuthCode",
"66": "AccountLogonDeniedNoMail",
"67": "HardwareNotCapableOfIPT",
"68": "IPTInitError",
"69": "ParentalControlRestricted",
"70": "FacebookQueryError",
"71": "ExpiredLoginAuthCode",
"72": "IPLoginRestrictionFailed",
"73": "AccountLockedDown",
"74": "AccountLogonDeniedVerifiedEmailRequired",
"75": "NoMatchingURL",
"76": "BadResponse",
"77": "RequirePasswordReEntry",
"78": "ValueOutOfRange",
"79": "UnexpectedError",
"80": "Disabled",
"81": "InvalidCEGSubmission",
"82": "RestrictedDevice",
"83": "RegionLocked",
"84": "RateLimitExceeded",
"85": "AccountLoginDeniedNeedTwoFactor",
"86": "ItemDeleted",
"87": "AccountLoginDeniedThrottle",
"88": "TwoFactorCodeMismatch",
"89": "TwoFactorActivationCodeMismatch",
"90": "AccountAssociatedToMultiplePartners",
"91": "NotModified",
"92": "NoMobileDevice",
"93": "TimeNotSynced",
"94": "SMSCodeFailed",
"95": "AccountLimitExceeded",
"96": "AccountActivityLimitExceeded",
"97": "PhoneActivityLimitExceeded",
"98": "RefundToWallet",
"99": "EmailSendFailure",
"100": "NotSettled",
"101": "NeedCaptcha",
"102": "GSLTDenied",
"103": "GSOwnerDenied",
"104": "InvalidItemType",
"105": "IPBanned",
"106": "GSLTExpired",
"107": "InsufficientFunds",
"108": "TooManyPending",
"109": "NoSiteLicensesFound",
"110": "WGNetworkSendExceeded",
"111": "AccountNotFriends",
"112": "LimitedUserAccount",
"113": "CantRemoveItem",
"114": "AccountHasBeenDeleted",
"115": "AccountHasAnExistingUserCancelledLicense",
"116": "DeniedDueToCommunityCooldown",
"117": "NoLauncherSpecified",
"118": "MustAgreeToSSA",
"119": "ClientNoLongerSupported",
};