steamcommunity 3.45.2 → 3.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,6 @@
1
1
  const EResult = require('../resources/EResult.js');
2
+ const request = require('request');
3
+ const xml2js = require('xml2js');
2
4
 
3
5
  exports.isSteamID = function(input) {
4
6
  var keys = Object.keys(input);
@@ -65,4 +67,42 @@ exports.decodeJwt = function(jwt) {
65
67
  .replace(/_/g, '/');
66
68
 
67
69
  return JSON.parse(Buffer.from(standardBase64, 'base64').toString('utf8'));
68
- }
70
+ };
71
+
72
+ /**
73
+ * Resolves a Steam profile URL to get steamID64 and vanityURL
74
+ * @param {String} url - Full steamcommunity profile URL or only the vanity part.
75
+ * @param {Object} callback - First argument is null/Error, second is object containing vanityURL (String) and steamID (String)
76
+ */
77
+ exports.resolveVanityURL = function(url, callback) {
78
+ // Precede url param if only the vanity was provided
79
+ if (!url.includes("steamcommunity.com")) {
80
+ url = "https://steamcommunity.com/id/" + url;
81
+ }
82
+
83
+ // Make request to get XML data
84
+ request(url + "/?xml=1", function(err, response, body) {
85
+ if (err) {
86
+ callback(err);
87
+ return;
88
+ }
89
+
90
+ // Parse XML data returned from Steam into an object
91
+ new xml2js.Parser().parseString(body, (err, parsed) => {
92
+ if (err) {
93
+ callback(new Error("Couldn't parse XML response"));
94
+ return;
95
+ }
96
+
97
+ if (parsed.response && parsed.response.error) {
98
+ callback(new Error("Couldn't find Steam ID"));
99
+ return;
100
+ }
101
+
102
+ let steamID64 = parsed.profile.steamID64;
103
+ let vanityURL = parsed.profile.customURL;
104
+
105
+ callback(null, {"vanityURL": vanityURL, "steamID": steamID64});
106
+ });
107
+ });
108
+ };
@@ -1,5 +1,6 @@
1
1
  var SteamCommunity = require('../index.js');
2
2
  var CEconItem = require('../classes/CEconItem.js');
3
+ var Helpers = require('./helpers.js');
3
4
  var SteamID = require('steamid');
4
5
  var request = require('request');
5
6
  var Cheerio = require('cheerio');
@@ -142,7 +143,7 @@ SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
142
143
  }
143
144
 
