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/SteamClient.js +3159 -3158
- package/_steamproto.js +7 -2
- package/full_steamproto.js +7 -2
- package/index.js +215 -70
- package/package.json +1 -1
- package/steamproto.js +7 -2
- package/utils.js +1300 -1282
package/_steamproto.js
CHANGED
@@ -12,8 +12,13 @@ export class SteamProto {
|
|
12
12
|
}
|
13
13
|
|
14
14
|
toProto() {
|
15
|
-
const
|
16
|
-
|
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/full_steamproto.js
CHANGED
@@ -12,8 +12,13 @@ export class SteamProto {
|
|
12
12
|
}
|
13
13
|
|
14
14
|
toProto() {
|
15
|
-
const
|
16
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
6875
|
-
|
6876
|
-
|
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
|
6880
|
-
|
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
|
6884
|
-
|
6885
|
-
|
6886
|
-
|
6887
|
-
|
6888
|
-
|
6889
|
-
|
6890
|
-
|
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 (
|
6895
|
-
return {
|
6956
|
+
if (response.data && response.data.response) {
|
6957
|
+
return {
|
6958
|
+
success: true,
|
6959
|
+
response: response.data.response,
|
6960
|
+
};
|
6896
6961
|
} else {
|
6897
|
-
|
6962
|
+
return {
|
6963
|
+
success: false,
|
6964
|
+
error: `Unexpected API response: ${JSON.stringify(response.data)}`,
|
6965
|
+
};
|
6898
6966
|
}
|
6899
|
-
} catch (
|
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
|
-
|
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
|
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
|
-
|
8287
|
-
|
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
|
-
|
8290
|
-
|
8291
|
-
|
8292
|
-
|
8293
|
-
|
8294
|
-
|
8295
|
-
|
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
|
-
|
8301
|
-
|
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
|
-
|
8304
|
-
|
8305
|
-
|
8306
|
-
|
8307
|
-
|
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
|
-
|
8311
|
-
|
8312
|
-
const
|
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
|
8427
|
+
return {
|
8428
|
+
success: false,
|
8429
|
+
message: "QR code not found in the image.",
|
8430
|
+
decodedQR: null,
|
8431
|
+
};
|
8316
8432
|
}
|
8317
|
-
|
8318
|
-
|
8433
|
+
return {
|
8434
|
+
success: true,
|
8435
|
+
message: "QR code successfully decoded.",
|
8436
|
+
decodedQR: decodedQR.data,
|
8437
|
+
};
|
8319
8438
|
} catch (error) {
|
8320
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
package/steamproto.js
CHANGED
@@ -12,8 +12,13 @@ export class SteamProto {
|
|
12
12
|
}
|
13
13
|
|
14
14
|
toProto() {
|
15
|
-
const
|
16
|
-
|
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);
|