Compare commits

..

75 Commits
v3.48.7 ... v4

Author SHA1 Message Date
Alex Corn
4ec372b44b Added packageName and packageVersion properties 2023-07-06 21:52:31 -04:00
Alex Corn
25dfa2cdd2 Rename uri to url everywhere 2023-06-27 04:31:25 -04:00
Alex Corn
c006526f69 Pass httpProxy down to steam-session 2023-06-27 04:28:28 -04:00
Alex Corn
8e1f214612 Log into steam using steam-session 2023-06-27 04:27:39 -04:00
Alex Corn
38d3fb39bf Updated webapi.js methods to use new http interface 2023-06-27 04:11:04 -04:00
Alex Corn
e65d50b5f7 Updated twofactor.js methods to use new http interface 2023-06-27 04:06:40 -04:00
Alex Corn
3a65768b3f Updated sharedfiles.js methods to use new http interface 2023-06-27 03:58:13 -04:00
Alex Corn
96e3e7ebc0 Updated help.js methods to use new http interface 2023-06-27 03:50:52 -04:00
Alex Corn
e9bc778e2a Removed request dependency 2023-06-27 03:45:04 -04:00
Alex Corn
c1901d5f55 Updated confirmations.js methods to use new http interface 2023-06-27 03:38:56 -04:00
Alex Corn
028ee43bda Updated index.js methods to use new http interface 2023-06-27 02:56:05 -04:00
Alex Corn
0ba889ee7e Fixed merge conflict remnant 2023-06-27 02:55:47 -04:00
Alex Corn
ccf16b64e6 Updated httpRequest to use StdLib HttpClient 2023-06-27 02:07:42 -04:00
Alex Corn
078e39584e Remove got dependency 2023-06-27 01:22:36 -04:00
Alex Corn
2094f0545e Update xml2js dependency 2023-06-27 01:21:31 -04:00
Alex Corn
b48e8fa537 Promote steam-session to a full dependency 2023-06-27 01:21:01 -04:00
Alex Corn
2bfaecd88f Removed oAuthLogin method 2023-06-27 01:20:22 -04:00
Alex Corn
f776d9fd4a Removed getWebApiOauthToken 2023-06-27 01:20:06 -04:00
Alex Corn
86e87e88ed Merge branch 'master' into v4
# Conflicts:
#	components/confirmations.js
#	components/inventoryhistory.js
#	components/twofactor.js
#	components/users.js
#	components/webapi.js
#	index.js
#	package.json
2023-06-27 01:19:00 -04:00
Alex Corn
711ab8a3ae Update stdlib version 2023-04-26 14:22:09 -04:00
Alex Corn
9218ec6dd9 Use stdlib 2.1.0 2023-04-24 23:50:24 -04:00
Alex Corn
d28c2cdde9 Require node 14 2023-04-24 18:01:19 -04:00
Alex Corn
b98997a595 Merge branch 'master' into v4
# Conflicts:
#	components/http.js
#	components/users.js
#	package.json
2023-04-24 17:57:25 -04:00
Alex Corn
7ebbd5806f Merge branch 'master' into v4
# Conflicts:
#	.idea/codeStyles/Project.xml
#	.idea/codeStyles/codeStyleConfig.xml
#	package.json
2022-02-02 03:43:42 -05:00
Alex Corn
43c0504232 Require node 12 or later 2021-07-30 02:07:04 -04:00
Alex Corn
48f0637c18 Use steamid@2.0.0 2021-07-30 02:06:58 -04:00
Alex Corn
ad095605b6 Clean up comment parsing code a little 2021-07-30 00:39:46 -04:00
Alex Corn
d09f18f055 Compare against PrivacyState rather than using a magic number 2021-07-30 00:27:21 -04:00
Alex Corn
c7a8c676b7 Simplify isSteamID 2021-07-30 00:26:11 -04:00
Alex Corn
fd5ed31afa Ignore unused vars starting with underscore 2021-07-29 22:55:26 -04:00
Alex Corn
e2ef03cc32 Disallow spaces before anonymous/named function parens 2021-07-29 22:09:06 -04:00
Alex Corn
e49c5c175e Fix dot-notation violation 2021-07-29 22:04:20 -04:00
Alex Corn
565132d1bc Enforce dot-notation 2021-07-29 22:03:14 -04:00
Alex Corn
7c6e531dbd Added rationale for not using eqeqeq 2021-07-29 21:58:56 -04:00
Alex Corn
ac19e0867b Start adding callback promises 2021-07-29 05:55:12 -04:00
Alex Corn
e9d3221ee6 Start replacing request with got 2021-07-29 05:47:24 -04:00
Alex Corn
dfda22cee4 Rename ConfirmationType to EConfirmationType for consistency 2021-07-29 05:20:34 -04:00
Alex Corn
485529e807 Use npm run lint in workflow 2021-07-29 05:06:21 -04:00
Alex Corn
3def387d56 Updated @doctormckay/stdlib 2021-07-29 05:06:01 -04:00
Alex Corn
605fb6268f Skip eslint workflow for internal pull requests
Refs:
- https://github.community/t/duplicate-checks-on-push-and-pull-request-simultaneous-event/18012/4
- 612732d587
2021-07-29 05:03:07 -04:00
Alex Corn
89d058fc99 Update EResult.js 2021-07-29 04:54:27 -04:00
Alex Corn
9d47d28a1e Some more refactoring 2021-07-29 03:20:14 -04:00
Alex Corn
40ee35ac89 Fixed last eslint error 2021-07-29 03:15:47 -04:00
Alex Corn
e252efc455 Change all eslint rules to errors so they'll hit on CI 2021-07-29 03:15:38 -04:00
Alex Corn
a4de664759 Disable eslint on generated files 2021-07-29 03:14:55 -04:00
Alex Corn
59df4dee1d eslint requires node 10 2021-07-29 03:11:03 -04:00
Alex Corn
ca887c46ba Use npx for eslint 2021-07-29 03:10:06 -04:00
Alex Corn
8c0994cec3 Fixed eslint action 2021-07-29 03:09:20 -04:00
Alex Corn
9ec329569f Added eslint action 2021-07-29 03:05:54 -04:00
Alex Corn
600a41143b Refactor all files 2021-07-29 02:55:56 -04:00
Alex Corn
4c59309eb4 Refactor index.js 2021-07-29 02:41:04 -04:00
Alex Corn
2adc23f0ac Added eslint config 2021-07-29 02:30:41 -04:00
Alex Corn
5b40683873 Merge branch 'master' into v4
# Conflicts:
#	components/help.js
2021-07-23 00:54:50 -04:00
Alex Corn
63b0b82531 Cleaned up users.js 2021-07-22 04:42:48 -04:00
Alex Corn
2b678bea06 Some more cleanup 2021-07-22 04:22:29 -04:00
Alex Corn
8ffa0b50d4 Cleaned up help.js 2021-07-22 04:10:20 -04:00
Alex Corn
d093466d27 Removed getWebApiOauthToken 2021-07-22 04:06:59 -04:00
Alex Corn
c69a5451eb Cleaned up webapi.js 2021-07-22 04:02:38 -04:00
Alex Corn
9a4be453ec Cleaned up twofactor.js 2021-07-22 03:59:55 -04:00
Alex Corn
c550b380fc Cleaned up profile.js 2021-07-22 03:54:38 -04:00
Alex Corn
247d804c77 Cleaned up market.js 2021-07-22 03:44:40 -04:00
Alex Corn
dfd0ccbbc7 Merge branch 'master' into v4
# Conflicts:
#	components/confirmations.js
#	components/groups.js
#	components/market.js
#	package.json
2021-07-22 03:37:06 -04:00
Alex Corn
fa253920e3 Code cleanup for http.js 2019-09-24 04:43:51 -04:00
Alex Corn
30ebf333b4 Code cleanup for helpers.js 2019-09-24 04:41:11 -04:00
Alex Corn
d7cfc396a7 Code cleanup for groups.js 2019-09-24 04:38:27 -04:00
Alex Corn
ac8e4a62de Code cleanup for confirmations.js 2019-09-24 04:28:05 -04:00
Alex Corn
8e9e3723f2 Code cleanup for chat.js 2019-09-24 04:23:40 -04:00
Alex Corn
6b0f958cfd Merge branch 'master' into v4
# Conflicts:
#	index.js
#	package.json
2019-09-24 04:14:09 -04:00
Alex Corn
8a7d6f5018 Merge branch 'master' into v4 2019-02-14 00:31:20 -05:00
Alex Corn
0cb0d22433 Cleaned up imports 2019-02-14 00:29:21 -05:00
Alex Corn
103e941501 Updated dependencies 2019-02-14 00:25:07 -05:00
Alex Corn
f99d86aa73 Removed async dependency 2019-02-14 00:23:17 -05:00
Alex Corn
5a855316f6 Removed inventoryhistory.js 2019-02-14 00:22:51 -05:00
Alex Corn
729f078a25 Removed deprecated confirmation checker 2019-02-14 00:22:06 -05:00
Alex Corn
80dbf9d688 Start v4 2019-02-14 00:19:20 -05:00
40 changed files with 2176 additions and 3057 deletions

50
.eslintrc.js Normal file
View File

@@ -0,0 +1,50 @@
module.exports = {
env: {
commonjs: true,
es2021: true,
node: true
},
extends: 'eslint:recommended',
parserOptions: {
ecmaVersion: 12
},
rules: {
// Use tabs for indentation and require 'case' in switch to be indented 1 level (default 0)
indent: ['error', 'tab', {SwitchCase: 1}],
// Single quotes for strings
quotes: ['error', 'single'],
// Always require semicolons
semi: ['error', 'always'],
// Don't use 'var'
'no-var': 'error',
// Only use quotes in object literal keys as needed
'quote-props': ['error', 'as-needed'],
// Don't allow trailing spaces after a line
'no-trailing-spaces': 'error',
// Require spaces before and after keywords (like "if")
'keyword-spacing': 'error',
// Don't allow unused variables, but allow unused function args (e.g. in callbacks) and global vars
'no-unused-vars': ['error', {vars: 'local', args: 'none', varsIgnorePattern: '^_'}],
// Require using dot notation (obj.prop instead of obj['prop']) where possible
'dot-notation': 'error',
// Don't use spaces before parens in anonymous or named functions
'space-before-function-paren': ['error', {anonymous: 'never', named: 'never', asyncArrow: 'always'}]
// We will NOT be using eqeqeq for a few reasons:
// 1. I would have to go through and check every single `==` to make sure that it's not depending on loose equality checks.
// 2. I'm only using ESLint to enforce style, not actual differences in functionality. ==/=== is not merely a style choice.
// Yes, I know that 'no-var' is actually enforcing a difference in functionality, but in practice nobody uses
// (or even knows about) var's hoisting functionality, so at this point it's effectively a style choice.
// 3. A lot of the time, you actually *want* loose equality checks, especially when interacting with a web server
// (as HTTP as no concept of anything but strings). Yes, most of our interaction is JSON, but not all. And even then,
// not all JSON actually serializes numbers as numbers.
// 4. `==` is really nowhere near as dangerous as memes would lead you to believe, if you know what you're doing.
// 5. If the idea behind enforcing `===` is to prevent inexperienced developers from unwittingly introducing bugs
// via loose quality checks, in my opinion it could be just as harmful to instruct a code quality tool to
// naively demand that all `==` become `===`. If a developer were to build code that works, but upon opening
// a pull request they see that ESLint demands they use `===` instead, they might just click "fix" and resubmit,
// expecting the code quality tool to know what it's doing. But it *doesn't* know what it's doing, since it's
// just blindly alerting when it sees `==`. The change in functionality from `==` to `===` could very well
// introduce a bug by itself.
}
};

31
.github/workflows/eslint.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: ESLint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
# Run for external PRs, but not on our own internal PRs as they'll be run by the push to the branch.
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'DoctorMcKay/node-steamcommunity'
strategy:
matrix:
node-version: [10.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: npm install
working-directory: .
run: npm install --ignore-scripts
- name: Run ESLint
run: npm run lint

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
node_modules/
node_modules/*
test.js
dev/

View File

@@ -1,15 +1,45 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="JSON">
<indentOptions>
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SMART_TABS" value="true" />
</value>
</option>
<option name="LINE_SEPARATOR" value="&#10;" />
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
</JSCodeStyleSettings>
<codeStyleSettings language="CSS">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="HTML">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<indentOptions>
<option name="INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="INDENT_CASE_FROM_SWITCH" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_FOR" value="false" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>
</component>

View File

@@ -3,4 +3,4 @@
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
</component>

View File

@@ -4,8 +4,10 @@
<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="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<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" />
<inspection_tool class="JSUnfilteredForInLoop" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@@ -4,8 +4,9 @@
[![license](https://img.shields.io/npm/l/steamcommunity.svg)](https://github.com/DoctorMcKay/node-steamcommunity/blob/master/LICENSE)
[![paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=N36YVAT42CZ4G&item_name=node%2dsteamcommunity&currency_code=USD)
This module provides an easy interface for the Steam Community website. This module can be used to simply login to
steamcommunity.com for use with other libraries, or to interact with steamcommunity.com.
This module provides an easy interface for the Steam Community website. This module can be used to simply login to steamcommunity.com for use with other libraries, or to interact with steamcommunity.com.
It supports Steam Guard and CAPTCHAs.
**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/).

View File

@@ -1,4 +1,6 @@
var SteamCommunity = require('../index.js');
const StdLib = require('@doctormckay/stdlib');
const SteamCommunity = require('../index.js');
module.exports = CConfirmation;
@@ -18,20 +20,33 @@ function CConfirmation(community, data) {
this.offerID = this.type == SteamCommunity.ConfirmationType.Trade ? this.creator : null;
}
/**
* @param {number} time
* @param {string} key
* @param {function} [callback]
* @return Promise<{offerID: number}>
*/
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;
return StdLib.Promises.callbackPromise(['offerID'], null, false, async (resolve, reject) => {
if (this.type && this.creator) {
if (this.type != SteamCommunity.ConfirmationType.Trade) {
return reject(new Error('Not a trade confirmation'));
}
return resolve({offerID: this.creator});
}
callback(null, this.creator);
return;
}
this._community.getConfirmationOfferID(this.id, time, key, callback);
return await this._community.getConfirmationOfferID(this.id, time, key, callback);
});
};
/**
* @param {number} time
* @param {string} key
* @param {boolean} accept
* @param {function} [callback]
* @return Promise<void>
*/
CConfirmation.prototype.respond = function(time, key, accept, callback) {
this._community.respondToConfirmation(this.id, this.key, time, key, accept, callback);
return this._community.respondToConfirmation(this.id, this.key, time, key, accept, callback);
};

View File

