steamutils 1.5.43 → 1.5.45
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/gbrowser_project.xml +11 -0
- package/.idea/git_toolbox_blame.xml +6 -0
- package/.idea/git_toolbox_prj.xml +15 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/prettier.xml +1 -0
- package/SteamClient.js +3159 -3159
- package/_steamproto.js +56 -56
- package/const.js +582 -570
- package/full_steamproto.js +2 -7
- package/index.js +118 -108
- package/package.json +1 -1
- package/parse_html.js +189 -0
- package/race.js +2241 -0
- package/steamproto.js +57 -57
- package/utils.js +1300 -1300
package/full_steamproto.js
CHANGED
@@ -1,7 +1,6 @@
|
|
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";
|
5
4
|
|
6
5
|
const __filename = fileURLToPath(import.meta.url);
|
7
6
|
const __dirname = path.dirname(__filename);
|
@@ -12,14 +11,10 @@ export class SteamProto {
|
|
12
11
|
}
|
13
12
|
|
14
13
|
toProto() {
|
15
|
-
const
|
16
|
-
|
17
|
-
const descriptorPath = gpf.getProtoPath("protobuf/descriptor.proto");
|
18
|
-
|
19
|
-
const root = new Protobuf.Root().loadSync([descriptorPath, localProto], {
|
14
|
+
const root = new Protobuf.Root().loadSync(path.join(`${__dirname}/protos/`, this._proto.filename), {
|
20
15
|
keepCase: true,
|
21
16
|
});
|
22
|
-
return root
|
17
|
+
return root[this._proto.name];
|
23
18
|
}
|
24
19
|
|
25
20
|
protoEncode(obj) {
|
package/index.js
CHANGED
@@ -17,10 +17,11 @@ import { StringUtils } from "alpha-common-utils/index.js";
|
|
17
17
|
import * as https from "https";
|
18
18
|
import path from "path";
|
19
19
|
import CookieManager from "./CookieManager.js";
|
20
|
-
import { AppID_CSGO, E1GameBanOnRecord, E1VACBanOnRecord, EActivityType, ECommentPrivacyState, ECurrentlyTradeBanned, EdaySinceLastBanRegExp, EFriendRelationship, ELanguage, EmptyProfileSummary, EMultipleGameBansOnRecord, EMultipleVACBansOnRecord, EPrivacyState, ErrorProcessingRequest, FRIEND_CODE_REPLACEMENTS, NotYetSetupProfileTextList, PrivacySettings, PrivateProfileTextList, SteamcommunityURL, SteamErrorTitle, SteamImageCDN } from "./const.js";
|
20
|
+
import { AppID_CSGO, E1GameBanOnRecord, E1VACBanOnRecord, EActivityType, ECommentPrivacyState, ECurrentlyTradeBanned, EdaySinceLastBanRegExp, EFriendRelationship, ELanguage, EmptyProfileSummary, EMultipleGameBansOnRecord, EMultipleVACBansOnRecord, EPrivacyState, ErrorProcessingRequest, FRIEND_CODE_REPLACEMENTS, MarketRestrictionReasonText, NotYetSetupProfileTextList, PrivacySettings, PrivateProfileTextList, SteamcommunityURL, SteamErrorTitle, SteamImageCDN } from "./const.js";
|
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
25
|
|
25
26
|
const eventEmitter = (globalThis.steamUserEventEmitter = new EventEmitter());
|
26
27
|
|
@@ -6181,37 +6182,58 @@ export default class SteamUser {
|
|
6181
6182
|
return response;
|
6182
6183
|
}
|
6183
6184
|
|
6184
|
-
|
6185
|
+
/**
|
6186
|
+
* Fetches and parses Steam Market unavailability info.
|
6187
|
+
*
|
6188
|
+
* @returns {Promise<{
|
6189
|
+
* marketWarning?: string,
|
6190
|
+
* marketRestrictions?: string[],
|
6191
|
+
* marketRestrictionExpire?: string,
|
6192
|
+
* marketTimeCanUse?: number | null
|
6193
|
+
* } | null>} Parsed unavailability data, empty object, or null on error.
|
6194
|
+
*/
|
6195
|
+
async getMarketRestrictions() {
|
6185
6196
|
const result = await this._httpRequest(`market`);
|
6186
6197
|
if (result instanceof ResponseError) {
|
6187
|
-
return result;
|
6188
|
-
}
|
6189
|
-
const data = result?.data;
|
6190
|
-
if (!data) {
|
6191
6198
|
return null;
|
6192
6199
|
}
|
6200
|
+
const data = result?.data;
|
6201
|
+
if (!data) return null;
|
6202
|
+
|
6193
6203
|
const $ = cheerio.load(data);
|
6194
|
-
|
6195
|
-
const
|
6196
|
-
|
6197
|
-
|
6198
|
-
|
6199
|
-
|
6200
|
-
|
6201
|
-
|
6202
|
-
|
6203
|
-
|
6204
|
-
|
6205
|
-
const market_timecanuse_header = StringUtils.cleanSpace(market_headertip_container_warning_el.find("#market_timecanuse_header").text());
|
6206
|
-
return {
|
6207
|
-
market_warning_header,
|
6208
|
-
market_restrictions,
|
6209
|
-
market_restriction_expire,
|
6210
|
-
market_timecanuse_header,
|
6211
|
-
};
|
6212
|
-
} else {
|
6213
|
-
return {};
|
6204
|
+
|
6205
|
+
const loginMsg1 = $("body").find(':contains("You need to sign in or create an account to do that.")').length;
|
6206
|
+
const loginMsg2 = $("body").find(':contains("Login to see, edit, or remove your Community Market listings")').length;
|
6207
|
+
const loginMsg3 = $("body").find(':contains("Login to view your Community Market history")').length;
|
6208
|
+
const loginLinkBlock = $(".market_login_link_ctn").length;
|
6209
|
+
if (loginMsg1 || loginMsg2 || loginMsg3 || loginLinkBlock) return null;
|
6210
|
+
|
6211
|
+
const warningContainer = $(".market_headertip_container_warning");
|
6212
|
+
const warningHeaderElement = warningContainer.find("#market_warning_header");
|
6213
|
+
if (!warningContainer.length || !warningHeaderElement.length) {
|
6214
|
+
return null;
|
6214
6215
|
}
|
6216
|
+
|
6217
|
+
const marketWarning = StringUtils.cleanSpace(warningHeaderElement.text());
|
6218
|
+
|
6219
|
+
const restrictionMessages = [];
|
6220
|
+
warningContainer.find("ul.market_restrictions > li").each(function () {
|
6221
|
+
const el = $(this);
|
6222
|
+
el.find("a").remove();
|
6223
|
+
restrictionMessages.push(StringUtils.cleanSpace(el.text()));
|
6224
|
+
});
|
6225
|
+
|
6226
|
+
const marketRestrictions = restrictionMessages.map((message) => Object.entries(MarketRestrictionReasonText).find(([, text]) => text === message)?.[0] || message).filter(Boolean);
|
6227
|
+
const marketRestrictionExpire = StringUtils.cleanSpace(warningContainer.find("#market_restriction_expire").text());
|
6228
|
+
const marketTimeCanUse = StringUtils.cleanSpace(warningContainer.find("#market_timecanuse_header").text());
|
6229
|
+
const marketTimeCanUseMoment = moment(marketTimeCanUse, "ddd, DD MMM YYYY HH:mm:ss Z", true);
|
6230
|
+
|
6231
|
+
return {
|
6232
|
+
marketWarning,
|
6233
|
+
marketRestrictions,
|
6234
|
+
marketRestrictionExpire,
|
6235
|
+
marketTimeCanUse: marketTimeCanUseMoment.isValid() ? marketTimeCanUseMoment.valueOf() : null,
|
6236
|
+
};
|
6215
6237
|
}
|
6216
6238
|
|
6217
6239
|
async getAmountSpentOnSteam() {
|
@@ -6299,43 +6321,77 @@ export default class SteamUser {
|
|
6299
6321
|
return null;
|
6300
6322
|
}
|
6301
6323
|
|
6302
|
-
const
|
6303
|
-
const list = [];
|
6304
|
-
$(".market_listing_row").each(function () {
|
6305
|
-
try {
|
6306
|
-
const $row = $(this);
|
6307
|
-
const [sElementPrefix, listingid, appid, contextid, itemid] = $row
|
6308
|
-
.find(".market_listing_cancel_button > a")
|
6309
|
-
.attr("href")
|
6310
|
-
.split("(")[1]
|
6311
|
-
.split(")")[0]
|
6312
|
-
.split(",")
|
6313
|
-
.map((r) => r.trim().replaceAll(`'`, "").replaceAll(`"`, ""));
|
6314
|
-
const image = $row.find(`#mylisting_${listingid}_image`).attr("src");
|
6315
|
-
const buyer_pays_price = StringUtils.cleanSpace($row.find(`.market_listing_price span[title="This is the price the buyer pays."]`).text().replaceAll(`(`, "").replaceAll(`)`, ""));
|
6316
|
-
const receive_price = StringUtils.cleanSpace($row.find(`.market_listing_price span[title="This is how much you will receive."]`).text().replaceAll(`(`, "").replaceAll(`)`, ""));
|
6317
|
-
const item_name = $row.find(".market_listing_item_name_link").text() || $row.find(".market_listing_item_name").text();
|
6318
|
-
const game_name = $row.find(".market_listing_game_name").text();
|
6319
|
-
const date_combined = formatMarketHistoryDate(StringUtils.cleanSpace($row.find(".market_listing_game_name + .market_listing_listed_date_combined").text()));
|
6320
|
-
list.push({
|
6321
|
-
listingid,
|
6322
|
-
appid,
|
6323
|
-
contextid,
|
6324
|
-
itemid,
|
6325
|
-
buyer_pays_price,
|
6326
|
-
receive_price,
|
6327
|
-
item_name,
|
6328
|
-
game_name,
|
6329
|
-
date_combined,
|
6330
|
-
image,
|
6331
|
-
});
|
6332
|
-
} catch (e) {}
|
6333
|
-
});
|
6324
|
+
const list = parseMarketListings(data.results_html);
|
6334
6325
|
const assets = Object.values(data.assets["730"]?.["2"] || {});
|
6335
6326
|
return { list, assets, success: true };
|
6336
6327
|
}
|
6337
6328
|
|
6338
|
-
|
6329
|
+
/**
|
6330
|
+
* @typedef {Object} MarketHistoryResponse
|
6331
|
+
* @property {boolean} success Whether the request was successful.
|
6332
|
+
* @property {number} pagesize Number of items per page.
|
6333
|
+
* @property {number} total_count Total count of market entries available.
|
6334
|
+
* @property {number} start Starting index of the results.
|
6335
|
+
* @property {Array<Asset>} assets Related inventory assets for the transactions.
|
6336
|
+
* @property {string} hovers Raw JavaScript code for web asset hovers.
|
6337
|
+
* @property {string} results_html Raw HTML snippet of market history.
|
6338
|
+
* @property {Array<MarketHistoryListing>} list Simplified, parsed list of market actions (buys/sells).
|
6339
|
+
* // Optionally, you may add below for pre-parsed hover info
|
6340
|
+
* // @property {Array<HoverItem>} [hoverItems] Parsed item hover info (if extracted from hovers).
|
6341
|
+
*/
|
6342
|
+
|
6343
|
+
/**
|
6344
|
+
* @typedef {Object} Asset
|
6345
|
+
* @property {number} currency
|
6346
|
+
* @property {number} appid
|
6347
|
+
* @property {string} contextid
|
6348
|
+
* @property {string} id
|
6349
|
+
* @property {string} classid
|
6350
|
+
* @property {string} instanceid
|
6351
|
+
* @property {string} amount
|
6352
|
+
* @property {number} status
|
6353
|
+
* @property {string} original_amount
|
6354
|
+
* @property {string} unowned_id
|
6355
|
+
* @property {string} unowned_contextid
|
6356
|
+
* @property {string} background_color
|
6357
|
+
* @property {string} icon_url
|
6358
|
+
* @property {Array<AssetDescription>} descriptions
|
6359
|
+
* @property {number} tradable
|
6360
|
+
* @property {Array<AssetAction>} [actions]
|
6361
|
+
* @property {string} name
|
6362
|
+
* @property {string} name_color
|
6363
|
+
* @property {string} type
|
6364
|
+
* @property {string} market_name
|
6365
|
+
* @property {string} market_hash_name
|
6366
|
+
* @property {Array<AssetAction>} [market_actions]
|
6367
|
+
* @property {number} commodity
|
6368
|
+
* @property {number} market_tradable_restriction
|
6369
|
+
* @property {number} market_marketable_restriction
|
6370
|
+
* @property {number} marketable
|
6371
|
+
* @property {string} app_icon
|
6372
|
+
* @property {number} owner
|
6373
|
+
*/
|
6374
|
+
|
6375
|
+
/**
|
6376
|
+
* @typedef {Object} AssetDescription
|
6377
|
+
* @property {string} type Description style or block type (e.g. "html").
|
6378
|
+
* @property {string} value The description's content.
|
6379
|
+
* @property {string} name Field/tag name.
|
6380
|
+
* @property {string} [color] (Optional) Hex color string for this description entry.
|
6381
|
+
*/
|
6382
|
+
|
6383
|
+
/**
|
6384
|
+
* @typedef {Object} AssetAction
|
6385
|
+
* @property {string} link URL or steam protocol link.
|
6386
|
+
* @property {string} name Action button text/description.
|
6387
|
+
*/
|
6388
|
+
|
6389
|
+
/**
|
6390
|
+
* Retrieves the user's Steam Market transaction history, including assets, listing info, and hover selectors.
|
6391
|
+
*
|
6392
|
+
* @returns {MarketHistoryResponse} The market history data structure.
|
6393
|
+
*/
|
6394
|
+
async getMarketHistory({ start = 0, count = 100 } = {}) {
|
6339
6395
|
const result = await this._httpRequestAjax({
|
6340
6396
|
url: `market/myhistory/render/?query=&start=${start}&count=${count}`,
|
6341
6397
|
});
|
@@ -6349,56 +6405,10 @@ export default class SteamUser {
|
|
6349
6405
|
return null;
|
6350
6406
|
}
|
6351
6407
|
|
6352
|
-
const hovers =
|
6408
|
+
const hovers = extractAssetItemsFromHovers(data.hovers);
|
6353
6409
|
const assetById = data.assets?.[730]?.[2] || {};
|
6354
|
-
const assestByListingId = {};
|
6355
|
-
|
6356
|
-
text = text.trim();
|
6357
|
-
if (!text.startsWith("(")) {
|
6358
|
-
return;
|
6359
|
-
}
|
6360
|
-
const texts = text.split(",");
|
6361
|
-
const listingId = texts[1]?.substringBetweenOrNull("history_row_", "_");
|
6362
|
-
const assestId = texts[4]?.trim()?.removeSurrounding("'");
|
6363
|
-
if (!listingId || !assestId || !assetById[assestId]) {
|
6364
|
-
return;
|
6365
|
-
}
|
6366
|
-
assestByListingId[listingId] = assestId;
|
6367
|
-
});
|
6368
|
-
|
6369
|
-
const $ = cheerio.load(data.results_html);
|
6370
|
-
const list = [...$(".market_listing_row")]
|
6371
|
-
.map(function (el) {
|
6372
|
-
el = $(el);
|
6373
|
-
const id = el.attr("id");
|
6374
|
-
const listingid = id.substringBetweenOrNull("history_row_", "_");
|
6375
|
-
if (!listingid) {
|
6376
|
-
return;
|
6377
|
-
}
|
6378
|
-
const gainOrLoss = StringUtils.cleanSpace(el.find(".market_listing_gainorloss").text());
|
6379
|
-
const image = el.find(`.market_listing_item_img`).attr("src");
|
6380
|
-
const price = parseInt(el.find(`.market_table_value .market_listing_price`).text().replaceAll(`(`, "").replaceAll(`)`, "").replaceAll(`₫`, "").replaceAll(`.`, "").replaceAll(`,`, "").trim()) || "";
|
6381
|
-
const item_name = el.find(".market_listing_item_name").text();
|
6382
|
-
const game_name = el.find(".market_listing_game_name").text();
|
6383
|
-
const listedOn = formatMarketHistoryDate(StringUtils.cleanSpace(el.find(".market_listing_listed_date + .market_listing_listed_date").text().replaceAll(`Listed:`, "")));
|
6384
|
-
const actedOn = formatMarketHistoryDate(StringUtils.cleanSpace(el.find(".market_listing_whoactedwith + .market_listing_listed_date").text().replaceAll(`Listed:`, "")));
|
6385
|
-
const status = StringUtils.cleanSpace(el.find(".market_listing_whoactedwith").text());
|
6386
|
-
|
6387
|
-
return {
|
6388
|
-
id,
|
6389
|
-
listingid,
|
6390
|
-
price,
|
6391
|
-
item_name,
|
6392
|
-
game_name,
|
6393
|
-
listedOn,
|
6394
|
-
actedOn,
|
6395
|
-
image,
|
6396
|
-
gainOrLoss,
|
6397
|
-
status,
|
6398
|
-
...(!!assestByListingId[listingid] && { assetId: assestByListingId[listingid] }),
|
6399
|
-
};
|
6400
|
-
})
|
6401
|
-
.filter(Boolean);
|
6410
|
+
const assestByListingId = hovers.reduce((map, item) => (item.listingId && item.assetid && (map[item.listingId] = item.assetid), map), {});
|
6411
|
+
const list = parseMarketHistoryListings(data.results_html, assestByListingId);
|
6402
6412
|
const assets = Object.values(assetById);
|
6403
6413
|
return { ...data, list, assets, success: true };
|
6404
6414
|
}
|
package/package.json
CHANGED
package/parse_html.js
ADDED
@@ -0,0 +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
|
+
}
|