Compare commits

...

176 Commits

Author SHA1 Message Date
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
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
Jarzon
91285ba1c7 Merge branch 'master' into groupcomments 2017-03-04 09:04:24 -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
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
48 changed files with 2288 additions and 401 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'

52
.gitignore vendored
View File

@@ -1,3 +1,53 @@
node_modules/* node_modules/*
test.js test.js
.idea/
# 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>

View File

@@ -28,6 +28,7 @@ Submitting a pull request? Great! Thanks for contributing your time and code! Pl
- Tabs for indentation - Tabs for indentation
- camelCase for variables and functions - camelCase for variables and functions
- Opening braces on the same line as the if/for/while statement - Opening braces on the same line as the if/for/while statement
- No omitting unnecessary braces
- etc. - 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 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 don't increment the version number in `package.json`. I'll do that myself when I publish it to npm.

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2015 Alexander Corn Copyright (c) 2015 Alex Corn
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -9,8 +9,6 @@ This module provides an easy interface for the Steam Community website. This mod
It supports Steam Guard and CAPTCHAs. It supports Steam Guard and CAPTCHAs.
This reports anonymous usage statistics to the author. [See here](https://github.com/DoctorMcKay/node-stats-reporter) for more information.
**Have a question about the module or coding in general? *Do not create a GitHub issue.* GitHub issues are for feature **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/). requests and bug reports. Instead, post in the [dedicated forum](https://dev.doctormckay.com/forum/8-node-steamcommunity/).
Such issues may be ignored!** Such issues may be ignored!**

View File

@@ -5,9 +5,9 @@ module.exports = CConfirmation;
function CConfirmation(community, data) { function CConfirmation(community, data) {
Object.defineProperty(this, "_community", {"value": community}); Object.defineProperty(this, "_community", {"value": community});
this.id = data.id; this.id = data.id.toString();
this.type = data.type; this.type = data.type;
this.creator = data.creator; this.creator = data.creator.toString();
this.key = data.key; this.key = data.key;
this.title = data.title; this.title = data.title;
this.receiving = data.receiving; this.receiving = data.receiving;

View File

@@ -66,11 +66,31 @@ function CEconItem(item, description, contextID) {
this.market_fee_app = parseInt(match[1], 10); 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 === "") { if (this.actions === "") {
this.actions = []; this.actions = [];
} }
delete this.currency; // 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() { CEconItem.prototype.getImageURL = function() {

View File

@@ -12,7 +12,7 @@ SteamCommunity.prototype.getMarketItem = function(appid, hashName, currency, cal
callback(err); callback(err);
return; return;
} }
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
if($('.market_listing_table_message') && $('.market_listing_table_message').text().trim() == 'There are no listings for this item.') { 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.")); callback(new Error("There are no listings for this item."));
@@ -35,7 +35,7 @@ function CMarketItem(appid, hashName, community, body, $) {
this._hashName = hashName; this._hashName = hashName;
this._community = community; this._community = community;
this._$ = $; this._$ = $;
this._country = "US"; this._country = "US";
var match = body.match(/var g_strCountryCode = "([^"]+)";/); var match = body.match(/var g_strCountryCode = "([^"]+)";/);
if(match) { if(match) {
@@ -47,14 +47,14 @@ function CMarketItem(appid, hashName, community, body, $) {
if(match) { if(match) {
this._language = match[1]; this._language = match[1];
} }
this.commodity = false; this.commodity = false;
var match = body.match(/Market_LoadOrderSpread\(\s*(\d+)\s*\);/); match = body.match(/Market_LoadOrderSpread\(\s*(\d+)\s*\);/);
if(match) { if(match) {
this.commodity = true; this.commodity = true;
this.commodityID = parseInt(match[1], 10); this.commodityID = parseInt(match[1], 10);
} }
this.medianSalePrices = null; this.medianSalePrices = null;
match = body.match(/var line1=([^;]+);/); match = body.match(/var line1=([^;]+);/);
if(match) { if(match) {
@@ -78,7 +78,8 @@ function CMarketItem(appid, hashName, community, body, $) {
if (match) { if (match) {
try { try {
this.assets = JSON.parse(match[1]); this.assets = JSON.parse(match[1]);
this.assets = this.assets['730']['2']; this.assets = this.assets[appid];
this.assets = this.assets[Object.keys(this.assets)[0]];
this.firstAsset = this.assets[Object.keys(this.assets)[0]]; this.firstAsset = this.assets[Object.keys(this.assets)[0]];
} catch (e) { } catch (e) {
// ignore // ignore
@@ -112,29 +113,29 @@ CMarketItem.prototype.updatePriceForCommodity = function(currency, callback) {
callback(err); callback(err);
return; return;
} }
if(body.success != 1) { if(body.success != 1) {
if(callback) { if(callback) {
callback(new Error("Error " + body.success)); callback(new Error("Error " + body.success));
} }
return; return;
} }
var match = (body.sell_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/); var match = (body.sell_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/);
if(match) { if(match) {
self.quantity = parseInt(match[1], 10); self.quantity = parseInt(match[1], 10);
} }
self.buyQuantity = 0; self.buyQuantity = 0;
match = (body.buy_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/); match = (body.buy_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/);
if(match) { if(match) {
self.buyQuantity = parseInt(match[1], 10); self.buyQuantity = parseInt(match[1], 10);
} }
self.lowestPrice = parseInt(body.lowest_sell_order, 10); self.lowestPrice = parseInt(body.lowest_sell_order, 10);
self.highestBuyOrder = parseInt(body.highest_buy_order, 10); self.highestBuyOrder = parseInt(body.highest_buy_order, 10);
// TODO: The tables? // TODO: The tables?
if(callback) { if(callback) {
callback(null); callback(null);

View File

@@ -3,26 +3,26 @@ var Cheerio = require('cheerio');
SteamCommunity.prototype.marketSearch = function(options, callback) { SteamCommunity.prototype.marketSearch = function(options, callback) {
var qs = {}; var qs = {};
if(typeof options === 'string') { if(typeof options === 'string') {
qs.query = options; qs.query = options;
} else { } else {
qs.query = options.query || ''; qs.query = options.query || '';
qs.appid = options.appid; qs.appid = options.appid;
qs.search_descriptions = options.searchDescriptions ? 1 : 0; qs.search_descriptions = options.searchDescriptions ? 1 : 0;
if(qs.appid) { if(qs.appid) {
for(var i in options) { for(var i in options) {
if(['query', 'appid', 'searchDescriptions'].indexOf(i) != -1) { if(['query', 'appid', 'searchDescriptions'].indexOf(i) != -1) {
continue; continue;
} }
// This is a tag // This is a tag
qs['category_' + qs.appid + '_' + i + '[]'] = 'tag_' + options[i]; qs['category_' + qs.appid + '_' + i + '[]'] = 'tag_' + options[i];
} }
} }
} }
qs.start = 0; qs.start = 0;
qs.count = 100; qs.count = 100;
qs.sort_column = 'price'; qs.sort_column = 'price';
@@ -80,10 +80,10 @@ SteamCommunity.prototype.marketSearch = function(options, callback) {
function CMarketSearchResult(row) { 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.appid = parseInt(match[1], 10);
this.market_hash_name = decodeURIComponent(match[2]); this.market_hash_name = decodeURIComponent(match[2]);
this.image = row.find('.market_listing_item_img').attr('src').match(/^https?:\/\/[^\/]+\/economy\/image\/[^\/]+\//)[0]; 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.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); this.quantity = parseInt(row.find('.market_listing_num_listings_qty').text().replace(/[^\d]+/g, ''), 10);
} }

View File

@@ -78,8 +78,8 @@ CSteamGroup.prototype.getAllAnnouncements = function(time, callback) {
this._community.getAllGroupAnnouncements(this.steamID, time, callback); this._community.getAllGroupAnnouncements(this.steamID, time, callback);
}; };
CSteamGroup.prototype.postAnnouncement = function(headline, content, callback) { CSteamGroup.prototype.postAnnouncement = function(headline, content, hidden, callback) {
this._community.postGroupAnnouncement(this.steamID, headline, content, callback); this._community.postGroupAnnouncement(this.steamID, headline, content, hidden, callback);
}; };
CSteamGroup.prototype.editAnnouncement = function(annoucementID, headline, content, callback) { CSteamGroup.prototype.editAnnouncement = function(annoucementID, headline, content, callback) {
@@ -113,3 +113,43 @@ CSteamGroup.prototype.kick = function(steamID, callback) {
CSteamGroup.prototype.getHistory = function(page, callback) { CSteamGroup.prototype.getHistory = function(page, callback) {
this._community.getGroupHistory(this.steamID, 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

@@ -7,29 +7,29 @@ SteamCommunity.prototype.getSteamUser = function(id, callback) {
if(typeof id !== 'string' && !Helpers.isSteamID(id)) { if(typeof id !== 'string' && !Helpers.isSteamID(id)) {
throw new Error("id parameter should be a user URL string or a SteamID object"); 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)) { 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"); throw new Error("SteamID must stand for an individual account in the public universe");
} }
var self = this; var self = this;
this.httpRequest("http://steamcommunity.com/" + (typeof id === 'string' ? "id/" + id : "profiles/" + id.toString()) + "/?xml=1", function(err, response, body) { this.httpRequest("http://steamcommunity.com/" + (typeof id === 'string' ? "id/" + id : "profiles/" + id.toString()) + "/?xml=1", function(err, response, body) {
if (err) { if (err) {
callback(err); callback(err);
return; return;
} }
xml2js.parseString(body, function(err, result) { xml2js.parseString(body, function(err, result) {
if(err || (!result.response && !result.profile)) { if(err || (!result.response && !result.profile)) {
callback(err || new Error("No valid response")); callback(err || new Error("No valid response"));
return; return;
} }
if(result.response && result.response.error && result.response.error.length) { if(result.response && result.response.error && result.response.error.length) {
callback(new Error(result.response.error[0])); callback(new Error(result.response.error[0]));
return; return;
} }
// Try and find custom URL from redirect // Try and find custom URL from redirect
var customurl = null; var customurl = null;
if(response.request.redirects && response.request.redirects.length) { if(response.request.redirects && response.request.redirects.length) {
@@ -43,7 +43,7 @@ SteamCommunity.prototype.getSteamUser = function(id, callback) {
callback(new Error("No valid response")); callback(new Error("No valid response"));
return; return;
} }
callback(null, new CSteamUser(self, result.profile, customurl)); callback(null, new CSteamUser(self, result.profile, customurl));
}); });
}, "steamcommunity"); }, "steamcommunity");
@@ -51,7 +51,7 @@ SteamCommunity.prototype.getSteamUser = function(id, callback) {
function CSteamUser(community, userData, customurl) { function CSteamUser(community, userData, customurl) {
this._community = community; this._community = community;
this.steamID = new SteamID(userData.steamID64[0]); this.steamID = new SteamID(userData.steamID64[0]);
this.name = processItem('steamID'); this.name = processItem('steamID');
this.onlineState = processItem('onlineState'); this.onlineState = processItem('onlineState');
@@ -67,9 +67,15 @@ function CSteamUser(community, userData, customurl) {
this.tradeBanState = processItem('tradeBanState', 'None'); this.tradeBanState = processItem('tradeBanState', 'None');
this.isLimitedAccount = processItem('isLimitedAccount') == 1; this.isLimitedAccount = processItem('isLimitedAccount') == 1;
this.customURL = processItem('customURL', customurl); this.customURL = processItem('customURL', customurl);
if(this.visibilityState == 3) { if(this.visibilityState == 3) {
this.memberSince = new Date(processItem('memberSince', '0').replace(/(\d{1,2})(st|nd|th)/, "$1")); 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.location = processItem('location');
this.realName = processItem('realname'); this.realName = processItem('realname');
this.summary = processItem('summary'); this.summary = processItem('summary');
@@ -79,19 +85,19 @@ function CSteamUser(community, userData, customurl) {
this.realName = null; this.realName = null;
this.summary = null; this.summary = null;
} }
// Maybe handle mostPlayedGames? // Maybe handle mostPlayedGames?
this.groups = null; this.groups = null;
this.primaryGroup = null; this.primaryGroup = null;
var self = this; var self = this;
if(userData.groups && userData.groups[0] && userData.groups[0].group) { if(userData.groups && userData.groups[0] && userData.groups[0].group) {
this.groups = userData.groups[0].group.map(function(group) { this.groups = userData.groups[0].group.map(function(group) {
if(group['$'] && group['$'].isPrimary === "1") { if(group['$'] && group['$'].isPrimary === "1") {
self.primaryGroup = new SteamID(group.groupID64[0]); self.primaryGroup = new SteamID(group.groupID64[0]);
} }
return new SteamID(group.groupID64[0]); return new SteamID(group.groupID64[0]);
}); });
} }
@@ -110,7 +116,7 @@ CSteamUser.getAvatarURL = function(hash, size, protocol) {
protocol = protocol || 'http://'; protocol = protocol || 'http://';
hash = hash || "72f78b4c8cc1f62323f8a33f6d53e27db57c2252"; // The default "?" avatar hash = hash || "72f78b4c8cc1f62323f8a33f6d53e27db57c2252"; // The default "?" avatar
var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + hash.substring(0, 2) + "/" + hash; var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + hash.substring(0, 2) + "/" + hash;
if(size == 'full' || size == 'medium') { if(size == 'full' || size == 'medium') {
return url + "_" + size + ".jpg"; return url + "_" + size + ".jpg";
@@ -148,6 +154,14 @@ CSteamUser.prototype.comment = function(message, callback) {
this._community.postUserComment(this.steamID, message, callback); 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) { CSteamUser.prototype.inviteToGroup = function(groupID, callback) {
this._community.inviteUserToGroup(this.steamID, groupID, callback); this._community.inviteUserToGroup(this.steamID, groupID, callback);
}; };
@@ -177,8 +191,27 @@ CSteamUser.prototype.getInventory = function(appID, contextID, tradableOnly, cal
* @param {int} appID - The Steam application ID of the game for which you want an inventory * @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 {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 {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 * @param callback
*/ */
CSteamUser.prototype.getInventoryContents = function(appID, contextID, tradableOnly, callback) { CSteamUser.prototype.getInventoryContents = function(appID, contextID, tradableOnly, language, callback) {
this._community.getUserInventoryContents(this.steamID, appID, contextID, tradableOnly, 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,44 +1,26 @@
var SteamCommunity = require('../index.js'); var SteamCommunity = require('../index.js');
var SteamID = require('steamid'); var SteamID = require('steamid');
SteamCommunity.ChatState = { SteamCommunity.ChatState = require('../resources/EChatState.js');
"Offline": 0, SteamCommunity.PersonaState = require('../resources/EPersonaState.js');
"LoggingOn": 1, SteamCommunity.PersonaStateFlag = require('../resources/EPersonaStateFlag.js');
"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
};
/**
* @deprecated No support for new Steam chat. Use steam-user instead.
* @param {int} interval
* @param {string} uiMode
*/
SteamCommunity.prototype.chatLogon = function(interval, uiMode) { SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
if(this.chatState == SteamCommunity.ChatState.LoggingOn || this.chatState == SteamCommunity.ChatState.LoggedOn) { if(this.chatState == SteamCommunity.ChatState.LoggingOn || this.chatState == SteamCommunity.ChatState.LoggedOn) {
return; return;
} }
interval = interval || 500; interval = interval || 500;
uiMode = uiMode || "web"; uiMode = uiMode || "web";
this.emit('debug', 'Requesting chat WebAPI token'); this.emit('debug', 'Requesting chat WebAPI token');
this.chatState = SteamCommunity.ChatState.LoggingOn; this.chatState = SteamCommunity.ChatState.LoggingOn;
var self = this; var self = this;
this.getWebApiOauthToken(function(err, token) { this.getWebApiOauthToken(function(err, token) {
if(err) { if(err) {
@@ -55,7 +37,7 @@ SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
self.emit('debug', "Cannot get oauth token: " + err.message); self.emit('debug', "Cannot get oauth token: " + err.message);
return; return;
} }
self.httpRequestPost({ self.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Logon/v1", "uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Logon/v1",
"form": { "form": {
@@ -71,7 +53,7 @@ SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
setTimeout(self.chatLogon.bind(self), 5000); setTimeout(self.chatLogon.bind(self), 5000);
return; return;
} }
if(body.error != 'OK') { if(body.error != 'OK') {
self.chatState = SteamCommunity.ChatState.LogOnFailed; self.chatState = SteamCommunity.ChatState.LogOnFailed;
self.emit('chatLogOnFailed', new Error(body.error), false); self.emit('chatLogOnFailed', new Error(body.error), false);
@@ -79,7 +61,7 @@ SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
setTimeout(self.chatLogon.bind(self), 5000); setTimeout(self.chatLogon.bind(self), 5000);
return; return;
} }
self._chat = { self._chat = {
"umqid": body.umqid, "umqid": body.umqid,
"message": body.message, "message": body.message,
@@ -87,9 +69,9 @@ SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
"interval": interval, "interval": interval,
"uiMode": uiMode "uiMode": uiMode
}; };
self.chatFriends = {}; self.chatFriends = {};
self.chatState = SteamCommunity.ChatState.LoggedOn; self.chatState = SteamCommunity.ChatState.LoggedOn;
self.emit('chatLoggedOn'); self.emit('chatLoggedOn');
self._chatPoll(); self._chatPoll();
@@ -97,20 +79,27 @@ SteamCommunity.prototype.chatLogon = function(interval, uiMode) {
}); });
}; };
/**
* @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) { SteamCommunity.prototype.chatMessage = function(recipient, text, type, callback) {
if(this.chatState != SteamCommunity.ChatState.LoggedOn) { if(this.chatState != SteamCommunity.ChatState.LoggedOn) {
throw new Error("Chat must be logged on before messages can be sent"); throw new Error("Chat must be logged on before messages can be sent");
} }
if(typeof recipient === 'string') { if(typeof recipient === 'string') {
recipient = new SteamID(recipient); recipient = new SteamID(recipient);
} }
if(typeof type === 'function') { if(typeof type === 'function') {
callback = type; callback = type;
type = 'saytext'; type = 'saytext';
} }
type = type || 'saytext'; type = type || 'saytext';
var self = this; var self = this;
@@ -128,12 +117,12 @@ SteamCommunity.prototype.chatMessage = function(recipient, text, type, callback)
if(!callback) { if(!callback) {
return; return;
} }
if (err) { if (err) {
callback(err); callback(err);
return; return;
} }
if(body.error != 'OK') { if(body.error != 'OK') {
callback(new Error(body.error)); callback(new Error(body.error));
} else { } else {
@@ -142,6 +131,9 @@ SteamCommunity.prototype.chatMessage = function(recipient, text, type, callback)
}, "steamcommunity"); }, "steamcommunity");
}; };
/**
* @deprecated No support for new Steam chat. Use steam-user instead.
*/
SteamCommunity.prototype.chatLogoff = function() { SteamCommunity.prototype.chatLogoff = function() {
var self = this; var self = this;
this.httpRequestPost({ this.httpRequestPost({
@@ -164,9 +156,12 @@ SteamCommunity.prototype.chatLogoff = function() {
}, "steamcommunity"); }, "steamcommunity");
}; };
/**
* @private
*/
SteamCommunity.prototype._chatPoll = function() { SteamCommunity.prototype._chatPoll = function() {
this.emit('debug', 'Doing chat poll'); this.emit('debug', 'Doing chat poll');
var self = this; var self = this;
this.httpRequestPost({ this.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Poll/v1", "uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Poll/v1",
@@ -181,12 +176,12 @@ SteamCommunity.prototype._chatPoll = function() {
}, },
"json": true "json": true
}, function(err, response, body) { }, function(err, response, body) {
if(self.chatState == SteamCommunity.ChatState.Offline) { if (self.chatState == SteamCommunity.ChatState.Offline) {
return; return;
} }
self._chat.timer = setTimeout(self._chatPoll.bind(self), self._chat.interval); self._chat.timer = setTimeout(self._chatPoll.bind(self), self._chat.interval);
if(err || response.statusCode != 200) { if(err || response.statusCode != 200) {
self.emit('debug', 'Error in chat poll: ' + (err ? err.message : "HTTP error " + response.statusCode)); self.emit('debug', 'Error in chat poll: ' + (err ? err.message : "HTTP error " + response.statusCode));
if (err.message == "Not Logged On") { if (err.message == "Not Logged On") {
@@ -195,7 +190,7 @@ SteamCommunity.prototype._chatPoll = function() {
return; return;
} }
if(!body || body.error != 'OK') { if(!body || body.error != 'OK') {
self.emit('debug', 'Error in chat poll: ' + (body && body.error ? body.error : "Malformed response")); self.emit('debug', 'Error in chat poll: ' + (body && body.error ? body.error : "Malformed response"));
if (body && body.error && body.error == "Not Logged On") { if (body && body.error && body.error == "Not Logged On") {
@@ -204,29 +199,29 @@ SteamCommunity.prototype._chatPoll = function() {
return; return;
} }
self._chat.message = body.messagelast; self._chat.message = body.messagelast;
(body.messages || []).forEach(function(message) { (body.messages || []).forEach(function(message) {
var sender = new SteamID(); var sender = new SteamID();
sender.universe = SteamID.Universe.PUBLIC; sender.universe = SteamID.Universe.PUBLIC;
sender.type = SteamID.Type.INDIVIDUAL; sender.type = SteamID.Type.INDIVIDUAL;
sender.instance = SteamID.Instance.DESKTOP; sender.instance = SteamID.Instance.DESKTOP;
sender.accountid = message.accountid_from; sender.accountid = message.accountid_from;
switch(message.type) { switch(message.type) {
case 'personastate': case 'personastate':
self._chatUpdatePersona(sender); self._chatUpdatePersona(sender);
break; break;
case 'saytext': case 'saytext':
self.emit('chatMessage', sender, message.text); self.emit('chatMessage', sender, message.text);
break; break;
case 'typing': case 'typing':
self.emit('chatTyping', sender); self.emit('chatTyping', sender);
break; break;
default: default:
self.emit('debug', 'Unhandled chat message type: ' + message.type); self.emit('debug', 'Unhandled chat message type: ' + message.type);
} }
@@ -234,6 +229,9 @@ SteamCommunity.prototype._chatPoll = function() {
}, "steamcommunity"); }, "steamcommunity");
}; };
/**
* @private
*/
SteamCommunity.prototype._relogWebChat = function() { SteamCommunity.prototype._relogWebChat = function() {
this.emit('debug', "Relogging web chat"); this.emit('debug', "Relogging web chat");
clearTimeout(this._chat.timer); clearTimeout(this._chat.timer);
@@ -241,13 +239,25 @@ SteamCommunity.prototype._relogWebChat = function() {
this.chatLogon(this._chat.interval, this._chat.uiMode); this.chatLogon(this._chat.interval, this._chat.uiMode);
}; };
/**
* @param {SteamID} steamID
* @private
*/
SteamCommunity.prototype._chatUpdatePersona = function(steamID) { 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); this.emit('debug', 'Updating persona data for ' + steamID);
var self = this; var self = this;
this.httpRequest({ this.httpRequest({
"uri": "https://steamcommunity.com/chat/friendstate/" + steamID.accountid, "uri": "https://steamcommunity.com/chat/friendstate/" + steamID.accountid,
"json": true "json": true
}, function(err, response, body) { }, function(err, response, body) {
if (!self.chatFriends || self.chatState == SteamCommunity.ChatState.Offline) {
return; // welp
}
if(err || response.statusCode != 200) { if(err || response.statusCode != 200) {
self.emit('debug', 'Chat update persona error: ' + (err ? err.message : "HTTP error " + response.statusCode)); self.emit('debug', 'Chat update persona error: ' + (err ? err.message : "HTTP error " + response.statusCode));
setTimeout(function() { setTimeout(function() {
@@ -255,7 +265,7 @@ SteamCommunity.prototype._chatUpdatePersona = function(steamID) {
}, 2000); }, 2000);
return; return;
} }
var persona = { var persona = {
"steamID": steamID, "steamID": steamID,
"personaName": body.m_strName, "personaName": body.m_strName,
@@ -270,4 +280,4 @@ SteamCommunity.prototype._chatUpdatePersona = function(steamID) {
self.emit('chatPersonaState', steamID, persona); self.emit('chatPersonaState', steamID, persona);
self.chatFriends[steamID.getSteamID64()] = persona; self.chatFriends[steamID.getSteamID64()] = persona;
}, "steamcommunity"); }, "steamcommunity");
}; };

View File

@@ -172,7 +172,7 @@ SteamCommunity.prototype.acceptConfirmationForObject = function(identitySecret,
setTimeout(function() { setTimeout(function() {
// Delete the saved time offset after 12 hours because why not // Delete the saved time offset after 12 hours because why not
delete self._timeOffset; delete self._timeOffset;
}, 1000 * 60 * 60 * 12); }, 1000 * 60 * 60 * 12).unref();
}); });
} }

View File

@@ -2,6 +2,8 @@ var SteamCommunity = require('../index.js');
var SteamID = require('steamid'); var SteamID = require('steamid');
var xml2js = require('xml2js'); var xml2js = require('xml2js');
var Cheerio = require('cheerio'); var Cheerio = require('cheerio');
var Helpers = require('./helpers.js');
var EResult = SteamCommunity.EResult;
SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link, addresses, addressIdx) { SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link, addresses, addressIdx) {
members = members || []; members = members || [];
@@ -142,7 +144,7 @@ SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback
headline: announcement.title[0], headline: announcement.title[0],
content: announcement.description[0], content: announcement.description[0],
date: new Date(announcement.pubDate[0]), date: new Date(announcement.pubDate[0]),
author: announcement.author[0], author: (typeof announcement.author === 'undefined') ? null : announcement.author[0],
aid: splitLink[splitLink.length - 1] aid: splitLink[splitLink.length - 1]
} }
}).filter(function(announcement) { }).filter(function(announcement) {
@@ -152,24 +154,35 @@ SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback
return callback(null, announcements); return callback(null, announcements);
}); });
}, "steamcommunity"); }, "steamcommunity");
} };
SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content, callback) { SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content, hidden, callback) {
if(typeof gid === 'string') { if(typeof gid === 'string') {
gid = new SteamID(gid); gid = new SteamID(gid);
} }
if(typeof hidden === 'function') {
callback = hidden;
hidden = false;
}
var self = this; 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({ this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements", "uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements",
"form": { form
"sessionID": this.getSessionID(),
"action": "post",
"headline": headline,
"body": content,
"languages[0][headline]": headline,
"languages[0][body]": content
}
}, function(err, response, body) { }, function(err, response, body) {
if(!callback) { if(!callback) {
return; return;
@@ -493,7 +506,7 @@ SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
sid.universe = SteamID.Universe.PUBLIC; sid.universe = SteamID.Universe.PUBLIC;
sid.type = SteamID.Type.INDIVIDUAL; sid.type = SteamID.Type.INDIVIDUAL;
sid.instance = SteamID.Instance.DESKTOP; sid.instance = SteamID.Instance.DESKTOP;
sid.accountid = $(users[0]).data('miniprofile'); sid.accountid = $(users[1]).data('miniprofile');
data.actor = sid; data.actor = sid;
} }
@@ -517,3 +530,203 @@ SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
callback(null, output); callback(null, output);
}, "steamcommunity"); }, "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);
};
}

