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.
@@ -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 root = new Protobuf.Root().loadSync(path.join(`${__dirname}/protos/`, this._proto.filename), {
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[this._proto.name];
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 result;
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
- const headerEl = $("#header_wallet_balance");
6793
- try {
6794
- return headerEl[0].children.map((el) => $(el).text().trim()).filter(Boolean);
6795
- } catch (e) {}
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 result;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "steamutils",
3
- "version": "1.5.50",
3
+ "version": "1.5.52",
4
4
  "main": "index.js",
5
5
  "dependencies": {
6
6
  "alpha-common-utils": "^1.0.6",
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
+ }