Compare commits

...

376 Commits

Author SHA1 Message Date
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
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
4e90811008 Merge pull request #213 from Royalgamer06/patch-2
Package 'fs' never used
2018-11-19 14:54:33 -05:00
Royalgamer06
018d3de5a2 Package 'fs' never used 2018-11-19 20:48:57 +01:00
Alex Corn
3949abe09f 3.38.0 2018-11-01 01:20:31 -04:00
Alex Corn
62394b9ffe Added ability to change friends list privacy 2018-11-01 01:20:19 -04:00
Alex Corn
ebac6c97ad Added IDE config to repo 2018-10-31 03:47:55 -04:00
Hongarc
a0ad33d5eb Add comment group 2018-10-20 13:29:52 +07:00
Alex Corn
b7a3e51bfc Merge pull request #205 from waylaidwanderer/master
Add the ability to set custom mobile user-agent
2018-07-11 02:31:04 -04:00
Joel
bf13224cbf Add the ability to set custom mobile user-agent 2018-07-10 23:30:12 -07:00
Alex Corn
117374a4c5 Updated Chrome user-agent 2018-07-11 02:22:39 -04:00
Alex Corn
aa84505132 3.37.1 2018-07-11 02:12:45 -04:00
Alex Corn
bb97565f2a Fixed steamID not being properly set 2018-07-11 02:12:37 -04:00
Alex Corn
7462e4b767 3.37.0 2018-07-11 00:00:16 -04:00
Alex Corn
adbf1d47a7 Added postProfileStatus and deleteProfileStatus (closes #200) 2018-07-10 23:59:57 -04:00
Alex Corn
8c4fde720a Updated EResult 2018-07-10 23:53:05 -04:00
Alex Corn
458d7b6db1 Renamed getProfileBackground to getUserProfileBackground and added it to CSteamUser 2018-07-10 23:28:26 -04:00
Alex Corn
411d74036f Updated getProfileBackground output a bit 2018-07-10 23:24:41 -04:00
Mert
1b3117b8dc Add getProfileBackground method
Adds ability to get URL of Steamcommunity profile background.
2018-07-10 23:18:03 -04:00
Alex Corn
6eec88d196 3.36.2 2018-05-18 17:32:48 -04:00
Alex Corn
3921ce17d0 Follow redirects when posting to own profile (fixes #178) 2018-05-18 17:32:33 -04:00
Alex Corn
4441d6f6e0 Bumped required request version 2018-05-18 17:29:14 -04:00
Alex Corn
870de6817e 3.36.1 2018-04-15 10:43:14 -04:00
Alex Corn
d5dbb230df Removed extra import 2018-04-15 10:43:05 -04:00
Alex Corn
5e9c06bdc4 3.36.0 2018-04-11 00:27:57 -04:00
Alex Corn
8d163d0d85 Added privacy controls for gameDetails and playtime 2018-04-11 00:27:10 -04:00
Alex Corn
051d376508 3.35.2 2018-04-10 01:08:14 -04:00
Alex Corn
913dff68ef Fixed crashes if somehow market items are really broken (fixes #197) 2018-04-10 01:06:52 -04:00
Alex Corn
754847ee88 Make CMarketItem not broken for non-CSGO apps (fixes #196) 2018-04-10 01:01:03 -04:00
Alex Corn
645e03b89c 3.35.1 2018-04-01 23:36:04 -04:00
Alex Corn
aa302bc09e Fixed various profile url requests 2018-04-01 23:35:56 -04:00
Alex Corn
1c0f8d4bca 3.35.0 2018-04-01 03:39:00 -04:00
Alex Corn
a7bd694f4b Added inventory gift redemption 2018-04-01 03:38:45 -04:00
Alex Corn
9e380e71f5 jsdoc 2018-04-01 03:31:07 -04:00
Alex Corn
e97e63fe6d Added ability to get gem value for an item and to turn items into gems 2018-04-01 03:22:00 -04:00
Alex Corn
f6c7c74ab6 Fixed Node.js process being kept running unnecessarily 2018-04-01 03:15:13 -04:00
Alex Corn
8846082f60 Fixed getMarketApps not returning all apps 2018-04-01 02:51:46 -04:00
Alexander Corn
eb609b5c42 Added some more explicit error handling if we try to do things while logged out 2018-02-27 01:40:59 -05:00
Alex Corn
238a36de0f v3.34.1 2018-01-22 02:02:56 -05:00
Alex Corn
c38111b61a Merge pull request #189 from Aareksio/patch-1
Fix infinite loop in getWebApiKey
2018-01-22 01:48:17 -05:00
Arkadiusz Sygulski
0eb27a15d2 Fix infinite loop in getWebApiKey
Fix infinite loop in getWebApiKey when trying to get web api key on account without email address confirmed
2018-01-22 03:48:45 +01:00
Alexander Corn
0ab3a94493 Use writeFileSync instead of writeFile without callback 2018-01-16 14:40:07 -05:00
Alexander Corn
7c2ec65b8c Add .editorconfig 2017-12-30 02:25:07 -05:00
Alexander Corn
8e761ffe6c 3.34.0 2017-12-28 00:34:23 -05:00
Alexander Corn
06f04887fd Updated CONTRIBUTING.md 2017-12-28 00:34:15 -05:00
Alexander Corn
e5f986a8bf Added author info 2017-12-28 00:33:01 -05:00
Alexander Corn
5da6e40ebb Fixed some code style issues 2017-12-28 00:32:06 -05:00
Alex Corn
0b8dd57a40 Merge pull request #186 from hell00world/master
Added changeTradeURL method
2017-12-28 00:21:00 -05:00
hell00world
6f8456882a Added changeTradeURL method 2017-12-28 00:19:57 +01:00
Alex Corn
0b57b47ecb Minor tweaks 2017-12-26 16:39:23 -05:00
Alex Corn
b5f50a6614 Style update 2017-12-26 16:34:19 -05:00
Alex Corn
4257140c15 Merge remote-tracking branch 'origin/master' 2017-12-26 16:34:02 -05:00
Alex Corn
724badd175 Merge pull request #163 from potato20/disable-mobile-flag
Flag to disable mobile login
2017-12-26 16:33:56 -05:00
Alex Corn
a9455b0ff6 Style updates, use HTTPS, coerce steamids to SteamId objects 2017-12-26 16:32:21 -05:00
Alex Corn
b3dfdd47d8 Fixed missing brace 2017-12-26 16:32:04 -05:00
Alex Corn
1efac6bc70 Merge pull request #159 from Jarzon/groupcomments
Steam group comments
2017-12-26 16:28:16 -05:00
Alex Corn
eacb85c711 3.33.2 2017-11-30 08:59:58 -05:00
Alex Corn
8eab34bf18 Fixed errors not being handled in getTradeURL 2017-11-30 08:59:49 -05:00
Alexander Corn
b45e8d97ed 3.33.1 2017-10-21 16:52:11 -04:00
Alex Corn
6abb14a9bc Merge pull request #183 from borianm/patch-1
Fix for issue #182
2017-10-21 16:48:23 -04:00
Alexander Corn
6049d7a259 Added deprecation notice to getInventoryHistory 2017-10-20 19:28:26 -04:00
AirboN
e0320b27d7 Fixed issue with undefined author in events 2017-10-17 00:48:06 +02:00
AirboN
7986a87b3f Fixed issue with undefined author in events 2017-10-17 00:42:26 +02:00
Alexander Corn
cf7e5f8995 3.33.0 2017-10-06 12:01:10 -04:00
Alexander Corn
a03dad9507 Added getTradeURL method 2017-10-02 14:55:29 -04:00
Alexander Corn
7da7ecfc56 3.32.3 2017-09-29 00:13:17 -04:00
Alex Corn
6bf3f26f2f Merge pull request #175 from Visagalis/patch-1
Typo/bug in GetGroupHistory code.
2017-09-29 00:13:00 -04:00
Jason Vaillancourt
dc2e5d09a7 Merge branch 'master' into groupcomments 2017-08-20 20:13:41 -04:00
Alexander Corn
d716e8a494 3.32.2 2017-08-17 13:12:44 -04:00
Alexander Corn
458a179852 Fixed reference to undefined self 2017-08-17 13:12:36 -04:00
Alexander Corn
f5d76527ba 3.32.1 2017-08-15 21:43:14 -04:00
Alexander Corn
5875f47ebc Ignore chat friendstate responses if we no longer care 2017-08-15 21:43:10 -04:00
Julius
d7095aa7c5 Typo/bug in GetGroupHistory code.
Typo which makes group history objects to contain incorrect actor (it contains same actor as user).
2017-08-08 21:52:12 +03:00
Alexander Corn
03e0a1bdac 3.32.0 2017-08-06 19:18:43 -04:00
Alexander Corn
a51973c9b9 Added CSteamGroup methods for group-join requests 2017-08-06 19:11:21 -04:00
Alexander Corn
96d87ee8e6 Added methods to deal with Steam group join requests 2017-08-06 19:08:45 -04:00
Alexander Corn
7beef548dc Merge pull request #174 from Unsigno/master
Updated getUserInventoryContexts
2017-07-11 12:32:27 -04:00
Unsigno
6d9ebabb17 Updated getUserInventoryContexts to return 0 inventories and handle private profile error 2017-07-11 14:02:01 +02:00
Alexander Corn
5d5d8548af 3.31.0 2017-06-15 10:06:54 -04:00
Alexander Corn
b5142ad0cc Added language argument to getUserInventoryContents 2017-06-15 10:04:37 -04:00
Alexander Corn
3045a5d0cc Added stats-reporter integration 2017-06-15 10:03:14 -04:00
Alexander Corn
2dcb123761 Update request dependency version
(make david-dm happy)
2017-06-11 13:26:52 -04:00
Alexander Corn
bd29d598b1 Throw Error if using confirmations without being logged in 2017-06-02 23:35:23 -04:00
Alexander Corn
7e39715427 Merge pull request #168 from morethanwords/patch-1
Update getUserInventoryContents
2017-04-21 14:23:25 -04:00
Eduard Kuzmenko
a97edfd492 Update users.js 2017-04-21 15:46:20 +03:00
Eduard Kuzmenko
68b1f21655 Update getUserInventoryContents
There will be a re-loop if description of the item not the last one in descriptions (and quickDescriptionLookup will rewrite everytime)
2017-04-21 15:26:45 +03:00
Alexander Corn
b4772baf39 3.30.7 2017-03-27 14:42:46 -04:00
Alexander Corn
47dc22ca0d Handle cases where inventories are private in getUserInventoryContexts 2017-03-27 14:39:41 -04:00
Jarzon
3bba7025d4 Changes for code consistency 2017-03-15 13:08:22 -04:00
Jarzon
e2f94cb1e7 Convert the number to a string 2017-03-15 12:55:48 -04:00
Alexander Corn
acbd71666b Merge pull request #165 from Aareksio/patch-1
Check if instance is logged in before comparing steamids (fixes #164)
2017-03-06 11:35:23 -05:00
Arkadiusz Sygulski
3c35026a32 Check if instance is logged in before comparing steamids (fixes #164) 2017-03-06 16:02:20 +01:00
Alexander Corn
aca375d9da Give different error message if family view unlock failed for rate-limit 2017-03-04 22:30:25 -05:00
Jarzon
91285ba1c7 Merge branch 'master' into groupcomments 2017-03-04 09:04:24 -05:00
Alexander Corn
758b749487 3.30.6 2017-02-25 19:12:42 -05:00
Alexander Corn
a9b16cef1e Fixed crash no non-string 403 responses
https://dev.doctormckay.com/topic/845-responsebodymatch-is-not-a-function/
2017-02-25 19:12:34 -05:00
Potato 2.0
c6ab392c37 Add disableMobile flag to login() so user has option to not pretend we're logging in from a phone 2017-02-18 14:13:15 -05:00
Alexander Corn
4542793175 3.30.5 2017-02-12 02:22:43 -05:00
Alexander Corn
d18a0244ec Updated default user-agent to latest Chrome on Win10 x64 2017-02-12 02:22:17 -05:00
Alexander Corn
b1c772d9b9 Keep HTTP connections open between requests 2017-02-11 22:28:22 -05:00
Jarzon
530bbcb9eb Pass a Error obj to the callback 2017-02-08 15:22:50 -05:00
Jarzon
f7ebe68bd8 Adding .deleteComment() 2017-02-08 15:08:34 -05:00
Jarzon
e2dae00af2 Simplifying the request's options 2017-02-07 18:34:12 -05:00
Jarzon
bbd80d4154 Fixing not getting the html in the comment 2017-02-02 07:53:18 -05:00
Jarzon
c180557f52 Using decodeSteamTime() to parse date
Adding to .decodeSteamTime() the ability to parse steam relative time
2017-02-01 13:30:38 -05:00
Jarzon
b3174b9f0b Revert using chrono to parse the date 2017-02-01 12:03:34 -05:00
Jarzon
12f1ddf6a0 Missing header Content-Type 2017-02-01 11:27:58 -05:00
Jarzon
db3aa4ace8 Catch the community id and the custom url of the author 2017-01-31 08:48:38 -05:00
Jarzon
f1b742dff4 Adding Chrono to parse relative date 2017-01-31 06:26:11 -05:00
Jarzon
444649ee8c Adding .getAllComments() 2017-01-31 06:16:38 -05:00
Alexander Corn
b9ec1ccfb5 3.30.4 2017-01-18 15:51:44 -05:00
Alexander Corn
0c7c030f78 Fixed string in callback instead of Error (fixes #153) 2017-01-18 15:50:11 -05:00
Alexander Corn
e5f1f14aad Fixed empty inventory resulting in "Malformed response" (fixes #156)
Display Steam error message when available instead of "HTTP error 500"
2017-01-18 15:49:27 -05:00
Alexander Corn
0f72ea7d93 3.30.3 2017-01-04 00:10:40 -05:00
Alexander Corn
d1906a2c83 Fallback to the default "?" avatar if we don't have a hash (fixes #151) 2017-01-04 00:10:11 -05:00
Alexander Corn
3964af7ade Throw a descriptive error if accountName or password is missing in login
Closes #119
2017-01-04 00:07:33 -05:00
Alexander Corn
5a5c5373aa Bump dependency versions to make david happy 2017-01-04 00:03:18 -05:00
Alexander Corn
ec60e1b73e 3.30.2 2016-12-30 17:11:12 -05:00
Alexander Corn
a22a75d4e1 Merge pull request #150 from Vankxr/master
Check if requested inventory is ours.
2016-12-30 17:06:21 -05:00
Vankxr
313753bb05 Check if requested inventory is ours.
Should fix https://github.com/DoctorMcKay/node-steamcommunity/issues/149
2016-12-30 21:24:53 +00:00
Alexander Corn
459fe4d8b1 3.30.1 2016-12-15 00:53:46 -05:00
Alexander Corn
489c0441a5 Restore market_fee_app property to CEconItem for Steam Community items 2016-12-15 00:53:26 -05:00
Alexander Corn
d91e74d02a 3.30.0 2016-12-13 01:53:57 -05:00
Alexander Corn
d120d09970 Fixed tag data to be identical to old endpoint 2016-12-13 01:53:30 -05:00
Alexander Corn
84ce979a78 Fixed new inventory loading not working properly 2016-12-13 01:52:52 -05:00
Alexander Corn
88b0495386 Renamed getUserInventory, added back old one and marked it as deprecated 2016-12-13 01:32:17 -05:00
Alexander Corn
d367e8eae4 Added better paging controls for new inventory endpoint 2016-12-13 01:29:06 -05:00
Alexander Corn
9a3cf927c0 Fixed some more currency detection 2016-12-13 01:28:48 -05:00
Alexander Corn
56cdfff672 3.29.1 2016-12-13 01:07:12 -05:00
Alexander Corn
35e6dd16e7 Re-add "pos" property to inventory items for backwards compatibility 2016-12-13 01:07:00 -05:00
Alexander Corn
d0af8584c1 Send referer when fetching inventories 2016-12-13 00:50:29 -05:00
Alexander Corn
f1afcd1962 3.29.0 2016-12-13 00:46:23 -05:00
Alexander Corn
513b0bff1c Added some optimizations and backwards-compatibility for inv fetching 2016-12-13 00:46:02 -05:00
Alexander Corn
ab037b1931 Pass response body back to caller for inventory fetching 2016-12-13 00:45:38 -05:00
Alexander Corn
06df0ecd42 Handle currencies with new endpoint's data in CEconItem 2016-12-13 00:45:23 -05:00
João Silva
b789e13a70 Update inventory loading endpoint (#148)
* Update inventory loading endpoint

Should fix 429s :)

* Fix missing contextID parameter at CEconItem class

* Revert "Fix missing contextID parameter at CEconItem class"

This reverts commit 416321105b.

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

This reverts commit 49ecd46954.

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

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

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

52
.gitignore vendored
View File

@@ -1,3 +1,53 @@
node_modules/*
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>

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

@@ -0,0 +1,18 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<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>

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

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
</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>

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

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<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>

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

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_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" />
</component>
</module>

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

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

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

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

1
.npmrc Normal file
View File

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

36
CONTRIBUTING.md Normal file
View File

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

View File

@@ -1,6 +1,6 @@
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
of this software and associated documentation files (the "Software"), to deal

View File

@@ -9,12 +9,17 @@ This module provides an easy interface for the Steam Community website. This mod
It supports Steam Guard and CAPTCHAs.
# Installation
This reports anonymous usage statistics to the author. [See here](https://github.com/DoctorMcKay/node-stats-reporter) for more information.
Install it from npm:
$ npm install steamcommunity
**Have a question about the module or coding in general? *Do not create a GitHub issue.* GitHub issues are for feature
requests and bug reports. Instead, post in the [dedicated forum](https://dev.doctormckay.com/forum/8-node-steamcommunity/).
Such issues may be ignored!**
# Documentation
As of version 1.0.0, documentation is on the [GitHub wiki](https://github.com/DoctorMcKay/node-steamcommunity/wiki).
Documentation is available on the [GitHub wiki](https://github.com/DoctorMcKay/node-steamcommunity/wiki).
# Support
Report bugs on the [issue tracker](https://github.com/DoctorMcKay/node-steamcommunity/issues) or ask questions
on the [dedicated forum](https://dev.doctormckay.com/forum/8-node-steamcommunity/).

35
classes/CConfirmation.js Normal file
View File

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

100
classes/CEconItem.js Normal file
View File

@@ -0,0 +1,100 @@
module.exports = CEconItem;
function CEconItem(item, description, contextID) {
var thing;
for (thing in item) {
if (item.hasOwnProperty(thing)) {
this[thing] = item[thing];
}
}
var isCurrency = !!(this.is_currency || this.currency) || typeof this.currencyid !== 'undefined'; // I don't want to put this on the object yet; it's nice to have the ids at the top of printed output
if (isCurrency) {
this.currencyid = this.id = (this.id || this.currencyid);
} else {
this.assetid = this.id = (this.id || this.assetid);
}
this.instanceid = this.instanceid || '0';
this.amount = parseInt(this.amount, 10);
this.contextid = this.contextid || contextID.toString();
// Merge the description
if (description) {
// Is this a listing of descriptions?
if (description[this.classid + '_' + this.instanceid]) {
description = description[this.classid + '_' + this.instanceid];
}
for (thing in description) {
if (description.hasOwnProperty(thing)) {
this[thing] = description[thing];
}
}
}
this.is_currency = isCurrency;
this.tradable = !!this.tradable;
this.marketable = !!this.marketable;
this.commodity = !!this.commodity;
this.market_tradable_restriction = (this.market_tradable_restriction ? parseInt(this.market_tradable_restriction, 10) : 0);
this.market_marketable_restriction = (this.market_marketable_restriction ? parseInt(this.market_marketable_restriction, 10) : 0);
this.fraudwarnings = this.fraudwarnings || [];
this.descriptions = this.descriptions || [];
if (this.owner && JSON.stringify(this.owner) == '{}') {
this.owner = null;
}
// Restore old property names of tags
if (this.tags) {
this.tags = this.tags.map(function(tag) {
return {
"internal_name": tag.internal_name,
"name": tag.localized_tag_name || tag.name,
"category": tag.category,
"color": tag.color || "",
"category_name": tag.localized_category_name || tag.category_name
};
});
}
// Restore market_fee_app, if applicable
var match;
if (this.appid == 753 && this.contextid == 6 && this.market_hash_name && (match = this.market_hash_name.match(/^(\d+)\-/))) {
this.market_fee_app = parseInt(match[1], 10);
}
if (this.actions === "") {
this.actions = [];
}
delete this.currency;
}
CEconItem.prototype.getImageURL = function() {
return "https://steamcommunity-a.akamaihd.net/economy/image/" + this.icon_url + "/";
};
CEconItem.prototype.getLargeImageURL = function() {
if(!this.icon_url_large) {
return this.getImageURL();
}
return "https://steamcommunity-a.akamaihd.net/economy/image/" + this.icon_url_large + "/";
};
CEconItem.prototype.getTag = function(category) {
if (!this.tags) {
return null;
}
for (var i = 0; i < this.tags.length; i++) {
if (this.tags[i].category == category) {
return this.tags[i];
}
}
return null;
};

View File

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

View File

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

View File

@@ -1,40 +1,38 @@
var SteamCommunity = require('../index.js');
var Helpers = require('../components/helpers.js');
var SteamID = require('steamid');
var xml2js = require('xml2js');
SteamCommunity.prototype.getSteamGroup = function(id, callback) {
if(typeof id !== 'string' && !(typeof id === 'object' && id.__proto__ === SteamID.prototype)) {
if(typeof id !== 'string' && !Helpers.isSteamID(id)) {
throw new Error("id parameter should be a group URL string or a SteamID object");
}
if(typeof id === 'object' && (id.universe != SteamID.Universe.PUBLIC || id.type != SteamID.Type.CLAN)) {
throw new Error("SteamID must stand for a clan account in the public universe");
}
var self = this;
this.request("https://steamcommunity.com/" + (typeof id === 'string' ? "groups/" + id : "gid/" + id.toString()) + "/memberslistxml/?xml=1", function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
this.httpRequest("https://steamcommunity.com/" + (typeof id === 'string' ? "groups/" + id : "gid/" + id.toString()) + "/memberslistxml/?xml=1", function(err, response, body) {
if (err) {
callback(err);
return;
}
if(self._checkCommunityError(body, callback)) {
return;
}
xml2js.parseString(body, function(err, result) {
if(err) {
callback(err);
return;
}
callback(null, new CSteamGroup(self, result.memberList));
});
});
}, "steamcommunity");
};
function CSteamGroup(community, groupData) {
this._community = community;
this.steamID = new SteamID(groupData.groupID64[0]);
this.name = groupData.groupDetails[0].groupName[0];
this.url = groupData.groupDetails[0].groupURL[0];
@@ -50,7 +48,7 @@ function CSteamGroup(community, groupData) {
CSteamGroup.prototype.getAvatarURL = function(size, protocol) {
size = size || '';
protocol = protocol || 'http://';
var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + this.avatarHash.substring(0, 2) + "/" + this.avatarHash;
if(size == 'full' || size == 'medium') {
return url + "_" + size + ".jpg";
@@ -76,14 +74,34 @@ CSteamGroup.prototype.leave = function(callback) {
this._community.leaveGroup(this.steamID, callback);
};
CSteamGroup.prototype.getAllAnnouncements = function(time, callback) {
this._community.getAllGroupAnnouncements(this.steamID, time, callback);
};
CSteamGroup.prototype.postAnnouncement = function(headline, content, callback) {
this._community.postGroupAnnouncement(this.steamID, headline, content, callback);
};
CSteamGroup.prototype.editAnnouncement = function(annoucementID, headline, content, callback) {
this._community.editGroupAnnouncement(this.steamID, annoucementID, headline, content, callback)
};
CSteamGroup.prototype.deleteAnnouncement = function(annoucementID, callback) {
this._community.deleteGroupAnnouncement(this.steamID, annoucementID, callback)
};
CSteamGroup.prototype.scheduleEvent = function(name, type, description, time, server, callback) {
this._community.scheduleGroupEvent(this.steamID, name, type, description, time, server, callback);
};
CSteamGroup.prototype.editEvent = function(id, name, type, description, time, server, callback) {
this._community.editGroupEvent(this.steamID, id, name, type, description, time, server, callback);
};
CSteamGroup.prototype.deleteEvent = function (id, callback) {
this._community.deleteGroupEvent(this.steamID, id, callback);
};
CSteamGroup.prototype.setPlayerOfTheWeek = function(steamID, callback) {
this._community.setGroupPlayerOfTheWeek(this.steamID, steamID, callback);
};
@@ -95,3 +113,43 @@ CSteamGroup.prototype.kick = function(steamID, callback) {
CSteamGroup.prototype.getHistory = function(page, callback) {
this._community.getGroupHistory(this.steamID, page, callback);
};
CSteamGroup.prototype.getAllComments = function(from, count, callback) {
this._community.getAllGroupComments(this.steamID, from, count, callback);
};
CSteamGroup.prototype.deleteComment = function(cid, callback) {
this._community.deleteGroupComment(this.steamID, cid, callback);
};
CSteamGroup.prototype.comment = function(message, callback) {
this._community.postGroupComment(this.steamID, message, callback);
};
/**
* Get requests to join this restricted group.
* @param {function} callback - First argument is null/Error, second is array of SteamID objects
*/
CSteamGroup.prototype.getJoinRequests = function(callback) {
this._community.getGroupJoinRequests(this.steamID, callback);
};
/**
* Respond to one or more join requests to this restricted group.
* @param {SteamID|string|SteamID[]|string[]} steamIDs - The SteamIDs of the users you want to approve or deny membership for (or a single value)
* @param {boolean} approve - True to put them in the group, false to deny their membership
* @param {function} callback - Takes only an Error object/null as the first argument
*/
CSteamGroup.prototype.respondToJoinRequests = function(steamIDs, approve, callback) {
this._community.respondToGroupJoinRequests(this.steamID, steamIDs, approve, callback);
};
/**
* Respond to *ALL* pending group-join requests for this group.
* @param {boolean} approve - True to allow everyone who requested into the group, false to not
* @param {function} callback - Takes only an Error object/null as the first argument
*/
CSteamGroup.prototype.respondToAllJoinRequests = function(approve, callback) {
this._community.respondToAllGroupJoinRequests(this.steamID, approve, callback);
};

View File

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

View File

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

425
components/confirmations.js Normal file
View File

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

View File

@@ -2,6 +2,8 @@ var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
var xml2js = require('xml2js');
var Cheerio = require('cheerio');
var Helpers = require('./helpers.js');
var EResult = SteamCommunity.EResult;
SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link, addresses, addressIdx) {
members = members || [];
@@ -38,8 +40,9 @@ SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link
}
var self = this;
this.request(options, function(err, response, body) {
if (self._checkHttpError(err, response, callback)) {
this.httpRequest(options, function(err, response, body) {
if (err) {
callback(err);
return;
}
@@ -60,7 +63,7 @@ SteamCommunity.prototype.getGroupMembers = function(gid, callback, members, link
callback(null, members);
}
});
});
}, "steamcommunity");
};
SteamCommunity.prototype.getGroupMembersEx = function(gid, addresses, callback) {
@@ -73,7 +76,7 @@ SteamCommunity.prototype.joinGroup = function(gid, callback) {
}
var self = this;
this.request.post({
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64(),
"form": {
"action": "join",
@@ -84,17 +87,8 @@ SteamCommunity.prototype.joinGroup = function(gid, callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(self._checkCommunityError(body, callback)) {
return;
}
callback(null);
});
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.leaveGroup = function(gid, callback) {
@@ -112,49 +106,129 @@ SteamCommunity.prototype.leaveGroup = function(gid, callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(self._checkCommunityError(body, callback)) {
return;
}
callback(null);
callback(err || null);
});
};
SteamCommunity.prototype.getAllGroupAnnouncements = function(gid, time, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
if(typeof time === 'function') {
callback = time;
time = new Date(0); // The beginnig of time...
}
var self = this;
this.httpRequest({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/rss/"
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
xml2js.parseString(body, function(err, results) {
if(err) {
return callback(err);
}
if(!results.rss.channel[0].item) {
return callback(null, []);
}
var announcements = results.rss.channel[0].item.map(function(announcement) {
var splitLink = announcement.link[0].split('/');
return {
headline: announcement.title[0],
content: announcement.description[0],
date: new Date(announcement.pubDate[0]),
author: (typeof announcement.author === 'undefined') ? null : announcement.author[0],
aid: splitLink[splitLink.length - 1]
}
}).filter(function(announcement) {
return (announcement.date > time);
});
return callback(null, announcements);
});
}, "steamcommunity");
};
SteamCommunity.prototype.postGroupAnnouncement = function(gid, headline, content, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
var self = this;
this.request.post({
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements",
"form": {
"sessionID": this.getSessionID(),
"action": "post",
"headline": headline,
"body": content
"body": content,
"languages[0][headline]": headline,
"languages[0][body]": content
}
}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.editGroupAnnouncement = function(gid, aid, headline, content, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
var self = this;
var submitData = {
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements",
"form": {
"sessionID": this.getSessionID(),
"gid": aid,
"action": "update",
"headline": headline,
"body": content,
"languages[0][headline]": headline,
"languages[0][body]": content,
"languages[0][updated]": 1
}
};
this.httpRequestPost(submitData, function(err, response, body) {
if(!callback) {
return;
}
if(self._checkCommunityError(body, callback)) {
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.deleteGroupAnnouncement = function(gid, aid, callback) {
if(typeof gid === 'string') {
gid = new SteamID(gid);
}
var self = this;
var submitData = {
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/announcements/delete/" + aid + "?sessionID=" + this.getSessionID()
};
this.httpRequestGet(submitData, function(err, response, body) {
if(!callback) {
return;
}
callback(null);
})
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.scheduleGroupEvent = function(gid, name, type, description, time, server, callback) {
@@ -170,7 +244,7 @@ SteamCommunity.prototype.scheduleGroupEvent = function(gid, name, type, descript
server = {"ip": "", "password": ""};
} else if(typeof server === 'string') {
server = {"ip": server, "password": ""};
} else {
} else if(typeof server !== 'object') {
server = {"ip": "", "password": ""};
}
@@ -202,25 +276,98 @@ SteamCommunity.prototype.scheduleGroupEvent = function(gid, name, type, descript
}
var self = this;
this.request.post({
"uri": "https://steamcommunity.com/gid/" + this.steamID.toString() + "/eventEdit",
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.toString() + "/eventEdit",
"form": form
}, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.editGroupEvent = function (gid, id, name, type, description, time, server, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
// Event types: ChatEvent - Chat, OtherEvent - A lil somethin somethin, PartyEvent - Party!, MeetingEvent - Important meeting, SpecialCauseEvent - Special cause (charity ball?), MusicAndArtsEvent - Music or Art type thing, SportsEvent - Sporting endeavor, TripEvent - Out of town excursion
// Passing a number for type will make it a game event for that appid
if (typeof server === 'function') {
callback = server;
server = {"ip": "", "password": ""};
} else if (typeof server === 'string') {
server = {"ip": server, "password": ""};
} else if (typeof server !== 'object') {
server = {"ip": "", "password": ""};
}
var form = {
"sessionid": this.getSessionID(),
"action": "updateEvent",
"eventID": id,
"tzOffset": new Date().getTimezoneOffset() * -60,
"name": name,
"type": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? "GameEvent" : type),
"appID": (typeof type === 'number' || !isNaN(parseInt(type, 10)) ? type : ''),
"serverIP": server.ip,
"serverPassword": server.password,
"notes": description,
"eventQuickTime": "now"
};
if (time === null) {
form.startDate = 'MM/DD/YY';
form.startHour = '12';
form.startMinute = '00';
form.startAMPM = 'PM';
form.timeChoice = 'quick';
} else {
form.startDate = (time.getMonth() + 1 < 10 ? '0' : '') + (time.getMonth() + 1) + '/' + (time.getDate() < 10 ? '0' : '') + time.getDate() + '/' + time.getFullYear().toString().substring(2);
form.startHour = (time.getHours() === 0 ? '12' : (time.getHours() > 12 ? time.getHours() - 12 : time.getHours()));
form.startMinute = (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
form.startAMPM = (time.getHours() <= 12 ? 'AM' : 'PM');
form.timeChoice = 'specific';
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.toString() + "/eventEdit",
"form": form
}, function(err, response, body) {
if(!callback) {
return;
}
if(self._checkCommunityError(body, callback)) {
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.deleteGroupEvent = function(gid, id, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var form = {
"sessionid": this.getSessionID(),
"action": "deleteEvent",
"eventID": id
};
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.toString() + "/eventEdit",
"form": form
}, function(err, response, body) {
if(!callback) {
return;
}
callback(null);
});
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.setGroupPlayerOfTheWeek = function(gid, steamID, callback) {
@@ -233,7 +380,7 @@ SteamCommunity.prototype.setGroupPlayerOfTheWeek = function(gid, steamID, callba
}
var self = this;
this.request.post({
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/potwEdit",
"form": {
"xml": 1,
@@ -263,7 +410,7 @@ SteamCommunity.prototype.setGroupPlayerOfTheWeek = function(gid, steamID, callba
callback(new Error(results.response.results[0]));
}
});
});
}, "steamcommunity");
};
SteamCommunity.prototype.kickGroupMember = function(gid, steamID, callback) {
@@ -276,7 +423,7 @@ SteamCommunity.prototype.kickGroupMember = function(gid, steamID, callback) {
}
var self = this;
this.request.post({
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/membersManage",
"form": {
"sessionID": this.getSessionID(),
@@ -289,17 +436,8 @@ SteamCommunity.prototype.kickGroupMember = function(gid, steamID, callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
if(self._checkCommunityError(body, callback)) {
return;
}
callback(null);
});
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
@@ -313,12 +451,9 @@ SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
}
var self = this;
this.request("https://steamcommunity.com/gid/" + gid.getSteamID64() + "/history?p=" + page, function(err, response, body) {
if(self._checkHttpError(err, response, callback)) {
return;
}
if(self._checkCommunityError(body, callback)) {
this.httpRequest("https://steamcommunity.com/gid/" + gid.getSteamID64() + "/history?p=" + page, function(err, response, body) {
if (err) {
callback(err);
return;
}
@@ -360,7 +495,7 @@ SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
sid.universe = SteamID.Universe.PUBLIC;
sid.type = SteamID.Type.INDIVIDUAL;
sid.instance = SteamID.Instance.DESKTOP;
sid.accountid = $(users[0]).data('miniprofile');
sid.accountid = $(users[1]).data('miniprofile');
data.actor = sid;
}
@@ -382,5 +517,205 @@ SteamCommunity.prototype.getGroupHistory = function(gid, page, callback) {
});
callback(null, output);
});
}, "steamcommunity");
};
SteamCommunity.prototype.getAllGroupComments = function(gid, from, count, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var options = {
"uri": "https://steamcommunity.com/comment/Clan/render/" + gid.getSteamID64() + "/-1/",
"form": {
"start": from,
"count": count
}
};
var self = this;
this.httpRequestPost(options, function(err, response, body) {
if (err) {
callback(err);
return;
}
var comments = [];
var $ = Cheerio.load(JSON.parse(body).comments_html);
$(".commentthread_comment_content").each(function () {
var comment = {};
var cachedSelector;
var $selector = $(this).find(".commentthread_author_link");
comment.authorName = $($selector).find("bdi").text();
comment.authorId = $($selector).attr("href").replace(/https?:\/\/steamcommunity.com\/(id|profiles)\//, "");
comment.date = Helpers.decodeSteamTime($(this).find(".commentthread_comment_timestamp").text().trim());
$selector = $(this).find(".commentthread_comment_text");
comment.commentId = $($selector).attr("id").replace("comment_content_", "");
comment.text = $($selector).html().trim();
comments.push(comment);
});
callback(null, comments);
}, "steamcommunity");
};
SteamCommunity.prototype.deleteGroupComment = function(gid, cid, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
if (typeof cid !== 'string') {
cid = cid.toString();
}
var options = {
"uri": "https://steamcommunity.com/comment/Clan/delete/" + gid.getSteamID64() + "/-1/",
"form": {
"sessionid": this.getSessionID(),
"gidcomment": cid
}
};
var self = this;
this.httpRequestPost(options, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.postGroupComment = function(gid, message, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var options = {
"uri": "https://steamcommunity.com/comment/Clan/post/" + gid.getSteamID64() + "/-1/",
"form": {
"comment": message,
"count": 6,
"sessionid": this.getSessionID()
}
};
var self = this;
this.httpRequestPost(options, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
/**
* Get requests to join a restricted group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {function} callback - First argument is null/Error, second is array of SteamID objects
*/
SteamCommunity.prototype.getGroupJoinRequests = function(gid, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
this.httpRequestGet("https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage", (err, res, body) => {
if (!body) {
callback(new Error("Malformed response"));
return;
}
var matches = body.match(/JoinRequests_ApproveDenyUser\(\W*['"](\d+)['"],\W0\W\)/g);
if (!matches) {
// no pending requests
callback(null, []);
return;
}
var requests = [];
for (var i = 0; i < matches.length; i++) {
requests.push(new SteamID("[U:1:" + matches[i].match(/JoinRequests_ApproveDenyUser\(\W*['"](\d+)['"],\W0\W\)/)[1] + "]"));
}
callback(null, requests);
}, "steamcommunity");
};
/**
* Respond to one or more join requests to a restricted group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {SteamID|string|SteamID[]|string[]} steamIDs - The SteamIDs of the users you want to approve or deny membership for (or a single value)
* @param {boolean} approve - True to put them in the group, false to deny their membership
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.respondToGroupJoinRequests = function(gid, steamIDs, approve, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
var rgAccounts = (!Array.isArray(steamIDs) ? [steamIDs] : steamIDs).map(sid => sid.toString());
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage",
"form": {
"rgAccounts": rgAccounts,
"bapprove": approve ? "1" : "0",
"json": "1",
"sessionID": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (!callback) {
return;
}
if (body != EResult.OK) {
var err = new Error(EResult[body] || ("Error " + body));
err.eresult = body;
callback(err);
} else {
callback(null);
}
}, "steamcommunity");
};
/**
* Respond to *ALL* pending group-join requests for a particular group.
* @param {SteamID|string} gid - The SteamID of the group you want to manage
* @param {boolean} approve - True to allow everyone who requested into the group, false to not
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.respondToAllGroupJoinRequests = function(gid, approve, callback) {
if (typeof gid === 'string') {
gid = new SteamID(gid);
}
this.httpRequestPost({
"uri": "https://steamcommunity.com/gid/" + gid.getSteamID64() + "/joinRequestsManage",
"form": {
"bapprove": approve ? "1" : "0",
"json": "1",
"action": "bulkrespond",
"sessionID": this.getSessionID()
},
"json": true
}, (err, res, body) => {
if (!callback) {
return;
}
if (body != EResult.OK) {
var err = new Error(EResult[body] || ("Error " + body));
err.eresult = body;
callback(err);
} else {
callback(null);
}
}, "steamcommunity");
};

56
components/helpers.js Normal file
View File

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

150
components/http.js Normal file
View File

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

View File

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

View File

@@ -1,16 +1,22 @@
var SteamCommunity = require('../index.js');
var Cheerio = require('cheerio');
/**
* Get a list of all apps on the market
* @param {function} callback - First argument is null|Error, second is an object of appid => name
*/
SteamCommunity.prototype.getMarketApps = function(callback) {
var self = this;
this.request('https://steamcommunity.com/market/', function (err, response, body) {
if(self._checkHttpError(err, response, callback)) {
this.httpRequest('https://steamcommunity.com/market/', function (err, response, body) {
if (err) {
callback(err);
return;
}
var $ = Cheerio.load(body);
if ($('.market_search_game_button_group')) {
apps = {};
$('.market_search_game_button_group > a').each(function (i, element) {
let apps = {};
$('.market_search_game_button_group a.game_button').each(function (i, element) {
var e = Cheerio.load(element);
var name = e('.game_button_game_name').text().trim();
var url = element.attribs.href;
@@ -21,5 +27,151 @@ SteamCommunity.prototype.getMarketApps = function(callback) {
} else {
callback(new Error("Malformed response"));
}
}, "steamcommunity");
};
/**
* Check if an item is eligible to be turned into gems and if so, get its gem value
* @param {int} appid
* @param {int|string} assetid
* @param {function} callback
*/
SteamCommunity.prototype.getGemValue = function(appid, assetid, callback) {
this._myProfile({
"endpoint": "ajaxgetgoovalue/",
"qs": {
"sessionid": this.getSessionID(),
"appid": appid,
"contextid": 6,
"assetid": assetid
},
"checkHttpError": false,
"json": true
}, null, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body.goo_value || !body.strTitle) {
callback(new Error("Malformed response"));
return;
}
callback(null, {"promptTitle": body.strTitle, "gemValue": parseInt(body.goo_value, 10)});
});
}
};
/**
* Turn an eligible item into gems.
* @param {int} appid
* @param {int|string} assetid
* @param {int} expectedGemsValue
* @param {function} callback
*/
SteamCommunity.prototype.turnItemIntoGems = function(appid, assetid, expectedGemsValue, callback) {
this._myProfile({
"endpoint": "ajaxgrindintogoo/",
"json": true,
"checkHttpError": false
}, {
"appid": appid,
"contextid": 6,
"assetid": assetid,
"goo_value_expected": expectedGemsValue,
"sessionid": this.getSessionID()
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
}
if (!body['goo_value_received '] || !body.goo_value_total) { // lol valve
callback(new Error("Malformed response"));
return;
}
callback(null, {"gemsReceived": parseInt(body['goo_value_received '], 10), "totalGems": parseInt(body.goo_value_total, 10)});
})
};
/**
* 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);
});
};

View File

@@ -1,7 +1,9 @@
var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
var Cheerio = require('cheerio');
var fs = require('fs');
const Cheerio = require('cheerio');
const FS = require('fs');
const SteamID = require('steamid');
const Helpers = require('./helpers.js');
const SteamCommunity = require('../index.js');
SteamCommunity.PrivacyState = {
"Private": 1,
@@ -10,9 +12,9 @@ SteamCommunity.PrivacyState = {
};
var CommentPrivacyState = {
"1": "commentselfonly",
"2": "commentfriendsonly",
"3": "commentanyone"
"1": 2, // private
"2": 0, // friends only
"3": 1 // anyone
};
SteamCommunity.prototype.setupProfile = function(callback) {
@@ -21,7 +23,7 @@ SteamCommunity.prototype.setupProfile = function(callback) {
if(!callback) {
return;
}
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
} else {
@@ -37,91 +39,95 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
if(callback) {
callback(err || new Error("HTTP error " + response.statusCode));
}
return;
}
var $ = Cheerio.load(body);
var form = $('#editForm');
if(!form) {
if(callback) {
callback(new Error("Malformed response"));
}
return;
}
var values = {};
form.serializeArray().forEach(function(item) {
values[item.name] = item.value;
});
for(var i in settings) {
if(!settings.hasOwnProperty(i)) {
continue;
}
switch(i) {
case 'name':
values.personaName = settings[i];
break;
case 'realName':
values.real_name = settings[i];
break;
case 'summary':
values.summary = settings[i];
break;
case 'country':
values.country = settings[i];
break;
case 'state':
values.state = settings[i];
break;
case 'city':
values.city = settings[i];
break;
case 'customURL':
values.customURL = settings[i];
break;
case 'background':
// The assetid of our desired profile background
values.profile_background = settings[i];
break;
case 'featuredBadge':
// Currently, game badges aren't supported
values.favorite_badge_badgeid = settings[i];
break;
case 'primaryGroup':
if(typeof settings[i] === 'object' && settings[i].accountid) {
values.primary_group_steamid = settings[i].accountid;
if(typeof settings[i] === 'object' && settings[i].getSteamID64) {
values.primary_group_steamid = settings[i].getSteamID64();
} else {
values.primary_group_steamid = new SteamID(settings[i]).accountid;
values.primary_group_steamid = new SteamID(settings[i]).getSteamID64();
}
break;
// TODO: profile showcases
}
}
self._myProfile("edit", values, function(err, response, body) {
if (settings.customURL) {
delete self._profileURL;
}
if(err || response.statusCode != 200) {
if(callback) {
callback(err || new Error("HTTP error " + response.statusCode));
}
return;
}
// Check for an error
var $ = Cheerio.load(body);
var error = $('#errorText .formRowFields');
@@ -131,11 +137,11 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
if(callback) {
callback(new Error(error));
}
return;
}
}
if(callback) {
callback(null);
}
@@ -144,70 +150,94 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
};
SteamCommunity.prototype.profileSettings = function(settings, callback) {
var self = this;
this._myProfile("edit/settings", null, function(err, response, body) {
if(err || response.statusCode != 200) {
if(callback) {
this._myProfile("edit/settings", null, (err, response, body) => {
if (err || response.statusCode != 200) {
if (callback) {
callback(err || new Error("HTTP error " + response.statusCode));
}
return;
}
var $ = Cheerio.load(body);
var form = $('#editForm');
if(!form) {
var existingSettings = $('.ProfileReactRoot[data-privacysettings]').data('privacysettings');
if (!existingSettings) {
if(callback) {
callback(new Error("Malformed response"));
}
return;
}
var values = {};
form.serializeArray().forEach(function(item) {
values[item.name] = item.value;
});
for(var i in settings) {
if(!settings.hasOwnProperty(i)) {
// PrivacySettings => {PrivacyProfile, PrivacyInventory, PrivacyInventoryGifts, PrivacyOwnedGames, PrivacyPlaytime}
// eCommentPermission
var privacy = existingSettings.PrivacySettings;
var commentPermission = existingSettings.eCommentPermission;
for (var i in settings) {
if (!settings.hasOwnProperty(i)) {
continue;
}
switch(i) {
switch (i) {
case 'profile':
values.privacySetting = settings[i];
privacy.PrivacyProfile = settings[i];
break;
case 'comments':
values.commentSetting = CommentPrivacyState[settings[i]];
commentPermission = CommentPrivacyState[settings[i]];
break;
case 'inventory':
values.inventoryPrivacySetting = settings[i];
privacy.PrivacyInventory = settings[i];
break;
case 'inventoryGifts':
values.inventoryGiftPrivacy = settings[i] ? 1 : 0;
privacy.PrivacyInventoryGifts = settings[i] ? SteamCommunity.PrivacyState.Private : SteamCommunity.PrivacyState.Public;
break;
case 'emailConfirmation':
values.tradeConfirmationSetting = settings[i] ? 1 : 0;
case 'gameDetails':
privacy.PrivacyOwnedGames = settings[i];
break;
case 'playtime':
privacy.PrivacyPlaytime = settings[i] ? SteamCommunity.PrivacyState.Private : SteamCommunity.PrivacyState.Public;
break;
case 'friendsList':
privacy.PrivacyFriendsList = settings[i];
break;
}
}
self._myProfile("edit/settings", values, function(err, response, body) {
if(err || response.statusCode != 200) {
if(callback) {
this._myProfile({
"method": "POST",
"endpoint": "ajaxsetprivacy/",
"json": true,
"formData": { // it's multipart because lolvalve
"sessionid": this.getSessionID(),
"Privacy": JSON.stringify(privacy),
"eCommentPermission": commentPermission
}
}, null, function(err, response, body) {
if (err || response.statusCode != 200) {
if (callback) {
callback(err || new Error("HTTP error " + response.statusCode));
}
return;
}
if(callback) {
callback(null);
if (body.success != 1) {
if (callback) {
callback(new Error(body.success ? "Error " + body.success : "Request was not successful"));
}
return;
}
if (callback) {
callback(null, body.Privacy);
}
});
});
@@ -219,12 +249,18 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
format = null;
}
// are we logged in?
if (!this.steamID) {
callback(new Error("Not Logged In"));
return;
}
var self = this;
if(image instanceof Buffer) {
doUpload(image);
} else if(image.match(/^https?:\/\//)) {
this.request.get({
this.httpRequestGet({
"uri": image,
"encoding": null
}, function(err, response, body) {
@@ -241,7 +277,7 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
}
doUpload(body);
})
}, "steamcommunity");
} else {
if(!format) {
format = image.match(/\.([^\.]+)$/);
@@ -250,7 +286,7 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
}
}
fs.readFile(image, function(err, file) {
FS.readFile(image, function(err, file) {
if(err) {
if(callback) {
callback(err);
@@ -304,7 +340,7 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
return;
}
self.request.post({
self.httpRequestPost({
"uri": "https://steamcommunity.com/actions/FileUploader",
"formData": {
"MAX_FILE_SIZE": buffer.length,
@@ -358,6 +394,71 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
if(callback) {
callback(null, body.images.full);
}
});
}, "steamcommunity");
}
};
/**
* Post a new status to your profile activity feed.
* @param {string} statusText - The text of this status update
* @param {{appID: int}} [options] - Options for this status update. All are optional. If you don't pass any options, this can be omitted.
* @param {function} callback - err, postID
*/
SteamCommunity.prototype.postProfileStatus = function(statusText, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
this._myProfile("ajaxpostuserstatus/", {
"appid": options.appID || 0,
"sessionid": this.getSessionID(),
"status_text": statusText
}, (err, res, body) => {
try {
body = JSON.parse(body);
if (body.message) {
callback(new Error(body.message));
return;
}
var match = body.blotter_html.match(/id="userstatus_(\d+)_/);
if (!match) {
callback(new Error("Malformed response"));
return;
}
callback(null, parseInt(match[1], 10));
} catch (ex) {
callback(ex);
}
});
};
/**
* Delete a previously-posted profile status update.
* @param {int} postID
* @param {function} [callback]
*/
SteamCommunity.prototype.deleteProfileStatus = function(postID, callback) {
this._myProfile("ajaxdeleteuserstatus/", {
"sessionid": this.getSessionID(),
"postid": postID
}, (err, res, body) => {
if (!callback) {
return;
}
try {
body = JSON.parse(body);
if (!body.success) {
callback(new Error("Malformed response"));
return;
}
callback(Helpers.eresultError(body.success));
} catch (ex) {
callback(ex);
}
});
};

159
components/twofactor.js Normal file
View File

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

505
components/users.js Normal file
View File

@@ -0,0 +1,505 @@
const Cheerio = require('cheerio');
const SteamID = require('steamid');
const SteamCommunity = require('../index.js');
const CEconItem = require('../classes/CEconItem.js');
const Helpers = require('./helpers.js');
SteamCommunity.prototype.addFriend = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/AddFriendAjax",
"form": {
"accept_invite": 0,
"sessionID": this.getSessionID(),
"steamid": userID.toString()
},
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
}
if(body.success) {
callback(null);
} else {
callback(new Error("Unknown error"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.acceptFriendRequest = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/AddFriendAjax",
"form": {
"accept_invite": 1,
"sessionID": this.getSessionID(),
"steamid": userID.toString()
}
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.removeFriend = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/RemoveFriendAjax",
"form": {
"sessionID": this.getSessionID(),
"steamid": userID.toString()
}
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.blockCommunication = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/BlockUserAjax",
"form": {
"sessionID": this.getSessionID(),
"steamid": userID.toString()
}
}, function(err, response, body) {
if(!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
};
SteamCommunity.prototype.unblockCommunication = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var form = {"action": "unignore"};
form['friends[' + userID.toString() + ']'] = 1;
this._myProfile('friends/blocked/', form, function(err, response, body) {
if(!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
return;
}
callback(null);
});
};
SteamCommunity.prototype.postUserComment = function(userID, message, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/comment/Profile/post/" + userID.toString() + "/-1",
"form": {
"comment": message,
"count": 6,
"sessionid": this.getSessionID()
},
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
}
if(body.success) {
callback(null);
} else if(body.error) {
callback(new Error(body.error));
} else {
callback(new Error("Unknown error"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.inviteUserToGroup = function(userID, groupID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/actions/GroupInvite",
"form": {
"group": groupID.toString(),
"invitee": userID.toString(),
"json": 1,
"sessionID": this.getSessionID(),
"type": "groupInvite"
},
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
if (err) {
callback(err);
return;
}
if(body.results == 'OK') {
callback(null);
} else if(body.results) {
callback(new Error(body.results));
} else {
callback(new Error("Unknown error"));
}
}, "steamcommunity");
};
SteamCommunity.prototype.getUserAliases = function(userID, callback) {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
this.httpRequestGet({
"uri": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/ajaxaliases",
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if (typeof body !== 'object') {
callback(new Error("Malformed response"));
return;
}
callback(null, body.map(function(entry) {
entry.timechanged = Helpers.decodeSteamTime(entry.timechanged);
return entry;
}));
}, "steamcommunity");
};
/**
* Get the background URL of user's profile.
* @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
* @param {function} callback
*/
SteamCommunity.prototype.getUserProfileBackground = function(userID, callback) {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64(), (err, response, body) => {
if (err) {
callback(err);
return;
}
var $ = Cheerio.load(body);
var $privateProfileInfo = $('.profile_private_info');
if ($privateProfileInfo.length > 0) {
callback(new Error($privateProfileInfo.text().trim()));
return;
}
if ($('body').hasClass('has_profile_background')) {
var backgroundUrl = $('div.profile_background_image_content').css('background-image');
var matcher = backgroundUrl.match(/\(([^)]+)\)/);
if (matcher.length != 2 || !matcher[1].length) {
callback(new Error("Malformed response"));
} else {
callback(null, matcher[1]);
}
} else {
callback(null, null);
}
}, "steamcommunity");
};
SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
if (typeof userID === 'function') {
callback = userID;
userID = this.steamID;
}
if (!userID) {
callback(new Error("No SteamID specified and not logged in"));
return;
}
var self = this;
this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory/", function(err, response, body) {
if (err) {
callback(err);
return;
}
var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
if (!match) {
var errorMessage = "Malformed response";
if(body.match(/0 items in their inventory\./)){
callback(null, {});
return;
}else if(body.match(/inventory is currently private\./)){
errorMessage = "Private inventory";
}else if(body.match(/profile\_private\_info/)){
errorMessage = "Private profile";
}
callback(new Error(errorMessage));
return;
}
var data;
try {
data = JSON.parse(match[1]);
} catch(e) {
callback(new Error("Malformed response"));
return;
}
callback(null, data);
}, "steamcommunity");
};
/**
* Get the contents of a user's inventory context.
* @deprecated Use getUserInventoryContents instead
* @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param {function} callback
*/
SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, tradableOnly, callback) {
var self = this;
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
var endpoint = "/profiles/" + userID.getSteamID64();
get([], []);
function get(inventory, currency, start) {
self.httpRequest({
"uri": "https://steamcommunity.com" + endpoint + "/inventory/json/" + appID + "/" + contextID,
"headers": {
"Referer": "https://steamcommunity.com" + endpoint + "/inventory"
},
"qs": {
"start": start,
"trading": tradableOnly ? 1 : undefined
},
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
}
if (!body || !body.success || !body.rgInventory || !body.rgDescriptions || !body.rgCurrency) {
if (body) {
callback(new Error(body.Error || "Malformed response"));
} else {
callback(new Error("Malformed response"));
}
return;
}
var i;
for (i in body.rgInventory) {
if (!body.rgInventory.hasOwnProperty(i)) {
continue;
}
inventory.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
}
for (i in body.rgCurrency) {
if (!body.rgCurrency.hasOwnProperty(i)) {
continue;
}
currency.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
}
if (body.more) {
var match = response.request.uri.href.match(/\/(profiles|id)\/([^\/]+)\//);
if(match) {
endpoint = "/" + match[1] + "/" + match[2];
}
get(inventory, currency, body.more_start);
} else {
callback(null, inventory, currency);
}
}, "steamcommunity");
}
};
/**
* Get the contents of a user's inventory context.
* @param {SteamID|string} userID - The user's SteamID as a SteamID object or a string which can parse into one
* @param {int} appID - The Steam application ID of the game for which you want an inventory
* @param {int} contextID - The ID of the "context" within the game you want to retrieve
* @param {boolean} tradableOnly - true to get only tradable items and currencies
* @param {string} [language] - The language of item descriptions to return. Omit for default (which may either be English or your account's chosen language)
* @param {function} callback
*/
SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, language, callback) {
var self = this;
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
if (typeof language === 'function') {
callback = language;
language = "english";
}
var pos = 1;
get([], []);
function get(inventory, currency, start) {
self.httpRequest({
"uri": "https://steamcommunity.com/inventory/" + userID.getSteamID64() + "/" + appID + "/" + contextID,
"headers": {
"Referer": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/inventory"
},
"qs": {
"l": language, // Default language
"count": 5000, // Max items per 'page'
"start_assetid": start
},
"json": true
}, function(err, response, body) {
if (err) {
if (err.message == "HTTP error 403" && body === null) {
// 403 with a body of "null" means the inventory/profile is private.
if (self.steamID && userID.getSteamID64() == self.steamID.getSteamID64()) {
// We can never get private profile error for our own inventory!
self._notifySessionExpired(err);
}
callback(new Error("This profile is private."));
return;
}
if (err.message == "HTTP error 500" && body && body.error) {
err = new Error(body.error);
var match = body.error.match(/^(.+) \((\d+)\)$/);
if (match) {
err.message = match[1];
err.eresult = match[2];
callback(err);
return;
}
}
callback(err);
return;
}
if (body && body.success && body.total_inventory_count === 0) {
// Empty inventory
callback(null, [], [], 0);
return;
}
if (!body || !body.success || !body.assets || !body.descriptions) {
if (body) {
// Dunno if the error/Error property even exists on this new endpoint
callback(new Error(body.error || body.Error || "Malformed response"));
} else {
callback(new Error("Malformed response"));
}
return;
}
for (var i = 0; i < body.assets.length; i++) {
var description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid);
if (!tradableOnly || (description && description.tradable)) {
body.assets[i].pos = pos++;
(body.assets[i].currencyid ? currency : inventory).push(new CEconItem(body.assets[i], description, contextID));
}
}
if (body.more_items) {
get(inventory, currency, body.last_assetid);
} else {
callback(null, inventory, currency, body.total_inventory_count);
}
}, "steamcommunity");
}
// A bit of optimization; objects are hash tables so it's more efficient to look up by key than to iterate an array
var quickDescriptionLookup = {};
function getDescription(descriptions, classID, instanceID) {
var key = classID + '_' + (instanceID || '0'); // instanceID can be undefined, in which case it's 0.
if (quickDescriptionLookup[key]) {
return quickDescriptionLookup[key];
}
for (var i = 0; i < descriptions.length; i++) {
quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i];
}
return quickDescriptionLookup[key];
}
};

73
components/webapi.js Normal file
View File

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

@@ -0,0 +1,61 @@
var SteamCommunity = require('../index.js');
var ReadLine = require('readline');
var fs = require('fs');
var community = new SteamCommunity();
var rl = ReadLine.createInterface({
"input": process.stdin,
"output": process.stdout
});
rl.question("Username: ", function(accountName) {
rl.question("Password: ", function(password) {
rl.question("Two-Factor Auth Code: ", function(authCode) {
rl.question("Revocation Code: R", function(rCode) {
doLogin(accountName, password, authCode, "", rCode);
});
});
});
});
function doLogin(accountName, password, authCode, captcha, rCode) {
community.login({
"accountName": accountName,
"password": password,
"twoFactorCode": authCode,
"captcha": captcha
}, function(err, sessionID, cookies, steamguard) {
if(err) {
if(err.message == 'SteamGuard') {
console.log("This account does not have two-factor authentication enabled.");
process.exit();
return;
}
if(err.message == 'CAPTCHA') {
console.log(err.captchaurl);
rl.question("CAPTCHA: ", function(captchaInput) {
doLogin(accountName, password, authCode, captchaInput);
});
return;
}
console.log(err);
process.exit();
return;
}
console.log("Logged on!");
community.disableTwoFactor("R" + rCode, function(err) {
if(err) {
console.log(err);
process.exit();
return;
}
console.log("Two-factor authentication disabled!");
process.exit();
});
});
}

View File

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

View File

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

598
index.js
View File

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

View File

@@ -1,22 +1,36 @@
{
"name": "steamcommunity",
"version": "3.6.0",
"version": "3.40.0",
"description": "Provides an interface for logging into and interacting with the Steam Community website",
"keywords": ["steam", "steam community"],
"keywords": [
"steam",
"steam community"
],
"homepage": "https://github.com/DoctorMcKay/node-steamcommunity",
"bugs": {
"url": "https://github.com/DoctorMcKay/node-steamcommunity/issues"
},
"license": "MIT",
"author": {
"name": "Alex Corn",
"email": "mckay@doctormckay.com",
"url": "https://www.doctormckay.com"
},
"repository": {
"type": "git",
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
},
"dependencies": {
"request": "^2.58.0",
"node-bignumber": "^1.2.1",
"steamid": "^0.3.0",
"xml2js": "^0.4.9",
"cheerio": "^0.19.0"
"@doctormckay/stats-reporter": "^1.0.2",
"request": "^2.86.0",
"node-bignumber": "^1.2.1",
"steamid": "^1.0.0",
"xml2js": "^0.4.11",
"cheerio": "0.22.0",
"async": "^2.1.4",
"steam-totp": "^1.3.0"
},
"engines": {
"node": ">=4.0.0"
}
}
}

14
resources/EChatState.js Normal file
View File

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

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
/**
* @enum EPersonaStateFlag
*/
module.exports = {
"HasRichPresence": 1,
"InJoinableGame": 2,
"Golden": 4, // removed "no longer has any effect"
"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,
// Value-to-name mapping for convenience
"1": "HasRichPresence",
"2": "InJoinableGame",
"4": "Golden",
"256": "ClientTypeWeb",
"512": "ClientTypeMobile",
"1024": "ClientTypeTenfoot",
"2048": "ClientTypeVR",
"4096": "LaunchTypeGamepad",
};

240
resources/EResult.js Normal file
View File

@@ -0,0 +1,240 @@
/**
* @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,
// 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",
};