View File

@@ -1,3 +1,5 @@
const EResult = require('../resources/EResult.js');
exports.isSteamID = function(input) { exports.isSteamID = function(input) {
var keys = Object.keys(input); var keys = Object.keys(input);
if (keys.length != 4) { if (keys.length != 4) {
@@ -13,11 +15,42 @@ exports.isSteamID = function(input) {
}; };
exports.decodeSteamTime = function(time) { exports.decodeSteamTime = function(time) {
var parts = time.split('@'); var date = new Date();
if (!parts[0].match(/,/)) {
// no year, assume current year if (time.includes("@")) {
parts[0] += ", " + (new Date()).getFullYear(); 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 new Date(parts.join('@').replace(/(am|pm)/, ' $1') + " UTC"); // add a space so JS can decode it 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;
}; };

View File

@@ -124,7 +124,7 @@ SteamCommunity.prototype._checkCommunityError = function(html, callback) {
return err; return err;
} }
if (typeof html === 'string' && html.match(/g_steamID = false;/) && html.match(/<h1>Sign In<\/h1>/)) { if (typeof html === 'string' && html.indexOf('g_steamID = false;') > -1 && html.indexOf('<title>Sign In</title>') > -1) {
err = new Error("Not Logged In"); err = new Error("Not Logged In");
callback(err); callback(err);
this._notifySessionExpired(err); this._notifySessionExpired(err);

View File

@@ -13,6 +13,11 @@ var Async = require('async');
* With "prev" set, you will request 30 trades that were completed MORE RECENTLY 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) { SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
if (typeof options === 'function') { if (typeof options === 'function') {
callback = options; callback = options;

View File

@@ -1,6 +1,12 @@
var SteamCommunity = require('../index.js'); const SteamCommunity = require('../index.js');
var Cheerio = require('cheerio'); 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) { SteamCommunity.prototype.getMarketApps = function(callback) {
var self = this; var self = this;
this.httpRequest('https://steamcommunity.com/market/', function (err, response, body) { this.httpRequest('https://steamcommunity.com/market/', function (err, response, body) {
@@ -11,8 +17,8 @@ SteamCommunity.prototype.getMarketApps = function(callback) {
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
if ($('.market_search_game_button_group')) { if ($('.market_search_game_button_group')) {
apps = {}; let apps = {};
$('.market_search_game_button_group > a').each(function (i, element) { $('.market_search_game_button_group a.game_button').each(function (i, element) {
var e = Cheerio.load(element); var e = Cheerio.load(element);
var name = e('.game_button_game_name').text().trim(); var name = e('.game_button_game_name').text().trim();
var url = element.attribs.href; var url = element.attribs.href;
@@ -25,3 +31,357 @@ SteamCommunity.prototype.getMarketApps = function(callback) {
} }
}, "steamcommunity"); }, "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,7 +1,9 @@
var SteamCommunity = require('../index.js'); const Cheerio = require('cheerio');
var SteamID = require('steamid'); const FS = require('fs');
var Cheerio = require('cheerio'); const SteamID = require('steamid');
var fs = require('fs');
const Helpers = require('./helpers.js');
const SteamCommunity = require('../index.js');
SteamCommunity.PrivacyState = { SteamCommunity.PrivacyState = {
"Private": 1, "Private": 1,
@@ -10,9 +12,9 @@ SteamCommunity.PrivacyState = {
}; };
var CommentPrivacyState = { var CommentPrivacyState = {
"1": "commentselfonly", "1": 2, // private
"2": "commentfriendsonly", "2": 0, // friends only
"3": "commentanyone" "3": 1 // anyone
}; };
SteamCommunity.prototype.setupProfile = function(callback) { SteamCommunity.prototype.setupProfile = function(callback) {
@@ -21,7 +23,7 @@ SteamCommunity.prototype.setupProfile = function(callback) {
if(!callback) { if(!callback) {
return; return;
} }
if(err || response.statusCode != 200) { if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error("HTTP error " + response.statusCode));
} else { } else {
@@ -32,182 +34,221 @@ SteamCommunity.prototype.setupProfile = function(callback) {
SteamCommunity.prototype.editProfile = function(settings, callback) { SteamCommunity.prototype.editProfile = function(settings, callback) {
var self = this; var self = this;
this._myProfile("edit", null, function(err, response, body) { this._myProfile('edit/info', null, function(err, response, body) {
if(err || response.statusCode != 200) { if (err || response.statusCode != 200) {
if(callback) { if (callback) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error('HTTP error ' + response.statusCode));
} }
return; return;
} }
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
var form = $('#editForm'); var existingSettings = $('#profile_edit_config').data('profile-edit');
if(!form) { if (!existingSettings || !existingSettings.strPersonaName) {
if(callback) { if (callback) {
callback(new Error("Malformed response")); callback(new Error('Malformed response'));
} }
return; return;
} }
var values = {}; var values = {
form.serializeArray().forEach(function(item) { sessionID: self.getSessionID(),
values[item.name] = item.value; type: 'profileSave',
}); weblink_1_title: '',
weblink_1_url: '',
for(var i in settings) { 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)) { if(!settings.hasOwnProperty(i)) {
continue; continue;
} }
switch(i) { switch(i) {
case 'name': case 'name':
values.personaName = settings[i]; values.personaName = settings[i];
break; break;
case 'realName': case 'realName':
values.real_name = settings[i]; values.real_name = settings[i];
break; break;
case 'summary': case 'summary':
values.summary = settings[i]; values.summary = settings[i];
break; break;
case 'country': case 'country':
values.country = settings[i]; values.country = settings[i];
break; break;
case 'state': case 'state':
values.state = settings[i]; values.state = settings[i];
break; break;
case 'city': case 'city':
values.city = settings[i]; values.city = settings[i];
break; break;
case 'customURL': case 'customURL':
values.customURL = settings[i]; values.customURL = settings[i];
break; break;
// These don't work right now
/*
case 'background': case 'background':
// The assetid of our desired profile background // The assetid of our desired profile background
values.profile_background = settings[i]; values.profile_background = settings[i];
break; break;
case 'featuredBadge': case 'featuredBadge':
// Currently, game badges aren't supported // Currently, game badges aren't supported
values.favorite_badge_badgeid = settings[i]; values.favorite_badge_badgeid = settings[i];
break; break;
case 'primaryGroup': case 'primaryGroup':
if(typeof settings[i] === 'object' && settings[i].getSteamID64) { if(typeof settings[i] === 'object' && settings[i].getSteamID64) {
values.primary_group_steamid = settings[i].getSteamID64(); values.primary_group_steamid = settings[i].getSteamID64();
} else { } else {
values.primary_group_steamid = new SteamID(settings[i]).getSteamID64(); values.primary_group_steamid = new SteamID(settings[i]).getSteamID64();
} }
break; break;
*/
// TODO: profile showcases // TODO: profile showcases
} }
} }
self._myProfile("edit", values, function(err, response, body) { self._myProfile('edit', values, function(err, response, body) {
if (settings.customURL) { if (settings.customURL) {
delete self._profileURL; delete self._profileURL;
} }
if(err || response.statusCode != 200) { if (!callback) {
if(callback) {
callback(err || new Error("HTTP error " + response.statusCode));
}
return; return;
} }
// Check for an error if (err || response.statusCode != 200) {
var $ = Cheerio.load(body); callback(err || new Error('HTTP error ' + response.statusCode));
var error = $('#errorText .formRowFields'); return;
if(error) { }
error = error.text().trim();
if(error) { try {
if(callback) { var json = JSON.parse(body);
callback(new Error(error)); if (!json.success || json.success != 1) {
} callback(new Error(json.errmsg || 'Request was not successful'));
return; return;
} }
}
if(callback) {
callback(null); callback(null);
} catch (ex) {
callback(ex);
} }
}); });
}); });
}; };
SteamCommunity.prototype.profileSettings = function(settings, callback) { SteamCommunity.prototype.profileSettings = function(settings, callback) {
var self = this; this._myProfile('edit/settings', null, (err, response, body) => {
this._myProfile("edit/settings", null, function(err, response, body) { if (err || response.statusCode != 200) {
if(err || response.statusCode != 200) { if (callback) {
if(callback) { callback(err || new Error('HTTP error ' + response.statusCode));
callback(err || new Error("HTTP error " + response.statusCode));
} }
return; return;
} }
var $ = Cheerio.load(body); var $ = Cheerio.load(body);
var form = $('#editForm'); var existingSettings = $('#profile_edit_config').data('profile-edit');
if(!form) { if (!existingSettings || !existingSettings.Privacy) {
if(callback) { if (callback) {
callback(new Error("Malformed response")); callback(new Error('Malformed response'));
} }
return; return;
} }
var values = {}; // PrivacySettings => {PrivacyProfile, PrivacyInventory, PrivacyInventoryGifts, PrivacyOwnedGames, PrivacyPlaytime}
form.serializeArray().forEach(function(item) { // eCommentPermission
values[item.name] = item.value; var privacy = existingSettings.Privacy.PrivacySettings;
}); var commentPermission = existingSettings.Privacy.eCommentPermission;
for(var i in settings) { for (var i in settings) {
if(!settings.hasOwnProperty(i)) { if (!settings.hasOwnProperty(i)) {
continue; continue;
} }
switch(i) { switch (i) {
case 'profile': case 'profile':
values.privacySetting = settings[i]; privacy.PrivacyProfile = settings[i];
break; break;
case 'comments': case 'comments':
values.commentSetting = CommentPrivacyState[settings[i]]; commentPermission = CommentPrivacyState[settings[i]];
break; break;
case 'inventory': case 'inventory':
values.inventoryPrivacySetting = settings[i]; privacy.PrivacyInventory = settings[i];
break; break;
case 'inventoryGifts': case 'inventoryGifts':
values.inventoryGiftPrivacy = settings[i] ? 1 : 0; privacy.PrivacyInventoryGifts = settings[i] ? SteamCommunity.PrivacyState.Private : SteamCommunity.PrivacyState.Public;
break;
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; break;
} }
} }
self._myProfile("edit/settings", values, function(err, response, body) { this._myProfile({
if(err || response.statusCode != 200) { method: 'POST',
if(callback) { endpoint: 'ajaxsetprivacy/',
callback(err || new Error("HTTP error " + response.statusCode)); 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; return;
} }
if(callback) { if (body.success != 1) {
callback(null); if (callback) {
callback(new Error(body.success ? 'Error ' + body.success : 'Request was not successful'));
}
return;
}
if (callback) {
callback(null, body.Privacy);
} }
}); });
}); });
@@ -219,6 +260,12 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
format = null; format = null;
} }
// are we logged in?
if (!this.steamID) {
callback(new Error("Not Logged In"));
return;
}
var self = this; var self = this;
if(image instanceof Buffer) { if(image instanceof Buffer) {
@@ -250,7 +297,7 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
} }
} }
fs.readFile(image, function(err, file) { FS.readFile(image, function(err, file) {
if(err) { if(err) {
if(callback) { if(callback) {
callback(err); callback(err);
@@ -361,3 +408,68 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
}, "steamcommunity"); }, "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);
}
});
};

View File

@@ -1,7 +1,12 @@
var SteamCommunity = require('../index.js'); const Cheerio = require('cheerio');
var SteamID = require('steamid'); const Crypto = require('crypto');
var CEconItem = require('../classes/CEconItem.js'); const imageSize = require('image-size');
var Helpers = require('./helpers.js'); 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) { SteamCommunity.prototype.addFriend = function(userID, callback) {
if(typeof userID === 'string') { if(typeof userID === 'string') {
@@ -131,7 +136,7 @@ SteamCommunity.prototype.postUserComment = function(userID, message, callback) {
"uri": "https://steamcommunity.com/comment/Profile/post/" + userID.toString() + "/-1", "uri": "https://steamcommunity.com/comment/Profile/post/" + userID.toString() + "/-1",
"form": { "form": {
"comment": message, "comment": message,
"count": 6, "count": 1,
"sessionid": this.getSessionID() "sessionid": this.getSessionID()
}, },
"json": true "json": true
@@ -146,9 +151,109 @@ SteamCommunity.prototype.postUserComment = function(userID, message, callback) {
} }
if(body.success) { 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); callback(null);
} else if(body.error) { } else if(body.error) {
callback(new Error(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 { } else {
callback(new Error("Unknown error")); callback(new Error("Unknown error"));
} }
@@ -217,6 +322,45 @@ SteamCommunity.prototype.getUserAliases = function(userID, callback) {
}, "steamcommunity"); }, "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) { SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
if (typeof userID === 'string') { if (typeof userID === 'string') {
userID = new SteamID(userID); userID = new SteamID(userID);
@@ -241,7 +385,18 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/); var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
if (!match) { if (!match) {
callback(new Error(body.match(/inventory is currently private\./) ? "Private inventory" : "Malformed response")); 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; return;
} }
@@ -344,17 +499,22 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
* @param {function} callback * @param {function} callback
*/ */
SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, language, 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; var self = this;
if (typeof userID === 'string') { if (typeof userID === 'string') {
userID = new SteamID(userID); userID = new SteamID(userID);
} }
if (typeof language === 'function') {
callback = language;
language = "english";
}
var pos = 1; var pos = 1;
get([], []); get([], []);
@@ -378,7 +538,7 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
// We can never get private profile error for our own inventory! // We can never get private profile error for our own inventory!
self._notifySessionExpired(err); self._notifySessionExpired(err);
} }
callback(new Error("This profile is private.")); callback(new Error("This profile is private."));
return; return;
} }
@@ -412,13 +572,13 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
} else { } else {
callback(new Error("Malformed response")); callback(new Error("Malformed response"));
} }
return; return;
} }
for (var i = 0; i < body.assets.length; i++) { for (var i = 0; i < body.assets.length; i++) {
var description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid); var description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid);
if (!tradableOnly || (description && description.tradable)) { if (!tradableOnly || (description && description.tradable)) {
body.assets[i].pos = pos++; body.assets[i].pos = pos++;
(body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID)); (body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID));
@@ -446,7 +606,162 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
for (var i = 0; i < descriptions.length; i++) { for (var i = 0; i < descriptions.length; i++) {
quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i]; quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i];
} }
return quickDescriptionLookup[key]; 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');
};

View File

@@ -15,6 +15,10 @@ SteamCommunity.prototype.getWebApiKey = function(domain, callback) {
return callback(new Error("Access Denied")); 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>/); var match = body.match(/<p>Key: ([0-9A-F]+)<\/p>/);
if(match) { if(match) {
// We already have an API key registered // We already have an API key registered
@@ -40,26 +44,14 @@ SteamCommunity.prototype.getWebApiKey = function(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) { SteamCommunity.prototype.getWebApiOauthToken = function(callback) {
var self = this; if (this.oAuthToken) {
return callback(null, this.oAuthToken);
if( this.oAuthToken ) {
return callback( null, this.oAuthToken );
} }
// Pull an oauth token from the webchat UI callback(new Error('This operation requires an OAuth token, which can only be obtained from node-steamcommunity\'s `login` method.'));
this.httpRequest("https://steamcommunity.com/chat", function(err, response, body) {
if (err) {
callback(err);
return;
}
var match = body.match(/"([0-9a-f]{32})"/);
if (!match) {
callback(new Error("Malformed response"));
return;
}
callback(null, match[1]);
}, "steamcommunity");
}; };

View File

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

View File

@@ -1,6 +1,5 @@
var SteamCommunity = require('../index.js'); var SteamCommunity = require('../index.js');
var ReadLine = require('readline'); var ReadLine = require('readline');
var fs = require('fs');
var community = new SteamCommunity(); var community = new SteamCommunity();
var rl = ReadLine.createInterface({ var rl = ReadLine.createInterface({

View File

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

306
index.js
View File

@@ -1,23 +1,21 @@
require('@doctormckay/stats-reporter').setup(require('./package.json')); const hex2b64 = require('node-bignumber').hex2b64;
const Request = require('request');
const RSA = require('node-bignumber').Key;
const SteamID = require('steamid');
var Request = require('request'); const Helpers = require('./components/helpers.js');
var RSA = require('node-bignumber').Key;
var hex2b64 = require('node-bignumber').hex2b64;
var SteamID = require('steamid');
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"; 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); require('util').inherits(SteamCommunity, require('events').EventEmitter);
module.exports = SteamCommunity; module.exports = SteamCommunity;
SteamCommunity.SteamID = SteamID; SteamCommunity.SteamID = SteamID;
SteamCommunity.ConfirmationType = { SteamCommunity.ConfirmationType = require('./resources/EConfirmationType.js');
// 1 is unknown, possibly "Invalid" SteamCommunity.EResult = require('./resources/EResult.js');
"Trade": 2, SteamCommunity.EFriendRelationship = require('./resources/EFriendRelationship.js');
"MarketListing": 3
// 4 is opt-out or other like account confirmation?
};
function SteamCommunity(options) { function SteamCommunity(options) {
options = options || {}; options = options || {};
@@ -66,23 +64,30 @@ SteamCommunity.prototype.login = function(details, callback) {
var parts = details.steamguard.split('||'); var parts = details.steamguard.split('||');
this._setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), true); this._setCookie(Request.cookie('steamMachineAuth' + parts[0] + '=' + encodeURIComponent(parts[1])), true);
} }
var disableMobile = details.disableMobile;
var self = this; var self = this;
// Delete the cache // Delete the cache
delete self._profileURL; 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 // headers required to convince steam that we're logging in from a mobile device so that we can get the oAuth data
var mobileHeaders = { var mobileHeaders = {};
"X-Requested-With": "com.valvesoftware.android.steam.community", if (!disableMobile) {
"Referer": "https://steamcommunity.com/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client", mobileHeaders = {
"User-Agent": "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", "X-Requested-With": "com.valvesoftware.android.steam.community",
"Accept": "text/javascript, text/html, application/xml, text/xml, */*" "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._setCookie(Request.cookie("mobileClientVersion=0 (2.1.3)"));
this._setCookie(Request.cookie("mobileClient=android"));
this.httpRequestPost("https://steamcommunity.com/login/getrsakey/", { this.httpRequestPost("https://steamcommunity.com/login/getrsakey/", {
"form": {"username": details.accountName}, "form": {"username": details.accountName},
"headers": mobileHeaders, "headers": mobileHeaders,
@@ -95,33 +100,39 @@ SteamCommunity.prototype.login = function(details, callback) {
return; return;
} }
if(!body.publickey_mod || !body.publickey_exp) { if (!body.publickey_mod || !body.publickey_exp) {
deleteMobileCookies(); deleteMobileCookies();
callback(new Error("Invalid RSA key received")); callback(new Error("Invalid RSA key received"));
return; return;
} }
var key = new RSA(); var key = new RSA();
key.setPublic(body.publickey_mod, body.publickey_exp); key.setPublic(body.publickey_mod, body.publickey_exp);
var formObj = {
"captcha_text": details.captcha || "",
"captchagid": self._captchaGid,
"emailauth": details.authCode || "",
"emailsteamid": "",
"password": hex2b64(key.encrypt(details.password)),
"remember_login": "true",
"rsatimestamp": body.timestamp,
"twofactorcode": details.twoFactorCode || "",
"username": details.accountName,
"loginfriendlyname": "",
"donotcache": Date.now()
};
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({ self.httpRequestPost({
"uri": "https://steamcommunity.com/login/dologin/", "uri": "https://steamcommunity.com/login/dologin/",
"json": true, "json": true,
"form": { "form": formObj,
"captcha_text": details.captcha || "",
"captchagid": self._captchaGid,
"emailauth": details.authCode || "",
"emailsteamid": "",
"password": hex2b64(key.encrypt(details.password)),
"remember_login": "true",
"rsatimestamp": body.timestamp,
"twofactorcode": details.twoFactorCode || "",
"username": details.accountName,
"oauth_client_id": "DE45CD61",
"oauth_scope": "read_profile write_profile read_client write_client",
"loginfriendlyname": "#login_emailauth_friendlyname_mobile",
"donotcache": Date.now()
},
"headers": mobileHeaders "headers": mobileHeaders
}, function(err, response, body) { }, function(err, response, body) {
deleteMobileCookies(); deleteMobileCookies();
@@ -132,38 +143,51 @@ SteamCommunity.prototype.login = function(details, callback) {
} }
var error; var error;
if(!body.success && body.emailauth_needed) { if (!body.success && body.emailauth_needed) {
// Steam Guard (email) // Steam Guard (email)
error = new Error("SteamGuard"); error = new Error("SteamGuard");
error.emaildomain = body.emaildomain; error.emaildomain = body.emaildomain;
callback(error); callback(error);
} else if(!body.success && body.requires_twofactor) { } else if (!body.success && body.requires_twofactor) {
// Steam Guard (app) // Steam Guard (app)
callback(new Error("SteamGuardMobile")); callback(new Error("SteamGuardMobile"));
} else if(!body.success && body.captcha_needed && body.message.match(/Please verify your humanity/)) { } else if (!body.success && body.captcha_needed && body.message.match(/Please verify your humanity/)) {
error = new Error("CAPTCHA"); error = new Error("CAPTCHA");
error.captchaurl = "https://steamcommunity.com/login/rendercaptcha/?gid=" + body.captcha_gid; error.captchaurl = "https://steamcommunity.com/login/rendercaptcha/?gid=" + body.captcha_gid;
self._captchaGid = body.captcha_gid; self._captchaGid = body.captcha_gid;
callback(error); callback(error);
} else if(!body.success) { } else if (!body.success) {
callback(new Error(body.message || "Unknown error")); callback(new Error(body.message || "Unknown error"));
} else if(!body.oauth) { } else if (!disableMobile && !body.oauth) {
callback(new Error("Malformed response")); callback(new Error("Malformed response"));
} else { } else {
var sessionID = generateSessionID(); var sessionID = generateSessionID();
var oAuth = JSON.parse( body.oauth ); var oAuth;
self._setCookie(Request.cookie('sessionid=' + sessionID)); self._setCookie(Request.cookie('sessionid=' + sessionID));
self.steamID = new SteamID(oAuth.steamid);
self.oAuthToken = oAuth.oauth_token;
var cookies = self._jar.getCookieString("https://steamcommunity.com").split(';').map(function(cookie) { var cookies = self._jar.getCookieString("https://steamcommunity.com").split(';').map(function(cookie) {
return cookie.trim(); return cookie.trim();
}); });
if (!disableMobile){
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 // Find the Steam Guard cookie
var steamguard = null; var steamguard = null;
for(var i = 0; i < cookies.length; i++) { for(var i = 0; i < cookies.length; i++) {
@@ -175,8 +199,8 @@ SteamCommunity.prototype.login = function(details, callback) {
} }
self.setCookies(cookies); self.setCookies(cookies);
callback(null, sessionID, cookies, steamguard, oAuth.oauth_token); callback(null, sessionID, cookies, steamguard, disableMobile ? null : oAuth.oauth_token);
} }
}, "steamcommunity"); }, "steamcommunity");
}, "steamcommunity"); }, "steamcommunity");
@@ -226,6 +250,40 @@ SteamCommunity.prototype.oAuthLogin = function(steamguard, token, callback) {
}, "steamcommunity"); }, "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) { SteamCommunity.prototype._setCookie = function(cookie, secure) {
var protocol = secure ? "https" : "http"; var protocol = secure ? "https" : "http";
cookie.secure = !!secure; cookie.secure = !!secure;
@@ -236,26 +294,25 @@ SteamCommunity.prototype._setCookie = function(cookie, secure) {
}; };
SteamCommunity.prototype.setCookies = function(cookies) { SteamCommunity.prototype.setCookies = function(cookies) {
var self = this; cookies.forEach((cookie) => {
cookies.forEach(function(cookie) {
var cookieName = cookie.match(/(.+)=/)[1]; var cookieName = cookie.match(/(.+)=/)[1];
if(cookieName == 'steamLogin') { if (cookieName == 'steamLogin' || cookieName == 'steamLoginSecure') {
self.steamID = new SteamID(cookie.match(/=(\d+)/)[1]); this.steamID = new SteamID(cookie.match(/=(\d+)/)[1]);
} }
self._setCookie(Request.cookie(cookie), !!(cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/))); this._setCookie(Request.cookie(cookie), !!(cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/)));
}); });
}; };
SteamCommunity.prototype.getSessionID = function() { SteamCommunity.prototype.getSessionID = function(host = "http://steamcommunity.com") {
var cookies = this._jar.getCookieString("http://steamcommunity.com").split(';'); var cookies = this._jar.getCookieString(host).split(';');
for(var i = 0; i < cookies.length; i++) { for(var i = 0; i < cookies.length; i++) {
var match = cookies[i].trim().match(/([^=]+)=(.+)/); var match = cookies[i].trim().match(/([^=]+)=(.+)/);
if(match[1] == 'sessionid') { if(match[1] == 'sessionid') {
return decodeURIComponent(match[2]); return decodeURIComponent(match[2]);
} }
} }
var sessionID = generateSessionID(); var sessionID = generateSessionID();
this._setCookie(Request.cookie('sessionid=' + sessionID)); this._setCookie(Request.cookie('sessionid=' + sessionID));
return sessionID; return sessionID;
@@ -267,27 +324,29 @@ function generateSessionID() {
SteamCommunity.prototype.parentalUnlock = function(pin, callback) { SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
var self = this; var self = this;
var sessionID = self.getSessionID();
this.httpRequestPost("https://steamcommunity.com/parental/ajaxunlock", { this.httpRequestPost("https://steamcommunity.com/parental/ajaxunlock", {
"json": true, "json": true,
"form": { "form": {
"pin": pin "pin": pin,
"sessionid": sessionID
} }
}, function(err, response, body) { }, function(err, response, body) {
if(!callback) { if(!callback) {
return; return;
} }
if (err) { if (err) {
callback(err); callback(err);
return; return;
} }
if (!body || typeof body.success !== 'boolean') { if (!body || typeof body.success !== 'boolean') {
callback("Invalid response"); callback("Invalid response");
return; return;
} }
if (!body.success) { if (!body.success) {
switch (body.eresult) { switch (body.eresult) {
case 15: case 15:
@@ -304,7 +363,7 @@ SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
return; return;
} }
callback(); callback();
}.bind(this), "steamcommunity"); }.bind(this), "steamcommunity");
}; };
@@ -324,7 +383,7 @@ SteamCommunity.prototype.getNotifications = function(callback) {
callback(new Error("Malformed response")); callback(new Error("Malformed response"));
return; return;
} }
var notifications = { var notifications = {
"trades": body.notifications[1] || 0, "trades": body.notifications[1] || 0,
"gameTurns": body.notifications[2] || 0, "gameTurns": body.notifications[2] || 0,
@@ -364,16 +423,76 @@ SteamCommunity.prototype.loggedIn = function(callback) {
callback(err || new Error("HTTP error " + response.statusCode)); callback(err || new Error("HTTP error " + response.statusCode));
return; return;
} }
if(response.statusCode == 403) { if(response.statusCode == 403) {
callback(null, true, true); callback(null, true, true);
return; return;
} }
callback(null, !!response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/), false); callback(null, !!response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/), false);
}, "steamcommunity"); }, "steamcommunity");
}; };
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) { SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
var self = this; var self = this;
@@ -395,27 +514,59 @@ SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
self._profileURL = match[1]; self._profileURL = match[1];
setTimeout(function () { setTimeout(function () {
delete self._profileURL; // delete the cache delete self._profileURL; // delete the cache
}, 60000); }, 60000).unref();
completeRequest(match[1]); completeRequest(match[1]);
}, "steamcommunity"); }, "steamcommunity");
} }
function completeRequest(url) { function completeRequest(url) {
var options = { var options = endpoint.endpoint ? endpoint : {};
"uri": "https://steamcommunity.com" + url + "/" + endpoint, options.uri = "https://steamcommunity.com" + url + "/" + (endpoint.endpoint || endpoint);
"method": "GET"
};
if (form) { if (form) {
options.method = "POST"; options.method = "POST";
options.form = form; options.form = form;
options.followAllRedirects = true;
} else if (!options.method) {
options.method = "GET";
} }
self.httpRequest(options, callback, "steamcommunity"); 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;
}
if (body.success != 1) {
callback(Helpers.eresultError(body.success));
return;
}
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);
});
};
require('./components/http.js'); require('./components/http.js');
require('./components/chat.js'); require('./components/chat.js');
require('./components/profile.js'); require('./components/profile.js');
@@ -426,6 +577,7 @@ require('./components/inventoryhistory.js');
require('./components/webapi.js'); require('./components/webapi.js');
require('./components/twofactor.js'); require('./components/twofactor.js');
require('./components/confirmations.js'); require('./components/confirmations.js');
require('./components/help.js');
require('./classes/CMarketItem.js'); require('./classes/CMarketItem.js');
require('./classes/CMarketSearchResult.js'); require('./classes/CMarketSearchResult.js');
require('./classes/CSteamGroup.js'); require('./classes/CSteamGroup.js');
@@ -434,4 +586,4 @@ require('./classes/CSteamUser.js');
/** /**
@callback SteamCommunity~genericErrorCallback @callback SteamCommunity~genericErrorCallback
@param {Error|null} err - An Error object on failure, or null on success @param {Error|null} err - An Error object on failure, or null on success
*/ */