144
145
  if (options.resolveVanityURLs) {
145
- Async.map(vanityURLs, resolveVanityURL, function(err, results) {
146
+ Async.map(vanityURLs, Helpers.resolveVanityURL, function(err, results) {
146
147
  if (err) {
147
148
  callback(err);
148
149
  return;
@@ -170,19 +171,3 @@ SteamCommunity.prototype.getInventoryHistory = function(options, callback) {
170
171
  }, "steamcommunity");
171
172
  };
172
173
 
173
- function resolveVanityURL(vanityURL, callback) {
174
- request("https://steamcommunity.com/id/" + vanityURL + "/?xml=1", function(err, response, body) {
175
- if (err) {
176
- callback(err);
177
- return;
178
- }
179
-
180
- var match = body.match(/<steamID64>(\d+)<\/steamID64>/);
181
- if (!match || !match[1]) {
182
- callback(new Error("Couldn't find Steam ID"));
183
- return;
184
- }
185
-
186
- callback(null, {"vanityURL": vanityURL, "steamID": match[1]});
187
- });
188
- }
@@ -0,0 +1,158 @@
1
+ var SteamID = require('steamid');
2
+
3
+ var SteamCommunity = require('../index.js');
4
+
5
+
6
+ /**
7
+ * Deletes a comment from a sharedfile's comment section
8
+ * @param {SteamID | String} userID - ID of the user associated to this sharedfile
9
+ * @param {String} sharedFileId - ID of the sharedfile
10
+ * @param {String} cid - ID of the comment to delete
11
+ * @param {function} callback - Takes only an Error object/null as the first argument
12
+ */
13
+ SteamCommunity.prototype.deleteSharedFileComment = function(userID, sharedFileId, cid, callback) {
14
+ if (typeof userID === "string") {
15
+ userID = new SteamID(userID);
16
+ }
17
+
18
+ this.httpRequestPost({
19
+ "uri": `https://steamcommunity.com/comment/PublishedFile_Public/delete/${userID.toString()}/${sharedFileId}/`,
20
+ "form": {
21
+ "gidcomment": cid,
22
+ "count": 10,
23
+ "sessionid": this.getSessionID()
24
+ }
25
+ }, function(err, response, body) {
26
+ if (!callback) {
27
+ return;
28
+ }
29
+
30
+ callback(err);
31
+ }, "steamcommunity");
32
+ };
33
+
34
+ /**
35
+ * Favorites a sharedfile
36
+ * @param {String} sharedFileId - ID of the sharedfile
37
+ * @param {String} appid - ID of the app associated to this sharedfile
38
+ * @param {function} callback - Takes only an Error object/null as the first argument
39
+ */
40
+ SteamCommunity.prototype.favoriteSharedFile = function(sharedFileId, appid, callback) {
41
+ this.httpRequestPost({
42
+ "uri": "https://steamcommunity.com/sharedfiles/favorite",
43
+ "form": {
44
+ "id": sharedFileId,
45
+ "appid": appid,
46
+ "sessionid": this.getSessionID()
47
+ }
48
+ }, function(err, response, body) {
49
+ if (!callback) {
50
+ return;
51
+ }
52
+
53
+ callback(err);
54
+ }, "steamcommunity");
55
+ };
56
+
57
+ /**
58
+ * Posts a comment to a sharedfile
59
+ * @param {SteamID | String} userID - ID of the user associated to this sharedfile
60
+ * @param {String} sharedFileId - ID of the sharedfile
61
+ * @param {String} message - Content of the comment to post
62
+ * @param {function} callback - Takes only an Error object/null as the first argument
63
+ */
64
+ SteamCommunity.prototype.postSharedFileComment = function(userID, sharedFileId, message, callback) {
65
+ if (typeof userID === "string") {
66
+ userID = new SteamID(userID);
67
+ }
68
+
69
+ this.httpRequestPost({
70
+ "uri": `https://steamcommunity.com/comment/PublishedFile_Public/post/${userID.toString()}/${sharedFileId}/`,
71
+ "form": {
72
+ "comment": message,
73
+ "count": 10,
74
+ "sessionid": this.getSessionID()
75
+ }
76
+ }, function(err, response, body) {
77
+ if (!callback) {
78
+ return;
79
+ }
80
+
81
+ callback(err);
82
+ }, "steamcommunity");
83
+ };
84
+
85
+ /**
86
+ * Subscribes to a sharedfile's comment section. Note: Checkbox on webpage does not update
87
+ * @param {SteamID | String} userID ID of the user associated to this sharedfile
88
+ * @param {String} sharedFileId ID of the sharedfile
89
+ * @param {function} callback - Takes only an Error object/null as the first argument
90
+ */
91
+ SteamCommunity.prototype.subscribeSharedFileComments = function(userID, sharedFileId, callback) {
92
+ if (typeof userID === "string") {
93
+ userID = new SteamID(userID);
94
+ }
95
+
96
+ this.httpRequestPost({
97
+ "uri": `https://steamcommunity.com/comment/PublishedFile_Public/subscribe/${userID.toString()}/${sharedFileId}/`,
98
+ "form": {
99
+ "count": 10,
100
+ "sessionid": this.getSessionID()
101
+ }
102
+ }, function(err, response, body) { // eslint-disable-line
103
+ if (!callback) {
104
+ return;
105
+ }
106
+
107
+ callback(err);
108
+ }, "steamcommunity");
109
+ };
110
+
111
+ /**
112
+ * Unfavorites a sharedfile
113
+ * @param {String} sharedFileId - ID of the sharedfile
114
+ * @param {String} appid - ID of the app associated to this sharedfile
115
+ * @param {function} callback - Takes only an Error object/null as the first argument
116
+ */
117
+ SteamCommunity.prototype.unfavoriteSharedFile = function(sharedFileId, appid, callback) {
118
+ this.httpRequestPost({
119
+ "uri": "https://steamcommunity.com/sharedfiles/unfavorite",
120
+ "form": {
121
+ "id": sharedFileId,
122
+ "appid": appid,
123
+ "sessionid": this.getSessionID()
124
+ }
125
+ }, function(err, response, body) {
126
+ if (!callback) {
127
+ return;
128
+ }
129
+
130
+ callback(err);
131
+ }, "steamcommunity");
132
+ };
133
+
134
+ /**
135
+ * Unsubscribes from a sharedfile's comment section. Note: Checkbox on webpage does not update
136
+ * @param {SteamID | String} userID - ID of the user associated to this sharedfile
137
+ * @param {String} sharedFileId - ID of the sharedfile
138
+ * @param {function} callback - Takes only an Error object/null as the first argument
139
+ */
140
+ SteamCommunity.prototype.unsubscribeSharedFileComments = function(userID, sharedFileId, callback) {
141
+ if (typeof userID === "string") {
142
+ userID = new SteamID(userID);
143
+ }
144
+
145
+ this.httpRequestPost({
146
+ "uri": `https://steamcommunity.com/comment/PublishedFile_Public/unsubscribe/${userID.toString()}/${sharedFileId}/`,
147
+ "form": {
148
+ "count": 10,
149
+ "sessionid": this.getSessionID()
150
+ }
151
+ }, function(err, response, body) { // eslint-disable-line
152
+ if (!callback) {
153
+ return;
154
+ }
155
+
156
+ callback(err);
157
+ }, "steamcommunity");
158
+ };
@@ -0,0 +1,35 @@
1
+ # node-steamcommunity examples
2
+
3
+ The files in this directory are example scripts that you can use as a getting-started point for using node-steamcommunity.
4
+
5
+ ## Enable or Disable Two-Factor Authentication
6
+
7
+ If you need to enable or disable 2FA on your bot account, you can use enable_twofactor.js and disable_twofactor.js to do so.
8
+ The way that you're intended to use these scripts is by cloning the repository locally, and then running them directly
9
+ from this examples directory.
10
+
11
+ For example:
12
+
13
+ ```shell
14
+ git clone https://github.com/DoctorMcKay/node-steamcommunity node-steamcommunity
15
+ cd node-steamcommunity
16
+ npm install
17
+ cd examples
18
+ node enable_twofactor.js
19
+ ```
20
+
21
+ ## Accept All Confirmations
22
+
23
+ If you need to accept trade or market confirmations on your bot account for which you have your identity secret, you can
24
+ use accept_all_confirmations.js to do so. The way that you're intended to use this script is by cloning the repository
25
+ locally, and then running it directly from this examples directory.
26
+
27
+ For example:
28
+
29
+ ```shell
30
+ git clone https://github.com/DoctorMcKay/node-steamcommunity node-steamcommunity
31
+ cd node-steamcommunity
32
+ npm install
33
+ cd examples
34
+ node accept_all_confirmations.js
35
+ ```
@@ -0,0 +1,173 @@
1
+ // If you aren't running this script inside of the repository, replace the following line with:
2
+ // const SteamCommunity = require('steamcommunity');
3
+ const SteamCommunity = require('../index.js');
4
+ const SteamSession = require('steam-session');
5
+ const SteamTotp = require('steam-totp');
6
+ const ReadLine = require('readline');
7
+
8
+ const EConfirmationType = SteamCommunity.ConfirmationType;
9
+
10
+ let g_AbortPromptFunc = null;
11
+
12
+ let community = new SteamCommunity();
13
+
14
+ main();
15
+ async function main() {
16
+ let accountName = await promptAsync('Username: ');
17
+ let password = await promptAsync('Password (hidden): ', true);
18
+
19
+ // Create a LoginSession for us to use to attempt to log into steam
20
+ let session = new SteamSession.LoginSession(SteamSession.EAuthTokenPlatformType.MobileApp);
21
+
22
+ // Go ahead and attach our event handlers before we do anything else.
23
+ session.on('authenticated', async () => {
24
+ abortPrompt();
25
+
26
+ let cookies = await session.getWebCookies();
27
+ community.setCookies(cookies);
28
+
29
+ doConfirmations();
30
+ });
31
+
32
+ session.on('timeout', () => {
33
+ abortPrompt();
34
+ console.log('This login attempt has timed out.');
35
+ });
36
+
37
+ session.on('error', (err) => {
38
+ abortPrompt();
39
+
40
+ // This should ordinarily not happen. This only happens in case there's some kind of unexpected error while
41
+ // polling, e.g. the network connection goes down or Steam chokes on something.
42
+
43
+ console.log(`ERROR: This login attempt has failed! ${err.message}`);
44
+ });
45
+
46
+ // Start our login attempt
47
+ let startResult = await session.startWithCredentials({accountName, password});
48
+ if (startResult.actionRequired) {
49
+ // Some Steam Guard action is required. We only care about email and device codes; in theory an
50
+ // EmailConfirmation and/or DeviceConfirmation action could be possible, but we're just going to ignore those.
51
+ // If the user does receive a confirmation and accepts it, LoginSession will detect and handle that automatically.
52
+ // The only consequence of ignoring it here is that we don't print a message to the user indicating that they
53
+ // could accept an email or device confirmation.
54
+
55
+ let codeActionTypes = [SteamSession.EAuthSessionGuardType.EmailCode, SteamSession.EAuthSessionGuardType.DeviceCode];
56
+ let codeAction = startResult.validActions.find(action => codeActionTypes.includes(action.type));
57
+ if (codeAction) {
58
+ if (codeAction.type == SteamSession.EAuthSessionGuardType.EmailCode) {
59
+ console.log(`A code has been sent to your email address at ${codeAction.detail}.`);
60
+ } else {
61
+ // We wouldn't expect this to happen since we're trying to enable 2FA, but just in case...
62
+ console.log('You need to provide a Steam Guard Mobile Authenticator code.');
63
+ }
64
+
65
+ let code = await promptAsync('Code or Shared Secret: ');
66
+ if (code) {
67
+ // The code might've been a shared secret
68
+ if (code.length > 10) {
69
+ code = SteamTotp.getAuthCode(code);
70
+ }
71
+ await session.submitSteamGuardCode(code);
72
+ }
73
+
74
+ // If we fall through here without submitting a Steam Guard code, that means one of two things:
75
+ // 1. The user pressed enter without providing a code, in which case the script will simply exit
76
+ // 2. The user approved a device/email confirmation, in which case 'authenticated' was emitted and the prompt was canceled
77
+ }
78
+ }
79
+ }
80
+
81
+ async function doConfirmations() {
82
+ let identitySecret = await promptAsync('Identity Secret: ');
83
+
84
+ let confs = await new Promise((resolve, reject) => {
85
+ let time = SteamTotp.time();
86
+ let key = SteamTotp.getConfirmationKey(identitySecret, time, 'conf');
87
+ community.getConfirmations(time, key, (err, confs) => {
88
+ if (err) {
89
+ return reject(err);
90
+ }
91
+
92
+ resolve(confs);
93
+ });
94
+ });
95
+
96
+ console.log(`Found ${confs.length} outstanding confirmations.`);
97
+
98
+ // We need to track the previous timestamp we used, as we cannot reuse timestamps.
99
+ let previousTime = 0;
100
+
101
+ for (let i = 0; i < confs.length; i++) {
102
+ let conf = confs[i];
103
+
104
+ process.stdout.write(`Accepting confirmation for ${EConfirmationType[conf.type]} - ${conf.title}... `);
105
+
106
+ try {
107
+ await new Promise((resolve, reject) => {
108
+ let time = SteamTotp.time();
109
+ if (time == previousTime) {
110
+ time++;
111
+ }
112
+
113
+ previousTime = time;
114
+ let key = SteamTotp.getConfirmationKey(identitySecret, time, 'allow');
115
+ conf.respond(time, key, true, (err) => {
116
+ err ? reject(err) : resolve();
117
+ });
118
+ });
119
+
120
+ console.log('success');
121
+ } catch (ex) {
122
+ console.log(`error: ${ex.message}`);
123
+ }
124
+
125
+ // sleep 500ms so we don't run too far away from the current timestamp
126
+ await new Promise(resolve => setTimeout(resolve, 500));
127
+ }
128
+
129
+ console.log('Finished processing confirmations');
130
+ process.exit(0);
131
+ }
132
+
133
+ // Nothing interesting below here, just code for prompting for input from the console.
134
+
135
+ function promptAsync(question, sensitiveInput = false) {
136
+ return new Promise((resolve) => {
137
+ let rl = ReadLine.createInterface({
138
+ input: process.stdin,
139
+ output: sensitiveInput ? null : process.stdout,
140
+ terminal: true
141
+ });
142
+
143
+ g_AbortPromptFunc = () => {
144
+ rl.close();
145
+ resolve('');
146
+ };
147
+
148
+ if (sensitiveInput) {
149
+ // We have to write the question manually if we didn't give readline an output stream
150
+ process.stdout.write(question);
151
+ }
152
+
153
+ rl.question(question, (result) => {
154
+ if (sensitiveInput) {
155
+ // We have to manually print a newline
156
+ process.stdout.write('\n');
157
+ }
158
+
159
+ g_AbortPromptFunc = null;
160
+ rl.close();
161
+ resolve(result);
162
+ });
163
+ });
164
+ }
165
+
166
+ function abortPrompt() {
167
+ if (!g_AbortPromptFunc) {
168
+ return;
169
+ }
170
+
171
+ g_AbortPromptFunc();
172
+ process.stdout.write('\n');
173
+ }
@@ -1,73 +1,85 @@
1
1
  // If you aren't running this script inside of the repository, replace the following line with:
2
2
  // const SteamCommunity = require('steamcommunity');
3
3
  const SteamCommunity = require('../index.js');
4
+ const SteamSession = require('steam-session');
4
5
  const ReadLine = require('readline');
5
6
 
7
+ let g_AbortPromptFunc = null;
8
+
6
9
  let community = new SteamCommunity();
7
- let rl = ReadLine.createInterface({
8
- input: process.stdin,
9
- output: process.stdout
10
- });
11
-
12
- rl.question('Username: ', (accountName) => {
13
- rl.question('Password: ', (password) => {
14
- rl.question('Two-Factor Auth Code: ', (authCode) =>{
15
- rl.question('Revocation Code: R', (rCode) => {
16
- doLogin(accountName, password, authCode, '', rCode);
17
- });
18
- });
19
- });
20
- });
21
-
22
- function doLogin(accountName, password, authCode, captcha, rCode) {
23
- community.login({
24
- accountName: accountName,
25
- password: password,
26
- twoFactorCode: authCode,
27
- captcha: captcha
28
- }, (err, sessionID, cookies, steamguard) => {
29
- if (err) {
30
- if (err.message == 'SteamGuard') {
31
- console.log('This account does not have two-factor authentication enabled.');
32
- process.exit();
33
- return;
34
- }
35
10
 
36
- if (err.message == 'CAPTCHA') {
37
- console.log(err.captchaurl);
38
- rl.question('CAPTCHA: ', (captchaInput) => {
39
- doLogin(accountName, password, authCode, captchaInput);
40
- });
11
+ main();
12
+ async function main() {
13
+ let accountName = await promptAsync('Username: ');
14
+ let password = await promptAsync('Password (hidden): ', true);
41
15
 
42
- return;
43
- }
16
+ // Create a LoginSession for us to use to attempt to log into steam
17
+ let session = new SteamSession.LoginSession(SteamSession.EAuthTokenPlatformType.MobileApp);
44
18
 
45
- console.log(err);
46
- process.exit();
47
- return;
48
- }
19
+ // Go ahead and attach our event handlers before we do anything else.
20
+ session.on('authenticated', async () => {
21
+ abortPrompt();
49
22
 
50
- console.log('Logged on!');
23
+ let accessToken = session.accessToken;
24
+ let cookies = await session.getWebCookies();
51
25
 
52
- if (community.mobileAccessToken) {
53
- // If we already have a mobile access token, we don't need to prompt for one.
54
- doRevoke(rCode);
55
- return;
56
- }
26
+ community.setCookies(cookies);
27
+ community.setMobileAppAccessToken(accessToken);
57
28
 
58
- console.log('You need to provide a mobile app access token to continue.');
59
- console.log('You can generate one using steam-session (https://www.npmjs.com/package/steam-session).');
60
- console.log('The access token needs to be generated using EAuthTokenPlatformType.MobileApp.');
61
- console.log('Make sure you provide an *ACCESS* token, not a refresh token.');
29
+ // Enabling or disabling 2FA is presently the only action in node-steamcommunity which requires an access token.
30
+ // In all other cases, using `community.setCookies(cookies)` is all you need to do in order to be logged in,
31
+ // although there's never any harm in setting a mobile app access token.
62
32
 
63
- rl.question('Access Token: ', (accessToken) => {
64
- community.setMobileAppAccessToken(accessToken);
65
- doRevoke(rCode);
66
- });
33
+ doRevoke();
34
+ });
35
+
36
+ session.on('timeout', () => {
37
+ abortPrompt();
38
+ console.log('This login attempt has timed out.');
39
+ });
40
+
41
+ session.on('error', (err) => {
42
+ abortPrompt();
43
+
44
+ // This should ordinarily not happen. This only happens in case there's some kind of unexpected error while
45
+ // polling, e.g. the network connection goes down or Steam chokes on something.
46
+
47
+ console.log(`ERROR: This login attempt has failed! ${err.message}`);
67
48
  });
49
+
50
+ // Start our login attempt
51
+ let startResult = await session.startWithCredentials({accountName, password});
52
+ if (startResult.actionRequired) {
53
+ // Some Steam Guard action is required. We only care about email and device codes; in theory an
54
+ // EmailConfirmation and/or DeviceConfirmation action could be possible, but we're just going to ignore those.
55
+ // If the user does receive a confirmation and accepts it, LoginSession will detect and handle that automatically.
56
+ // The only consequence of ignoring it here is that we don't print a message to the user indicating that they
57
+ // could accept an email or device confirmation.
58
+
59
+ let codeActionTypes = [SteamSession.EAuthSessionGuardType.EmailCode, SteamSession.EAuthSessionGuardType.DeviceCode];
60
+ let codeAction = startResult.validActions.find(action => codeActionTypes.includes(action.type));
61
+ if (codeAction) {
62
+ if (codeAction.type == SteamSession.EAuthSessionGuardType.EmailCode) {
63
+ // We wouldn't expect this to happen since we're trying to disable 2FA, but just in case...
64
+ console.log(`A code has been sent to your email address at ${codeAction.detail}.`);
65
+ } else {
66
+ console.log('You need to provide a Steam Guard Mobile Authenticator code.');
67
+ }
68
+
69
+ let code = await promptAsync('Code: ');
70
+ if (code) {
71
+ await session.submitSteamGuardCode(code);
72
+ }
73
+
74
+ // If we fall through here without submitting a Steam Guard code, that means one of two things:
75
+ // 1. The user pressed enter without providing a code, in which case the script will simply exit
76
+ // 2. The user approved a device/email confirmation, in which case 'authenticated' was emitted and the prompt was canceled
77
+ }
78
+ }
68
79
  }
69
80
 
70
- function doRevoke(rCode) {
81
+ async function doRevoke() {
82
+ let rCode = await promptAsync('Revocation Code: R');
71
83
  community.disableTwoFactor('R' + rCode, (err) => {
72
84
  if (err) {
73
85
  console.log(err);
@@ -79,3 +91,45 @@ function doRevoke(rCode) {
79
91
  process.exit();
80
92
  });
81
93
  }
94
+
95
+ // Nothing interesting below here, just code for prompting for input from the console.
96
+
97
+ function promptAsync(question, sensitiveInput = false) {
98
+ return new Promise((resolve) => {
99
+ let rl = ReadLine.createInterface({
100
+ input: process.stdin,
101
+ output: sensitiveInput ? null : process.stdout,
102
+ terminal: true
103
+ });
104
+
105
+ g_AbortPromptFunc = () => {
106
+ rl.close();
107
+ resolve('');
108
+ };
109
+
110
+ if (sensitiveInput) {
111
+ // We have to write the question manually if we didn't give readline an output stream
112
+ process.stdout.write(question);
113
+ }
114
+
115
+ rl.question(question, (result) => {
116
+ if (sensitiveInput) {
117
+ // We have to manually print a newline
118
+ process.stdout.write('\n');
119
+ }
120
+
121
+ g_AbortPromptFunc = null;
122
+ rl.close();
123
+ resolve(result);
124
+ });
125
+ });
126
+ }
127
+
128
+ function abortPrompt() {
129
+ if (!g_AbortPromptFunc) {
130
+ return;
131
+ }
132
+
133
+ g_AbortPromptFunc();
134
+ process.stdout.write('\n');
135
+ }