@@ -1,14 +1,11 @@
module.exports = CEconItem;
function CEconItem(item, description, contextID) {
var thing;
for (thing in item) {
if (item.hasOwnProperty(thing)) {
this[thing] = item[thing];
}
for (let thing in item) {
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
let 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);
@@ -27,10 +24,8 @@ function CEconItem(item, description, contextID) {
description = description[this.classid + '_' + this.instanceid];
}
for (thing in description) {
if (description.hasOwnProperty(thing)) {
this[thing] = description[thing];
}
for (let thing in description) {
this[thing] = description[thing];
}
}
@@ -49,28 +44,26 @@ function CEconItem(item, description, contextID) {
// 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
};
});
this.tags = this.tags.map((tag) => ({
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+)\-/))) {
let match;
if (this.appid == 753 && this.contextid == 6 && this.market_hash_name && (match = this.market_hash_name.match(/^(\d+)-/))) {
this.market_fee_app = parseInt(match[1], 10);
}
// Restore cache_expiration, if we can (for CS:GO items)
if (this.appid == 730 && this.contextid == 2 && this.owner_descriptions) {
let description = this.owner_descriptions.find(d => d.value && d.value.indexOf('Tradable/Marketable After ') == 0);
let description = this.owner_descriptions.find(d => d.value && d.value.indexOf('Tradable After ') == 0);
if (description) {
let date = new Date(description.value.substring(26).replace(/[,()]/g, ''));
let date = new Date(description.value.substring(15).replace(/[,()]/g, ''));
if (date) {
this.cache_expiration = date.toISOString();
}
@@ -82,7 +75,7 @@ function CEconItem(item, description, contextID) {
this.cache_expiration = this.item_expiration;
}
if (this.actions === "") {
if (this.actions === '') {
this.actions = [];
}
@@ -94,15 +87,15 @@ function CEconItem(item, description, contextID) {
}
CEconItem.prototype.getImageURL = function() {
return "https://steamcommunity-a.akamaihd.net/economy/image/" + this.icon_url + "/";
return 'https://steamcommunity-a.akamaihd.net/economy/image/' + this.icon_url + '/';
};
CEconItem.prototype.getLargeImageURL = function() {
if(!this.icon_url_large) {
if (!this.icon_url_large) {
return this.getImageURL();
}
return "https://steamcommunity-a.akamaihd.net/economy/image/" + this.icon_url_large + "/";
return 'https://steamcommunity-a.akamaihd.net/economy/image/' + this.icon_url_large + '/';
};
CEconItem.prototype.getTag = function(category) {
@@ -110,7 +103,7 @@ CEconItem.prototype.getTag = function(category) {
return null;
}
for (var i = 0; i < this.tags.length; i++) {
for (let i = 0; i < this.tags.length; i++) {
if (this.tags[i].category == category) {
return this.tags[i];
}

View File

@@ -1,33 +1,35 @@
var SteamCommunity = require('../index.js');
var Cheerio = require('cheerio');
const Cheerio = require('cheerio');
const SteamCommunity = require('../index.js');
SteamCommunity.prototype.getMarketItem = function(appid, hashName, currency, callback) {
if (typeof currency == "function") {
if (typeof currency == 'function') {
callback = currency;
currency = 1;
}
var self = this;
this.httpRequest("https://steamcommunity.com/market/listings/" + appid + "/" + encodeURIComponent(hashName), function(err, response, body) {
this.httpRequest('https://steamcommunity.com/market/listings/' + appid + '/' + encodeURIComponent(hashName), (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."));
let $ = Cheerio.load(body);
let $listingTableMessage = $('.market_listing_table_message');
if ($listingTableMessage && $listingTableMessage.text().trim() == 'There are no listings for this item.') {
callback(new Error('There are no listings for this item.'));
return;
}
var item = new CMarketItem(appid, hashName, self, body, $);
item.updatePrice(currency, function(err) {
if(err) {
let item = new CMarketItem(appid, hashName, this, body, $);
item.updatePrice(currency, (err) => {
if (err) {
callback(err);
} else {
callback(null, item);
}
});
}, "steamcommunity");
}, 'steamcommunity');
};
function CMarketItem(appid, hashName, community, body, $) {
@@ -36,38 +38,36 @@ function CMarketItem(appid, hashName, community, body, $) {
this._community = community;
this._$ = $;
this._country = "US";
var match = body.match(/var g_strCountryCode = "([^"]+)";/);
if(match) {
this._country = 'US';
let match = body.match(/var g_strCountryCode = "([^"]+)";/);
if (match) {
this._country = match[1];
}
this._language = "english";
this._language = 'english';
match = body.match(/var g_strLanguage = "([^"]+)";/);
if(match) {
if (match) {
this._language = match[1];
}
this.commodity = false;
match = body.match(/Market_LoadOrderSpread\(\s*(\d+)\s*\);/);
if(match) {
if (match) {
this.commodity = true;
this.commodityID = parseInt(match[1], 10);
}
this.medianSalePrices = null;
match = body.match(/var line1=([^;]+);/);
if(match) {
if (match) {
try {
this.medianSalePrices = JSON.parse(match[1]);
this.medianSalePrices = this.medianSalePrices.map(function(item) {
return {
"hour": new Date(item[0]),
"price": item[1],
"quantity": parseInt(item[2], 10)
};
});
} catch(e) {
this.medianSalePrices = this.medianSalePrices.map((item) => ({
hour: new Date(item[0]),
price: item[1],
quantity: parseInt(item[2], 10)
}));
} catch (e) {
// ignore
}
}
@@ -91,7 +91,7 @@ function CMarketItem(appid, hashName, community, body, $) {
// TODO: Buying listings and placing buy orders
}
CMarketItem.prototype.updatePrice = function (currency, callback) {
CMarketItem.prototype.updatePrice = function(currency, callback) {
if (this.commodity) {
this.updatePriceForCommodity(currency, callback);
} else {
@@ -100,90 +100,88 @@ CMarketItem.prototype.updatePrice = function (currency, callback) {
};
CMarketItem.prototype.updatePriceForCommodity = function(currency, callback) {
if(!this.commodity) {
throw new Error("Cannot update price for non-commodity item");
if (!this.commodity) {
throw new Error('Cannot update price for non-commodity item');
}
var self = this;
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 (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) {
url: 'https://steamcommunity.com/market/itemordershistogram?country=US&language=english&currency=' + currency + '&item_nameid=' + this.commodityID,
json: true
}, (err, response, body) => {
if (err) {
callback(err);
return;
}
if (body.success != 1) {
callback && callback(new Error("Error " + body.success));
if (callback) {
callback(new Error('Error ' + body.success));
}
return;
}
var match = body.total_count;
let match = (body.sell_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/);
if (match) {
self.quantity = parseInt(match, 10);
this.quantity = parseInt(match[1], 10);
}
var lowestPrice;
var $ = Cheerio.load(body.results_html);
match = $(".market_listing_price.market_listing_price_with_fee");
this.buyQuantity = 0;
match = (body.buy_order_summary || '').match(/<span class="market_commodity_orders_header_promote">(\d+)<\/span>/);
if (match) {
for (var i = 0; i < match.length; i++) {
lowestPrice = parseFloat($(match[i]).text().replace(",", ".").replace(/[^\d.]/g, ''));
this.buyQuantity = parseInt(match[1], 10);
}
this.lowestPrice = parseInt(body.lowest_sell_order, 10);
this.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');
}
this._community.httpRequest({
url: 'https://steamcommunity.com/market/listings/' +
this._appid + '/' +
encodeURIComponent(this._hashName) +
'/render/?query=&start=0&count=10&country=US&language=english&currency=' + currency,
json: true
}, (err, response, body) => {
if (err) {
callback(err);
return;
}
if (body.success != 1) {
callback && callback(new Error('Error ' + body.success));
return;
}
let match = body.total_count;
if (match) {
this.quantity = parseInt(match, 10);
}
let lowestPrice;
let $ = Cheerio.load(body.results_html);
match = $('.market_listing_price.market_listing_price_with_fee');
if (match) {
for (let i = 0; i < match.length; i++) {
lowestPrice = parseFloat($(match[i]).text().replace(',', '.').replace(/[^\d.]/g, ''));
if (!isNaN(lowestPrice)) {
self.lowestPrice = lowestPrice;
this.lowestPrice = lowestPrice;
break;
}
}
}
callback && callback(null);
}, "steamcommunity");
}, 'steamcommunity');
};

View File

@@ -1,19 +1,20 @@
var SteamCommunity = require('../index.js');
var Cheerio = require('cheerio');
const Cheerio = require('cheerio');
const SteamCommunity = require('../index.js');
SteamCommunity.prototype.marketSearch = function(options, callback) {
var qs = {};
let qs = {};
if(typeof options === 'string') {
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) {
if (qs.appid) {
for (let i in options) {
if (['query', 'appid', 'searchDescriptions'].indexOf(i) != -1) {
continue;
}
@@ -28,62 +29,61 @@ SteamCommunity.prototype.marketSearch = function(options, callback) {
qs.sort_column = 'price';
qs.sort_dir = 'asc';
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"
let results = [];
const performSearch = () => {
this.httpRequest({
url: 'https://steamcommunity.com/market/search/render/',
qs: qs,
headers: {
referer: 'https://steamcommunity.com/market/search'
},
"json": true
}, function(err, response, body) {
json: true
}, (err, response, body) => {
if (err) {
callback(err);
return;
}
if(!body.success) {
callback(new Error("Success is not true"));
if (!body.success) {
callback(new Error('Success is not true'));
return;
}
if(!body.results_html) {
callback(new Error("No results_html in response"));
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) {
let $ = Cheerio.load(body.results_html);
let $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++) {
let rows = $('.market_listing_row_link');
for (let i = 0; i < rows.length; i++) {
results.push(new CMarketSearchResult($(rows[i])));
}
if(body.start + body.pagesize >= body.total_count) {
if (body.start + body.pagesize >= body.total_count) {
callback(null, results);
} else {
qs.start += body.pagesize;
performSearch();
}
}, "steamcommunity");
}
}, 'steamcommunity');
};
performSearch();
};
function CMarketSearchResult(row) {
var match = row.attr('href').match(/\/market\/listings\/(\d+)\/([^\?\/]+)/);
let 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.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,33 +1,33 @@
var SteamCommunity = require('../index.js');
var Helpers = require('../components/helpers.js');
var SteamID = require('steamid');
var xml2js = require('xml2js');
const SteamID = require('steamid');
const XML2JS = require('xml2js');
const Helpers = require('../components/helpers.js');
const SteamCommunity = require('../index.js');
SteamCommunity.prototype.getSteamGroup = function(id, callback) {
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 !== '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");
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.httpRequest("https://steamcommunity.com/" + (typeof id === 'string' ? "groups/" + id : "gid/" + id.toString()) + "/memberslistxml/?xml=1", function(err, response, body) {
this.httpRequest('https://steamcommunity.com/' + (typeof id === 'string' ? 'groups/' + id : 'gid/' + id.toString()) + '/memberslistxml/?xml=1', (err, response, body) => {
if (err) {
callback(err);
return;
}
xml2js.parseString(body, function(err, result) {
if(err) {
XML2JS.parseString(body, (err, result) => {
if (err) {
callback(err);
return;
}
callback(null, new CSteamGroup(self, result.memberList));
callback(null, new CSteamGroup(this, result.memberList));
});
}, "steamcommunity");
}, 'steamcommunity');
};
function CSteamGroup(community, groupData) {
@@ -49,16 +49,16 @@ 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";
let 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';
} else {
return url + ".jpg";
return url + '.jpg';
}
};
CSteamGroup.prototype.getMembers = function(addresses, callback) {
if(typeof addresses === 'function') {
if (typeof addresses === 'function') {
callback = addresses;
addresses = null;
}
@@ -83,11 +83,11 @@ CSteamGroup.prototype.postAnnouncement = function(headline, content, hidden, cal
};
CSteamGroup.prototype.editAnnouncement = function(annoucementID, headline, content, callback) {
this._community.editGroupAnnouncement(this.steamID, 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)
this._community.deleteGroupAnnouncement(this.steamID, annoucementID, callback);
};
CSteamGroup.prototype.scheduleEvent = function(name, type, description, time, server, callback) {
@@ -98,7 +98,7 @@ CSteamGroup.prototype.editEvent = function(id, name, type, description, time, se
this._community.editGroupEvent(this.steamID, id, name, type, description, time, server, callback);
};
CSteamGroup.prototype.deleteEvent = function (id, callback) {
CSteamGroup.prototype.deleteEvent = function(id, callback) {
this._community.deleteGroupEvent(this.steamID, id, callback);
};

View File

@@ -31,7 +31,7 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) {
};
// Get DOM of sharedfile
this.httpRequestGet(`https://steamcommunity.com/sharedfiles/filedetails/?id=${sharedFileId}`, (err, res, body) => {
this.httpRequestGet(`https://steamcommunity.com/sharedfiles/filedetails/?id=${sharedFileId}`, async (err, res, body) => {
try {
/* --------------------- Preprocess output --------------------- */
@@ -136,18 +136,10 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) {
// Find owner profile link, convert to steamID64 using SteamIdResolver lib and create a SteamID object
let ownerHref = $(".friendBlockLinkOverlay").attr()["href"];
Helpers.resolveVanityURL(ownerHref, (err, data) => { // This request takes <1 sec
if (err) {
callback(err);
return;
}
sharedfile.owner = new SteamID(data.steamID);
// Make callback when ID was resolved as otherwise owner will always be null
callback(null, new CSteamSharedFile(this, sharedfile));
});
let {steamID} = await this._resolveVanityURL(ownerHref);
sharedfile.owner = steamID;
callback(null, new CSteamSharedFile(this, sharedfile));
} catch (err) {
callback(err, null);
}

View File

@@ -1,52 +1,52 @@
var SteamCommunity = require('../index.js');
var Helpers = require('../components/helpers.js');
var SteamID = require('steamid');
var xml2js = require('xml2js');
const SteamID = require('steamid');
const XML2JS = require('xml2js');
const Helpers = require('../components/helpers.js');
const SteamCommunity = require('../index.js');
SteamCommunity.prototype.getSteamUser = function(id, callback) {
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 !== '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");
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.httpRequest("https://steamcommunity.com/" + (typeof id === 'string' ? "id/" + id : "profiles/" + id.toString()) + "/?xml=1", function(err, response, body) {
this.httpRequest('http://steamcommunity.com/' + (typeof id === 'string' ? 'id/' + id : 'profiles/' + id.toString()) + '/?xml=1', (err, response, body) => {
if (err) {
callback(err);
return;
}
xml2js.parseString(body, function(err, result) {
if(err || (!result.response && !result.profile)) {
callback(err || new Error("No valid response"));
XML2JS.parseString(body, (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) {
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) {
var match = response.request.redirects[0].redirectUri.match(/https?:\/\/steamcommunity\.com\/id\/([^/])+\/\?xml=1/);
if(match) {
let customurl = null;
if (response.request.redirects && response.request.redirects.length) {
let match = response.request.redirects[0].redirectUri.match(/https?:\/\/steamcommunity\.com\/id\/([^/])+\/\?xml=1/);
if (match) {
customurl = match[1];
}
}
if(!result.profile.steamID64) {
callback(new Error("No valid response"));
if (!result.profile.steamID64) {
callback(new Error('No valid response'));
return;
}
callback(null, new CSteamUser(self, result.profile, customurl));
callback(null, new CSteamUser(this, result.profile, customurl));
});
}, "steamcommunity");
}, 'steamcommunity');
};
function CSteamUser(community, userData, customurl) {
@@ -59,7 +59,7 @@ function CSteamUser(community, userData, customurl) {
this.privacyState = processItem('privacyState', 'uncreated');
this.visibilityState = processItem('visibilityState');
this.avatarHash = processItem('avatarIcon', '').match(/([0-9a-f]+)\.[a-z]+$/);
if(this.avatarHash) {
if (this.avatarHash) {
this.avatarHash = this.avatarHash[1];
}
@@ -68,8 +68,8 @@ function CSteamUser(community, userData, customurl) {
this.isLimitedAccount = processItem('isLimitedAccount') == 1;
this.customURL = processItem('customURL', customurl);
if(this.visibilityState == 3) {
let memberSinceValue = processItem('memberSince', '0').replace(/(\d{1,2})(st|nd|th)/, "$1");
if (this.visibilityState == SteamCommunity.PrivacyState.Public) {
let memberSinceValue = processItem('memberSince', '0').replace(/(\d{1,2})(st|nd|th)/, '$1');
if (memberSinceValue.indexOf(',') === -1) {
memberSinceValue += ', ' + new Date().getFullYear();
@@ -91,11 +91,10 @@ function CSteamUser(community, userData, customurl) {
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 === "1") {
self.primaryGroup = new SteamID(group.groupID64[0]);
if (userData.groups && userData.groups[0] && userData.groups[0].group) {
this.groups = userData.groups[0].group.map((group) => {
if (group.$ && group.$.isPrimary === '1') {
this.primaryGroup = new SteamID(group.groupID64[0]);
}
return new SteamID(group.groupID64[0]);
@@ -103,7 +102,7 @@ function CSteamUser(community, userData, customurl) {
}
function processItem(name, defaultVal) {
if(!userData[name]) {
if (!userData[name]) {
return defaultVal;
}
@@ -115,13 +114,13 @@ CSteamUser.getAvatarURL = function(hash, size, protocol) {
size = size || '';
protocol = protocol || 'http://';
hash = hash || "72f78b4c8cc1f62323f8a33f6d53e27db57c2252"; // The default "?" avatar
hash = hash || '72f78b4c8cc1f62323f8a33f6d53e27db57c2252'; // The default "?" avatar
var url = protocol + "steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + hash.substring(0, 2) + "/" + hash;
if(size == 'full' || size == 'medium') {
return url + "_" + size + ".jpg";
let url = protocol + 'steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/' + hash.substring(0, 2) + '/' + hash;
if (size == 'full' || size == 'medium') {
return url + '_' + size + '.jpg';
} else {
return url + ".jpg";
return url + '.jpg';
}
};
@@ -166,14 +165,6 @@ CSteamUser.prototype.inviteToGroup = function(groupID, callback) {
this._community.inviteUserToGroup(this.steamID, groupID, callback);
};
CSteamUser.prototype.follow = function(callback) {
this._community.followUser(this.steamID, callback);
};
CSteamUser.prototype.unfollow = function(callback) {
this._community.unfollowUser(this.steamID, callback);
};
CSteamUser.prototype.getAliases = function(callback) {
this._community.getUserAliases(this.steamID, callback);
};

View File

@@ -1,283 +0,0 @@
var SteamCommunity = require('../index.js');
var SteamID = require('steamid');
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.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;
}
self.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Logon/v1",
"form": {
"ui_mode": uiMode,
"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));
setTimeout(self.chatLogon.bind(self), 5000);
return;
}
if(body.error != 'OK') {
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": 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';
var self = this;
this.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Message/v1",
"form": {
"access_token": this._chat.accessToken,
"steamid_dst": recipient.toString(),
"text": text,
"type": type,
"umqid": this._chat.umqid
},
"json": true
}, function(err, response, body) {
if(!callback) {
return;
}
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.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Logoff/v1",
"form": {
"access_token": this._chat.accessToken,
"umqid": this._chat.umqid
}
}, function(err, response, body) {
if(err || response.statusCode != 200) {
self.emit('debug', 'Error logging off of chat: ' + (err ? err.message : "HTTP error " + response.statusCode));
setTimeout(self.chatLogoff.bind(self), 1000);
} else {
self.emit('chatLoggedOff');
clearTimeout(self._chat.timer);
delete self._chat;
delete self.chatFriends;
self.chatState = SteamCommunity.ChatState.Offline;
}
}, "steamcommunity");
};
/**
* @private
*/
SteamCommunity.prototype._chatPoll = function() {
this.emit('debug', 'Doing chat poll');
var self = this;
this.httpRequestPost({
"uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Poll/v1",
"form": {
"umqid": self._chat.umqid,
"message": self._chat.message,
"pollid": 1,
"sectimeout": 20,
"secidletime": 0,
"use_accountids": 1,
"access_token": self._chat.accessToken
},
"json": true
}, function(err, response, body) {
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 || 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.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() {
self._chatUpdatePersona(steamID);
}, 2000);
return;
}
var persona = {
"steamID": steamID,
"personaName": body.m_strName,
"personaState": body.m_ePersonaState,
"personaStateFlags": body.m_nPersonaStateFlags || 0,
"avatarHash": body.m_strAvatarHash,
"inGame": !!body.m_bInGame,
"inGameAppID": body.m_nInGameAppID ? parseInt(body.m_nInGameAppID, 10) : null,
"inGameName": body.m_strInGameName || null
};
self.emit('chatPersonaState', steamID, persona);
self.chatFriends[steamID.getSteamID64()] = persona;
}, "steamcommunity");
};

View File

@@ -1,47 +1,34 @@
var SteamCommunity = require('../index.js');
var Cheerio = require('cheerio');
var SteamTotp = require('steam-totp');
var Async = require('async');
const Cheerio = require('cheerio');
const StdLib = require('@doctormckay/stdlib');
const SteamTotp = require('steam-totp');
var CConfirmation = require('../classes/CConfirmation.js');
var EConfirmationType = require('../resources/EConfirmationType.js');
const SteamCommunity = require('../index.js');
const CConfirmation = require('../classes/CConfirmation.js');
const EConfirmationType = SteamCommunity.EConfirmationType;
/**
* 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
* @param {SteamCommunity~getConfirmations} [callback] - Called when the list of confirmations is received
* @return Promise<{confirmations: CConfirmation[]}>
*/
SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
var self = this;
// Ugly hack to maintain backward compatibility
var tag = 'conf';
if (typeof key == 'object') {
tag = key.tag;
key = key.key;
}
// The official Steam app uses the tag 'list', but 'conf' still works so let's use that for backward compatibility.
request(this, 'getlist', key, time, tag, null, true, function(err, body) {
if (err) {
callback(err);
return;
}
return StdLib.Promises.callbackPromise(['confirmations'], callback, false, async (resolve, reject) => {
let body = await request(this, 'getlist', key, time, 'list', null);
if (!body.success) {
if (body.needauth) {
var err = new Error('Not Logged In');
self._notifySessionExpired(err);
callback(err);
return;
let err = new Error('Not Logged In');
this._notifySessionExpired(err);
return reject(err);
}
callback(new Error(body.message || body.detail || 'Failed to get confirmation list'));
return;
return reject(new Error(body.message || body.detail || 'Failed to get confirmation list'));
}
var confs = (body.conf || []).map(conf => new CConfirmation(self, {
let confs = (body.conf || []).map(conf => new CConfirmation(this, {
id: conf.id,
type: conf.type,
creator: conf.creator_id,
@@ -54,14 +41,14 @@ SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
icon: conf.icon || ''
}));
callback(null, confs);
resolve({confirmations: 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
* @param {CConfirmation[]} [confirmations] - An array of CConfirmation objects
*/
/**
@@ -69,29 +56,24 @@ SteamCommunity.prototype.getConfirmations = function(time, key, callback) {
* @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 "detail" (this key can be reused)
* @param {SteamCommunity~getConfirmationOfferID} callback
* @param {SteamCommunity~getConfirmationOfferID} [callback]
* @return Promise<{offerID: string|null}>
*/
SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, callback) {
// The official Steam app uses the tag 'detail', but 'details' still works so let's use that for backward compatibility
request(this, 'detailspage/' + confID, key, time, 'details', null, false, function(err, body) {
if (err) {
callback(err);
return;
}
return StdLib.Promises.callbackPromise(['offerID'], callback, false, async (resolve, reject) => {
let body = await request(this, 'detailspage/' + confID, key, time, 'detail', null);
if (typeof body != 'string') {
callback(new Error("Cannot load confirmation details"));
return;
return reject(new Error('Cannot load confirmation details'));
}
var $ = Cheerio.load(body);
var offer = $('.tradeoffer');
if(offer.length < 1) {
callback(null, null);
return;
let $ = Cheerio.load(body);
let offer = $('.tradeoffer');
if (offer.length < 1) {
return resolve({offerID: null});
}
callback(null, offer.attr('id').split('_')[1]);
resolve({offerID: offer.attr('id').split('_')[1]});
});
};
@@ -103,47 +85,30 @@ SteamCommunity.prototype.getConfirmationOfferID = function(confID, time, key, ca
/**
* Confirm or cancel a given confirmation.
* @param {int|int[]} confID - The ID of the confirmation in question, or an array of confirmation IDs
* @param {int|int[]|string|string[]} 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
* @param {SteamCommunity~genericErrorCallback} [callback] - Called when the request is complete
* @return Promise<void>
*/
SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time, key, accept, callback) {
// Ugly hack to maintain backward compatibility
var tag = accept ? 'allow' : 'cancel';
if (typeof key == 'object') {
tag = key.tag;
key = key.key;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
let tag = accept ? 'accept' : 'reject';
// The official app uses tags reject/accept, but cancel/allow still works so use these for backward compatibility
request(this, (confID instanceof Array) ? 'multiajaxop' : 'ajaxop', key, time, tag, {
op: accept ? 'allow' : 'cancel',
cid: confID,
ck: confKey
}, true, function(err, body) {
if (!callback) {
return;
}
if (err) {
callback(err);
return;
}
// The official app uses tags reject/accept, but cancel/allow still works so use these for backward compatibility
let body = await request(this, (confID instanceof Array) ? 'multiajaxop' : 'ajaxop', key, time, tag, {
op: accept ? 'allow' : 'cancel',
cid: confID,
ck: confKey
});
if (body.success) {
callback(null);
return;
return resolve();
}
if (body.message) {
callback(new Error(body.message));
return;
}
callback(new Error('Could not act on confirmation'));
reject(new Error(body.message || body.detail || 'Could not act on confirmation'));
});
};
@@ -151,101 +116,79 @@ SteamCommunity.prototype.respondToConfirmation = function(confID, confKey, time,
* Accept a confirmation for a given object (trade offer or market listing) automatically.
* @param {string} identitySecret
* @param {number|string} objectID
* @param {SteamCommunity~genericErrorCallback} callback
* @param {SteamCommunity~genericErrorCallback} [callback]
* @return Promise<void>
*/
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;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
// Figure out our time offset
if (typeof this._timeOffset == 'undefined') {
await new Promise((resolve) => {
SteamTotp.getTimeOffset((err, offset) => {
if (err) {
// not critical that this succeeds
return resolve();
}
self._timeOffset = offset;
doConfirmation();
this._timeOffset = offset;
resolve();
});
});
}
setTimeout(function() {
// Delete the saved time offset after 12 hours because why not
delete self._timeOffset;
}, 1000 * 60 * 60 * 12).unref();
});
}
let offset = this._timeOffset;
let time = SteamTotp.time(offset);
let key = SteamTotp.getConfirmationKey(identitySecret, time, 'list');
let {confirmations} = await this.getConfirmations(time, key);
function doConfirmation() {
var offset = self._timeOffset;
var time = SteamTotp.time(offset);
var confKey = SteamTotp.getConfirmationKey(identitySecret, time, 'list');
self.getConfirmations(time, {tag: 'list', key: confKey}, function(err, confs) {
if (err) {
callback(err);
return;
}
let conf = confirmations.find(conf => conf.creator == objectID);
if (!conf) {
return reject(new Error(`Could not find confirmation for object ${objectID}`));
}
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;
}
// make sure we don't reuse the same time
let localOffset = 0;
do {
time = SteamTotp.time(offset) + localOffset++;
} while (this._usedConfTimes.includes(time));
conf = conf[0];
this._usedConfTimes.push(time);
if (this._usedConfTimes.length > 60) {
this._usedConfTimes.splice(0, this._usedConfTimes.length - 60); // we don't need to save more than 60 entries
}
// 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
}
confKey = SteamTotp.getConfirmationKey(identitySecret, time, 'accept');
conf.respond(time, {tag: 'accept', key: confKey}, true, callback);
});
}
await conf.respond(time, SteamTotp.getConfirmationKey(identitySecret, time, 'accept'), true);
});
};
/**
* 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
* @param {string} listKey
* @param {string} acceptKey
* @param {function} [callback]
* @return Promise<{confirmations: CConfirmation[]}>
*/
SteamCommunity.prototype.acceptAllConfirmations = function(time, confKey, allowKey, callback) {
var self = this;
SteamCommunity.prototype.acceptAllConfirmations = function(time, listKey, acceptKey, callback) {
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
let {confirmations} = await this.getConfirmations(time, listKey);
this.getConfirmations(time, confKey, function(err, confs) {
if (err) {
callback(err);
return;
if (confirmations.length == 0) {
return resolve({confirmations: []});
}
if (confs.length == 0) {
callback(null, []);
return;
}
let confIds = confirmations.map(conf => conf.id);
let confKeys = confirmations.map(conf => conf.key);
await this.respondToConfirmation(confIds, confKeys, time, acceptKey, true);
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);
});
resolve({confirmations});
});
};
function request(community, url, key, time, tag, params, json, callback) {
async function request(community, url, key, time, tag, params) {
if (!community.steamID) {
throw new Error('Must be logged in before trying to do anything with confirmations');
}
@@ -258,10 +201,10 @@ function request(community, url, key, time, tag, params, json, callback) {
params.m = 'react';
params.tag = tag;
var req = {
let req = {
method: url == 'multiajaxop' ? 'POST' : 'GET',
uri: 'https://steamcommunity.com/mobileconf/' + url,
json: !!json
url: `https://steamcommunity.com/mobileconf/${url}`,
source: 'steamcommunity'
};
if (req.method == 'GET') {
@@ -270,159 +213,6 @@ function request(community, url, key, time, tag, params, json, callback) {
req.form = params;
}
community.httpRequest(req, function(err, response, body) {
if (err) {
callback(err);
return;
}
callback(null, body);
}, 'steamcommunity');
let result = await community.httpRequest(req);
return result.jsonBody || result.textBody;
}
// 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});
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,32 @@
const SteamCommunity = require('../index.js');
const StdLib = require('@doctormckay/stdlib');
// eslint-disable-next-line no-unused-vars
const {HttpResponse} = require('@doctormckay/stdlib/http');
const Helpers = require('./helpers.js');
const SteamCommunity = require('../index.js');
const HELP_SITE_DOMAIN = 'https://help.steampowered.com';
/**
* Restore a previously removed steam package from your steam account.
* @param {int|string} packageID
* @param {function} callback
* @param {function} [callback]
* @return Promise<void>
*/
SteamCommunity.prototype.restorePackage = function(packageID, callback) {
this.httpRequestPost({
uri: HELP_SITE_DOMAIN + '/wizard/AjaxDoPackageRestore',
form: {
packageid: packageID,
sessionid: this.getSessionID(HELP_SITE_DOMAIN),
wizard_ajax: 1
},
json: true
}, wizardAjaxHandler(callback));
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
let result = await this.httpRequest({
method: 'POST',
url: `${HELP_SITE_DOMAIN}/wizard/AjaxDoPackageRestore`,
form: {
packageid: packageID,
sessionid: this.getSessionID(HELP_SITE_DOMAIN),
wizard_ajax: 1
},
source: 'steamcommunity'
});
wizardAjaxHandler(result, resolve, reject);
});
};
/**
@@ -27,38 +35,32 @@ SteamCommunity.prototype.restorePackage = function(packageID, callback) {
* @param {function} callback
*/
SteamCommunity.prototype.removePackage = function(packageID, callback) {
this.httpRequestPost({
uri: HELP_SITE_DOMAIN + '/wizard/AjaxDoPackageRemove',
form: {
packageid: packageID,
sessionid: this.getSessionID(HELP_SITE_DOMAIN),
wizard_ajax: 1
},
json: true
}, wizardAjaxHandler(callback));
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
let result = await this.httpRequest({
method: 'POST',
url: `${HELP_SITE_DOMAIN}/wizard/AjaxDoPackageRemove`,
form: {
packageid: packageID,
sessionid: this.getSessionID(HELP_SITE_DOMAIN),
wizard_ajax: 1
},
source: 'steamcommunity'
});
wizardAjaxHandler(result, resolve, reject);
});
};
/**
* Returns a handler for wizard ajax HTTP requests.
* @param {function} callback
* @returns {(function(*=, *, *): void)|*}
*
* @param {HttpResponse} result
* @param {function} resolve
* @param {function} reject
*/
function wizardAjaxHandler(callback) {
return (err, res, body) => {
if (!callback) {
return;
}
function wizardAjaxHandler(result, resolve, reject) {
if (!result.jsonBody || !result.jsonBody.success) {
return reject(new Error((result.jsonBody || {}).errorMsg || 'Unexpected error'));
}
if (err) {
callback(err);
return;
}
if (!body.success) {
callback(body.errorMsg ? new Error(body.errorMsg) : Helpers.eresultError(body.success));
return;
}
callback(null);
};
resolve();
}

View File

@@ -1,41 +1,32 @@
const request = require('request');
const SteamID = require('steamid');
const xml2js = require('xml2js');
const EResult = require('../resources/EResult.js');
/**
* Make sure that a provided input is a valid SteamID object.
* @param {object} input
* @returns {boolean}
*/
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;
return ['universe', 'type', 'instance', 'accountid'].every(prop => typeof input[prop] == 'number' || typeof input[prop] == 'bigint');
};
exports.decodeSteamTime = function(time) {
var date = new Date();
let date = new Date();
if (time.includes("@")) {
var parts = time.split('@');
if (!parts[0].includes(",")) {
if (time.includes('@')) {
let parts = time.split('@');
if (!parts[0].includes(',')) {
// no year, assume current year
parts[0] += ", " + date.getFullYear();
parts[0] += ', ' + date.getFullYear();
}
date = new Date(parts.join('@').replace(/(am|pm)/, ' $1') + " UTC"); // add a space so JS can decode it
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");
let amount = time.replace(/(\d) (minutes|hour|hours) ago/, '$1');
if(time.includes("minutes")) {
if (time.includes('minutes')) {
date.setMinutes(date.getMinutes() - amount);
} else if(time.match(/hour|hours/)) {
} else if (time.match(/hour|hours/)) {
date.setHours(date.getHours() - amount);
}
}
@@ -45,16 +36,17 @@ exports.decodeSteamTime = function(time) {
/**
* Get an Error object for a particular EResult
* @param {int} eresult
* @param {int|EResult} eresult
* @param {string} [message] - If eresult is a failure code and message exists, this message will be used in the Error object instead
* @returns {null|Error}
*/
exports.eresultError = function(eresult) {
exports.eresultError = function(eresult, message) {
if (eresult == EResult.OK) {
// no error
return null;
}
var err = new Error(EResult[eresult] || ("Error " + eresult));
let err = new Error(message || EResult[eresult] || `Error ${eresult}`);
err.eresult = eresult;
return err;
};
@@ -70,59 +62,3 @@ exports.decodeJwt = function(jwt) {
return JSON.parse(Buffer.from(standardBase64, 'base64').toString('utf8'));
};
/**
* Resolves a Steam profile URL to get steamID64 and vanityURL
* @param {String} url - Full steamcommunity profile URL or only the vanity part.
* @param {Object} callback - First argument is null/Error, second is object containing vanityURL (String) and steamID (String)
*/
exports.resolveVanityURL = function(url, callback) {
// Precede url param if only the vanity was provided
if (!url.includes("steamcommunity.com")) {
url = "https://steamcommunity.com/id/" + url;
}
// Make request to get XML data
request(url + "/?xml=1", function(err, response, body) {
if (err) {
callback(err);
return;
}
// Parse XML data returned from Steam into an object
new xml2js.Parser().parseString(body, (err, parsed) => {
if (err) {
callback(new Error("Couldn't parse XML response"));
return;
}
if (parsed.response && parsed.response.error) {
callback(new Error("Couldn't find Steam ID"));
return;
}
let steamID64 = parsed.profile.steamID64[0];
let vanityURL = parsed.profile.customURL[0];
callback(null, {"vanityURL": vanityURL, "steamID": steamID64});
});
});
};
/**
* Converts `input` into a SteamID object, if it's a parseable string.
* @param {SteamID|string} input
* @return {SteamID}
*/
exports.steamID = function(input) {
if (exports.isSteamID(input)) {
return input;
}
if (typeof input != 'string') {
throw new Error(`Input SteamID value "${input}" is not a string`);
}
// This will throw if the input is not a well-formed SteamID
return new SteamID(input);
};

View File

@@ -1,144 +1,166 @@
var URL = require('url');
const {HttpResponse} = require('@doctormckay/stdlib/http'); // eslint-disable-line
const {betterPromise} = require('@doctormckay/stdlib/promises');
var SteamCommunity = require('../index.js');
const 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 = {};
}
/**
* @param {object} options
* @param {string} options.method
* @param {string} options.url
* @param {string} [options.source='']
* @param {object} [options.qs]
* @param {*} [options.body]
* @param {object} [options.form]
* @param {object} [options.multipartForm]
* @param {boolean} [options.json=false] - Controls whether the *REQUEST* should be sent as json.
* @param {boolean} [options.followRedirect=true]
* @param {boolean} [options.checkHttpError=true]
* @param {boolean} [options.checkCommunityError=true]
* @param {boolean} [options.checkTradeError=true]
* @param {boolean} [options.checkJsonError=true]
* @return {Promise<HttpResponse>}
*/
SteamCommunity.prototype.httpRequest = function(options) {
return betterPromise(async (resolve, reject) => {
let requestID = ++this._httpRequestID;
let source = options.source || '';
options.url = options.uri = uri;
if (this._httpRequestConvenienceMethod) {
options.method = this._httpRequestConvenienceMethod;
delete this._httpRequestConvenienceMethod;
}
// Add origin header if necessary
// https://github.com/DoctorMcKay/node-steamcommunity/issues/351
if ((options.method || 'GET').toUpperCase() != 'GET') {
options.headers = options.headers || {};
if (!options.headers.origin) {
var parsedUrl = URL.parse(options.url);
options.headers.origin = parsedUrl.protocol + '//' + parsedUrl.host;
}
}
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);
}
await betterPromise((resolve, reject) => {
if (!this.onPreHttpRequest || !this.onPreHttpRequest(requestID, source, options, (err) => {
err ? reject(err) : resolve();
})) {
// No pre-hook, or the pre-hook doesn't want to delay the request.
resolve();
}
});
let result = await this._httpClient.request({
method: options.method,
url: options.url,
queryString: options.qs,
headers: options.headers,
body: options.body,
urlEncodedForm: options.form,
multipartForm: options.multipartForm,
json: options.json,
followRedirects: options.followRedirect
});
let httpError = options.checkHttpError !== false && this._checkHttpError(result);
let communityError = !options.json && options.checkCommunityError !== false && this._checkCommunityError(result);
let tradeError = !options.json && options.checkTradeError !== false && this._checkTradeError(result);
let jsonError = options.json && options.checkJsonError !== false && !result.jsonBody ? new Error('Malformed JSON response') : null;
this.emit('postHttpRequest', {
requestID,
source,
options,
response: result,
body: result.textBody,
error: httpError || communityError || tradeError || jsonError || null,
httpError,
communityError,
tradeError,
jsonError
});
resolve(result);
});
};
/**
* @param {string|object} endpoint
* @param {object} [form]
* @private
*/
SteamCommunity.prototype._myProfile = async function(endpoint, form) {
if (!this._profileURL) {
let result = await this.httpRequest({
method: 'GET',
url: 'https://steamcommunity.com/my',
followRedirect: false,
source: 'steamcommunity'
});
if (result.statusCode != 302) {
throw new Error(`HTTP error ${result.statusCode}`);
}
let match = result.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^/]+)\/?/);
if (!match) {
throw new Error('Can\'t get profile URL');
}
this._profileURL = match[1];
setTimeout(() => {
delete this._profileURL; // delete the cache
}, 60000).unref();
}
};
SteamCommunity.prototype.httpRequestGet = function() {
this._httpRequestConvenienceMethod = "GET";
return this.httpRequest.apply(this, arguments);
};
let options = endpoint.endpoint ? endpoint : {};
options.url = `https://steamcommunity.com${this._profileURL}/${endpoint.endpoint || endpoint}`;
options.followRedirect = true;
SteamCommunity.prototype.httpRequestPost = function() {
this._httpRequestConvenienceMethod = "POST";
return this.httpRequest.apply(this, arguments);
if (form) {
options.method = 'POST';
options.form = form;
} else if (!options.method) {
options.method = 'GET';
}
options.source = 'steamcommunity';
return await this.httpRequest(options);
};
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;
}
/**
* @param {HttpResponse} response
* @return {Error|boolean}
* @private
*/
SteamCommunity.prototype._checkHttpError = function(response) {
if (response.statusCode >= 300 && response.statusCode <= 399 && response.headers.location.indexOf('/login') != -1) {
err = new Error("Not Logged In");
callback(err, response, body);
let err = new Error('Not Logged In');
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 == 403
&& typeof response.textBody == 'string'
&& response.textBody.match(/<div id="parental_notice_instructions">Enter your PIN below to exit Family View.<\/div>/)
) {
return new Error('Family View Restricted');
}
if (response.statusCode >= 400) {
err = new Error("HTTP error " + response.statusCode);
let 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;
/**
* @param {HttpResponse} response
* @return {Error|boolean}
* @private
*/
SteamCommunity.prototype._checkCommunityError = function(response) {
let html = response.textBody;
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(/<h1>Sorry!<\/h1>/)) {
let match = html.match(/<h3>(.+)<\/h3>/);
return new Error(match ? match[1] : 'Unknown error occurred');
}
if (typeof html === 'string' && html.indexOf('g_steamID = false;') > -1 && html.indexOf('<title>Sign In</title>') > -1) {
err = new Error("Not Logged In");
callback(err);
if (typeof html == 'string' && html.indexOf('g_steamID = false;') > -1 && html.indexOf('<title>Sign In</title>') > -1) {
let err = new Error('Not Logged In');
this._notifySessionExpired(err);
return err;
}
@@ -146,16 +168,21 @@ SteamCommunity.prototype._checkCommunityError = function(html, callback) {
return false;
};
SteamCommunity.prototype._checkTradeError = function(html, callback) {
/**
* @param {HttpResponse} response
* @return {Error|boolean}
* @private
*/
SteamCommunity.prototype._checkTradeError = function(response) {
let html = response.textBody;
if (typeof html !== 'string') {
return false;
}
var match = html.match(/<div id="error_msg">\s*([^<]+)\s*<\/div>/);
let match = html.match(/<div id="error_msg">\s*([^<]+)\s*<\/div>/);
if (match) {
var err = new Error(match[1].trim());
callback(err);
return err;
return new Error(match[1].trim());
}
return false;

View File

@@ -1,173 +0,0 @@
var SteamCommunity = require('../index.js');
var CEconItem = require('../classes/CEconItem.js');
var Helpers = require('./helpers.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, Helpers.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");
};

View File

@@ -1,110 +0,0 @@
const {chrome} = require('@doctormckay/user-agents');
const SteamCommunity = require('../index.js');
/**
* @typedef LogOnDetails
* @property {string} accountName
* @property {string} password
* @property {string} [steamguard]
* @property {string} [authCode]
* @property {string} [twoFactorCode]
* @property {boolean} disableMobile
*/
/**
* @typedef LogOnResponse
* @property {string} sessionID
* @property {string[]} cookies
* @property {string} steamguard
* @property {string} [mobileAccessToken]
*/
/**
*
* @param {LogOnDetails} logOnDetails
* @returns {Promise<LogOnResponse>}
* @private
*/
SteamCommunity.prototype._modernLogin = function(logOnDetails) {
return new Promise(async (resolve, reject) => {
if (!isNodeVersionNewEnough()) {
return reject(new Error(`Node.js version is too old! Need >=12.22.0 or later, got ${process.versions.node}.`));
}
if (this._options.request) {
return reject(new Error('SteamCommunity.login() is incompatible with node-steamcommunity v3\'s usage of \'request\'. If you need to specify a custom \'request\' instance (e.g. when using a proxy), use https://www.npmjs.com/package/steam-session directly to log onto Steam.'));
}
// Import this here so we don't cause problems on old Node versions if this code path isn't taken.
const {LoginSession, EAuthTokenPlatformType, EAuthSessionGuardType} = require('steam-session');
let session = new LoginSession(
logOnDetails.disableMobile
? EAuthTokenPlatformType.WebBrowser
: EAuthTokenPlatformType.MobileApp,
{
localAddress: this._options.localAddress,
userAgent: this._options.userAgent || chrome()
}
);
session.on('authenticated', async () => {
try {
let webCookies = await session.getWebCookies();
let sessionIdCookie = webCookies.find(c => c.startsWith('sessionid='));
resolve({
sessionID: sessionIdCookie.split('=')[1].split(';')[0].trim(),
cookies: webCookies,
steamguard: session.steamGuardMachineToken,
mobileAccessToken: logOnDetails.disableMobile ? null : session.accessToken
});
} catch (ex) {
reject(ex);
}
});
session.on('error', (err) => {
reject(err);
});
try {
let startResult = await session.startWithCredentials({
accountName: logOnDetails.accountName,
password: logOnDetails.password,
steamGuardMachineToken: logOnDetails.steamguard,
steamGuardCode: logOnDetails.authCode || logOnDetails.twoFactorCode
});
if (startResult.actionRequired) {
// Cannot continue with login, need something from the user
session.cancelLoginAttempt();
let emailMfaAction = startResult.validActions.find(action => action.type == EAuthSessionGuardType.EmailCode);
if (emailMfaAction) {
let err = new Error('SteamGuard');
err.emaildomain = emailMfaAction.detail;
return reject(err);
}
return reject(new Error('SteamGuardMobile'));
}
} catch (ex) {
return reject(ex);
}
});
};
function isNodeVersionNewEnough() {
let [major, minor] = process.versions.node.split('.');
if (major < 12) {
return false;
}
if (major == 12 && minor < 22) {
return false;
}
return true;
}

View File

@@ -1,6 +1,6 @@
const SteamCommunity = require('../index.js');
const Cheerio = require('cheerio');
const SteamCommunity = require('../index.js');
const Helpers = require('./helpers.js');
/**
@@ -8,28 +8,27 @@ const Helpers = require('./helpers.js');
* @param {function} callback - First argument is null|Error, second is an object of appid => name
*/
SteamCommunity.prototype.getMarketApps = function(callback) {
var self = this;
this.httpRequest('https://steamcommunity.com/market/', function (err, response, body) {
this.httpRequest('https://steamcommunity.com/market/', (err, response, body) => {
if (err) {
callback(err);
return;
}
var $ = Cheerio.load(body);
let $ = Cheerio.load(body);
if ($('.market_search_game_button_group')) {
let apps = {};
$('.market_search_game_button_group a.game_button').each(function (i, element) {
var e = Cheerio.load(element);
var name = e('.game_button_game_name').text().trim();
var url = element.attribs.href;
var appid = url.substr(url.indexOf('=') + 1);
$('.market_search_game_button_group a.game_button').each((i, element) => {
let e = Cheerio.load(element);
let name = e('.game_button_game_name').text().trim();
let url = element.attribs.href;
let appid = url.substr(url.indexOf('=') + 1);
apps[appid] = name;
});
callback(null, apps);
} else {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
}
}, "steamcommunity");
}, 'steamcommunity');
};
/**
@@ -40,34 +39,32 @@ SteamCommunity.prototype.getMarketApps = function(callback) {
*/
SteamCommunity.prototype.getGemValue = function(appid, assetid, callback) {
this._myProfile({
"endpoint": "ajaxgetgoovalue/",
"qs": {
"sessionid": this.getSessionID(),
"appid": appid,
"contextid": 6,
"assetid": assetid
endpoint: 'ajaxgetgoovalue/',
qs: {
sessionid: this.getSessionID(),
appid: appid,
contextid: 6,
assetid: assetid
},
"checkHttpError": false,
"json": true
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;
let err2 = Helpers.eresultError(body.success, body.message);
if (err2) {
return callback(err2);
}
if (!body.goo_value || !body.strTitle) {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
return;
}
callback(null, {"promptTitle": body.strTitle, "gemValue": parseInt(body.goo_value, 10)});
callback(null, {promptTitle: body.strTitle, gemValue: parseInt(body.goo_value, 10)});
});
};
@@ -80,35 +77,33 @@ SteamCommunity.prototype.getGemValue = function(appid, assetid, callback) {
*/
SteamCommunity.prototype.turnItemIntoGems = function(appid, assetid, expectedGemsValue, callback) {
this._myProfile({
"endpoint": "ajaxgrindintogoo/",
"json": true,
"checkHttpError": false
endpoint: 'ajaxgrindintogoo/',
json: true,
checkHttpError: false
}, {
"appid": appid,
"contextid": 6,
"assetid": assetid,
"goo_value_expected": expectedGemsValue,
"sessionid": this.getSessionID()
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);
let err2 = Helpers.eresultError(body.success, body.message);
if (err2) {
return callback(err2);
}
if (!body['goo_value_received '] || !body.goo_value_total) { // lol valve, that trailing space is real
callback(new Error('Malformed response'));
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)});
})
callback(null, {gemsReceived: parseInt(body['goo_value_received '], 10), totalGems: parseInt(body.goo_value_total, 10)});
});
};
/**
@@ -119,33 +114,31 @@ SteamCommunity.prototype.turnItemIntoGems = function(appid, assetid, expectedGem
*/
SteamCommunity.prototype.openBoosterPack = function(appid, assetid, callback) {
this._myProfile({
"endpoint": "ajaxunpackbooster/",
"json": true,
"checkHttpError": false
endpoint: 'ajaxunpackbooster/',
json: true,
checkHttpError: false
}, {
"appid": appid,
"communityitemid": assetid,
"sessionid": this.getSessionID()
appid: appid,
communityitemid: assetid,
sessionid: this.getSessionID()
}, (err, res, body) => {
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
let err = new Error(body.message || SteamCommunity.EResult[body.success]);
err.eresult = err.code = body.success;
callback(err);
return;
let err2 = Helpers.eresultError(body.success, body.message);
if (err2) {
return callback(err2);
}
if (!body.rgItems) {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
return;
}
callback(null, body.rgItems);
})
});
};
/**
@@ -228,7 +221,7 @@ SteamCommunity.prototype.createBoosterPack = function(appid, useUntradableGems,
}
this.httpRequestPost({
uri: 'https://steamcommunity.com/tradingcards/ajaxcreatebooster/',
url: 'https://steamcommunity.com/tradingcards/ajaxcreatebooster/',
form: {
sessionid: this.getSessionID(),
appid,
@@ -254,6 +247,7 @@ SteamCommunity.prototype.createBoosterPack = function(appid, useUntradableGems,
// We can now check HTTP status codes
if (this._checkHttpError(err, res, callback, body)) {
// TODO v4
return;
}
@@ -273,33 +267,31 @@ SteamCommunity.prototype.createBoosterPack = function(appid, useUntradableGems,
*/
SteamCommunity.prototype.getGiftDetails = function(giftID, callback) {
this.httpRequestPost({
"uri": "https://steamcommunity.com/gifts/" + giftID + "/validateunpack",
"form": {
"sessionid": this.getSessionID()
url: `https://steamcommunity.com/gifts/${giftID}/validateunpack`,
form: {
sessionid: this.getSessionID()
},
"json": true
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;
let err2 = Helpers.eresultError(body.success, body.message);
if (err2) {
return callback(err2);
}
if (!body.packageid || !body.gift_name) {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
return;
}
callback(null, {
"giftName": body.gift_name,
"packageID": parseInt(body.packageid, 10),
"owned": body.owned
giftName: body.gift_name,
packageID: parseInt(body.packageid, 10),
owned: body.owned
});
});
};
@@ -311,23 +303,20 @@ SteamCommunity.prototype.getGiftDetails = function(giftID, callback) {
*/
SteamCommunity.prototype.redeemGift = function(giftID, callback) {
this.httpRequestPost({
"uri": "https://steamcommunity.com/gifts/" + giftID + "/unpack",
"form": {
"sessionid": this.getSessionID()
url: `https://steamcommunity.com/gifts/${giftID}/unpack`,
form: {
sessionid: this.getSessionID()
},
"json": true
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;
let err2 = Helpers.eresultError(body.success, body.message);
if (err2) {
return callback(err2);
}
callback(null);

View File

@@ -1,40 +1,46 @@
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,
"FriendsOnly": 2,
"Public": 3
Private: 1,
FriendsOnly: 2,
Public: 3
};
var CommentPrivacyState = {
"1": 2, // private
"2": 0, // friends only
"3": 1 // anyone
const CommentPrivacyState = {
1: 2, // private
2: 0, // friends only
3: 1 // anyone
};
/**
* Creates a profile page if you don't already have one.
* @param {function} callback
*/
SteamCommunity.prototype.setupProfile = function(callback) {
var self = this;
this._myProfile("edit?welcomed=1", null, function(err, response, body) {
if(!callback) {
this._myProfile('edit?welcomed=1', null, (err, response, body) => {
if (!callback) {
return;
}
if(err || response.statusCode != 200) {
callback(err || new Error("HTTP error " + response.statusCode));
if (err || response.statusCode != 200) {
callback(err || new Error('HTTP error ' + response.statusCode));
} else {
callback(null);
}
});
};
/**
* Edits your profile details.
* @param {object} settings
* @param {function} callback
*/
SteamCommunity.prototype.editProfile = function(settings, callback) {
var self = this;
this._myProfile('edit/info', null, function(err, response, body) {
this._myProfile('edit/info', null, (err, response, body) => {
if (err || response.statusCode != 200) {
if (callback) {
callback(err || new Error('HTTP error ' + response.statusCode));
@@ -43,8 +49,8 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
return;
}
var $ = Cheerio.load(body);
var existingSettings = $('#profile_edit_config').data('profile-edit');
let $ = Cheerio.load(body);
let existingSettings = $('#profile_edit_config').data('profile-edit');
if (!existingSettings || !existingSettings.strPersonaName) {
if (callback) {
callback(new Error('Malformed response'));
@@ -53,8 +59,8 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
return;
}
var values = {
sessionID: self.getSessionID(),
let values = {
sessionID: this.getSessionID(),
type: 'profileSave',
weblink_1_title: '',
weblink_1_url: '',
@@ -72,12 +78,8 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
json: 1
};
for (var i in settings) {
if(!settings.hasOwnProperty(i)) {
continue;
}
switch(i) {
for (let i in settings) {
switch (i) {
case 'name':
values.personaName = settings[i];
break;
@@ -131,9 +133,9 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
}
}
self._myProfile('edit', values, function(err, response, body) {
this._myProfile('edit', values, (err, response, body) => {
if (settings.customURL) {
delete self._profileURL;
delete this._profileURL;
}
if (!callback) {
@@ -146,10 +148,10 @@ SteamCommunity.prototype.editProfile = function(settings, callback) {
}
try {
var json = JSON.parse(body);
if (!json.success || json.success != 1) {
callback(new Error(json.errmsg || 'Request was not successful'));
return;
let json = JSON.parse(body);
let err2 = Helpers.eresultError(json.success, json.errmsg);
if (err2) {
return callback(err2);
}
callback(null);
@@ -170,8 +172,8 @@ SteamCommunity.prototype.profileSettings = function(settings, callback) {
return;
}
var $ = Cheerio.load(body);
var existingSettings = $('#profile_edit_config').data('profile-edit');
let $ = Cheerio.load(body);
let existingSettings = $('#profile_edit_config').data('profile-edit');
if (!existingSettings || !existingSettings.Privacy) {
if (callback) {
callback(new Error('Malformed response'));
@@ -182,14 +184,10 @@ SteamCommunity.prototype.profileSettings = function(settings, callback) {
// PrivacySettings => {PrivacyProfile, PrivacyInventory, PrivacyInventoryGifts, PrivacyOwnedGames, PrivacyPlaytime}
// eCommentPermission
var privacy = existingSettings.Privacy.PrivacySettings;
var commentPermission = existingSettings.Privacy.eCommentPermission;
for (var i in settings) {
if (!settings.hasOwnProperty(i)) {
continue;
}
let privacy = existingSettings.Privacy.PrivacySettings;
let commentPermission = existingSettings.Privacy.eCommentPermission;
for (let i in settings) {
switch (i) {
case 'profile':
privacy.PrivacyProfile = settings[i];
@@ -230,7 +228,7 @@ SteamCommunity.prototype.profileSettings = function(settings, callback) {
Privacy: JSON.stringify(privacy),
eCommentPermission: commentPermission
}
}, null, function(err, response, body) {
}, null, (err, response, body) => {
if (err || response.statusCode != 200) {
if (callback) {
callback(err || new Error('HTTP error ' + response.statusCode));
@@ -239,11 +237,9 @@ SteamCommunity.prototype.profileSettings = function(settings, callback) {
return;
}
if (body.success != 1) {
if (callback) {
callback(new Error(body.success ? 'Error ' + body.success : 'Request was not successful'));
}
let err2 = Helpers.eresultError(body.success);
if (err2) {
callback && callback(err2);
return;
}
@@ -255,78 +251,34 @@ SteamCommunity.prototype.profileSettings = function(settings, callback) {
};
SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
if(typeof format === 'function') {
if (typeof format === 'function') {
callback = format;
format = null;
}
// are we logged in?
if (!this.steamID) {
callback(new Error("Not Logged In"));
callback(new Error('Not Logged In'));
return;
}
var self = this;
if(image instanceof Buffer) {
doUpload(image);
} else if(image.match(/^https?:\/\//)) {
this.httpRequestGet({
"uri": image,
"encoding": null
}, function(err, response, body) {
if(err || response.statusCode != 200) {
if(callback) {
callback(err ? new Error(err.message + " downloading image") : new Error("HTTP error " + response.statusCode + " downloading image"));
}
return;
}
if(!format) {
format = response.headers['content-type'];
}
doUpload(body);
}, "steamcommunity");
} else {
if(!format) {
format = image.match(/\.([^\.]+)$/);
if(format) {
format = format[1];
}
}
FS.readFile(image, function(err, file) {
if(err) {
if(callback) {
callback(err);
}
return;
}
doUpload(file);
})
}
function doUpload(buffer) {
if(!format) {
if(callback) {
callback(new Error("Unknown image format"));
const doUpload = (buffer) => {
if (!format) {
if (callback) {
callback(new Error('Unknown image format'));
}
return;
}
if(format.match(/^image\//)) {
if (format.match(/^image\//)) {
format = format.substring(6);
}
var filename = '';
var contentType = '';
let filename = '';
let contentType = '';
switch(format.toLowerCase()) {
switch (format.toLowerCase()) {
case 'jpg':
case 'jpeg':
filename = 'avatar.jpg';
@@ -344,68 +296,96 @@ SteamCommunity.prototype.uploadAvatar = function(image, format, callback) {
break;
default:
if(callback) {
callback(new Error("Unknown or invalid image format"));
if (callback) {
callback(new Error('Unknown or invalid image format'));
}
return;
}
self.httpRequestPost({
"uri": "https://steamcommunity.com/actions/FileUploader",
"formData": {
"MAX_FILE_SIZE": buffer.length,
"type": "player_avatar_image",
"sId": self.steamID.getSteamID64(),
"sessionid": self.getSessionID(),
"doSub": 1,
"json": 1,
"avatar": {
"value": buffer,
"options": {
"filename": filename,
"contentType": contentType
this.httpRequestPost({
url: 'https://steamcommunity.com/actions/FileUploader',
formData: {
MAX_FILE_SIZE: buffer.length,
type: 'player_avatar_image',
sId: this.steamID.getSteamID64(),
sessionid: this.getSessionID(),
doSub: 1,
json: 1,
avatar: {
value: buffer,
options: {
filename: filename,
contentType: contentType
}
}
},
"json": true
}, function(err, response, body) {
if(err) {
if(callback) {
json: true
}, (err, response, body) => {
if (err) {
callback && callback(err);
return;
}
if (body && !body.success && body.message) {
callback && callback(new Error(body.message));
return;
}
if (response.statusCode != 200) {
callback && callback(new Error(`HTTP error ${response.statusCode}`));
return;
}
if (!body || !body.success) {
callback && callback(new Error('Malformed response'));
return;
}
callback && callback(null, body.images.full);
}, 'steamcommunity');
};
if (image instanceof Buffer) {
doUpload(image);
} else if (image.match(/^https?:\/\//)) {
this.httpRequestGet({
url: image,
encoding: null
}, (err, response, body) => {
if (err || response.statusCode != 200) {
if (callback) {
callback(new Error(err ? `${err.message} downloading image` : `HTTP error ${response.statusCode} downloading image`));
}
return;
}
if (!format) {
format = response.headers['content-type'];
}
doUpload(body);
}, 'steamcommunity');
} else {
if (!format) {
format = image.match(/\.([^.]+)$/);
if (format) {
format = format[1];
}
}
FS.readFile(image, (err, file) => {
if (err) {
if (callback) {
callback(err);
}
return;
}
if(body && !body.success && body.message) {
if(callback) {
callback(new Error(body.message));
}
return;
}
if(response.statusCode != 200) {
if(callback) {
callback(new Error("HTTP error " + response.statusCode));
}
return;
}
if(!body || !body.success) {
if(callback) {
callback(new Error("Malformed response"));
}
return;
}
if(callback) {
callback(null, body.images.full);
}
}, "steamcommunity");
doUpload(file);
});
}
};
@@ -421,10 +401,10 @@ SteamCommunity.prototype.postProfileStatus = function(statusText, options, callb
options = {};
}
this._myProfile("ajaxpostuserstatus/", {
"appid": options.appID || 0,
"sessionid": this.getSessionID(),
"status_text": statusText
this._myProfile('ajaxpostuserstatus/', {
appid: options.appID || 0,
sessionid: this.getSessionID(),
status_text: statusText
}, (err, res, body) => {
try {
body = JSON.parse(body);
@@ -433,9 +413,9 @@ SteamCommunity.prototype.postProfileStatus = function(statusText, options, callb
return;
}
var match = body.blotter_html.match(/id="userstatus_(\d+)_/);
let match = body.blotter_html.match(/id="userstatus_(\d+)_/);
if (!match) {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
return;
}
@@ -452,9 +432,9 @@ SteamCommunity.prototype.postProfileStatus = function(statusText, options, callb
* @param {function} [callback]
*/
SteamCommunity.prototype.deleteProfileStatus = function(postID, callback) {
this._myProfile("ajaxdeleteuserstatus/", {
"sessionid": this.getSessionID(),
"postid": postID
this._myProfile('ajaxdeleteuserstatus/', {
sessionid: this.getSessionID(),
postid: postID
}, (err, res, body) => {
if (!callback) {
return;
@@ -463,7 +443,7 @@ SteamCommunity.prototype.deleteProfileStatus = function(postID, callback) {
try {
body = JSON.parse(body);
if (!body.success) {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
return;
}

View File

@@ -1,6 +1,7 @@
var SteamID = require('steamid');
const StdLib = require('@doctormckay/stdlib');
const SteamID = require('steamid');
var SteamCommunity = require('../index.js');
const SteamCommunity = require('../index.js');
/**
@@ -8,50 +9,52 @@ var SteamCommunity = require('../index.js');
* @param {SteamID | String} userID - ID of the user associated to this sharedfile
* @param {String} sharedFileId - ID of the sharedfile
* @param {String} cid - ID of the comment to delete
* @param {function} callback - Takes only an Error object/null as the first argument
* @param {function} [callback] - Takes only an Error object/null as the first argument
* @return Promise<void>
*/
SteamCommunity.prototype.deleteSharedFileComment = function(userID, sharedFileId, cid, callback) {
if (typeof userID === "string") {
if (typeof userID == 'string') {
userID = new SteamID(userID);
}
this.httpRequestPost({
"uri": `https://steamcommunity.com/comment/PublishedFile_Public/delete/${userID.toString()}/${sharedFileId}/`,
"form": {
"gidcomment": cid,
"count": 10,
"sessionid": this.getSessionID()
}
}, function(err, response, body) {
if (!callback) {
return;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
await this.httpRequest({
method: 'POST',
url: `https://steamcommunity.com/comment/PublishedFile_Public/delete/${userID.toString()}/${sharedFileId}/`,
form: {
gidcomment: cid,
count: 10,
sessionid: this.getSessionID()
},
source: 'steamcommunity'
});
callback(err);
}, "steamcommunity");
resolve();
});
};
/**
* Favorites a sharedfile
* @param {String} sharedFileId - ID of the sharedfile
* @param {String} appid - ID of the app associated to this sharedfile
* @param {function} callback - Takes only an Error object/null as the first argument
* @param {function} [callback] - Takes only an Error object/null as the first argument
* @return Promise<void>
*/
SteamCommunity.prototype.favoriteSharedFile = function(sharedFileId, appid, callback) {
this.httpRequestPost({
"uri": "https://steamcommunity.com/sharedfiles/favorite",
"form": {
"id": sharedFileId,
"appid": appid,
"sessionid": this.getSessionID()
}
}, function(err, response, body) {
if (!callback) {
return;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
await this.httpRequest({
method: 'POST',
url: 'https://steamcommunity.com/sharedfiles/favorite',
form: {
id: sharedFileId,
appid,
sessionid: this.getSessionID()
},
source: 'steamcommunity'
});
callback(err);
}, "steamcommunity");
resolve();
});
};
/**
@@ -59,76 +62,79 @@ SteamCommunity.prototype.favoriteSharedFile = function(sharedFileId, appid, call
* @param {SteamID | String} userID - ID of the user associated to this sharedfile
* @param {String} sharedFileId - ID of the sharedfile
* @param {String} message - Content of the comment to post
* @param {function} callback - Takes only an Error object/null as the first argument
* @param {function} [callback] - Takes only an Error object/null as the first argument
* @return Promise<void>
*/
SteamCommunity.prototype.postSharedFileComment = function(userID, sharedFileId, message, callback) {
if (typeof userID === "string") {
if (typeof userID == 'string') {
userID = new SteamID(userID);
}
this.httpRequestPost({
"uri": `https://steamcommunity.com/comment/PublishedFile_Public/post/${userID.toString()}/${sharedFileId}/`,
"form": {
"comment": message,
"count": 10,
"sessionid": this.getSessionID()
}
}, function(err, response, body) {
if (!callback) {
return;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
await this.httpRequest({
method: 'POST',
url: `https://steamcommunity.com/comment/PublishedFile_Public/post/${userID.toString()}/${sharedFileId}/`,
form: {
comment: message,
count: 10,
sessionid: this.getSessionID()
},
source: 'steamcommunity'
});
callback(err);
}, "steamcommunity");
resolve();
});
};
/**
* Subscribes to a sharedfile's comment section. Note: Checkbox on webpage does not update
* @param {SteamID | String} userID ID of the user associated to this sharedfile
* @param {String} sharedFileId ID of the sharedfile
* @param {function} callback - Takes only an Error object/null as the first argument
* @param {function} [callback] - Takes only an Error object/null as the first argument
* @return Promise<void>
*/
SteamCommunity.prototype.subscribeSharedFileComments = function(userID, sharedFileId, callback) {
if (typeof userID === "string") {
if (typeof userID == 'string') {
userID = new SteamID(userID);
}
this.httpRequestPost({
"uri": `https://steamcommunity.com/comment/PublishedFile_Public/subscribe/${userID.toString()}/${sharedFileId}/`,
"form": {
"count": 10,
"sessionid": this.getSessionID()
}
}, function(err, response, body) { // eslint-disable-line
if (!callback) {
return;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
await this.httpRequest({
method: 'POST',
url: `https://steamcommunity.com/comment/PublishedFile_Public/subscribe/${userID.toString()}/${sharedFileId}/`,
form: {
count: 10,
sessionid: this.getSessionID()
},
source: 'steamcommunity'
});
callback(err);
}, "steamcommunity");
resolve();
});
};
/**
* Unfavorites a sharedfile
* @param {String} sharedFileId - ID of the sharedfile
* @param {String} appid - ID of the app associated to this sharedfile
* @param {function} callback - Takes only an Error object/null as the first argument
* @param {function} [callback] - Takes only an Error object/null as the first argument
* @return Promise<void>
*/
SteamCommunity.prototype.unfavoriteSharedFile = function(sharedFileId, appid, callback) {
this.httpRequestPost({
"uri": "https://steamcommunity.com/sharedfiles/unfavorite",
"form": {
"id": sharedFileId,
"appid": appid,
"sessionid": this.getSessionID()
}
}, function(err, response, body) {
if (!callback) {
return;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
await this.httpRequest({
method: 'POST',
url: 'https://steamcommunity.com/sharedfiles/unfavorite',
form: {
id: sharedFileId,
appid,
sessionid: this.getSessionID()
},
source: 'steamcommunity'
});
callback(err);
}, "steamcommunity");
resolve();
});
};
/**
@@ -138,21 +144,20 @@ SteamCommunity.prototype.unfavoriteSharedFile = function(sharedFileId, appid, ca
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.unsubscribeSharedFileComments = function(userID, sharedFileId, callback) {
if (typeof userID === "string") {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
this.httpRequestPost({
"uri": `https://steamcommunity.com/comment/PublishedFile_Public/unsubscribe/${userID.toString()}/${sharedFileId}/`,
"form": {
"count": 10,
"sessionid": this.getSessionID()
}
}, function(err, response, body) { // eslint-disable-line
if (!callback) {
return;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
await this.httpRequest({
method: 'POST',
url: `https://steamcommunity.com/comment/PublishedFile_Public/unsubscribe/${userID.toString()}/${sharedFileId}/`,
form: {
count: 10,
sessionid: this.getSessionID()
}
});
callback(err);
}, "steamcommunity");
resolve();
});
};

View File

@@ -1,152 +1,164 @@
var SteamTotp = require('steam-totp');
var SteamCommunity = require('../index.js');
const StdLib = require('@doctormckay/stdlib');
const SteamTotp = require('steam-totp');
var ETwoFactorTokenType = {
const SteamCommunity = require('../index.js');
const Helpers = require('./helpers.js');
const 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.
ThirdParty: 2 // Tokens generated using literally everyone else's standard charset (6 digits, numeric). This is disabled on the backend.
};
/**
* @param {function} [callback]
* @return {Promise<object>}
*/
SteamCommunity.prototype.enableTwoFactor = function(callback) {
this._verifyMobileAccessToken();
return StdLib.Promises.callbackPromise(null, callback, false, async (resolve, reject) => {
this._verifyMobileAccessToken();
if (!this.mobileAccessToken) {
callback(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()'));
return;
}
this.httpRequestPost({
uri: "https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/?access_token=" + this.mobileAccessToken,
// TODO: Send this as protobuf to more closely mimic official app behavior
form: {
steamid: this.steamID.getSteamID64(),
authenticator_type: ETwoFactorTokenType.ValveMobileApp,
device_identifier: SteamTotp.getDeviceID(this.steamID),
sms_phone_id: '1',
version: 2
},
json: true
}, (err, response, body) => {
if (err) {
callback(err);
return;
if (!this.mobileAccessToken) {
return reject(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()'));
}
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) {
this._verifyMobileAccessToken();
if (!this.mobileAccessToken) {
callback(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()'));
return;
}
let attemptsLeft = 30;
let diff = 0;
let finalize = () => {
let code = SteamTotp.generateAuthCode(secret, diff);
this.httpRequestPost({
uri: 'https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/?access_token=' + this.mobileAccessToken,
let {jsonBody} = await this.httpRequest({
method: 'POST',
url: `https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/?access_token=${this.mobileAccessToken}`,
// TODO: Send this as protobuf to more closely mimic official app behavior
form: {
steamid: this.steamID.getSteamID64(),
authenticator_code: code,
authenticator_time: Math.floor(Date.now() / 1000),
activation_code: activationCode
authenticator_type: ETwoFactorTokenType.ValveMobileApp,
device_identifier: SteamTotp.getDeviceID(this.steamID),
sms_phone_id: '1'
},
json: true
}, function(err, response, body) {
if (err) {
callback(err);
return;
source: 'steamcommunity'
});
if (!jsonBody.response) {
return reject(new Error('Malformed response'));
}
if (jsonBody.response.status != 1) {
let error = new Error(`Error ${jsonBody.response.status}`);
error.eresult = jsonBody.response.status;
return reject(error);
}
resolve(jsonBody.response);
});
};
/**
* @param {string} secret
* @param {string} activationCode
* @param {function} [callback]
* @return Promise<void>
*/
SteamCommunity.prototype.finalizeTwoFactor = function(secret, activationCode, callback) {
return StdLib.Promises.callbackPromise(null, callback, false, async (resolve, reject) => {
this._verifyMobileAccessToken();
if (!this.mobileAccessToken) {
return reject(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()'));
}
let attemptsLeft = 30;
let diff = 0;
await new Promise((resolve, reject) => {
SteamTotp.getTimeOffset(function(err, offset, latency) {
if (err) {
return reject(err);
}
diff = offset;
resolve();
});
});
let finalize = async () => {
let code = SteamTotp.generateAuthCode(secret, diff);
let {jsonBody} = this.httpRequest({
method: 'POST',
url: `https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/?access_token=${this.mobileAccessToken}`,
form: {
steamid: this.steamID.getSteamID64(),
authenticator_code: code,
authenticator_time: Math.floor(Date.now() / 1000),
activation_code: activationCode
},
source: 'steamcommunity'
});
if (!jsonBody.response) {
return reject(new Error('Malformed response'));
}
if (!body.response) {
callback(new Error('Malformed response'));
return;
jsonBody = jsonBody.response;
if (jsonBody.server_time) {
diff = jsonBody.server_time - Math.floor(Date.now() / 1000);
}
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--;
if (jsonBody.status == SteamCommunity.EResult.TwoFactorActivationCodeMismatch) {
return reject(new Error('Invalid activation code'));
} else if (jsonBody.want_more) {
if (--attemptsLeft <= 0) {
// We made more than 30 attempts, something must be wrong
return reject(Helpers.eresultError(SteamCommunity.EResult.Fail));
}
diff += 30;
finalize();
} else if(!body.success) {
callback(new Error('Error ' + body.status));
} else if (!jsonBody.success) {
return reject(new Error(`Error ${jsonBody.status}`));
} else {
callback(null);
resolve();
}
}, 'steamcommunity');
}
};
SteamTotp.getTimeOffset(function(err, offset, latency) {
if (err) {
callback(err);
return;
}
diff = offset;
finalize();
});
};
/**
* @param {string} revocationCode
* @param {function} [callback]
* @return Promise<void>
*/
SteamCommunity.prototype.disableTwoFactor = function(revocationCode, callback) {
this._verifyMobileAccessToken();
return StdLib.Promises.callbackPromise(null, callback, false, async (resolve, reject) => {
this._verifyMobileAccessToken();
if (!this.mobileAccessToken) {
callback(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()'));
return;
}
this.httpRequestPost({
uri: 'https://api.steampowered.com/ITwoFactorService/RemoveAuthenticator/v1/?access_token=' + this.mobileAccessToken,
form: {
steamid: this.steamID.getSteamID64(),
revocation_code: revocationCode,
steamguard_scheme: 1
},
json: true
}, function(err, response, body) {
if (err) {
callback(err);
if (!this.mobileAccessToken) {
callback(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()'));
return;
}
if (!body.response) {
callback(new Error('Malformed response'));
return;
let {jsonBody} = await this.httpRequest({
method: 'POST',
url: `https://api.steampowered.com/ITwoFactorService/RemoveAuthenticator/v1/?access_token=${this.mobileAccessToken}`,
form: {
steamid: this.steamID.getSteamID64(),
revocation_code: revocationCode,
steamguard_scheme: 1
},
source: 'steamcommunity'
});
if (!jsonBody.response) {
return reject(new Error('Malformed response'));
}
if (!body.response.success) {
callback(new Error('Request failed'));
return;
if (!jsonBody.response.success) {
return reject(new Error('Request failed'));
}
// success = true means it worked
callback(null);
}, 'steamcommunity');
resolve();
});
};

View File

@@ -9,116 +9,111 @@ const CEconItem = require('../classes/CEconItem.js');
const Helpers = require('./helpers.js');
SteamCommunity.prototype.addFriend = function(userID, callback) {
if(typeof userID === 'string') {
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()
url: 'https://steamcommunity.com/actions/AddFriendAjax',
form: {
accept_invite: 0,
sessionID: this.getSessionID(),
steamid: userID.toString()
},
"json": true
}, function(err, response, body) {
if(!callback) {
json: true
}, (err, response, body) => {
if (!callback) {
return;
}
if (err) {
callback(err);
return;
return callback(err);
}
if(body.success) {
if (body.success) {
callback(null);
} else {
callback(new Error("Unknown error"));
callback(new Error('Unknown error'));
}
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.acceptFriendRequest = function(userID, callback) {
if(typeof userID === 'string') {
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()
url: 'https://steamcommunity.com/actions/AddFriendAjax',
form: {
accept_invite: 1,
sessionID: this.getSessionID(),
steamid: userID.toString()
}
}, function(err, response, body) {
if(!callback) {
}, (err, response, body) => {
if (!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.removeFriend = function(userID, callback) {
if(typeof userID === 'string') {
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()
url: 'https://steamcommunity.com/actions/RemoveFriendAjax',
form: {
sessionID: this.getSessionID(),
steamid: userID.toString()
}
}, function(err, response, body) {
if(!callback) {
}, (err, response, body) => {
if (!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.blockCommunication = function(userID, callback) {
if(typeof userID === 'string') {
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()
url: 'https://steamcommunity.com/actions/BlockUserAjax',
form: {
sessionID: this.getSessionID(),
steamid: userID.toString()
}
}, function(err, response, body) {
if(!callback) {
}, (err, response, body) => {
if (!callback) {
return;
}
callback(err || null);
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.unblockCommunication = function(userID, callback) {
if(typeof userID === 'string') {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
var form = {"action": "unignore"};
let form = {action: 'unignore'};
form['friends[' + userID.toString() + ']'] = 1;
this._myProfile('friends/blocked/', form, function(err, response, body) {
if(!callback) {
this._myProfile('friends/blocked/', form, (err, response, body) => {
if (!callback) {
return;
}
if(err || response.statusCode >= 400) {
callback(err || new Error("HTTP error " + response.statusCode));
if (err || response.statusCode >= 400) {
callback(err || new Error(`HTTP error ${response.statusCode}`));
return;
}
@@ -127,21 +122,20 @@ SteamCommunity.prototype.unblockCommunication = function(userID, callback) {
};
SteamCommunity.prototype.postUserComment = function(userID, message, callback) {
if(typeof userID === 'string') {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/comment/Profile/post/" + userID.toString() + "/-1",
"form": {
"comment": message,
"count": 1,
"sessionid": this.getSessionID()
url: `https://steamcommunity.com/comment/Profile/post/${userID.toString()}/-1`,
form: {
comment: message,
count: 1,
sessionid: this.getSessionID()
},
"json": true
}, function(err, response, body) {
if(!callback) {
json: true
}, (err, response, body) => {
if (!callback) {
return;
}
@@ -150,37 +144,36 @@ SteamCommunity.prototype.postUserComment = function(userID, message, callback) {
return;
}
if(body.success) {
if (body.success) {
const $ = Cheerio.load(body.comments_html);
const commentID = $('.commentthread_comment').attr('id').split('_')[1];
callback(null, commentID);
} else if(body.error) {
} else if (body.error) {
callback(new Error(body.error));
} else {
callback(new Error("Unknown error"));
callback(new Error('Unknown error'));
}
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.deleteUserComment = function(userID, commentID, callback) {
if(typeof userID === 'string') {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
var self = this;
this.httpRequestPost({
"uri": "https://steamcommunity.com/comment/Profile/delete/" + userID.toString() + "/-1",
"form": {
"gidcomment": commentID,
"start": 0,
"count": 1,
"sessionid": this.getSessionID(),
"feature2": -1
url: `https://steamcommunity.com/comment/Profile/delete/${userID.toString()}/-1`,
form: {
gidcomment: commentID,
start: 0,
count: 1,
sessionid: this.getSessionID(),
feature2: -1
},
"json": true
}, function(err, response, body) {
if(!callback) {
json: true
}, (err, response, body) => {
if (!callback) {
return;
}
@@ -189,20 +182,20 @@ SteamCommunity.prototype.deleteUserComment = function(userID, commentID, callbac
return;
}
if(body.success && !body.comments_html.includes(commentID)) {
if (body.success && !body.comments_html.includes(commentID)) {
callback(null);
} else if(body.error) {
} else if (body.error) {
callback(new Error(body.error));
} else if(body.comments_html.includes(commentID)) {
callback(new Error("Failed to delete comment"));
} else if (body.comments_html.includes(commentID)) {
callback(new Error('Failed to delete comment'));
} else {
callback(new Error("Unknown error"));
callback(new Error('Unknown error'));
}
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.getUserComments = function(userID, options, callback) {
if(typeof userID === 'string') {
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
@@ -211,19 +204,19 @@ SteamCommunity.prototype.getUserComments = function(userID, options, callback) {
options = {};
}
var form = Object.assign({
"start": 0,
"count": 0,
"feature2": -1,
"sessionid": this.getSessionID()
let form = Object.assign({
start: 0,
count: 0,
feature2: -1,
sessionid: this.getSessionID()
}, options);
this.httpRequestPost({
"uri": "https://steamcommunity.com/comment/Profile/render/" + userID.toString() + "/-1",
"form": form,
"json": true
}, function(err, response, body) {
if(!callback) {
url: `https://steamcommunity.com/comment/Profile/render/${userID.toString()}/-1`,
form,
json: true
}, (err, response, body) => {
if (!callback) {
return;
}
@@ -232,52 +225,51 @@ SteamCommunity.prototype.getUserComments = function(userID, options, callback) {
return;
}
if(body.success) {
if (body.success) {
const $ = Cheerio.load(body.comments_html);
const comments = $(".commentthread_comment.responsive_body_text[id]").map((i, elem) => {
var $elem = $(elem),
$commentContent = $elem.find(".commentthread_comment_text");
const comments = $('.commentthread_comment.responsive_body_text[id]').map((i, elem) => {
let $elem = $(elem),
$commentContent = $elem.find('.commentthread_comment_text');
return {
id: $elem.attr("id").split("_")[1],
id: $elem.attr('id').split('_')[1],
author: {
steamID: new SteamID("[U:1:" + $elem.find("[data-miniprofile]").data("miniprofile") + "]"),
name: $elem.find("bdi").text(),
avatar: $elem.find(".playerAvatar img[src]").attr("src"),
state: $elem.find(".playerAvatar").attr("class").split(" ").pop()
steamID: new SteamID('[U:1:' + $elem.find('[data-miniprofile]').data('miniprofile') + ']'),
name: $elem.find('bdi').text(),
avatar: $elem.find('.playerAvatar img[src]').attr('src'),
state: $elem.find('.playerAvatar').attr('class').split(' ').pop()
},
date: new Date($elem.find(".commentthread_comment_timestamp").data("timestamp") * 1000),
date: new Date($elem.find('.commentthread_comment_timestamp').data('timestamp') * 1000),
text: $commentContent.text().trim(),
html: $commentContent.html().trim()
}
};
}).get();
callback(null, comments, body.total_count);
} else if(body.error) {
} else if (body.error) {
callback(new Error(body.error));
} else {
callback(new Error("Unknown error"));
callback(new Error('Unknown error'));
}
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.inviteUserToGroup = function(userID, groupID, callback) {
if(typeof userID === 'string') {
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"
url: '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) {
json: true
}, (err, response, body) => {
if (!callback) {
return;
}
@@ -286,74 +278,14 @@ SteamCommunity.prototype.inviteUserToGroup = function(userID, groupID, callback)
return;
}
if(body.results == 'OK') {
if (body.results == 'OK') {
callback(null);
} else if(body.results) {
} else if (body.results) {
callback(new Error(body.results));
} else {
callback(new Error("Unknown error"));
callback(new Error('Unknown error'));
}
}, "steamcommunity");
};
SteamCommunity.prototype.followUser = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
this.httpRequestPost({
"uri": `https://steamcommunity.com/profiles/${userID.toString()}/followuser/`,
"form": {
"sessionid": this.getSessionID(),
},
"json": true
}, function(err, response, body) {
if (!callback) {
return;
}
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
callback(Helpers.eresultError(body.success));
return;
}
callback(null);
}, "steamcommunity");
};
SteamCommunity.prototype.unfollowUser = function(userID, callback) {
if(typeof userID === 'string') {
userID = new SteamID(userID);
}
this.httpRequestPost({
"uri": `https://steamcommunity.com/profiles/${userID.toString()}/unfollowuser/`,
"form": {
"sessionid": this.getSessionID(),
},
"json": true
}, function(err, response, body) {
if (!callback) {
return;
}
if (err) {
callback(err);
return;
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
callback(Helpers.eresultError(body.success));
return;
}
callback(null);
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.getUserAliases = function(userID, callback) {
@@ -362,24 +294,24 @@ SteamCommunity.prototype.getUserAliases = function(userID, callback) {
}
this.httpRequestGet({
"uri": "https://steamcommunity.com/profiles/" + userID.getSteamID64() + "/ajaxaliases",
"json": true
}, function(err, response, body) {
url: `https://steamcommunity.com/profiles/${userID.getSteamID64()}/ajaxaliases`,
json: true
}, (err, response, body) => {
if (err) {
callback(err);
return;
}
if (typeof body !== 'object') {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
return;
}
callback(null, body.map(function(entry) {
callback(null, body.map((entry) => {
entry.timechanged = Helpers.decodeSteamTime(entry.timechanged);
return entry;
}));
}, "steamcommunity");
}, 'steamcommunity');
};
/**
@@ -392,33 +324,33 @@ SteamCommunity.prototype.getUserProfileBackground = function(userID, callback) {
userID = new SteamID(userID);
}
this.httpRequest("https://steamcommunity.com/profiles/" + userID.getSteamID64(), (err, response, body) => {
this.httpRequest(`https://steamcommunity.com/profiles/${userID.getSteamID64()}`, (err, response, body) => {
if (err) {
callback(err);
return;
}
var $ = Cheerio.load(body);
let $ = Cheerio.load(body);
var $privateProfileInfo = $('.profile_private_info');
let $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(/\(([^)]+)\)/);
let backgroundUrl = $('div.profile_background_image_content').css('background-image');
let matcher = backgroundUrl.match(/\(([^)]+)\)/);
if (matcher.length != 2 || !matcher[1].length) {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
} else {
callback(null, matcher[1]);
}
} else {
callback(null, null);
}
}, "steamcommunity");
}, 'steamcommunity');
};
SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
@@ -432,28 +364,27 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
}
if (!userID) {
callback(new Error("No SteamID specified and not logged in"));
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) {
this.httpRequest(`https://steamcommunity.com/profiles/${userID.getSteamID64()}/inventory/`, (err, response, body) => {
if (err) {
callback(err);
return;
}
var match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
let match = body.match(/var g_rgAppContextData = ([^\n]+);\r?\n/);
if (!match) {
callback(new Error('Malformed response'));
return;
}
var data;
let data;
try {
data = JSON.parse(match[1]);
} catch(e) {
callback(new Error("Malformed response"));
} catch (e) {
callback(new Error('Malformed response'));
return;
}
@@ -473,7 +404,7 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
}
callback(null, data);
}, "steamcommunity");
}, 'steamcommunity');
};
/**
@@ -486,27 +417,22 @@ SteamCommunity.prototype.getUserInventoryContexts = function(userID, callback) {
* @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"
const get = (inventory, currency, start) => {
this.httpRequest({
url: `https://steamcommunity.com${endpoint}/inventory/json/${appID}/${contextID}`,
headers: {
Referer: `https://steamcommunity.com${endpoint}/inventory`
},
"qs": {
"start": start,
"trading": tradableOnly ? 1 : undefined
qs: {
start: start,
trading: tradableOnly ? 1 : undefined
},
"json": true
}, function(err, response, body) {
json: true
}, (err, response, body) => {
if (err) {
callback(err);
return;
@@ -514,43 +440,37 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
if (!body || !body.success || !body.rgInventory || !body.rgDescriptions || !body.rgCurrency) {
if (body) {
callback(new Error(body.Error || "Malformed response"));
callback(new Error(body.Error || 'Malformed response'));
} else {
callback(new Error("Malformed response"));
callback(new Error('Malformed response'));
}
return;
}
var i;
for (i in body.rgInventory) {
if (!body.rgInventory.hasOwnProperty(i)) {
continue;
}
for (let i in body.rgInventory) {
inventory.push(new CEconItem(body.rgInventory[i], body.rgDescriptions, contextID));
}
for (i in body.rgCurrency) {
if (!body.rgCurrency.hasOwnProperty(i)) {
continue;
}
for (let i in body.rgCurrency) {
currency.push(new CEconItem(body.rgCurrency[i], body.rgDescriptions, contextID));
}
if (body.more) {
var match = response.request.uri.href.match(/\/(profiles|id)\/([^\/]+)\//);
if(match) {
endpoint = "/" + match[1] + "/" + match[2];
let 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");
}
}, 'steamcommunity');
};
let endpoint = `/profiles/${userID.getSteamID64()}`;
get([], []);
};
/**
@@ -565,52 +485,64 @@ SteamCommunity.prototype.getUserInventory = function(userID, appID, contextID, t
SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, contextID, tradableOnly, language, callback) {
if (typeof language === 'function') {
callback = language;
language = "english";
language = 'english';
}
if (!userID) {
callback(new Error("The user's SteamID is invalid or missing."));
callback(new Error('The user\'s SteamID is invalid or missing.'));
return;
}
var self = this;
if (typeof userID === 'string') {
userID = new SteamID(userID);
}
var pos = 1;
get([], []);
// A bit of optimization; objects are hash tables so it's more efficient to look up by key than to iterate an array
let quickDescriptionLookup = {};
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"
const getDescription = (descriptions, classID, instanceID) => {
let key = classID + '_' + (instanceID || '0'); // instanceID can be undefined, in which case it's 0.
if (quickDescriptionLookup[key]) {
return quickDescriptionLookup[key];
}
for (let i = 0; i < descriptions.length; i++) {
quickDescriptionLookup[descriptions[i].classid + '_' + (descriptions[i].instanceid || '0')] = descriptions[i];
}
return quickDescriptionLookup[key];
};
const get = (inventory, currency, start) => {
this.httpRequest({
url: `https://steamcommunity.com/inventory/${userID.getSteamID64()}/${appID}/${contextID}`,
headers: {
Referer: `https://steamcommunity.com/profiles/${userID.getSteamID64()}/inventory`
},
"qs": {
"l": language, // Default language
"count": 1000, // Max items per 'page'
"start_assetid": start
qs: {
l: language, // Default language
count: 2000, // Max items per 'page'
start_assetid: start
},
"json": true
}, function(err, response, body) {
json: true
}, (err, response, body) => {
if (err) {
if (err.message == "HTTP error 403" && body === null) {
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()) {
if (this.steamID && userID.getSteamID64() == this.steamID.getSteamID64()) {
// We can never get private profile error for our own inventory!
self._notifySessionExpired(err);
this._notifySessionExpired(err);
}
callback(new Error("This profile is private."));
callback(new Error('This profile is private.'));
return;
}
if (err.message == "HTTP error 500" && body && body.error) {
if (err.message == 'HTTP error 500' && body && body.error) {
err = new Error(body.error);
var match = body.error.match(/^(.+) \((\d+)\)$/);
let match = body.error.match(/^(.+) \((\d+)\)$/);
if (match) {
err.message = match[1];
err.eresult = match[2];
@@ -629,26 +561,19 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
return;
}
if (appID == 730 && body && body.success && !body.assets) {
// CS inventory has no visible items. We need a special case for this because Valve is incapable of
// doing anything not dumb.
callback(null, [], [], body.total_inventory_count);
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"));
callback(new Error(body.error || body.Error || 'Malformed response'));
} else {
callback(new Error("Malformed response"));
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);
for (let i = 0; i < body.assets.length; i++) {
let description = getDescription(body.descriptions, body.assets[i].classid, body.assets[i].instanceid);
if (!tradableOnly || (description && description.tradable)) {
body.assets[i].pos = pos++;
@@ -661,25 +586,11 @@ SteamCommunity.prototype.getUserInventoryContents = function(userID, appID, cont
} else {
callback(null, inventory, currency, body.total_inventory_count);
}
}, "steamcommunity");
}
}, '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];
}
let pos = 1;
get([], []);
};
/**
@@ -711,7 +622,7 @@ SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer,
return;
}
var imageDetails = null;
let imageDetails = null;
try {
imageDetails = imageSize(imageContentsBuffer);
} catch (ex) {
@@ -719,14 +630,14 @@ SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer,
return;
}
var imageHash = Crypto.createHash('sha1');
let imageHash = Crypto.createHash('sha1');
imageHash.update(imageContentsBuffer);
imageHash = imageHash.digest('hex');
var filename = Date.now() + '_image.' + imageDetails.type;
let filename = Date.now() + '_image.' + imageDetails.type;
this.httpRequestPost({
uri: 'https://steamcommunity.com/chat/beginfileupload/?l=english',
url: 'https://steamcommunity.com/chat/beginfileupload/?l=english',
headers: {
referer: 'https://steamcommunity.com/chat/'
},
@@ -744,7 +655,7 @@ SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer,
}, (err, res, body) => {
if (err) {
if (body && body.success) {
var err2 = Helpers.eresultError(body.success);
let err2 = Helpers.eresultError(body.success);
if (body.message) {
err2.message = body.message;
}
@@ -760,9 +671,9 @@ SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer,
return;
}
var hmac = body.hmac;
var timestamp = body.timestamp;
var startResult = body.result;
let hmac = body.hmac;
let timestamp = body.timestamp;
let startResult = body.result;
if (!startResult || !startResult.ugcid || !startResult.url_host || !startResult.request_headers) {
callback(new Error('Malformed response'));
@@ -770,14 +681,14 @@ SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer,
}
// Okay, now we need to PUT the file to the provided URL
var uploadUrl = (startResult.use_https ? 'https' : 'http') + '://' + startResult.url_host + startResult.url_path;
var headers = {};
let uploadUrl = (startResult.use_https ? 'https' : 'http') + '://' + startResult.url_host + startResult.url_path;
let headers = {};
startResult.request_headers.forEach((header) => {
headers[header.name.toLowerCase()] = header.value;
});
this.httpRequest({
uri: uploadUrl,
url: uploadUrl,
method: 'PUT',
headers,
body: imageContentsBuffer
@@ -789,7 +700,7 @@ SteamCommunity.prototype.sendImageToUser = function(userID, imageContentsBuffer,
// Now we need to commit the upload
this.httpRequestPost({
uri: 'https://steamcommunity.com/chat/commitfileupload/',
url: 'https://steamcommunity.com/chat/commitfileupload/',
headers: {
referer: 'https://steamcommunity.com/chat/'
},

View File

@@ -1,166 +1,54 @@
const StdLib = require('@doctormckay/stdlib');
const SteamCommunity = require('../index.js');
const Helpers = require('./helpers.js');
/**
* Retrieves your account's Steam Web API key, if you already have one. If you don't yet have one, this will fail.
* To create a Web API key, use `createWebApiKey()`.
*
* @param {null|function} unused - No longer used, kept for backward compatibility. You can omit this parameter and pass
* your callback directly as the first parameter if you want.
* @param {function} callback
* @param {string} domain
* @param {function} [callback]
* @return Promise<{key: string}>
*/
SteamCommunity.prototype.getWebApiKey = function(unused, callback) {
if (typeof unused == 'function') {
callback = unused;
}
SteamCommunity.prototype.getWebApiKey = function(domain, callback) {
return StdLib.Promises.callbackPromise(['key'], callback, false, async (resolve, reject) => {
let {textBody} = await this.httpRequest({
method: 'GET',
url: 'https://steamcommunity.com/dev/apikey?l=english',
followRedirect: false,
source: 'steamcommunity'
});
this.httpRequest({
uri: 'https://steamcommunity.com/dev/apikey?l=english',
followRedirect: false
}, (err, response, body) => {
if (err) {
callback(err);
return;
if (textBody.includes('<h2>Access Denied</h2>')) {
return reject(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.'));
if (textBody.includes('You must have a validated email address to create a Steam Web API key.')) {
return reject(new Error('You must have a validated email address to create a Steam Web API key.'));
}
if (body.match(/Your account requires (<a [^>]+>)?Steam Guard Mobile Authenticator/)) {
return callback(new Error('Steam Guard Mobile Authenticator required to create a Steam Web API key'));
}
if (body.match(/<h2>Access Denied<\/h2>/)) {
return callback(new Error('Access Denied'));
}
let match = body.match(/<p>Key: ([0-9A-F]+)<\/p>/);
let match = textBody.match(/<p>Key: ([0-9A-F]+)<\/p>/);
if (match) {
// We already have an API key registered
callback(null, match[1]);
} else {
callback(new Error('No API key created for this account'));
}
}, "steamcommunity");
};
/**
* @typedef CreateApiKeyOptions
* @property {string} domain - The domain to associate with your API key
* @property {string} [requestID] - If finalizing an existing create request, include the request ID
* @property {string|Buffer} [identitySecret] - If you pass your identity_secret here, then steamcommunity will
* internally handle accepting any confirmations.
*/
/**
* @typedef CreateApiKeyResponse
* @property {boolean} confirmationRequired
* @property {string} [apiKey] - If creating your API key succeeded, this is the new key
* @property {CreateApiKeyOptions} [finalizeOptions] - If confirmation is required to create a key, then accept the
* confirmation, then call createWebApiKey again and pass this whole object for the `options` parameter.
*/
/**
* @callback createWebApiKeyCallback
* @param {Error|null} err
* @param {CreateApiKeyResponse} [result]
*/
/**
* Starts the process to create a Steam Web API key. When the callback is fired, you will need to approve a mobile
* confirmation in your app or using getConfirmations().
*
* @param {CreateApiKeyOptions} options
* @param {createWebApiKeyCallback} callback
*/
SteamCommunity.prototype.createWebApiKey = function(options, callback) {
if (!options.domain) {
callback(new Error('Passing a domain is required to register an API key'));
return;
}
this.httpRequestPost({
uri: 'https://steamcommunity.com/dev/requestkey',
form: {
domain: options.domain,
request_id: options.requestID || '0',
sessionid: this.getSessionID(),
agreeToTerms: 'true'
},
json: true
}, (err, res, body) => {
if (err) {
callback(err);
return;
return resolve({key: match[1]});
}
// body.requires_confirmation is 1/0, but the Steam website doesn't check this value and instead only checks the
// value of `success`. So let's just do that.
// We need to register a new API key
await this.httpRequest({
method: 'POST',
url: 'https://steamcommunity.com/dev/registerkey?l=english',
form: {
domain,
agreeToTerms: 'agreed',
sessionid: this.getSessionID(),
Submit: 'Register'
},
source: 'steamcommunity'
});
// This is a mess. I'm glad we have promises and await now.
switch (body.success) {
case SteamCommunity.EResult.OK:
if (body.api_key) {
callback(null, {confirmationRequired: false, apiKey: body.api_key});
return;
}
// It's not been observed that we get result OK without api_key included, but the Steam website doesn't
// use this value so let's be safe just in case it disappears in the future.
this.getWebApiKey((err, key) => {
if (err) {
callback(err);
return;
}
callback(null, {confirmationRequired: false, apiKey: key});
});
return;
case SteamCommunity.EResult.Pending:
let finalizeOptions = {
domain: options.domain,
requestID: body.request_id || options.requestID
}
if (options.identitySecret) {
this.acceptConfirmationForObject(options.identitySecret, finalizeOptions.requestID, (err) => {
if (err) {
callback(err);
} else {
this.createWebApiKey(finalizeOptions, callback);
}
});
return;
}
callback(null, {
confirmationRequired: true,
finalizeOptions: finalizeOptions
});
return;
default:
callback(Helpers.eresultError(body.success));
}
resolve({key: await this.getWebApiKey(domain)});
});
};
/**
* @deprecated No longer works. Will be removed in a future release.
* @param {function} callback
*/
SteamCommunity.prototype.getWebApiOauthToken = function(callback) {
if (this.oAuthToken) {
return callback(null, this.oAuthToken);
}
callback(new Error('This operation requires an OAuth token, which is no longer issued by Steam.'));
};
/**
* Sets an access_token generated by steam-session using EAuthTokenPlatformType.MobileApp.
* Required for some operations such as 2FA enabling and disabling.

1
examples/.gitignore vendored
View File

@@ -1 +0,0 @@
twofactor_*.json

View File

@@ -1,7 +1,7 @@
// If you aren't running this script inside of the repository, replace the following line with:
// const SteamCommunity = require('steamcommunity');
const SteamCommunity = require('../index.js');
const SteamTotp = require('steam-totp');
const SteamSession = require('steam-session');
const ReadLine = require('readline');
let g_AbortPromptFunc = null;
@@ -13,32 +13,69 @@ async function main() {
let accountName = await promptAsync('Username: ');
let password = await promptAsync('Password (hidden): ', true);
attemptLogin(accountName, password);
}
// Create a LoginSession for us to use to attempt to log into steam
let session = new SteamSession.LoginSession(SteamSession.EAuthTokenPlatformType.MobileApp);
function attemptLogin(accountName, password, twoFactorCode) {
community.login({
accountName,
password,
twoFactorCode,
disableMobile: false
}, async (err) => {
if (err && err.message == 'SteamGuardMobile') {
let code = await promptAsync('Steam Guard App Code OR Shared Secret: ');
if (code.length > 5) {
// If we were provided a shared secret, turn it into a code.
code = SteamTotp.getAuthCode(code);
}
attemptLogin(accountName, password, code);
return;
}
// Go ahead and attach our event handlers before we do anything else.
session.on('authenticated', async () => {
abortPrompt();
if (err) {
throw err;
}
let accessToken = session.accessToken;
let cookies = await session.getWebCookies();
community.setCookies(cookies);
community.setMobileAppAccessToken(accessToken);
// Enabling or disabling 2FA is presently the only action in node-steamcommunity which requires an access token.
// In all other cases, using `community.setCookies(cookies)` is all you need to do in order to be logged in,
// although there's never any harm in setting a mobile app access token.
doRevoke();
});
session.on('timeout', () => {
abortPrompt();
console.log('This login attempt has timed out.');
});
session.on('error', (err) => {
abortPrompt();
// This should ordinarily not happen. This only happens in case there's some kind of unexpected error while
// polling, e.g. the network connection goes down or Steam chokes on something.
console.log(`ERROR: This login attempt has failed! ${err.message}`);
});
// Start our login attempt
let startResult = await session.startWithCredentials({accountName, password});
if (startResult.actionRequired) {
// Some Steam Guard action is required. We only care about email and device codes; in theory an
// EmailConfirmation and/or DeviceConfirmation action could be possible, but we're just going to ignore those.
// If the user does receive a confirmation and accepts it, LoginSession will detect and handle that automatically.
// The only consequence of ignoring it here is that we don't print a message to the user indicating that they
// could accept an email or device confirmation.
let codeActionTypes = [SteamSession.EAuthSessionGuardType.EmailCode, SteamSession.EAuthSessionGuardType.DeviceCode];
let codeAction = startResult.validActions.find(action => codeActionTypes.includes(action.type));
if (codeAction) {
if (codeAction.type == SteamSession.EAuthSessionGuardType.EmailCode) {
// We wouldn't expect this to happen since we're trying to disable 2FA, but just in case...
console.log(`A code has been sent to your email address at ${codeAction.detail}.`);
} else {
console.log('You need to provide a Steam Guard Mobile Authenticator code.');
}
let code = await promptAsync('Code: ');
if (code) {
await session.submitSteamGuardCode(code);
}
// If we fall through here without submitting a Steam Guard code, that means one of two things:
// 1. The user pressed enter without providing a code, in which case the script will simply exit
// 2. The user approved a device/email confirmation, in which case 'authenticated' was emitted and the prompt was canceled
}
}
}
async function doRevoke() {

View File

@@ -1,47 +1,47 @@
var SteamCommunity = require('../index.js');
var ReadLine = require('readline');
let SteamCommunity = require('../index.js');
let ReadLine = require('readline');
var community = new SteamCommunity();
var rl = ReadLine.createInterface({
"input": process.stdin,
"output": process.stdout
let community = new SteamCommunity();
let rl = ReadLine.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Username: ", function(accountName) {
rl.question("Password: ", function(password) {
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
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) {
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) {
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') {
if (err.message == 'CAPTCHA') {
console.log(err.captchaurl);
rl.question("CAPTCHA: ", function(captchaInput) {
rl.question('CAPTCHA: ', function(captchaInput) {
doLogin(accountName, password, authCode, twoFactorCode, captchaInput);
});
@@ -53,9 +53,9 @@ function doLogin(accountName, password, authCode, twoFactorCode, captcha) {
return;
}
console.log("Logged on!");
console.log('Logged on!');
rl.question("Group ID: ", function(gid) {
rl.question('Group ID: ', function(gid) {
community.getSteamGroup(gid, function(err, group) {
if (err) {
console.log(err);
@@ -64,19 +64,19 @@ function doLogin(accountName, password, authCode, twoFactorCode, captcha) {
group.getAllAnnouncements(function(err, announcements) {
if(announcements.length === 0) {
return console.log("This group has no 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);
};
for (let 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) {
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);
@@ -96,10 +96,10 @@ function doLogin(accountName, password, authCode, twoFactorCode, captcha) {
function editAnnouncement(group, aid, header, content) {
// Actual community method.
group.editAnnouncement(aid, header, content, function(error) {
if(!error) {
console.log("Annoucement edited!");
if (!error) {
console.log('Annoucement edited!');
} else {
console.log("Unable to edit annoucement! %j", error);
console.log('Unable to edit annoucement! %j', error);
process.exit(1);
}
});
@@ -109,10 +109,10 @@ function deleteAnnouncement(group, aid) {
// group.deleteAnnouncement(aid);
// Or
group.deleteAnnouncement(aid, function(err) {
if(!err) {
console.log("Deleted");
if (!err) {
console.log('Deleted');
} else {
console.log("Error deleting announcement.");
console.log('Error deleting announcement.');
}
})
});
}

View File

@@ -1,6 +1,7 @@
// If you aren't running this script inside of the repository, replace the following line with:
// const SteamCommunity = require('steamcommunity');
const SteamCommunity = require('../index.js');
const SteamSession = require('steam-session');
const ReadLine = require('readline');
const FS = require('fs');
@@ -15,28 +16,69 @@ async function main() {
let accountName = await promptAsync('Username: ');
let password = await promptAsync('Password (hidden): ', true);
attemptLogin(accountName, password);
}
// Create a LoginSession for us to use to attempt to log into steam
let session = new SteamSession.LoginSession(SteamSession.EAuthTokenPlatformType.MobileApp);
function attemptLogin(accountName, password, authCode) {
community.login({
accountName,
password,
authCode,
disableMobile: false
}, async (err) => {
if (err && err.message == 'SteamGuard') {
let code = await promptAsync('Steam Guard Email Code: ');
attemptLogin(accountName, password, code);
return;
}
// Go ahead and attach our event handlers before we do anything else.
session.on('authenticated', async () => {
abortPrompt();
if (err) {
throw err;
}
let accessToken = session.accessToken;
let cookies = await session.getWebCookies();
community.setCookies(cookies);
community.setMobileAppAccessToken(accessToken);
// Enabling or disabling 2FA is presently the only action in node-steamcommunity which requires an access token.
// In all other cases, using `community.setCookies(cookies)` is all you need to do in order to be logged in,
// although there's never any harm in setting a mobile app access token.
doSetup();
});
session.on('timeout', () => {
abortPrompt();
console.log('This login attempt has timed out.');
});
session.on('error', (err) => {
abortPrompt();
// This should ordinarily not happen. This only happens in case there's some kind of unexpected error while
// polling, e.g. the network connection goes down or Steam chokes on something.
console.log(`ERROR: This login attempt has failed! ${err.message}`);
});
// Start our login attempt
let startResult = await session.startWithCredentials({accountName, password});
if (startResult.actionRequired) {
// Some Steam Guard action is required. We only care about email and device codes; in theory an
// EmailConfirmation and/or DeviceConfirmation action could be possible, but we're just going to ignore those.
// If the user does receive a confirmation and accepts it, LoginSession will detect and handle that automatically.
// The only consequence of ignoring it here is that we don't print a message to the user indicating that they
// could accept an email or device confirmation.
let codeActionTypes = [SteamSession.EAuthSessionGuardType.EmailCode, SteamSession.EAuthSessionGuardType.DeviceCode];
let codeAction = startResult.validActions.find(action => codeActionTypes.includes(action.type));
if (codeAction) {
if (codeAction.type == SteamSession.EAuthSessionGuardType.EmailCode) {
console.log(`A code has been sent to your email address at ${codeAction.detail}.`);
} else {
// We wouldn't expect this to happen since we're trying to enable 2FA, but just in case...
console.log('You need to provide a Steam Guard Mobile Authenticator code.');
}
let code = await promptAsync('Code: ');
if (code) {
await session.submitSteamGuardCode(code);
}
// If we fall through here without submitting a Steam Guard code, that means one of two things:
// 1. The user pressed enter without providing a code, in which case the script will simply exit
// 2. The user approved a device/email confirmation, in which case 'authenticated' was emitted and the prompt was canceled
}
}
}
function doSetup() {
@@ -76,13 +118,10 @@ function doSetup() {
async function promptActivationCode(response) {
if (response.phone_number_hint) {
console.log(`An activation code has been sent to your phone ending in ${response.phone_number_hint}.`);
} else if (response.confirm_type == 3) {
// Exact meaning of confirm_type is unknown, but 3 appears to be email code
console.log('An activation code has been sent to your email.');
console.log(`A code has been sent to your phone ending in ${response.phone_number_hint}.`);
}
let smsCode = await promptAsync('Activation Code: ');
let smsCode = await promptAsync('SMS Code: ');
community.finalizeTwoFactor(response.shared_secret, smsCode, (err) => {
if (err) {
if (err.message == 'Invalid activation code') {

659
index.js
View File

@@ -1,176 +1,214 @@
const {chrome} = require('@doctormckay/user-agents');
const Request = require('request');
const {EventEmitter} = require('events');
const StdLib = require('@doctormckay/stdlib');
const SteamID = require('steamid');
const {LoginSession, EAuthTokenPlatformType, EAuthSessionGuardType} = require('steam-session');
const Util = require('util');
const xml2js = require('xml2js');
const Helpers = require('./components/helpers.js');
const Package = require('./package.json');
require('util').inherits(SteamCommunity, require('events').EventEmitter);
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36';
Util.inherits(SteamCommunity, EventEmitter);
module.exports = SteamCommunity;
SteamCommunity.SteamID = SteamID;
SteamCommunity.ConfirmationType = require('./resources/EConfirmationType.js');
SteamCommunity.EConfirmationType = require('./resources/EConfirmationType.js');
SteamCommunity.EResult = require('./resources/EResult.js');
SteamCommunity.ESharedFileType = require('./resources/ESharedFileType.js');
SteamCommunity.EFriendRelationship = require('./resources/EFriendRelationship.js');
/**
*
* @param {object} [options]
* @param {number} [options.timeout=50000] - The time in milliseconds that SteamCommunity will wait for HTTP requests to complete.
* @param {string} [options.localAddress] - The local IP address that SteamCommunity will use for its HTTP requests.
* @param {string} [options.httpProxy] - A string containing the URI of an HTTP proxy to use for all requests, e.g. `http://user:pass@1.2.3.4:8888`
* @param {object} [options.defaultHttpHeaders] - An object containing some headers to send for every HTTP request
* @constructor
*/
function SteamCommunity(options) {
options = options || {};
this._jar = Request.jar();
this.packageName = Package.name;
this.packageVersion = Package.version;
this._jar = new StdLib.HTTP.CookieJar();
this._captchaGid = -1;
this._httpRequestID = 0;
this.chatState = SteamCommunity.ChatState.Offline;
var defaults = {
"jar": this._jar,
"timeout": options.timeout || 50000,
"gzip": true,
"headers": {
"User-Agent": options.userAgent || chrome()
}
let defaultHeaders = {
'user-agent': USER_AGENT
};
if (typeof options == "string") {
options = {
localAddress: options
};
// Apply the user's custom default headers
for (let i in (options.defaultHttpHeaders || {})) {
// Make sure all header names are lower case to avoid conflicts
defaultHeaders[i.toLowerCase()] = options.defaultHttpHeaders[i];
}
this._httpClient = new StdLib.HTTP.HttpClient({
httpAgent: options.httpProxy ? StdLib.HTTP.getProxyAgent(false, options.httpProxy) : null,
httpsAgent: options.httpProxy ? StdLib.HTTP.getProxyAgent(true, options.httpProxy) : null,
localAddress: options.localAddress,
defaultHeaders,
defaultTimeout: options.timeout || 50000,
cookieJar: this._jar,
gzip: true
});
this._options = options;
if (options.localAddress) {
defaults.localAddress = options.localAddress;
}
this.request = options.request || Request.defaults({"forever": true}); // "forever" indicates that we want a keep-alive agent
this.request = this.request.defaults(defaults);
// English
this._setCookie(Request.cookie('Steam_Language=english'));
this._setCookie('Steam_Language=english');
// UTC
this._setCookie(Request.cookie('timezoneOffset=0,0'));
this._setCookie('timezoneOffset=0,0');
}
SteamCommunity.prototype.login = function(details, callback) {
if (!details.accountName || !details.password) {
throw new Error("Missing either accountName or password to login; both are needed");
/**
* @param {object} details
* @param {string} details.accountName
* @param {string} details.password
* @param {string} [details.authCode]
* @param {string} [details.twoFactorCode]
* @param {string} [details.authTokenPlatformType] - A value from steam-session's EAuthTokenPlatformType enum. Defaults to MobileApp.
* @return Promise<{cookies: string[], sessionID: string, refreshToken: string}>
*/
SteamCommunity.prototype.login = function(details) {
if (typeof details.accountName != 'string' || typeof details.password != 'string') {
throw new Error('You must provide your accountName and password to login to steamcommunity.com');
}
// Delete the cache
delete this._profileURL;
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
let platformType = details.authTokenPlatformType || EAuthTokenPlatformType.MobileApp;
let session = new LoginSession(platformType, {
httpProxy: this._options.httpProxy
});
// default disableMobile to true
let logOnOptions = Object.assign({}, details);
logOnOptions.disableMobile = details.disableMobile !== false;
session.on('authenticated', async () => {
try {
let cookies = await session.getWebCookies();
this.setCookies(cookies);
this._modernLogin(logOnOptions).then(({sessionID, cookies, steamguard, mobileAccessToken}) => {
this.setCookies(cookies);
if (platformType == EAuthTokenPlatformType.MobileApp) {
this.setMobileAppAccessToken(session.accessToken);
}
if (mobileAccessToken) {
this.setMobileAppAccessToken(mobileAccessToken);
// TODO set refresh token for session keep-alive
let sessionID = this.getSessionID();
if (!cookies.some(c => c.startsWith('sessionid='))) {
// make sure that the sessionid we return is in the cookies list we return
cookies.push(`sessionid=${sessionID}`);
}
resolve({
cookies,
sessionID,
refreshToken: session.refreshToken
});
} catch (ex) {
reject(ex);
}
});
session.on('timeout', () => {
// This really shouldn't happen
reject(new Error('Login attempt timed out'));
});
session.on('error', reject);
try {
let startResult = await session.startWithCredentials({
accountName: details.accountName,
password: details.password,
steamGuardCode: details.twoFactorCode || details.authCode
});
if (!startResult.actionRequired) {
return; // 'authenticated' should get emitted soon
}
session.cancelLoginAttempt();
if (startResult.validActions.some(a => a.type == EAuthSessionGuardType.EmailCode)) {
return reject(new Error('SteamGuard'));
}
if (startResult.validActions.some(a => a.type == EAuthSessionGuardType.DeviceCode)) {
return reject(new Error('SteamGuardMobile'));
}
let validActions = startResult.validActions.map(a => a.type).join(', ');
return reject(new Error(`Unexpected guard action(s) ${validActions}`));
} catch (ex) {
reject(ex);
}
callback(null, sessionID, cookies, steamguard, null);
}).catch(err => callback(err));
};
/**
* @deprecated
* @param {string} steamguard
* @param {string} token
* @param {function} callback
*/
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
* @param {function} [callback]
* @return Promise<{steamID: SteamID, accountName: string, webLogonToken: string}>
*/
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;
}
return StdLib.Promises.callbackPromise(null, callback, false, async (resolve, reject) => {
let {jsonBody} = await this.httpRequest({
method: 'GET',
url: 'https://steamcommunity.com/chat/clientjstoken',
source: 'steamcommunity'
});
if (!body.logged_in) {
if (!jsonBody.logged_in) {
let e = new Error('Not Logged In');
callback(e);
this._notifySessionExpired(e);
return;
return reject(e);
}
if (!body.steamid || !body.account_name || !body.token) {
callback(new Error('Malformed response'));
return;
if (!jsonBody.steamid || !jsonBody.account_name || !jsonBody.token) {
return reject(new Error('Malformed response'));
}
callback(null, {
"steamID": new SteamID(body.steamid),
"accountName": body.account_name,
"webLogonToken": body.token
resolve({
steamID: new SteamID(jsonBody.steamid),
accountName: jsonBody.account_name,
webLogonToken: jsonBody.token
});
});
};
SteamCommunity.prototype._setCookie = function(cookie, secure) {
var protocol = secure ? "https" : "http";
cookie.secure = !!secure;
if (cookie.domain) {
this._jar.setCookie(cookie.clone(), protocol + '://' + cookie.domain);
} else {
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");
}
/**
* Sets a single cookie in our cookie jar.
* @param {string} cookie
* @private
*/
SteamCommunity.prototype._setCookie = function(cookie) {
this._jar.add(cookie, 'steamcommunity.com');
this._jar.add(cookie, 'store.steampowered.com');
this._jar.add(cookie, 'help.steampowered.com');
};
/**
* Set one or more cookies in this SteamCommunity's cookie jar.
* @param {string|string[]} cookies
*/
SteamCommunity.prototype.setCookies = function(cookies) {
if (!Array.isArray(cookies)) {
cookies = [cookies];
}
cookies.forEach((cookie) => {
var cookieName = cookie.trim().split('=')[0];
let cookieName = cookie.match(/(.+)=/)[1];
if (cookieName == 'steamLogin' || cookieName == 'steamLoginSecure') {
this.steamID = new SteamID(cookie.match(/steamLogin(Secure)?=(\d+)/)[2]);
this.steamID = new SteamID(cookie.match(/=(\d+)/)[1]);
}
this._setCookie(Request.cookie(cookie), !!(cookieName.match(/^steamMachineAuth/) || cookieName.match(/Secure$/)));
this._setCookie(cookie);
});
// The account we're logged in as might have changed, so verify that our mobile access token (if any) is still valid
@@ -178,278 +216,281 @@ SteamCommunity.prototype.setCookies = function(cookies) {
this._verifyMobileAccessToken();
};
SteamCommunity.prototype.getSessionID = function(host = "http://steamcommunity.com") {
var cookies = this._jar.getCookieString(host).split(';');
for(var i = 0; i < cookies.length; i++) {
var match = cookies[i].trim().match(/([^=]+)=(.+)/);
if(match[1] == 'sessionid') {
return decodeURIComponent(match[2]);
}
SteamCommunity.prototype.getSessionID = function(domain = 'steamcommunity.com') {
let sessionIdCookie = this._jar.cookies
.filter(c => c.domain == domain)
.find(c => c.name == 'sessionid');
if (sessionIdCookie) {
return sessionIdCookie.content;
}
var sessionID = generateSessionID();
this._setCookie(Request.cookie('sessionid=' + sessionID));
// No cookie found? Generate a new session id
let sessionID = require('crypto').randomBytes(12).toString('hex');
this._setCookie(`sessionid=${sessionID}`);
return sessionID;
};
function generateSessionID() {
return require('crypto').randomBytes(12).toString('hex');
}
/**
* @param {string} pin
* @param {function} [callback]
* @return Promise<void>
*/
SteamCommunity.prototype.parentalUnlock = function(pin, callback) {
var self = this;
var sessionID = self.getSessionID();
let sessionID = this.getSessionID();
this.httpRequestPost("https://steamcommunity.com/parental/ajaxunlock", {
"json": true,
"form": {
"pin": pin,
"sessionid": sessionID
}
}, function(err, response, body) {
if(!callback) {
return;
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
let {jsonBody} = await this.httpRequest({
method: 'POST',
url: 'https://steamcommunity.com/parental/ajaxunlock',
form: {
pin: pin,
sessionid: sessionID
},
source: 'steamcommunity'
});
if (!jsonBody || typeof jsonBody.success !== 'boolean') {
return reject('Invalid response');
}
if (err) {
callback(err);
return;
}
if (!jsonBody.success) {
switch (jsonBody.eresult) {
case SteamCommunity.EResult.AccessDenied:
return reject('Incorrect PIN');
if (!body || typeof body.success !== 'boolean') {
callback("Invalid response");
return;
}
if (!body.success) {
switch (body.eresult) {
case 15:
callback("Incorrect PIN");
break;
case 25:
callback("Too many invalid PIN attempts");
break;
case SteamCommunity.EResult.LimitExceeded:
return reject('Too many invalid PIN attempts');
default:
callback("Error " + body.eresult);
return reject('Error ' + jsonBody.eresult);
}
return;
}
callback();
}.bind(this), "steamcommunity");
resolve();
});
};
/**
* @param {function} [callback]
* @return Promise<object>
*/
SteamCommunity.prototype.getNotifications = function(callback) {
var self = this;
this.httpRequestGet({
"uri": "https://steamcommunity.com/actions/GetNotificationCounts",
"json": true
}, function(err, response, body) {
if (err) {
callback(err);
return;
return StdLib.Promises.callbackPromise(null, callback, false, async (resolve, reject) => {
let {jsonBody} = await this.httpRequest({
method: 'GET',
url: 'https://steamcommunity.com/actions/GetNotificationCounts',
source: 'steamcommunity'
});
if (!jsonBody || !jsonBody.notifications) {
return reject(new Error('Malformed response'));
}
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,
let notifications = {
trades: jsonBody.notifications[1] || 0,
gameTurns: jsonBody.notifications[2] || 0,
moderatorMessages: jsonBody.notifications[3] || 0,
comments: jsonBody.notifications[4] || 0,
items: jsonBody.notifications[5] || 0,
invites: jsonBody.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
gifts: jsonBody.notifications[8] || 0,
chat: jsonBody.notifications[9] || 0,
helpRequestReplies: jsonBody.notifications[10] || 0,
accountAlerts: jsonBody.notifications[11] || 0
};
callback(null, notifications);
}, "steamcommunity");
resolve(notifications);
});
};
/**
* @param {function} [callback]
* @return Promise<void>
*/
SteamCommunity.prototype.resetItemNotifications = function(callback) {
var self = this;
this.httpRequestGet("https://steamcommunity.com/my/inventory", function(err, response, body) {
if(!callback) {
return;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
await this.httpRequest({
method: 'GET',
url: 'https://steamcommunity.com/my/inventory',
source: 'steamcommunity'
});
callback(err || null);
}, "steamcommunity");
resolve();
});
};
/**
* @param {function} [callback]
* @return Promise<{loggedIn: boolean, familyView: boolean}>
*/
SteamCommunity.prototype.loggedIn = function(callback) {
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;
return StdLib.Promises.callbackPromise(['loggedIn', 'familyView'], callback, false, async (resolve, reject) => {
let result = await this.httpRequest({
method: 'GET',
url: 'https://steamcommunity.com/my',
followRedirect: false,
checkHttpError: false,
source: 'steamcommunity'
});
if (result.statusCode != 302 && result.statusCode != 403) {
return reject(new Error(`HTTP error ${result.statusCode}`));
}
if(response.statusCode == 403) {
callback(null, true, true);
return;
if (result.statusCode == 403) {
// TODO check response body to see if this is an akamai block
return resolve({
loggedIn: true,
familyView: true
});
}
callback(null, !!response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/), false);
}, "steamcommunity");
return resolve({
loggedIn: !!result.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^/]+)\/?/),
familyView: false
});
});
};
/**
* @param {function} [callback]
* @return Promise<{url: string, token: string}>
*/
SteamCommunity.prototype.getTradeURL = function(callback) {
this._myProfile("tradeoffers/privacy", null, (err, response, body) => {
if (err) {
callback(err);
return;
return StdLib.Promises.callbackPromise(['url', 'token'], callback, false, async (resolve, reject) => {
let {textBody} = await this._myProfile('tradeoffers/privacy');
let match = textBody.match(/https?:\/\/(www.)?steamcommunity.com\/tradeoffer\/new\/?\?partner=\d+(&|&amp;)token=([a-zA-Z0-9-_]+)/);
if (!match) {
return reject(new Error('Malformed response'));
}
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");
let token = match[3];
resolve({
url: match[0],
token
});
});
};
/**
* @param [callback]
* @return Promise<{url: string, token: string}>
*/
SteamCommunity.prototype.changeTradeURL = function(callback) {
this._myProfile("tradeoffers/newtradeurl", {"sessionid": this.getSessionID()}, (err, response, body) => {
if (!callback) {
return;
return StdLib.Promises.callbackPromise(['url', 'token'], callback, true, async (resolve, reject) => {
let {textBody} = await this._myProfile('tradeoffers/newtradeurl', {sessionid: this.getSessionID()});
if (!textBody || typeof textBody !== 'string' || textBody.length < 3 || textBody.indexOf('"') !== 0) {
return reject(new Error('Malformed response'));
}
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");
let newToken = textBody.replace(/"/g, ''); //"t1o2k3e4n" => t1o2k3e4n
resolve({
url: `https://steamcommunity.com/tradeoffer/new/?partner=${this.steamID.accountid}&token=${newToken}`,
token: newToken
});
});
};
/**
* Clear your profile name (alias) history.
* @param {function} callback
* @param {function} [callback]
* @return Promise<void>
*/
SteamCommunity.prototype.clearPersonaNameHistory = function(callback) {
this._myProfile("ajaxclearaliashistory/", {"sessionid": this.getSessionID()}, (err, res, body) => {
if (!callback) {
return;
}
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
let {statusCode, textBody} = await this._myProfile('ajaxclearaliashistory/', {sessionid: this.getSessionID()});
if (err) {
return callback(err);
}
if (res.statusCode != 200) {
return callback(new Error("HTTP error " + res.statusCode));
if (statusCode != 200) {
return reject(new Error(`HTTP error ${statusCode}`));
}
try {
body = JSON.parse(body);
callback(Helpers.eresultError(body.success));
let body = JSON.parse(textBody);
let err = Helpers.eresultError(body.success);
return err ? reject(err) : resolve();
} catch (ex) {
return callback(new Error("Malformed response"));
return reject(new Error('Malformed response'));
}
});
};
SteamCommunity.prototype._myProfile = function(endpoint, form, callback) {
var self = this;
if (this._profileURL) {
completeRequest(this._profileURL);
} else {
this.httpRequest("https://steamcommunity.com/my", {"followRedirect": false}, function(err, response, body) {
if(err || response.statusCode != 302) {
callback(err || "HTTP error " + response.statusCode);
return;
}
var match = response.headers.location.match(/steamcommunity\.com(\/(id|profiles)\/[^\/]+)\/?/);
if(!match) {
callback(new Error("Can't get profile URL"));
return;
}
self._profileURL = match[1];
setTimeout(function () {
delete self._profileURL; // delete the cache
}, 60000).unref();
completeRequest(match[1]);
}, "steamcommunity");
}
function completeRequest(url) {
var options = endpoint.endpoint ? endpoint : {};
options.uri = "https://steamcommunity.com" + url + "/" + (endpoint.endpoint || endpoint);
if (form) {
options.method = "POST";
options.form = form;
options.followAllRedirects = true;
} else if (!options.method) {
options.method = "GET";
}
self.httpRequest(options, callback, "steamcommunity");
}
};
/**
* Returns an object whose keys are 64-bit SteamIDs, and whose values are values from the EFriendRelationship enum.
* Therefore, you can deduce your friends or blocked list from this object.
* @param {function} callback
* @param {function} [callback]
* @return Promise<object[]>
*/
SteamCommunity.prototype.getFriendsList = function(callback) {
this.httpRequestGet({
"uri": "https://steamcommunity.com/textfilter/ajaxgetfriendslist",
"json": true
}, (err, res, body) => {
if (err) {
callback(err ? err : new Error('HTTP error ' + res.statusCode));
return;
return StdLib.Promises.callbackPromise(['friends'], callback, false, async (resolve, reject) => {
let {jsonBody} = await this.httpRequest({
method: 'GET',
url: 'https://steamcommunity.com/textfilter/ajaxgetfriendslist',
source: 'steamcommunity'
});
if (jsonBody.success != SteamCommunity.EResult.OK) {
return reject(Helpers.eresultError(jsonBody.success));
}
if (body.success != 1) {
callback(Helpers.eresultError(body.success));
return;
}
if (!body.friendslist || !body.friendslist.friends) {
callback(new Error('Malformed response'));
return;
if (!jsonBody.friendslist || !jsonBody.friendslist.friends) {
return reject(new Error('Malformed response'));
}
const friends = {};
body.friendslist.friends.forEach(friend => (friends[friend.ulfriendid] = friend.efriendrelationship));
callback(null, friends);
jsonBody.friendslist.friends.forEach(friend => (friends[friend.ulfriendid] = friend.efriendrelationship));
resolve({friends});
});
};
/**
* @param {string} url
* @return Promise<{vanityURL: string, steamID: SteamID}>
* @private
*/
SteamCommunity.prototype._resolveVanityURL = async function(url) {
// Precede url param if only the vanity was provided
if (!url.includes('steamcommunity.com')) {
url = `https://steamcommunity.com/id/${url}`;
}
// Make request to get XML data
let {textBody} = await this._httpRequest({
method: 'GET',
url,
source: 'steamcommunity'
});
return await new Promise((resolve, reject) => {
// Parse XML data returned from Steam into an object
new xml2js.Parser().parseString(textBody, (err, parsed) => {
if (err) {
return reject(new Error('Couldn\'t parse XML response'));
}
if (parsed.response && parsed.response.error) {
return reject(new Error('Couldn\'t find Steam ID'));
}
let steamID64 = parsed.profile.steamID64[0];
let vanityURL = parsed.profile.customURL[0];
resolve({
vanityURL,
steamID: new SteamID(steamID64)
});
});
});
};
require('./components/login.js');
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/sharedfiles.js');
require('./components/inventoryhistory.js');
require('./components/webapi.js');
require('./components/twofactor.js');
require('./components/confirmations.js');

View File

@@ -1,14 +1,8 @@
{
"name": "steamcommunity",
"version": "3.48.7",
"version": "4.0.0-dev",
"private": true,
"description": "Provides an interface for logging into and interacting with the Steam Community website",
"files": [
"/classes",
"/components",
"/examples",
"/resources",
"/index.js"
],
"keywords": [
"steam",
"steam community"
@@ -28,17 +22,22 @@
"url": "https://github.com/DoctorMcKay/node-steamcommunity.git"
},
"dependencies": {
"@doctormckay/user-agents": "^1.0.0",
"async": "^2.6.3",
"@doctormckay/stdlib": "^2.6.0",
"cheerio": "0.22.0",
"image-size": "^0.8.2",
"request": "^2.88.0",
"steam-session": "^1.9.1",
"steam-totp": "^1.5.0",
"steamid": "^1.1.3",
"xml2js": "^0.6.2"
"steam-session": "^1.2.4",
"steam-totp": "^2.1.0",
"steamid": "^2.0.0",
"tough-cookie": "^4.0.0",
"xml2js": "^0.6.0"
},
"engines": {
"node": ">=4.0.0"
"node": ">=14.0.0"
},
"devDependencies": {
"eslint": "^7.31.0"
},
"scripts": {
"lint": "npx eslint . --ext .js,.jsx,.ts,.tsx"
}
}

View File

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

View File

@@ -1,3 +1,5 @@
/* eslint-disable */
/**
* @enum EConfirmationType
*/

View File

@@ -1,7 +1,9 @@
/* eslint-disable */
/**
* @enum EFriendRelationship
*/
module.exports = {
module.exports = {
"None": 0,
"Blocked": 1,
"RequestRecipient": 2,

View File

@@ -1,3 +1,5 @@
/* eslint-disable */
/**
* @enum EPersonaState
*/

View File

@@ -1,3 +1,5 @@
/* eslint-disable */
/**
* @enum EPersonaStateFlag
*/

View File

@@ -1,3 +1,6 @@
/* eslint-disable */
// Auto-generated by generate-enums script on Thu Jul 29 2021 04:43:52 GMT-0400 (Eastern Daylight Time)
/**
* @enum EResult
*/
@@ -129,7 +132,8 @@ module.exports = {
"DeniedDueToCommunityCooldown": 116,
"NoLauncherSpecified": 117,
"MustAgreeToSSA": 118,
"ClientNoLongerSupported": 119,
"ClientNoLongerSupported": 119, // obsolete
"LauncherMigrated": 119,
// Value-to-name mapping for convenience
"0": "Invalid",
@@ -250,5 +254,5 @@ module.exports = {
"116": "DeniedDueToCommunityCooldown",
"117": "NoLauncherSpecified",
"118": "MustAgreeToSSA",
"119": "ClientNoLongerSupported",
"119": "LauncherMigrated",
};