View File

@@ -1,6 +1,6 @@
{ {
"name": "steamcommunity", "name": "steamcommunity",
"version": "3.31.0", "version": "3.44.2",
"description": "Provides an interface for logging into and interacting with the Steam Community website", "description": "Provides an interface for logging into and interacting with the Steam Community website",
"keywords": [ "keywords": [
"steam", "steam",
@@ -11,19 +11,24 @@
"url": "https://github.com/DoctorMcKay/node-steamcommunity/issues" "url": "https://github.com/DoctorMcKay/node-steamcommunity/issues"
}, },
"license": "MIT", "license": "MIT",
"author": {
"name": "Alex Corn",
"email": "mckay@doctormckay.com",
"url": "https://www.doctormckay.com"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git" "url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
}, },
"dependencies": { "dependencies": {
"@doctormckay/stats-reporter": "^1.0.2", "async": "^2.6.3",
"request": "^2.81.0",
"node-bignumber": "^1.2.1",
"steamid": "^1.0.0",
"xml2js": "^0.4.11",
"cheerio": "0.22.0", "cheerio": "0.22.0",
"async": "^2.1.4", "image-size": "^0.8.2",
"steam-totp": "^1.3.0" "node-bignumber": "^1.2.1",
"request": "^2.88.0",
"steam-totp": "^1.5.0",
"steamid": "^1.1.3",
"xml2js": "^0.4.22"
}, },
"engines": { "engines": {
"node": ">=4.0.0" "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",
};