steamutils 1.5.50 → 1.5.52
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 +82 -46
- package/package.json +1 -1
- package/parse_html.js +189 -189
- package/remote.js +2503 -2503
- package/steamproto.js +57 -57
- package/utils.js +1300 -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, formatMarketHistoryDate, getCleanObject, getImageSize, isJWT, JSON_parse, JSON_stringify, removeSpaceKeys, secretAsBuffer, sleep } from "./utils.js";
|
8
|
+
import { console_log, convertLongsToNumbers, downloadImage, formatMarketCurrency, formatMarketHistoryDate, getCleanObject, getImageSize, getMarketPriceValueAsInt, 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";
|
@@ -6779,20 +6779,62 @@ export default class SteamUser {
|
|
6779
6779
|
return friendSince;
|
6780
6780
|
}
|
6781
6781
|
|
6782
|
+
/**
|
6783
|
+
* Retrieves the Steam Wallet balances (main and pending) and the account currency
|
6784
|
+
* for the currently authenticated user by parsing the wallet info from the profile HTML.
|
6785
|
+
*
|
6786
|
+
* @async
|
6787
|
+
* @returns {Promise<{mainBalance: string|null, pending: string|null, currency: string|null}|undefined>}
|
6788
|
+
* A Promise that resolves to an object containing:
|
6789
|
+
* - mainBalance: The main wallet balance as a string (e.g., "276.400,82₫"), or null if not found.
|
6790
|
+
* - pending: The pending wallet balance as a string (e.g., "90.176,70₫"), or null if not found.
|
6791
|
+
* - currency: The detected currency symbol or code (e.g., "₫", "€", "$", "TL"), or null if not found.
|
6792
|
+
* The function returns undefined if an error occurs or valid HTML is not available.
|
6793
|
+
*
|
6794
|
+
* @example
|
6795
|
+
* const { mainBalance, pending, currency } = await getWalletBalance();
|
6796
|
+
* console.log(mainBalance); // e.g., "276.400,82₫"
|
6797
|
+
* console.log(pending); // e.g., "90.176,70₫"
|
6798
|
+
* console.log(currency); // e.g., "₫"
|
6799
|
+
*/
|
6782
6800
|
async getWalletBalance() {
|
6783
6801
|
const result = await this._httpRequest(`my?l=english`);
|
6784
6802
|
if (result instanceof ResponseError) {
|
6785
|
-
return
|
6803
|
+
return;
|
6786
6804
|
}
|
6787
6805
|
const html = result?.data;
|
6788
6806
|
if (!html || typeof html !== "string") {
|
6789
6807
|
return;
|
6790
6808
|
}
|
6791
6809
|
const $ = cheerio.load(html);
|
6792
|
-
|
6793
|
-
|
6794
|
-
|
6795
|
-
|
6810
|
+
|
6811
|
+
const mainBalance = $("#header_wallet_balance").clone().children().remove().end().text().trim() || null;
|
6812
|
+
|
6813
|
+
let currency = null;
|
6814
|
+
if (mainBalance) {
|
6815
|
+
// This regex matches all non-digit, non-comma, non-dot characters at the end (currency symbol/code)
|
6816
|
+
// Handles "12,34€", "$12.34", "12,34 TL", etc.
|
6817
|
+
const m = mainBalance.match(/([^\d.,\s].*)$/);
|
6818
|
+
if (m) currency = m[1].trim();
|
6819
|
+
else {
|
6820
|
+
// fallback for prefix (e.g. "$12.34")
|
6821
|
+
const m2 = mainBalance.match(/^([^\d.,\s]+)/);
|
6822
|
+
if (m2) currency = m2[1].trim();
|
6823
|
+
}
|
6824
|
+
}
|
6825
|
+
|
6826
|
+
let pendingStr = $("#header_wallet_balance span.tooltip").text();
|
6827
|
+
let pending = null;
|
6828
|
+
if (pendingStr) {
|
6829
|
+
const match = pendingStr.match(/Pending:\s*([\d.,]+[^\s]*)/);
|
6830
|
+
if (match) pending = match[1];
|
6831
|
+
}
|
6832
|
+
|
6833
|
+
return {
|
6834
|
+
mainBalance,
|
6835
|
+
pending,
|
6836
|
+
currency,
|
6837
|
+
};
|
6796
6838
|
}
|
6797
6839
|
|
6798
6840
|
async acceptConfirmationForObject(accessToken, identitySecret, objectID) {
|
@@ -7249,6 +7291,39 @@ export default class SteamUser {
|
|
7249
7291
|
};
|
7250
7292
|
}
|
7251
7293
|
|
7294
|
+
/**
|
7295
|
+
* @typedef {Object} FriendGameplayInfoEntry
|
7296
|
+
* @property {string} steamId - The SteamID64 of the friend.
|
7297
|
+
* @property {number} minutes_played - Number of minutes played recently or currently.
|
7298
|
+
* @property {number} minutes_played_forever - Total minutes this friend has played forever.
|
7299
|
+
*/
|
7300
|
+
|
7301
|
+
/**
|
7302
|
+
* @typedef {Object} YourInfo
|
7303
|
+
* @property {number} minutes_played - Number of minutes you have played recently or currently.
|
7304
|
+
* @property {number} minutes_played_forever - Total minutes you have played forever.
|
7305
|
+
* @property {boolean} in_wishlist - Whether the app is in your wishlist.
|
7306
|
+
* @property {boolean} owned - Whether you own the app.
|
7307
|
+
*/
|
7308
|
+
|
7309
|
+
/**
|
7310
|
+
* @typedef {Object} FriendsGameplayInfoResponse
|
7311
|
+
* @property {FriendGameplayInfoEntry[]} in_game - Friends currently playing the game.
|
7312
|
+
* @property {FriendGameplayInfoEntry[]} played_recently - Friends who played the game recently.
|
7313
|
+
* @property {FriendGameplayInfoEntry[]} played_ever - Friends who have ever played the game.
|
7314
|
+
* @property {FriendGameplayInfoEntry[]} owns - Friends who own the game.
|
7315
|
+
* @property {YourInfo} your_info - Information about your own playtime, wishlist and ownership for this app.
|
7316
|
+
*/
|
7317
|
+
|
7318
|
+
/**
|
7319
|
+
* Retrieves Steam friends' gameplay stats for a specific app.
|
7320
|
+
*
|
7321
|
+
* @param {string} accessToken - Steam user access token.
|
7322
|
+
* @param {number|string} appId - The App ID of the game to query.
|
7323
|
+
* @returns {Promise<FriendsGameplayInfoResponse|undefined>}
|
7324
|
+
* Resolves with gameplay info: lists of friends (by status) and your stats for the app,
|
7325
|
+
* or `undefined` if the result is empty or on unauthorized access.
|
7326
|
+
*/
|
7252
7327
|
async getFriendsGameplayInfo(accessToken, appId) {
|
7253
7328
|
if (!appId) {
|
7254
7329
|
return;
|
@@ -7269,7 +7344,7 @@ export default class SteamUser {
|
|
7269
7344
|
responseType: "arraybuffer",
|
7270
7345
|
});
|
7271
7346
|
if (result instanceof ResponseError) {
|
7272
|
-
return
|
7347
|
+
return;
|
7273
7348
|
}
|
7274
7349
|
|
7275
7350
|
if (!result || result.status === 401) {
|
@@ -7297,45 +7372,6 @@ export default class SteamUser {
|
|
7297
7372
|
owns: formatList(owns),
|
7298
7373
|
your_info: _.omit(your_info, "steamid"),
|
7299
7374
|
};
|
7300
|
-
|
7301
|
-
const example = [
|
7302
|
-
{
|
7303
|
-
public_data: {
|
7304
|
-
visibility_state: 3,
|
7305
|
-
privacy_state: 0,
|
7306
|
-
profile_state: 1,
|
7307
|
-
ban_expires_time: 0,
|
7308
|
-
account_flags: 0,
|
7309
|
-
persona_name: "LOL haha",
|
7310
|
-
profile_url: "",
|
7311
|
-
content_country_restricted: false,
|
7312
|
-
steamId: "76561199243542939",
|
7313
|
-
avatarHash: "0000000000000000000000000000000000000000",
|
7314
|
-
},
|
7315
|
-
private_data: {
|
7316
|
-
persona_state: 0,
|
7317
|
-
persona_state_flags: 0,
|
7318
|
-
time_created: 1644675779,
|
7319
|
-
game_id: 0,
|
7320
|
-
game_server_ip_address: 0,
|
7321
|
-
game_server_port: 0,
|
7322
|
-
game_extra_info: "",
|
7323
|
-
account_name: "",
|
7324
|
-
lobby_steam_id: 0,
|
7325
|
-
rich_presence_kv: "",
|
7326
|
-
watching_broadcast_accountid: 0,
|
7327
|
-
watching_broadcast_appid: 0,
|
7328
|
-
watching_broadcast_viewers: 0,
|
7329
|
-
watching_broadcast_title: "",
|
7330
|
-
last_logoff_time: 0,
|
7331
|
-
last_seen_online: 0,
|
7332
|
-
game_os_type: 0,
|
7333
|
-
game_device_type: 0,
|
7334
|
-
game_device_name: "",
|
7335
|
-
game_is_private: false,
|
7336
|
-
},
|
7337
|
-
},
|
7338
|
-
];
|
7339
7375
|
}
|
7340
7376
|
|
7341
7377
|
/**
|
package/package.json
CHANGED
package/parse_html.js
CHANGED
@@ -1,189 +1,189 @@
|
|
1
|
-
import { StringUtils } from "alpha-common-utils/index.js";
|
2
|
-
import { formatMarketHistoryDate } from "./utils.js";
|
3
|
-
import * as cheerio from "cheerio";
|
4
|
-
|
5
|
-
/**
|
6
|
-
* @typedef {Object} HoverItem
|
7
|
-
* @property {string|null} listingId The listing ID extracted from the selector, if available (as string).
|
8
|
-
* @property {number} appid The app (game) ID for this item.
|
9
|
-
* @property {number} contextid The context ID for this item.
|
10
|
-
* @property {number} assetid The unique asset ID.
|
11
|
-
* @property {string} unknown A string field captured from the arguments (usually "0").
|
12
|
-
* @property {string} [name_selector] The HTML selector for the item's name, if present.
|
13
|
-
* @property {string} [image_selector] The HTML selector for the item's image, if present.
|
14
|
-
* @property {string} [other_selector] The selector string if it does not match name/image pattern.
|
15
|
-
*/
|
16
|
-
|
17
|
-
/**
|
18
|
-
* Extracts item information from Steam inventory trade history or similar HTML fragments.
|
19
|
-
* Scans the given HTML string for calls to `CreateItemHoverFromContainer` and extracts
|
20
|
-
* key data for each referenced item. Returns each item's info in a structured array.
|
21
|
-
*
|
22
|
-
* @param {string} html - The HTML string to parse.
|
23
|
-
* @returns {HoverItem[]} Array of hover item objects.
|
24
|
-
*/
|
25
|
-
export function extractAssetItemsFromHovers(html) {
|
26
|
-
// Use your preferred HTML/space scrubber.
|
27
|
-
html = StringUtils.cleanSpace(html);
|
28
|
-
|
29
|
-
// RegExp for CreateItemHoverFromContainer arguments
|
30
|
-
const re = /CreateItemHoverFromContainer\s*\(\s*g_rgAssets\s*,\s*'([^']+)'\s*,\s*(\d+)\s*,\s*'(\d+)'\s*,\s*'(\d+)'\s*,\s*(\d+)\s*\)/g;
|
31
|
-
|
32
|
-
/** @type {Record<number, HoverItem>} */
|
33
|
-
const itemsMap = {};
|
34
|
-
let match;
|
35
|
-
|
36
|
-
while ((match = re.exec(html)) !== null) {
|
37
|
-
const [_, selector, appid, contextid, assetid, unknown] = match;
|
38
|
-
|
39
|
-
// Extract listingId as a string if present: "history_row_<listingId>_"
|
40
|
-
let listingId = null;
|
41
|
-
const lidMatch = selector.match(/history_row_(\d+)_/);
|
42
|
-
if (lidMatch) listingId = lidMatch[1]; // assign as string
|
43
|
-
|
44
|
-
const appidNum = Number(appid);
|
45
|
-
const contextidNum = Number(contextid);
|
46
|
-
const assetidNum = Number(assetid);
|
47
|
-
|
48
|
-
if (!itemsMap[assetidNum]) {
|
49
|
-
itemsMap[assetidNum] = {
|
50
|
-
listingId,
|
51
|
-
appid: appidNum,
|
52
|
-
contextid: contextidNum,
|
53
|
-
assetid: assetidNum,
|
54
|
-
unknown,
|
55
|
-
};
|
56
|
-
}
|
57
|
-
|
58
|
-
if (selector.endsWith("_name")) {
|
59
|
-
itemsMap[assetidNum].name_selector = selector;
|
60
|
-
} else if (selector.endsWith("_image")) {
|
61
|
-
itemsMap[assetidNum].image_selector = selector;
|
62
|
-
} else {
|
63
|
-
itemsMap[assetidNum].other_selector = selector;
|
64
|
-
}
|
65
|
-
}
|
66
|
-
return Object.values(itemsMap);
|
67
|
-
}
|
68
|
-
|
69
|
-
/**
|
70
|
-
* @typedef {Object} MarketHistoryListing
|
71
|
-
* @property {string} id - HTML row id attribute (e.g. "history_row_4316182845737233773_4316182845737233774").
|
72
|
-
* @property {string} listingId - Listing id (e.g. "4316182845737233773").
|
73
|
-
* @property {number} price - Price in integer form (e.g. 80270).
|
74
|
-
* @property {string} itemName - The market item's name (e.g. "UMP-45 | Mudder").
|
75
|
-
* @property {string} gameName - The game's name (e.g. "Counter-Strike 2").
|
76
|
-
* @property {string} listedOn - Listing date as string (e.g. "27 May 2024").
|
77
|
-
* @property {string} actedOn - Acted on date or empty string.
|
78
|
-
* @property {string} image - Full image URL for the item.
|
79
|
-
* @property {string} gainOrLoss - "+" for buys, "-" for sells.
|
80
|
-
* @property {string} status - Status text, usually empty string.
|
81
|
-
* @property {string} [assetId] - (optional) Associated asset id, if available.
|
82
|
-
*/
|
83
|
-
|
84
|
-
/**
|
85
|
-
* Parses Steam market history listing rows into structured data objects.
|
86
|
-
*
|
87
|
-
* @param {string} html - The raw HTML string to parse.
|
88
|
-
* @param {Object} [assetByListingId={}] - Optional mapping of listingId to assetId.
|
89
|
-
* @returns {MarketHistoryListing[]} Array of market listing objects.
|
90
|
-
*/
|
91
|
-
export function parseMarketHistoryListings(html, assetByListingId = {}) {
|
92
|
-
const $ = cheerio.load(html);
|
93
|
-
return $(".market_listing_row")
|
94
|
-
.toArray()
|
95
|
-
.map((el) => {
|
96
|
-
const $el = $(el);
|
97
|
-
const id = $el.attr("id");
|
98
|
-
const listingId = id?.match(/history_row_(.*?)_/)?.[1];
|
99
|
-
if (!listingId) return null;
|
100
|
-
const [gainOrLoss, image, priceText, itemName, gameName, listedOnText, actedOnText, status] = [$el.find(".market_listing_gainorloss").text().trim(), $el.find(".market_listing_item_img").attr("src"), $el.find(".market_table_value .market_listing_price").text(), $el.find(".market_listing_item_name").text().trim(), $el.find(".market_listing_game_name").text().trim(), $el.find(".market_listing_listed_date + .market_listing_listed_date").text(), $el.find(".market_listing_whoactedwith + .market_listing_listed_date").text(), $el.find(".market_listing_whoactedwith").text().trim()];
|
101
|
-
const price = parseInt(priceText.replace(/[₫().,]/g, "").trim(), 10) || "";
|
102
|
-
const listedOn = typeof formatMarketHistoryDate === "function" ? formatMarketHistoryDate(listedOnText.replace(/Listed:/, "").trim()) : listedOnText.replace(/Listed:/, "").trim();
|
103
|
-
const actedOn = typeof formatMarketHistoryDate === "function" ? formatMarketHistoryDate(actedOnText.replace(/Listed:/, "").trim()) : actedOnText.replace(/Listed:/, "").trim();
|
104
|
-
|
105
|
-
const result = {
|
106
|
-
id,
|
107
|
-
listingId,
|
108
|
-
price,
|
109
|
-
itemName,
|
110
|
-
gameName,
|
111
|
-
listedOn,
|
112
|
-
actedOn,
|
113
|
-
image,
|
114
|
-
gainOrLoss,
|
115
|
-
status,
|
116
|
-
};
|
117
|
-
if (assetByListingId?.[listingId]) result.assetId = assetByListingId[listingId];
|
118
|
-
return result;
|
119
|
-
})
|
120
|
-
.filter(Boolean);
|
121
|
-
}
|
122
|
-
|
123
|
-
/**
|
124
|
-
* Parses Steam Market HTML listings to an array of items.
|
125
|
-
*
|
126
|
-
* @param {string} html - HTML string containing listing rows.
|
127
|
-
* @returns {Array<{
|
128
|
-
* listingId: string,
|
129
|
-
* appId: number,
|
130
|
-
* contextId: number,
|
131
|
-
* itemId: number,
|
132
|
-
* imageUrl: string|null,
|
133
|
-
* buyerPaysPrice: string,
|
134
|
-
* receivePrice: string,
|
135
|
-
* itemName: string,
|
136
|
-
* gameName: string,
|
137
|
-
* listedDate: string
|
138
|
-
* }>}
|
139
|
-
*/
|
140
|
-
export function parseMarketListings(html) {
|
141
|
-
const $ = cheerio.load(html);
|
142
|
-
const results = [];
|
143
|
-
|
144
|
-
$(".market_listing_row").each((index, element) => {
|
145
|
-
const $row = $(element);
|
146
|
-
|
147
|
-
const cancelLink = $row.find(".market_listing_cancel_button > a").attr("href");
|
148
|
-
if (!cancelLink) return;
|
149
|
-
|
150
|
-
const paramsMatch = cancelLink.match(/\(([^)]*)\)/);
|
151
|
-
if (!paramsMatch) return;
|
152
|
-
|
153
|
-
let [, listingId, appId, contextId, itemId] = paramsMatch[1].split(",").map((param) => param.trim().replace(/^['"]|['"]$/g, ""));
|
154
|
-
|
155
|
-
appId = Number(appId);
|
156
|
-
contextId = Number(contextId);
|
157
|
-
itemId = Number(itemId);
|
158
|
-
|
159
|
-
const imageUrl = $row.find(`#mylisting_${listingId}_image`).attr("src") || null;
|
160
|
-
|
161
|
-
const buyerPaysElem = $row.find('.market_listing_price span[title="This is the price the buyer pays."]');
|
162
|
-
const buyerPaysPrice = StringUtils.cleanSpace(buyerPaysElem.text().replace(/[()]/g, ""));
|
163
|
-
|
164
|
-
const receiveElem = $row.find('.market_listing_price span[title="This is how much you will receive."]');
|
165
|
-
const receivePrice = StringUtils.cleanSpace(receiveElem.text().replace(/[()]/g, ""));
|
166
|
-
|
167
|
-
const itemName = $row.find(".market_listing_item_name_link").text() || $row.find(".market_listing_item_name").text();
|
168
|
-
|
169
|
-
const gameName = $row.find(".market_listing_game_name").text();
|
170
|
-
|
171
|
-
const listedDateElem = $row.find(".market_listing_game_name + .market_listing_listed_date_combined");
|
172
|
-
const listedDate = formatMarketHistoryDate(StringUtils.cleanSpace(listedDateElem.text()));
|
173
|
-
|
174
|
-
results.push({
|
175
|
-
listingId,
|
176
|
-
appId,
|
177
|
-
contextId,
|
178
|
-
itemId,
|
179
|
-
imageUrl,
|
180
|
-
buyerPaysPrice,
|
181
|
-
receivePrice,
|
182
|
-
itemName,
|
183
|
-
gameName,
|
184
|
-
listedDate,
|
185
|
-
});
|
186
|
-
});
|
187
|
-
|
188
|
-
return results;
|
189
|
-
}
|
1
|
+
import { StringUtils } from "alpha-common-utils/index.js";
|
2
|
+
import { formatMarketHistoryDate } from "./utils.js";
|
3
|
+
import * as cheerio from "cheerio";
|
4
|
+
|
5
|
+
/**
|
6
|
+
* @typedef {Object} HoverItem
|
7
|
+
* @property {string|null} listingId The listing ID extracted from the selector, if available (as string).
|
8
|
+
* @property {number} appid The app (game) ID for this item.
|
9
|
+
* @property {number} contextid The context ID for this item.
|
10
|
+
* @property {number} assetid The unique asset ID.
|
11
|
+
* @property {string} unknown A string field captured from the arguments (usually "0").
|
12
|
+
* @property {string} [name_selector] The HTML selector for the item's name, if present.
|
13
|
+
* @property {string} [image_selector] The HTML selector for the item's image, if present.
|
14
|
+
* @property {string} [other_selector] The selector string if it does not match name/image pattern.
|
15
|
+
*/
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Extracts item information from Steam inventory trade history or similar HTML fragments.
|
19
|
+
* Scans the given HTML string for calls to `CreateItemHoverFromContainer` and extracts
|
20
|
+
* key data for each referenced item. Returns each item's info in a structured array.
|
21
|
+
*
|
22
|
+
* @param {string} html - The HTML string to parse.
|
23
|
+
* @returns {HoverItem[]} Array of hover item objects.
|
24
|
+
*/
|
25
|
+
export function extractAssetItemsFromHovers(html) {
|
26
|
+
// Use your preferred HTML/space scrubber.
|
27
|
+
html = StringUtils.cleanSpace(html);
|
28
|
+
|
29
|
+
// RegExp for CreateItemHoverFromContainer arguments
|
30
|
+
const re = /CreateItemHoverFromContainer\s*\(\s*g_rgAssets\s*,\s*'([^']+)'\s*,\s*(\d+)\s*,\s*'(\d+)'\s*,\s*'(\d+)'\s*,\s*(\d+)\s*\)/g;
|
31
|
+
|
32
|
+
/** @type {Record<number, HoverItem>} */
|
33
|
+
const itemsMap = {};
|
34
|
+
let match;
|
35
|
+
|
36
|
+
while ((match = re.exec(html)) !== null) {
|
37
|
+
const [_, selector, appid, contextid, assetid, unknown] = match;
|
38
|
+
|
39
|
+
// Extract listingId as a string if present: "history_row_<listingId>_"
|
40
|
+
let listingId = null;
|
41
|
+
const lidMatch = selector.match(/history_row_(\d+)_/);
|
42
|
+
if (lidMatch) listingId = lidMatch[1]; // assign as string
|
43
|
+
|
44
|
+
const appidNum = Number(appid);
|
45
|
+
const contextidNum = Number(contextid);
|
46
|
+
const assetidNum = Number(assetid);
|
47
|
+
|
48
|
+
if (!itemsMap[assetidNum]) {
|
49
|
+
itemsMap[assetidNum] = {
|
50
|
+
listingId,
|
51
|
+
appid: appidNum,
|
52
|
+
contextid: contextidNum,
|
53
|
+
assetid: assetidNum,
|
54
|
+
unknown,
|
55
|
+
};
|
56
|
+
}
|
57
|
+
|
58
|
+
if (selector.endsWith("_name")) {
|
59
|
+
itemsMap[assetidNum].name_selector = selector;
|
60
|
+
} else if (selector.endsWith("_image")) {
|
61
|
+
itemsMap[assetidNum].image_selector = selector;
|
62
|
+
} else {
|
63
|
+
itemsMap[assetidNum].other_selector = selector;
|
64
|
+
}
|
65
|
+
}
|
66
|
+
return Object.values(itemsMap);
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
* @typedef {Object} MarketHistoryListing
|
71
|
+
* @property {string} id - HTML row id attribute (e.g. "history_row_4316182845737233773_4316182845737233774").
|
72
|
+
* @property {string} listingId - Listing id (e.g. "4316182845737233773").
|
73
|
+
* @property {number} price - Price in integer form (e.g. 80270).
|
74
|
+
* @property {string} itemName - The market item's name (e.g. "UMP-45 | Mudder").
|
75
|
+
* @property {string} gameName - The game's name (e.g. "Counter-Strike 2").
|
76
|
+
* @property {string} listedOn - Listing date as string (e.g. "27 May 2024").
|
77
|
+
* @property {string} actedOn - Acted on date or empty string.
|
78
|
+
* @property {string} image - Full image URL for the item.
|
79
|
+
* @property {string} gainOrLoss - "+" for buys, "-" for sells.
|
80
|
+
* @property {string} status - Status text, usually empty string.
|
81
|
+
* @property {string} [assetId] - (optional) Associated asset id, if available.
|
82
|
+
*/
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Parses Steam market history listing rows into structured data objects.
|
86
|
+
*
|
87
|
+
* @param {string} html - The raw HTML string to parse.
|
88
|
+
* @param {Object} [assetByListingId={}] - Optional mapping of listingId to assetId.
|
89
|
+
* @returns {MarketHistoryListing[]} Array of market listing objects.
|
90
|
+
*/
|
91
|
+
export function parseMarketHistoryListings(html, assetByListingId = {}) {
|
92
|
+
const $ = cheerio.load(html);
|
93
|
+
return $(".market_listing_row")
|
94
|
+
.toArray()
|
95
|
+
.map((el) => {
|
96
|
+
const $el = $(el);
|
97
|
+
const id = $el.attr("id");
|
98
|
+
const listingId = id?.match(/history_row_(.*?)_/)?.[1];
|
99
|
+
if (!listingId) return null;
|
100
|
+
const [gainOrLoss, image, priceText, itemName, gameName, listedOnText, actedOnText, status] = [$el.find(".market_listing_gainorloss").text().trim(), $el.find(".market_listing_item_img").attr("src"), $el.find(".market_table_value .market_listing_price").text(), $el.find(".market_listing_item_name").text().trim(), $el.find(".market_listing_game_name").text().trim(), $el.find(".market_listing_listed_date + .market_listing_listed_date").text(), $el.find(".market_listing_whoactedwith + .market_listing_listed_date").text(), $el.find(".market_listing_whoactedwith").text().trim()];
|
101
|
+
const price = parseInt(priceText.replace(/[₫().,]/g, "").trim(), 10) || "";
|
102
|
+
const listedOn = typeof formatMarketHistoryDate === "function" ? formatMarketHistoryDate(listedOnText.replace(/Listed:/, "").trim()) : listedOnText.replace(/Listed:/, "").trim();
|
103
|
+
const actedOn = typeof formatMarketHistoryDate === "function" ? formatMarketHistoryDate(actedOnText.replace(/Listed:/, "").trim()) : actedOnText.replace(/Listed:/, "").trim();
|
104
|
+
|
105
|
+
const result = {
|
106
|
+
id,
|
107
|
+
listingId,
|
108
|
+
price,
|
109
|
+
itemName,
|
110
|
+
gameName,
|
111
|
+
listedOn,
|
112
|
+
actedOn,
|
113
|
+
image,
|
114
|
+
gainOrLoss,
|
115
|
+
status,
|
116
|
+
};
|
117
|
+
if (assetByListingId?.[listingId]) result.assetId = assetByListingId[listingId];
|
118
|
+
return result;
|
119
|
+
})
|
120
|
+
.filter(Boolean);
|
121
|
+
}
|
122
|
+
|
123
|
+
/**
|
124
|
+
* Parses Steam Market HTML listings to an array of items.
|
125
|
+
*
|
126
|
+
* @param {string} html - HTML string containing listing rows.
|
127
|
+
* @returns {Array<{
|
128
|
+
* listingId: string,
|
129
|
+
* appId: number,
|
130
|
+
* contextId: number,
|
131
|
+
* itemId: number,
|
132
|
+
* imageUrl: string|null,
|
133
|
+
* buyerPaysPrice: string,
|
134
|
+
* receivePrice: string,
|
135
|
+
* itemName: string,
|
136
|
+
* gameName: string,
|
137
|
+
* listedDate: string
|
138
|
+
* }>}
|
139
|
+
*/
|
140
|
+
export function parseMarketListings(html) {
|
141
|
+
const $ = cheerio.load(html);
|
142
|
+
const results = [];
|
143
|
+
|
144
|
+
$(".market_listing_row").each((index, element) => {
|
145
|
+
const $row = $(element);
|
146
|
+
|
147
|
+
const cancelLink = $row.find(".market_listing_cancel_button > a").attr("href");
|
148
|
+
if (!cancelLink) return;
|
149
|
+
|
150
|
+
const paramsMatch = cancelLink.match(/\(([^)]*)\)/);
|
151
|
+
if (!paramsMatch) return;
|
152
|
+
|
153
|
+
let [, listingId, appId, contextId, itemId] = paramsMatch[1].split(",").map((param) => param.trim().replace(/^['"]|['"]$/g, ""));
|
154
|
+
|
155
|
+
appId = Number(appId);
|
156
|
+
contextId = Number(contextId);
|
157
|
+
itemId = Number(itemId);
|
158
|
+
|
159
|
+
const imageUrl = $row.find(`#mylisting_${listingId}_image`).attr("src") || null;
|
160
|
+
|
161
|
+
const buyerPaysElem = $row.find('.market_listing_price span[title="This is the price the buyer pays."]');
|
162
|
+
const buyerPaysPrice = StringUtils.cleanSpace(buyerPaysElem.text().replace(/[()]/g, ""));
|
163
|
+
|
164
|
+
const receiveElem = $row.find('.market_listing_price span[title="This is how much you will receive."]');
|
165
|
+
const receivePrice = StringUtils.cleanSpace(receiveElem.text().replace(/[()]/g, ""));
|
166
|
+
|
167
|
+
const itemName = $row.find(".market_listing_item_name_link").text() || $row.find(".market_listing_item_name").text();
|
168
|
+
|
169
|
+
const gameName = $row.find(".market_listing_game_name").text();
|
170
|
+
|
171
|
+
const listedDateElem = $row.find(".market_listing_game_name + .market_listing_listed_date_combined");
|
172
|
+
const listedDate = formatMarketHistoryDate(StringUtils.cleanSpace(listedDateElem.text()));
|
173
|
+
|
174
|
+
results.push({
|
175
|
+
listingId,
|
176
|
+
appId,
|
177
|
+
contextId,
|
178
|
+
itemId,
|
179
|
+
imageUrl,
|
180
|
+
buyerPaysPrice,
|
181
|
+
receivePrice,
|
182
|
+
itemName,
|
183
|
+
gameName,
|
184
|
+
listedDate,
|
185
|
+
});
|
186
|
+
});
|
187
|
+
|
188
|
+
return results;
|
189
|
+
}
|