steamutils 1.5.51 → 1.5.53
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/.idea/prettier.xml +0 -1
- package/SteamClient.js +3159 -3159
- package/_steamproto.js +56 -56
- package/const.js +582 -582
- package/full_steamproto.js +7 -2
- package/index.js +64 -43
- package/package.json +1 -1
- package/parse_html.js +148 -1
- package/remote.js +2503 -2503
- package/steamproto.js +57 -57
- package/utils.js +1311 -1300
- package/.idea/gbrowser_project.xml +0 -11
- package/.idea/git_toolbox_blame.xml +0 -6
- package/.idea/git_toolbox_prj.xml +0 -15
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/jsLinters/eslint.xml +0 -6
- package/race.js +0 -2241
package/full_steamproto.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import path from "path";
|
2
2
|
import { fileURLToPath } from "url";
|
3
3
|
import Protobuf from "protobufjs";
|
4
|
+
import gpf from "google-proto-files";
|
4
5
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
6
7
|
const __dirname = path.dirname(__filename);
|
@@ -11,10 +12,14 @@ export class SteamProto {
|
|
11
12
|
}
|
12
13
|
|
13
14
|
toProto() {
|
14
|
-
const
|
15
|
+
const localProto = path.join(`${__dirname}/protos/`, this._proto.filename);
|
16
|
+
|
17
|
+
const descriptorPath = gpf.getProtoPath("protobuf/descriptor.proto");
|
18
|
+
|
19
|
+
const root = new Protobuf.Root().loadSync([descriptorPath, localProto], {
|
15
20
|
keepCase: true,
|
16
21
|
});
|
17
|
-
return root
|
22
|
+
return root.lookupType(this._proto.name);
|
18
23
|
}
|
19
24
|
|
20
25
|
protoEncode(obj) {
|
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,
|
8
|
+
import { console_log, convertLongsToNumbers, downloadImage, 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";
|
@@ -21,7 +21,7 @@ import { AppID_CSGO, E1GameBanOnRecord, E1VACBanOnRecord, EActivityType, ECommen
|
|
21
21
|
import SteamTotp from "steam-totp";
|
22
22
|
import { SteamProto, SteamProtoType } from "./steamproto.js";
|
23
23
|
import EventEmitter from "node:events";
|
24
|
-
import { extractAssetItemsFromHovers, parseMarketHistoryListings, parseMarketListings } from "./parse_html.js";
|
24
|
+
import { extractAssetItemsFromHovers, parseMarketHistoryListings, parseMarketListings, parseSteamProfileXmlToJson, parseUserProfile } from "./parse_html.js";
|
25
25
|
|
26
26
|
const eventEmitter = (globalThis.steamUserEventEmitter = new EventEmitter());
|
27
27
|
|
@@ -397,8 +397,9 @@ export default class SteamUser {
|
|
397
397
|
|
398
398
|
const result = await this._httpRequest(url);
|
399
399
|
if (result instanceof ResponseError) {
|
400
|
-
return
|
400
|
+
return null;
|
401
401
|
}
|
402
|
+
|
402
403
|
return SteamUser._parseUserProfile(result?.data || "");
|
403
404
|
}
|
404
405
|
|
@@ -537,6 +538,32 @@ export default class SteamUser {
|
|
537
538
|
};
|
538
539
|
}
|
539
540
|
|
541
|
+
/**
|
542
|
+
* Fetches a Steam Community profile (in XML format) by SteamID, parses it,
|
543
|
+
* and returns a normalized JSON representation.
|
544
|
+
*
|
545
|
+
* @async
|
546
|
+
* @param {string} [steamId] - 64-bit SteamID. Defaults to the result of `this.getSteamIdUser()`.
|
547
|
+
* @returns {Promise<SteamProfileXml|undefined>} Promise resolving to the normalized Steam profile object, or `undefined` on failure.
|
548
|
+
*
|
549
|
+
*/
|
550
|
+
async getProfileFromXml(steamId = this.getSteamIdUser()) {
|
551
|
+
const result = await this._httpRequest(`https://steamcommunity.com/profiles/${steamId}/?xml=1`);
|
552
|
+
if (result instanceof ResponseError) {
|
553
|
+
return;
|
554
|
+
}
|
555
|
+
try {
|
556
|
+
return parseSteamProfileXmlToJson(result?.data);
|
557
|
+
} catch (e) {}
|
558
|
+
}
|
559
|
+
|
560
|
+
static async getProfileFromXml(steamId) {
|
561
|
+
try {
|
562
|
+
const xml = (await axios.get(`https://steamcommunity.com/profiles/${steamId}/?xml=1`)).data;
|
563
|
+
return parseSteamProfileXmlToJson(xml);
|
564
|
+
} catch (e) {}
|
565
|
+
}
|
566
|
+
|
540
567
|
static async getUsersSummaryByWebApiKey(webApiKey, steamIds) {
|
541
568
|
if (!Array.isArray(steamIds)) {
|
542
569
|
steamIds = [steamIds];
|
@@ -7291,6 +7318,39 @@ export default class SteamUser {
|
|
7291
7318
|
};
|
7292
7319
|
}
|
7293
7320
|
|
7321
|
+
/**
|
7322
|
+
* @typedef {Object} FriendGameplayInfoEntry
|
7323
|
+
* @property {string} steamId - The SteamID64 of the friend.
|
7324
|
+
* @property {number} minutes_played - Number of minutes played recently or currently.
|
7325
|
+
* @property {number} minutes_played_forever - Total minutes this friend has played forever.
|
7326
|
+
*/
|
7327
|
+
|
7328
|
+
/**
|
7329
|
+
* @typedef {Object} YourInfo
|
7330
|
+
* @property {number} minutes_played - Number of minutes you have played recently or currently.
|
7331
|
+
* @property {number} minutes_played_forever - Total minutes you have played forever.
|
7332
|
+
* @property {boolean} in_wishlist - Whether the app is in your wishlist.
|
7333
|
+
* @property {boolean} owned - Whether you own the app.
|
7334
|
+
*/
|
7335
|
+
|
7336
|
+
/**
|
7337
|
+
* @typedef {Object} FriendsGameplayInfoResponse
|
7338
|
+
* @property {FriendGameplayInfoEntry[]} in_game - Friends currently playing the game.
|
7339
|
+
* @property {FriendGameplayInfoEntry[]} played_recently - Friends who played the game recently.
|
7340
|
+
* @property {FriendGameplayInfoEntry[]} played_ever - Friends who have ever played the game.
|
7341
|
+
* @property {FriendGameplayInfoEntry[]} owns - Friends who own the game.
|
7342
|
+
* @property {YourInfo} your_info - Information about your own playtime, wishlist and ownership for this app.
|
7343
|
+
*/
|
7344
|
+
|
7345
|
+
/**
|
7346
|
+
* Retrieves Steam friends' gameplay stats for a specific app.
|
7347
|
+
*
|
7348
|
+
* @param {string} accessToken - Steam user access token.
|
7349
|
+
* @param {number|string} appId - The App ID of the game to query.
|
7350
|
+
* @returns {Promise<FriendsGameplayInfoResponse|undefined>}
|
7351
|
+
* Resolves with gameplay info: lists of friends (by status) and your stats for the app,
|
7352
|
+
* or `undefined` if the result is empty or on unauthorized access.
|
7353
|
+
*/
|
7294
7354
|
async getFriendsGameplayInfo(accessToken, appId) {
|
7295
7355
|
if (!appId) {
|
7296
7356
|
return;
|
@@ -7311,7 +7371,7 @@ export default class SteamUser {
|
|
7311
7371
|
responseType: "arraybuffer",
|
7312
7372
|
});
|
7313
7373
|
if (result instanceof ResponseError) {
|
7314
|
-
return
|
7374
|
+
return;
|
7315
7375
|
}
|
7316
7376
|
|
7317
7377
|
if (!result || result.status === 401) {
|
@@ -7339,45 +7399,6 @@ export default class SteamUser {
|
|
7339
7399
|
owns: formatList(owns),
|
7340
7400
|
your_info: _.omit(your_info, "steamid"),
|
7341
7401
|
};
|
7342
|
-
|
7343
|
-
const example = [
|
7344
|
-
{
|
7345
|
-
public_data: {
|
7346
|
-
visibility_state: 3,
|
7347
|
-
privacy_state: 0,
|
7348
|
-
profile_state: 1,
|
7349
|
-
ban_expires_time: 0,
|
7350
|
-
account_flags: 0,
|
7351
|
-
persona_name: "LOL haha",
|
7352
|
-
profile_url: "",
|
7353
|
-
content_country_restricted: false,
|
7354
|
-
steamId: "76561199243542939",
|
7355
|
-
avatarHash: "0000000000000000000000000000000000000000",
|
7356
|
-
},
|
7357
|
-
private_data: {
|
7358
|
-
persona_state: 0,
|
7359
|
-
persona_state_flags: 0,
|
7360
|
-
time_created: 1644675779,
|
7361
|
-
game_id: 0,
|
7362
|
-
game_server_ip_address: 0,
|
7363
|
-
game_server_port: 0,
|
7364
|
-
game_extra_info: "",
|
7365
|
-
account_name: "",
|
7366
|
-
lobby_steam_id: 0,
|
7367
|
-
rich_presence_kv: "",
|
7368
|
-
watching_broadcast_accountid: 0,
|
7369
|
-
watching_broadcast_appid: 0,
|
7370
|
-
watching_broadcast_viewers: 0,
|
7371
|
-
watching_broadcast_title: "",
|
7372
|
-
last_logoff_time: 0,
|
7373
|
-
last_seen_online: 0,
|
7374
|
-
game_os_type: 0,
|
7375
|
-
game_device_type: 0,
|
7376
|
-
game_device_name: "",
|
7377
|
-
game_is_private: false,
|
7378
|
-
},
|
7379
|
-
},
|
7380
|
-
];
|
7381
7402
|
}
|
7382
7403
|
|
7383
7404
|
/**
|
package/package.json
CHANGED
package/parse_html.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import { StringUtils } from "alpha-common-utils/index.js";
|
2
|
-
import { formatMarketHistoryDate } from "./utils.js";
|
2
|
+
import { formatMarketHistoryDate, getAvatarHashFromUrl } from "./utils.js";
|
3
3
|
import * as cheerio from "cheerio";
|
4
|
+
import { getJSObjectFronXML } from "./xml2json.js";
|
4
5
|
|
5
6
|
/**
|
6
7
|
* @typedef {Object} HoverItem
|
@@ -187,3 +188,149 @@ export function parseMarketListings(html) {
|
|
187
188
|
|
188
189
|
return results;
|
189
190
|
}
|
191
|
+
|
192
|
+
/**
|
193
|
+
* Represents a Steam Community group for a profile.
|
194
|
+
* @typedef {Object} SteamProfileGroupXml
|
195
|
+
* @property {string} groupID64
|
196
|
+
* @property {string} groupName
|
197
|
+
* @property {string} groupURL
|
198
|
+
* @property {string} headline
|
199
|
+
* @property {string} summary
|
200
|
+
* @property {string} avatarIcon
|
201
|
+
* @property {string} avatarMedium
|
202
|
+
* @property {string} avatarFull
|
203
|
+
* @property {number} memberCount
|
204
|
+
* @property {number} membersInChat
|
205
|
+
* @property {number} membersInGame
|
206
|
+
* @property {number} membersOnline
|
207
|
+
*/
|
208
|
+
|
209
|
+
/**
|
210
|
+
* Array of Steam Community groups.
|
211
|
+
* @typedef {SteamProfileGroupXml[]} SteamProfileGroupsXml
|
212
|
+
*/
|
213
|
+
|
214
|
+
/**
|
215
|
+
* Represents a most played game on a Steam profile.
|
216
|
+
* @typedef {Object} SteamProfileMostPlayedGameXml
|
217
|
+
* @property {string} gameName
|
218
|
+
* @property {string} gameLink
|
219
|
+
* @property {string} gameIcon
|
220
|
+
* @property {string} gameLogo
|
221
|
+
* @property {string} gameLogoSmall
|
222
|
+
* @property {number} hoursPlayed
|
223
|
+
* @property {number} hoursOnRecord
|
224
|
+
* @property {string} statsName
|
225
|
+
*/
|
226
|
+
|
227
|
+
/**
|
228
|
+
* Array of most played games.
|
229
|
+
* @typedef {SteamProfileMostPlayedGameXml[]} SteamProfileMostPlayedGamesXml
|
230
|
+
*/
|
231
|
+
|
232
|
+
/**
|
233
|
+
* Steam Community profile (parsed from XML, normalized).
|
234
|
+
* @typedef {Object} SteamProfileXml
|
235
|
+
* @property {string} name
|
236
|
+
* @property {string} steamId
|
237
|
+
* @property {string} onlineState
|
238
|
+
* @property {string} stateMessage
|
239
|
+
* @property {string} privacyState
|
240
|
+
* @property {number} visibilityState
|
241
|
+
* @property {string} avatarIcon
|
242
|
+
* @property {string} avatarMedium
|
243
|
+
* @property {string} avatarFull
|
244
|
+
* @property {string} avatarHash
|
245
|
+
* @property {number} vacBanned
|
246
|
+
* @property {string} tradeBanState
|
247
|
+
* @property {number} isLimitedAccount
|
248
|
+
* @property {string} customURL
|
249
|
+
* @property {string} memberSince
|
250
|
+
* @property {string} steamRating
|
251
|
+
* @property {number} hoursPlayed2Wk
|
252
|
+
* @property {string} headline
|
253
|
+
* @property {string} location
|
254
|
+
* @property {string} realname
|
255
|
+
* @property {string} summary
|
256
|
+
* @property {SteamProfileMostPlayedGamesXml} [mostPlayedGames]
|
257
|
+
* @property {SteamProfileGroupsXml} [groups]
|
258
|
+
*/
|
259
|
+
|
260
|
+
/**
|
261
|
+
* Parses a Steam profile XML into a normalized JSON format.
|
262
|
+
*
|
263
|
+
* @param {string} xml - The XML string as returned by the Steam Community profile endpoint.
|
264
|
+
* @returns {SteamProfileXml|undefined} The normalized profile object or undefined on error.
|
265
|
+
*
|
266
|
+
* @example
|
267
|
+
* import { parseSteamProfileXmlToJson } from "./your-module";
|
268
|
+
* const xml = await fetch('https://steamcommunity.com/profiles/76561198386265483/?xml=1').then(r => r.text());
|
269
|
+
* const profile = parseSteamProfileXmlToJson(xml);
|
270
|
+
* console.log(profile.name);
|
271
|
+
*/
|
272
|
+
export function parseSteamProfileXmlToJson(xml) {
|
273
|
+
const parsed = getJSObjectFronXML(xml);
|
274
|
+
if (!parsed || !parsed.profile) return;
|
275
|
+
|
276
|
+
// Flatten out the xml2js _structure, for direct children
|
277
|
+
const profile = {};
|
278
|
+
Object.entries(parsed.profile).forEach(([key, value]) => {
|
279
|
+
// If only text, keep as string
|
280
|
+
if (typeof value === "object" && value !== null && "_" in value && Object.keys(value).length === 1) {
|
281
|
+
profile[key] = value._;
|
282
|
+
} else {
|
283
|
+
profile[key] = value;
|
284
|
+
}
|
285
|
+
});
|
286
|
+
|
287
|
+
// Rename fields
|
288
|
+
if ("steamID" in profile) {
|
289
|
+
profile.name = profile.steamID;
|
290
|
+
delete profile.steamID;
|
291
|
+
}
|
292
|
+
if ("steamID64" in profile) {
|
293
|
+
profile.steamId = profile.steamID64;
|
294
|
+
delete profile.steamID64;
|
295
|
+
}
|
296
|
+
|
297
|
+
// Convert numbers
|
298
|
+
const numberFields = ["vacBanned", "isLimitedAccount", "visibilityState", "memberCount", "membersInChat", "membersInGame", "membersOnline", "hoursOnRecord"];
|
299
|
+
const floatFields = ["hoursPlayed2Wk", "hoursPlayed"];
|
300
|
+
for (const field of numberFields) {
|
301
|
+
if (field in profile) profile[field] = parseInt(profile[field], 10) || 0;
|
302
|
+
}
|
303
|
+
for (const field of floatFields) {
|
304
|
+
if (field in profile) profile[field] = parseFloat(profile[field]) || 0;
|
305
|
+
}
|
306
|
+
|
307
|
+
// Add avatarHash
|
308
|
+
const avatarUrl = profile.avatarFull || profile.avatarMedium || profile.avatarIcon;
|
309
|
+
profile.avatarHash = getAvatarHashFromUrl(avatarUrl);
|
310
|
+
|
311
|
+
// Ensure all simple fields are strings if empty object
|
312
|
+
for (const key in profile) {
|
313
|
+
if (typeof profile[key] === "object" && profile[key] !== null && Object.keys(profile[key]).length === 0) {
|
314
|
+
profile[key] = "";
|
315
|
+
}
|
316
|
+
}
|
317
|
+
|
318
|
+
// Move groups.group to top-level 'groups' if present
|
319
|
+
if (profile.groups && profile.groups.group) {
|
320
|
+
profile.groups = profile.groups.group;
|
321
|
+
}
|
322
|
+
|
323
|
+
// Remove _attributes from groups (if present)
|
324
|
+
if (Array.isArray(profile.groups)) {
|
325
|
+
profile.groups = profile.groups.map((group) => {
|
326
|
+
if (group && typeof group === "object" && "_attributes" in group) {
|
327
|
+
// shallow clone and remove _attributes
|
328
|
+
const { _attributes, ...groupWithoutAttributes } = group;
|
329
|
+
return groupWithoutAttributes;
|
330
|
+
}
|
331
|
+
return group;
|
332
|
+
});
|
333
|
+
}
|
334
|
+
|
335
|
+
return profile;
|
336
|
+
}
|