steamutils 1.5.41 → 1.5.42

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.
package/_steamproto.js CHANGED
@@ -12,8 +12,13 @@ export class SteamProto {
12
12
  }
13
13
 
14
14
  toProto() {
15
- const filePath = path.join(`${__dirname}/protos/`, this._proto.filename);
16
- const root = new Protobuf.Root().loadSync([gpf.getProtoPath("protobuf/descriptor.proto"), filePath], {
15
+ const localProto = path.join(`${__dirname}/protos/`, this._proto.filename);
16
+ console.log(`localProto: ${localProto}`);
17
+
18
+ const descriptorPath = gpf.getProtoPath("protobuf/descriptor.proto");
19
+ console.log(`descriptorPath: ${descriptorPath}`);
20
+
21
+ const root = new Protobuf.Root().loadSync([descriptorPath, localProto], {
17
22
  keepCase: true,
18
23
  });
19
24
  return root.lookupType(this._proto.name);
@@ -12,8 +12,13 @@ export class SteamProto {
12
12
  }
13
13
 
14
14
  toProto() {
15
- const filePath = path.join(`${__dirname}/protos/`, this._proto.filename);
16
- const root = new Protobuf.Root().loadSync([gpf.getProtoPath("google/protobuf/descriptor.proto"), filePath], {
15
+ const localProto = path.join(`${__dirname}/protos/`, this._proto.filename);
16
+ console.log(`localProto: ${localProto}`);
17
+
18
+ const descriptorPath = gpf.getProtoPath("protobuf/descriptor.proto");
19
+ console.log(`descriptorPath: ${descriptorPath}`);
20
+
21
+ const root = new Protobuf.Root().loadSync([descriptorPath, localProto], {
17
22
  keepCase: true,
18
23
  });
19
24
  return root.lookupType(this._proto.name);
package/index.js CHANGED
@@ -5,7 +5,7 @@ import moment from "moment";
5
5
  import { hex2b64, Key as RSA } from "node-bignumber";
6
6
  import SteamID from "steamid";
7
7
  import qs from "qs";
8
- import { console_log, convertLongsToNumbers, downloadImage, formatMarketHistoryDate, getCleanObject, getImageSize, JSON_parse, JSON_stringify, removeSpaceKeys, secretAsBuffer, sleep } from "./utils.js";
8
+ import { console_log, convertLongsToNumbers, downloadImage, formatMarketHistoryDate, getCleanObject, getImageSize, isJWT, JSON_parse, JSON_stringify, removeSpaceKeys, secretAsBuffer, sleep } from "./utils.js";
9
9
  import { Header, request } from "./axios.js";
10
10
  import { getTableHasHeaders, querySelectorAll, table2json } from "./cheerio.js";
11
11
  import { getJSObjectFronXML } from "./xml2json.js";
@@ -807,9 +807,25 @@ export default class SteamUser {
807
807
  };
808
808
  }
809
809
 
810
- static async getAppVersion(appID) {
810
+ /**
811
+ * Fetches up-to-date status and version information for a Steam app by appID.
812
+ *
813
+ * @param {number|string} appID - The Steam app ID to query.
814
+ * @returns {Promise<{
815
+ * response: {
816
+ * success: boolean,
817
+ * up_to_date: boolean,
818
+ * version_is_listable: boolean,
819
+ * required_version: number,
820
+ * message: string
821
+ * }
822
+ * }|undefined>} The API response object, or undefined if an error occurs.
823
+ *
824
+ */
825
+ static async getSteamAppVersionInfo(appID) {
811
826
  try {
812
- return (await request(`https://api.steampowered.com/ISteamApps/UpToDateCheck/v1/?format=json&appid=${appID}&version=0`)).data.response.required_version;
827
+ const response = await fetch(`https://api.steampowered.com/ISteamApps/UpToDateCheck/v1/?format=json&appid=${appID}&version=0`);
828
+ return await response.json();
813
829
  } catch (e) {}
814
830
  }
815
831
 
@@ -6871,34 +6887,102 @@ export default class SteamUser {
6871
6887
  return { error };
6872
6888
  }
6873
6889
 
6874
- async enableTwoFactor(accessToken) {
6875
- if (!accessToken || typeof accessToken !== "string") {
6876
- return { error: "Invalid accessToken: " + accessToken };
6890
+ /**
6891
+ * @typedef {Object} SteamAuthenticatorResponse
6892
+ * @property {string} shared_secret
6893
+ * @property {string} serial_number
6894
+ * @property {string} revocation_code
6895
+ * @property {string} uri
6896
+ * @property {string} server_time
6897
+ * @property {string} account_name
6898
+ * @property {string} token_gid
6899
+ * @property {string} identity_secret
6900
+ * @property {string} secret_1
6901
+ * @property {number} status
6902
+ * @property {number} confirm_type
6903
+ */
6904
+
6905
+ /**
6906
+ * @typedef {Object} AddSteamAuthenticatorResult
6907
+ * @property {boolean} success - Whether enabling 2FA was successful.
6908
+ * @property {SteamAuthenticatorResponse} [response] - The response object if successful.
6909
+ * @property {string} [error] - Error message if unsuccessful.
6910
+ */
6911
+
6912
+ /**
6913
+ * Adds a Steam mobile authenticator to the account.
6914
+ *
6915
+ * @async
6916
+ * @param {string} accessToken - Steam access token (must be a valid JWT).
6917
+ * @returns {Promise<AddSteamAuthenticatorResult>}
6918
+ */
6919
+ async addSteamAuthenticator(accessToken) {
6920
+ if (!isJWT(accessToken)) {
6921
+ return {
6922
+ success: false,
6923
+ error: `Invalid accessToken (must be JWT): ${accessToken}`,
6924
+ };
6877
6925
  }
6878
6926
 
6879
- const SteamCommunity = (await import("steamcommunity")).default;
6880
- let error = null;
6927
+ const steamId = this.getSteamIdUser();
6928
+ const formData = new URLSearchParams({
6929
+ steamid: steamId,
6930
+ authenticator_type: 1,
6931
+ device_identifier: SteamTotp.getDeviceID(steamId),
6932
+ sms_phone_id: "1",
6933
+ version: 2,
6934
+ }).toString();
6935
+
6936
+ const url = `https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/?access_token=${encodeURIComponent(accessToken)}`;
6937
+ const headersBase = {
6938
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
6939
+ Origin: "https://api.steampowered.com",
6940
+ "Content-Type": "application/x-www-form-urlencoded",
6941
+ };
6942
+
6943
+ let lastError = null;
6944
+
6881
6945
  for (const cookie of this._cookies) {
6882
6946
  try {
6883
- const community = new SteamCommunity();
6884
- community.setCookies(cookie.toString().split(";"));
6885
- community.oAuthToken = accessToken;
6886
- try {
6887
- community.setMobileAppAccessToken(accessToken);
6888
- } catch (e) {}
6889
- const response = await new Promise((resolve) => {
6890
- community.enableTwoFactor((error, response) => {
6891
- resolve({ error, response });
6892
- });
6947
+ const response = await axios.request({
6948
+ method: "POST",
6949
+ url,
6950
+ headers: {
6951
+ ...headersBase,
6952
+ Cookie: cookie,
6953
+ },
6954
+ data: formData,
6893
6955
  });
6894
- if (!response.error) {
6895
- return { ...response, success: true };
6956
+ if (response.data && response.data.response) {
6957
+ return {
6958
+ success: true,
6959
+ response: response.data.response,
6960
+ };
6896
6961
  } else {
6897
- error = response.error;
6962
+ return {
6963
+ success: false,
6964
+ error: `Unexpected API response: ${JSON.stringify(response.data)}`,
6965
+ };
6898
6966
  }
6899
- } catch (e) {}
6967
+ } catch (error) {
6968
+ // Prepare a clear error message
6969
+ let apiError = "Unknown error";
6970
+ if (error.code === "ECONNABORTED") {
6971
+ apiError = "Request timed out";
6972
+ } else if (error.response && error.response.data) {
6973
+ apiError = typeof error.response.data === "object" ? JSON.stringify(error.response.data) : String(error.response.data);
6974
+ } else if (error.message) {
6975
+ apiError = error.message;
6976
+ }
6977
+ lastError = apiError;
6978
+ }
6900
6979
  }
6901
- return { error };
6980
+
6981
+ // All attempts failed.
6982
+ return {
6983
+ success: false,
6984
+ error: lastError || "Failed to add Steam authenticator: No cookies provided or all attempts failed.",
6985
+ };
6902
6986
  }
6903
6987
 
6904
6988
  async finalizeTwoFactor(accessToken, identitySecret, finalizeTwoFactorCode) {
@@ -7660,6 +7744,7 @@ export default class SteamUser {
7660
7744
  });
7661
7745
  return revokeRefreshTokenResult?.data;
7662
7746
  }
7747
+
7663
7748
  async revokeAccessToken(accessToken, tokenId, sharedSecret) {
7664
7749
  if (!accessToken || typeof accessToken !== "string") {
7665
7750
  console.error("revokeAccessToken", "invalid accessToken", accessToken);
@@ -8091,7 +8176,7 @@ export default class SteamUser {
8091
8176
  /**
8092
8177
  * Fetches Steam batched loyalty reward items for given app ID(s).
8093
8178
  * @param {number|number[]} appIds - A Steam App ID or array of App IDs.
8094
- * @returns {Promise<Array<{eresult: number, response: BatchedLoyaltyRewardItemsResponse}>>}
8179
+ * @returns {Promise<Array<{eresult: number, response: BatchedLoyaltyRewardItemsResponse}>|null|undefined>}
8095
8180
  */
8096
8181
  static async fetchBatchedLoyaltyRewardItems(appIds) {
8097
8182
  if (!appIds) {
@@ -8137,7 +8222,7 @@ export default class SteamUser {
8137
8222
  responseType: "arraybuffer",
8138
8223
  });
8139
8224
  if (result instanceof ResponseError) {
8140
- return result;
8225
+ return;
8141
8226
  }
8142
8227
 
8143
8228
  try {
@@ -8283,71 +8368,132 @@ export default class SteamUser {
8283
8368
  return apps.filter((app) => app.event_app);
8284
8369
  }
8285
8370
 
8286
- static async decodeQRImage(imageURL) {
8287
- let response = null;
8371
+ /**
8372
+ * Downloads an image from the given URL, decodes its QR code and returns the result.
8373
+ *
8374
+ * @param {string} imageURL - The URL of the image containing the QR code.
8375
+ * @returns {Promise<{ success: boolean, message: string, decodedQR: string | null }>}
8376
+ */
8377
+ static async extractQRCodeFromImageURL(imageURL) {
8378
+ // Validate URL format first
8288
8379
  try {
8289
- response = await axios.get(imageURL, {
8290
- responseType: "arraybuffer",
8291
- });
8292
- } catch (e) {}
8293
- const imageBuffer = response?.data;
8294
- if (!imageBuffer) {
8295
- return;
8380
+ new URL(imageURL);
8381
+ } catch (e) {
8382
+ return {
8383
+ success: false,
8384
+ message: "The provided imageURL is not a valid URL.",
8385
+ decodedQR: null,
8386
+ };
8296
8387
  }
8297
- try {
8298
- const { Jimp } = await import("jimp");
8299
8388
 
8300
- // Load the image with Jimp
8301
- const image = await Jimp.read(imageBuffer);
8389
+ let jimp, jsQR, imageBuffer;
8390
+ try {
8391
+ // Parallelize imports and fetch
8392
+ const [{ Jimp }, { default: jsQRmod }, response] = await Promise.all([import("jimp"), import("jsqr"), fetch(imageURL)]);
8393
+ jimp = Jimp;
8394
+ jsQR = jsQRmod;
8302
8395
 
8303
- // Get the image data
8304
- const imageData = {
8305
- data: new Uint8ClampedArray(image.bitmap.data),
8306
- width: image.bitmap.width,
8307
- height: image.bitmap.height,
8396
+ if (!response.ok) {
8397
+ return {
8398
+ success: false,
8399
+ message: `Failed to fetch image: HTTP ${response.status} ${response.statusText}`,
8400
+ decodedQR: null,
8401
+ };
8402
+ }
8403
+ imageBuffer = await response.arrayBuffer();
8404
+ if (!imageBuffer) {
8405
+ return {
8406
+ success: false,
8407
+ message: "Empty image data.",
8408
+ decodedQR: null,
8409
+ };
8410
+ }
8411
+ } catch (e) {
8412
+ return {
8413
+ success: false,
8414
+ message: `Initialization or fetch failure: ${e.message || e}`,
8415
+ decodedQR: null,
8308
8416
  };
8417
+ }
8309
8418
 
8310
- // Use jsQR to decode the QR code
8311
- const jsQR = (await import("jsqr")).default;
8312
- const decodedQR = jsQR(imageData.data, imageData.width, imageData.height);
8419
+ try {
8420
+ // Jimp accepts Buffer in Node.js, use Buffer.from(arrayBuffer)
8421
+ const image = await jimp.read(Buffer.from(imageBuffer));
8422
+ const { data, width, height } = image.bitmap;
8423
+ const clampedData = data instanceof Uint8ClampedArray ? data : new Uint8ClampedArray(data);
8424
+ const decodedQR = jsQR(clampedData, width, height);
8313
8425
 
8314
8426
  if (!decodedQR) {
8315
- return "QR code not found in the image.";
8427
+ return {
8428
+ success: false,
8429
+ message: "QR code not found in the image.",
8430
+ decodedQR: null,
8431
+ };
8316
8432
  }
8317
-
8318
- return decodedQR.data;
8433
+ return {
8434
+ success: true,
8435
+ message: "QR code successfully decoded.",
8436
+ decodedQR: decodedQR.data,
8437
+ };
8319
8438
  } catch (error) {
8320
- console.error("Error decoding QR code:", error);
8439
+ return {
8440
+ success: false,
8441
+ message: `Error decoding QR code: ${error.message || error}`,
8442
+ decodedQR: null,
8443
+ };
8321
8444
  }
8322
8445
  }
8323
8446
 
8324
- static async approveLoginRequest(steamId, url, sharedSecret, accessToken) {
8447
+ /**
8448
+ * Approves a Steam login request.
8449
+ * @param {string} steamId - Steam user ID.
8450
+ * @param {string} qrChallengeUrl - QR challenge URL.
8451
+ * @param {string} sharedSecret - Shared secret for TOTP.
8452
+ * @param {string} accessToken - API access token.
8453
+ * @returns {Promise<{steamId: string, error: string|null, success: boolean}>}
8454
+ */
8455
+ static async approveLoginRequest(steamId, qrChallengeUrl, sharedSecret, accessToken) {
8325
8456
  try {
8326
8457
  const { LoginApprover } = await import("steam-session");
8327
- const approver = new LoginApprover(accessToken, sharedSecret, {});
8328
- await approver.approveAuthSession({
8329
- qrChallengeUrl: url,
8330
- approve: true,
8331
- });
8332
-
8333
- return {
8334
- steamId,
8335
- error: null,
8336
- };
8458
+ const approver = new LoginApprover(accessToken, sharedSecret);
8459
+ await approver.approveAuthSession({ qrChallengeUrl, approve: true });
8460
+ return { steamId, error: null, success: true };
8337
8461
  } catch (error) {
8338
- console.error(`[${steamId}] approveLoginRequest Error`, error);
8339
- return {
8340
- steamId,
8341
- error: error || "Error",
8342
- };
8462
+ return { steamId, error: error?.message || "Unknown error", success: false };
8343
8463
  }
8344
8464
  }
8345
8465
 
8346
- static async getAuthSessionInfo(steamId, url, sharedSecret, accessToken) {
8466
+ /**
8467
+ * @typedef {Object} SessionInfo
8468
+ * @property {string} ip - The IP address of the session.
8469
+ * @property {Object} location - Geolocation details.
8470
+ * @property {string} location.geoloc - Latitude and longitude as a string.
8471
+ * @property {string} location.city - City of the session.
8472
+ * @property {string} location.state - State or region of the session.
8473
+ * @property {number} platformType - Numeric platform type indicator.
8474
+ * @property {string} deviceFriendlyName - Friendly name of the device.
8475
+ * @property {number} version - Version of the protocol/session.
8476
+ * @property {number} loginHistory - Login history flag or value.
8477
+ * @property {boolean} locationMismatch - Whether the location mismatches profile.
8478
+ * @property {boolean} highUsageLogin - Whether this is a high-usage login.
8479
+ * @property {number} requestedPersistence - Type/level of session persistence requested.
8480
+ * @property {string} steamId - Steam ID associated with the session.
8481
+ */
8482
+
8483
+ /**
8484
+ * Retrieves Steam authentication session information.
8485
+ *
8486
+ * @param {string} steamId - The Steam user ID.
8487
+ * @param {string} qrChallengeUrl - The QR challenge URL.
8488
+ * @param {string} sharedSecret - The shared secret for authentication.
8489
+ * @param {string} accessToken - The access token.
8490
+ * @returns {Promise<{steamId: string, sessionInfo?: SessionInfo, error?: string}>} Result containing the original Steam ID, session info if successful, or error message if failed.
8491
+ */
8492
+ static async getAuthSessionInfo(steamId, qrChallengeUrl, sharedSecret, accessToken) {
8347
8493
  try {
8348
8494
  const { LoginApprover } = await import("steam-session");
8349
8495
  const approver = new LoginApprover(accessToken, sharedSecret, {});
8350
- const sessionInfo = await approver.getAuthSessionInfo(url);
8496
+ const sessionInfo = await approver.getAuthSessionInfo(qrChallengeUrl);
8351
8497
  sessionInfo.steamId = steamId;
8352
8498
 
8353
8499
  return {
@@ -8355,10 +8501,9 @@ export default class SteamUser {
8355
8501
  sessionInfo,
8356
8502
  };
8357
8503
  } catch (error) {
8358
- console.error(`[${steamId}] getAuthSessionInfo Error`, error);
8359
8504
  return {
8360
8505
  steamId,
8361
- error,
8506
+ error: error?.message || "Unknown error",
8362
8507
  };
8363
8508
  }
8364
8509
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "steamutils",
3
- "version": "1.5.41",
3
+ "version": "1.5.42",
4
4
  "main": "index.js",
5
5
  "dependencies": {
6
6
  "alpha-common-utils": "^1.0.6",
package/steamproto.js CHANGED
@@ -12,8 +12,13 @@ export class SteamProto {
12
12
  }
13
13
 
14
14
  toProto() {
15
- const filePath = path.join(`${__dirname}/protos/`, this._proto.filename);
16
- const root = new Protobuf.Root().loadSync([gpf.getProtoPath("google/protobuf/descriptor.proto"), filePath], {
15
+ const localProto = path.join(`${__dirname}/protos/`, this._proto.filename);
16
+ console.log(`localProto: ${localProto}`);
17
+
18
+ const descriptorPath = gpf.getProtoPath("protobuf/descriptor.proto");
19
+ console.log(`descriptorPath: ${descriptorPath}`);
20
+
21
+ const root = new Protobuf.Root().loadSync([descriptorPath, localProto], {
17
22
  keepCase: true,
18
23
  });
19
24
  return root.lookupType(this._proto.name);