steamutils 1.5.40 → 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 +338 -91
- 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
|
|
@@ -4496,20 +4512,48 @@ export default class SteamUser {
|
|
4496
4512
|
return await SteamUser.resolveUsers(steamIDs, this.getCookies());
|
4497
4513
|
}
|
4498
4514
|
|
4499
|
-
|
4515
|
+
/**
|
4516
|
+
* @typedef {Object} SteamUserProfile
|
4517
|
+
* @property {string} steamId - The user's 64-bit SteamID.
|
4518
|
+
* @property {number} accountId - The user's numeric account ID.
|
4519
|
+
* @property {string} name - The Steam persona name.
|
4520
|
+
* @property {string} realName - The user's real name (may be empty).
|
4521
|
+
* @property {string} avatarHash - The user's avatar hash or identifier.
|
4522
|
+
* @property {string} customURL - The user's custom profile URL (may be empty).
|
4523
|
+
* @property {number} personaState - The user's online status/state.
|
4524
|
+
* @property {string} city - The user's city (may be empty).
|
4525
|
+
* @property {string} state - The user's state/region (may be empty).
|
4526
|
+
* @property {string} country - The user's country code (may be empty).
|
4527
|
+
* @property {boolean} isFriend - Whether the user is a friend.
|
4528
|
+
* @property {number} friendsInCommon - Number of mutual friends.
|
4529
|
+
*/
|
4530
|
+
|
4531
|
+
/**
|
4532
|
+
* Resolves an array of Steam user IDs to their profile information using the Steam Community API.
|
4533
|
+
*
|
4534
|
+
* @param {(string|number)[]} steamIds - Array of Steam IDs (each a string or number).
|
4535
|
+
* @param {string} [cookie] - Optional session cookie for Steam authentication.
|
4536
|
+
* @returns {Promise<SteamUserProfile[]>} Promise resolving to an array of Steam user profile objects.
|
4537
|
+
*
|
4538
|
+
* @example
|
4539
|
+
* const users = await SteamUser.resolveUsers(['76561199409201417']);
|
4540
|
+
*/
|
4541
|
+
static async resolveUsers(steamIds, cookie) {
|
4500
4542
|
//steamIDs max = 200
|
4501
|
-
|
4502
|
-
|
4543
|
+
|
4544
|
+
if (!Array.isArray(steamIds) || !steamIds.every((id) => typeof id === "string" || typeof id === "number")) {
|
4545
|
+
console.error("steamIds must be an array of string or number.");
|
4546
|
+
return [];
|
4503
4547
|
}
|
4504
4548
|
|
4505
|
-
|
4549
|
+
steamIds = _.uniq(steamIds);
|
4506
4550
|
|
4507
|
-
if (
|
4508
|
-
return _.flatten(await Promise.all(_.chunk(
|
4551
|
+
if (steamIds.length > 200) {
|
4552
|
+
return _.flatten(await Promise.all(_.chunk(steamIds, 200).map((_steamIds) => SteamUser.resolveUsers(_steamIds, cookie))));
|
4509
4553
|
}
|
4510
4554
|
|
4511
4555
|
const { data } = await request({
|
4512
|
-
url: `https://steamcommunity.com/actions/ajaxresolveusers?steamids=${
|
4556
|
+
url: `https://steamcommunity.com/actions/ajaxresolveusers?steamids=${steamIds.join(",")}`,
|
4513
4557
|
headers: { ...(cookie && { cookie }) },
|
4514
4558
|
});
|
4515
4559
|
|
@@ -4520,21 +4564,43 @@ export default class SteamUser {
|
|
4520
4564
|
return data.map(function (player) {
|
4521
4565
|
return {
|
4522
4566
|
steamId: player.steamid,
|
4523
|
-
|
4567
|
+
accountId: player.accountid,
|
4524
4568
|
name: player.persona_name,
|
4525
|
-
|
4569
|
+
realName: player.real_name,
|
4526
4570
|
avatarHash: player.avatar_url,
|
4527
4571
|
customURL: player.profile_url,
|
4528
|
-
|
4572
|
+
personaState: player.persona_state,
|
4529
4573
|
city: player.city,
|
4530
4574
|
state: player.state,
|
4531
4575
|
country: player.country,
|
4532
|
-
|
4533
|
-
|
4576
|
+
isFriend: player.is_friend,
|
4577
|
+
friendsInCommon: player.friends_in_common,
|
4534
4578
|
};
|
4535
4579
|
});
|
4536
4580
|
}
|
4537
4581
|
|
4582
|
+
/**
|
4583
|
+
* Resolves a single Steam user ID to its profile information using the Steam Community API.
|
4584
|
+
*
|
4585
|
+
* @param {string|number} steamId - The Steam ID to resolve.
|
4586
|
+
* @param {string} [cookie] - Optional session cookie for Steam authentication.
|
4587
|
+
* @returns {Promise<SteamUserProfile|undefined>} Resolves to a user profile object, or undefined if not found or if input is invalid.
|
4588
|
+
*
|
4589
|
+
* @example
|
4590
|
+
* const user = await SteamUser.resolveUser('76561199409201417');
|
4591
|
+
* if (user) {
|
4592
|
+
* console.log(user.name);
|
4593
|
+
* }
|
4594
|
+
*/
|
4595
|
+
static async resolveUser(steamId, cookie) {
|
4596
|
+
if (typeof steamId !== "string" && typeof steamId !== "number") {
|
4597
|
+
console.error("resolveUser: steamId must be a string or number. Received:", steamId);
|
4598
|
+
return;
|
4599
|
+
}
|
4600
|
+
const users = await this.resolveUsers([steamId], cookie);
|
4601
|
+
return users[0];
|
4602
|
+
}
|
4603
|
+
|
4538
4604
|
static GetAvatarURLFromHash(hash, size) {
|
4539
4605
|
if (!hash) {
|
4540
4606
|
return;
|
@@ -4658,10 +4724,25 @@ export default class SteamUser {
|
|
4658
4724
|
return list;
|
4659
4725
|
}
|
4660
4726
|
|
4727
|
+
/**
|
4728
|
+
* Retrieves the current Steam Guard status for the account.
|
4729
|
+
*
|
4730
|
+
* Makes an HTTP request to the Steam two-factor management page,
|
4731
|
+
* parses the response, and determines the type of Steam Guard authentication enabled:
|
4732
|
+
* - "steam_authenticator" (mobile authenticator is enabled)
|
4733
|
+
* - "email_authenticator" (email confirmation is enabled)
|
4734
|
+
* - "none_authenticator" (no two-factor is enabled)
|
4735
|
+
*
|
4736
|
+
* If the request fails (ResponseError), returns null.
|
4737
|
+
*
|
4738
|
+
* @async
|
4739
|
+
* @returns {Promise<"steam_authenticator"|"email_authenticator"|"none_authenticator"|null>}
|
4740
|
+
* A string representing the authenticator type, or null if the request fails.
|
4741
|
+
*/
|
4661
4742
|
async getSteamGuardStatus() {
|
4662
4743
|
const result = await this._httpRequest(`https://store.steampowered.com/twofactor/manage_action`);
|
4663
4744
|
if (result instanceof ResponseError) {
|
4664
|
-
return
|
4745
|
+
return nuill;
|
4665
4746
|
}
|
4666
4747
|
const $ = cheerio.load(result?.data || "");
|
4667
4748
|
|
@@ -4678,7 +4759,20 @@ export default class SteamUser {
|
|
4678
4759
|
}
|
4679
4760
|
}
|
4680
4761
|
|
4681
|
-
|
4762
|
+
/**
|
4763
|
+
* Attempts to initiate the process of turning Steam Guard off for the account.
|
4764
|
+
*
|
4765
|
+
* Sends a POST request to Steam's two-factor management endpoint to request the
|
4766
|
+
* removal of Steam Guard. It then checks the response to verify whether the request
|
4767
|
+
* to turn Steam Guard off was successfully triggered (an email confirmation is sent).
|
4768
|
+
*
|
4769
|
+
* @async
|
4770
|
+
* @returns {Promise<boolean>}
|
4771
|
+
* Returns `true` if turning Steam Guard off requires email confirmation and the
|
4772
|
+
* corresponding message is found in the response;
|
4773
|
+
* returns `false` if an error occurs (e.g., a ResponseError is returned).
|
4774
|
+
*/
|
4775
|
+
async requestSteamGuardDeactivation() {
|
4682
4776
|
const result = await this._httpRequest({
|
4683
4777
|
url: `https://store.steampowered.com/twofactor/manage_action`,
|
4684
4778
|
headers: {
|
@@ -4692,13 +4786,25 @@ export default class SteamUser {
|
|
4692
4786
|
method: "POST",
|
4693
4787
|
});
|
4694
4788
|
if (result instanceof ResponseError) {
|
4695
|
-
return
|
4789
|
+
return false;
|
4696
4790
|
}
|
4697
4791
|
const $ = cheerio.load(result?.data || "");
|
4698
4792
|
return $("title").text() === `Steam Guard Mobile Authenticator` && !!result?.data?.includes?.(`Turning Steam Guard off requires confirmation. We've sent you an email with a link to confirm disabling Steam Guard.`);
|
4699
4793
|
}
|
4700
4794
|
|
4701
|
-
|
4795
|
+
/**
|
4796
|
+
* Activates the email-based Steam Guard authenticator for the account.
|
4797
|
+
*
|
4798
|
+
* Sends a POST request to Steam's two-factor management endpoint to enable email-based authentication.
|
4799
|
+
* Returns `true` if the response confirms that the email authenticator is enabled,
|
4800
|
+
* or `false` if the request fails or the response does not indicate successful activation.
|
4801
|
+
*
|
4802
|
+
* @async
|
4803
|
+
* @returns {Promise<boolean>}
|
4804
|
+
* Returns `true` if the email authenticator is activated successfully,
|
4805
|
+
* or `false` otherwise.
|
4806
|
+
*/
|
4807
|
+
async activateEmailSteamGuard() {
|
4702
4808
|
const result = await this._httpRequest({
|
4703
4809
|
url: `https://store.steampowered.com/twofactor/manage_action`,
|
4704
4810
|
headers: {
|
@@ -4713,19 +4819,31 @@ export default class SteamUser {
|
|
4713
4819
|
method: "POST",
|
4714
4820
|
});
|
4715
4821
|
if (result instanceof ResponseError) {
|
4716
|
-
return
|
4822
|
+
return false;
|
4717
4823
|
}
|
4718
4824
|
const $ = cheerio.load(result?.data || "");
|
4719
4825
|
return $("title").text() === `Steam Guard Mobile Authenticator` && $("#email_authenticator_check")?.attr("checked") == "checked";
|
4720
4826
|
}
|
4721
4827
|
|
4722
|
-
|
4723
|
-
|
4828
|
+
/**
|
4829
|
+
* Retrieves the current phone number status associated with the Steam account.
|
4830
|
+
*
|
4831
|
+
* Performs an HTTP request to the Steam phone management page and parses the response to
|
4832
|
+
* determine if a phone number is bound to the account.
|
4833
|
+
* - Returns `'none'` if no phone number is linked.
|
4834
|
+
* - Returns a string describing the phone (e.g., 'Ends in 17') if one is bound.
|
4835
|
+
* - Returns `null` on request failure or unexpected page content.
|
4836
|
+
*
|
4837
|
+
* @async
|
4838
|
+
* @returns {Promise<string|null>}
|
4839
|
+
* The phone number status: `'none'`, a phone description string (like 'Ends in 17'), or `null` if the request fails.
|
4840
|
+
*/
|
4841
|
+
async getPhoneNumberStatus() {
|
4724
4842
|
const result = await this._httpRequest({
|
4725
4843
|
url: `https://store.steampowered.com/phone/manage`,
|
4726
4844
|
});
|
4727
4845
|
if (result instanceof ResponseError) {
|
4728
|
-
return
|
4846
|
+
return null;
|
4729
4847
|
}
|
4730
4848
|
const $ = cheerio.load(result?.data || "");
|
4731
4849
|
const pageheader = $("h2.pageheader").text();
|
@@ -4733,7 +4851,7 @@ export default class SteamUser {
|
|
4733
4851
|
case "Add a phone number to your account":
|
4734
4852
|
return "none";
|
4735
4853
|
case "Manage phone number":
|
4736
|
-
return $(".phone_header_description > span").text(); //Ends in 17
|
4854
|
+
return $(".phone_header_description > span").text(); //Example: Ends in 17
|
4737
4855
|
}
|
4738
4856
|
}
|
4739
4857
|
|
@@ -6769,34 +6887,102 @@ export default class SteamUser {
|
|
6769
6887
|
return { error };
|
6770
6888
|
}
|
6771
6889
|
|
6772
|
-
|
6773
|
-
|
6774
|
-
|
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
|
+
};
|
6775
6925
|
}
|
6776
6926
|
|
6777
|
-
const
|
6778
|
-
|
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
|
+
|
6779
6945
|
for (const cookie of this._cookies) {
|
6780
6946
|
try {
|
6781
|
-
const
|
6782
|
-
|
6783
|
-
|
6784
|
-
|
6785
|
-
|
6786
|
-
|
6787
|
-
|
6788
|
-
|
6789
|
-
resolve({ error, response });
|
6790
|
-
});
|
6947
|
+
const response = await axios.request({
|
6948
|
+
method: "POST",
|
6949
|
+
url,
|
6950
|
+
headers: {
|
6951
|
+
...headersBase,
|
6952
|
+
Cookie: cookie,
|
6953
|
+
},
|
6954
|
+
data: formData,
|
6791
6955
|
});
|
6792
|
-
if (
|
6793
|
-
return {
|
6956
|
+
if (response.data && response.data.response) {
|
6957
|
+
return {
|
6958
|
+
success: true,
|
6959
|
+
response: response.data.response,
|
6960
|
+
};
|
6794
6961
|
} else {
|
6795
|
-
|
6962
|
+
return {
|
6963
|
+
success: false,
|
6964
|
+
error: `Unexpected API response: ${JSON.stringify(response.data)}`,
|
6965
|
+
};
|
6796
6966
|
}
|
6797
|
-
} 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
|
+
}
|
6798
6979
|
}
|
6799
|
-
|
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
|
+
};
|
6800
6986
|
}
|
6801
6987
|
|
6802
6988
|
async finalizeTwoFactor(accessToken, identitySecret, finalizeTwoFactorCode) {
|
@@ -7558,6 +7744,7 @@ export default class SteamUser {
|
|
7558
7744
|
});
|
7559
7745
|
return revokeRefreshTokenResult?.data;
|
7560
7746
|
}
|
7747
|
+
|
7561
7748
|
async revokeAccessToken(accessToken, tokenId, sharedSecret) {
|
7562
7749
|
if (!accessToken || typeof accessToken !== "string") {
|
7563
7750
|
console.error("revokeAccessToken", "invalid accessToken", accessToken);
|
@@ -7989,7 +8176,7 @@ export default class SteamUser {
|
|
7989
8176
|
/**
|
7990
8177
|
* Fetches Steam batched loyalty reward items for given app ID(s).
|
7991
8178
|
* @param {number|number[]} appIds - A Steam App ID or array of App IDs.
|
7992
|
-
* @returns {Promise<Array<{eresult: number, response: BatchedLoyaltyRewardItemsResponse}
|
8179
|
+
* @returns {Promise<Array<{eresult: number, response: BatchedLoyaltyRewardItemsResponse}>|null|undefined>}
|
7993
8180
|
*/
|
7994
8181
|
static async fetchBatchedLoyaltyRewardItems(appIds) {
|
7995
8182
|
if (!appIds) {
|
@@ -8035,7 +8222,7 @@ export default class SteamUser {
|
|
8035
8222
|
responseType: "arraybuffer",
|
8036
8223
|
});
|
8037
8224
|
if (result instanceof ResponseError) {
|
8038
|
-
return
|
8225
|
+
return;
|
8039
8226
|
}
|
8040
8227
|
|
8041
8228
|
try {
|
@@ -8181,71 +8368,132 @@ export default class SteamUser {
|
|
8181
8368
|
return apps.filter((app) => app.event_app);
|
8182
8369
|
}
|
8183
8370
|
|
8184
|
-
|
8185
|
-
|
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
|
8186
8379
|
try {
|
8187
|
-
|
8188
|
-
|
8189
|
-
|
8190
|
-
|
8191
|
-
|
8192
|
-
|
8193
|
-
|
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
|
+
};
|
8194
8387
|
}
|
8195
|
-
try {
|
8196
|
-
const { Jimp } = await import("jimp");
|
8197
8388
|
|
8198
|
-
|
8199
|
-
|
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;
|
8200
8395
|
|
8201
|
-
|
8202
|
-
|
8203
|
-
|
8204
|
-
|
8205
|
-
|
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,
|
8206
8416
|
};
|
8417
|
+
}
|
8207
8418
|
|
8208
|
-
|
8209
|
-
|
8210
|
-
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);
|
8211
8425
|
|
8212
8426
|
if (!decodedQR) {
|
8213
|
-
return
|
8427
|
+
return {
|
8428
|
+
success: false,
|
8429
|
+
message: "QR code not found in the image.",
|
8430
|
+
decodedQR: null,
|
8431
|
+
};
|
8214
8432
|
}
|
8215
|
-
|
8216
|
-
|
8433
|
+
return {
|
8434
|
+
success: true,
|
8435
|
+
message: "QR code successfully decoded.",
|
8436
|
+
decodedQR: decodedQR.data,
|
8437
|
+
};
|
8217
8438
|
} catch (error) {
|
8218
|
-
|
8439
|
+
return {
|
8440
|
+
success: false,
|
8441
|
+
message: `Error decoding QR code: ${error.message || error}`,
|
8442
|
+
decodedQR: null,
|
8443
|
+
};
|
8219
8444
|
}
|
8220
8445
|
}
|
8221
8446
|
|
8222
|
-
|
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) {
|
8223
8456
|
try {
|
8224
8457
|
const { LoginApprover } = await import("steam-session");
|
8225
|
-
const approver = new LoginApprover(accessToken, sharedSecret
|
8226
|
-
await approver.approveAuthSession({
|
8227
|
-
|
8228
|
-
approve: true,
|
8229
|
-
});
|
8230
|
-
|
8231
|
-
return {
|
8232
|
-
steamId,
|
8233
|
-
error: null,
|
8234
|
-
};
|
8458
|
+
const approver = new LoginApprover(accessToken, sharedSecret);
|
8459
|
+
await approver.approveAuthSession({ qrChallengeUrl, approve: true });
|
8460
|
+
return { steamId, error: null, success: true };
|
8235
8461
|
} catch (error) {
|
8236
|
-
|
8237
|
-
return {
|
8238
|
-
steamId,
|
8239
|
-
error: error || "Error",
|
8240
|
-
};
|
8462
|
+
return { steamId, error: error?.message || "Unknown error", success: false };
|
8241
8463
|
}
|
8242
8464
|
}
|
8243
8465
|
|
8244
|
-
|
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) {
|
8245
8493
|
try {
|
8246
8494
|
const { LoginApprover } = await import("steam-session");
|
8247
8495
|
const approver = new LoginApprover(accessToken, sharedSecret, {});
|
8248
|
-
const sessionInfo = await approver.getAuthSessionInfo(
|
8496
|
+
const sessionInfo = await approver.getAuthSessionInfo(qrChallengeUrl);
|
8249
8497
|
sessionInfo.steamId = steamId;
|
8250
8498
|
|
8251
8499
|
return {
|
@@ -8253,10 +8501,9 @@ export default class SteamUser {
|
|
8253
8501
|
sessionInfo,
|
8254
8502
|
};
|
8255
8503
|
} catch (error) {
|
8256
|
-
console.error(`[${steamId}] getAuthSessionInfo Error`, error);
|
8257
8504
|
return {
|
8258
8505
|
steamId,
|
8259
|
-
error,
|
8506
|
+
error: error?.message || "Unknown error",
|
8260
8507
|
};
|
8261
8508
|
}
|
8262
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);
|