steamutils 1.5.43 → 1.5.44
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/full_steamproto.js +2 -7
- package/index.js +71 -82
- 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/_steamproto.js
CHANGED
@@ -1,56 +1,56 @@
|
|
1
|
-
import path from "path";
|
2
|
-
import { fileURLToPath } from "url";
|
3
|
-
import Protobuf from "protobufjs";
|
4
|
-
import gpf from "google-proto-files";
|
5
|
-
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
7
|
-
const __dirname = path.dirname(__filename);
|
8
|
-
|
9
|
-
export class SteamProto {
|
10
|
-
constructor(proto) {
|
11
|
-
this._proto = proto;
|
12
|
-
}
|
13
|
-
|
14
|
-
toProto() {
|
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], {
|
20
|
-
keepCase: true,
|
21
|
-
});
|
22
|
-
return root.lookupType(this._proto.name);
|
23
|
-
}
|
24
|
-
|
25
|
-
protoEncode(obj) {
|
26
|
-
const protobuf = this.toProto();
|
27
|
-
return protobuf.encode(protobuf.create(obj)).finish();
|
28
|
-
}
|
29
|
-
|
30
|
-
protoEncodeBase64(obj) {
|
31
|
-
return this.protoEncode(obj).toString("base64");
|
32
|
-
}
|
33
|
-
|
34
|
-
protoDecode(obj) {
|
35
|
-
if (isBase64(obj)) {
|
36
|
-
obj = Buffer.from(obj, "base64");
|
37
|
-
}
|
38
|
-
|
39
|
-
const protobuf = this.toProto();
|
40
|
-
try {
|
41
|
-
return protobuf.toObject(protobuf.decode(obj), { defaults: true });
|
42
|
-
} catch (e) {
|
43
|
-
console.error(`[${this._proto.name}] protoDecode 1`, typeof obj, obj);
|
44
|
-
console.error(`[${this._proto.name}] protoDecode 2`, e);
|
45
|
-
return null;
|
46
|
-
}
|
47
|
-
}
|
48
|
-
}
|
49
|
-
|
50
|
-
function isBase64(str) {
|
51
|
-
if (typeof str !== "string") return false;
|
52
|
-
// remove whitespace and check
|
53
|
-
str = str.trim();
|
54
|
-
// base64 should only contain A-Z, a-z, 0-9, +, /, and possibly = at the end
|
55
|
-
return /^[A-Za-z0-9+/]+={0,2}$/.test(str) && str.length % 4 === 0;
|
56
|
-
}
|
1
|
+
import path from "path";
|
2
|
+
import { fileURLToPath } from "url";
|
3
|
+
import Protobuf from "protobufjs";
|
4
|
+
import gpf from "google-proto-files";
|
5
|
+
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
7
|
+
const __dirname = path.dirname(__filename);
|
8
|
+
|
9
|
+
export class SteamProto {
|
10
|
+
constructor(proto) {
|
11
|
+
this._proto = proto;
|
12
|
+
}
|
13
|
+
|
14
|
+
toProto() {
|
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], {
|
20
|
+
keepCase: true,
|
21
|
+
});
|
22
|
+
return root.lookupType(this._proto.name);
|
23
|
+
}
|
24
|
+
|
25
|
+
protoEncode(obj) {
|
26
|
+
const protobuf = this.toProto();
|
27
|
+
return protobuf.encode(protobuf.create(obj)).finish();
|
28
|
+
}
|
29
|
+
|
30
|
+
protoEncodeBase64(obj) {
|
31
|
+
return this.protoEncode(obj).toString("base64");
|
32
|
+
}
|
33
|
+
|
34
|
+
protoDecode(obj) {
|
35
|
+
if (isBase64(obj)) {
|
36
|
+
obj = Buffer.from(obj, "base64");
|
37
|
+
}
|
38
|
+
|
39
|
+
const protobuf = this.toProto();
|
40
|
+
try {
|
41
|
+
return protobuf.toObject(protobuf.decode(obj), { defaults: true });
|
42
|
+
} catch (e) {
|
43
|
+
console.error(`[${this._proto.name}] protoDecode 1`, typeof obj, obj);
|
44
|
+
console.error(`[${this._proto.name}] protoDecode 2`, e);
|
45
|
+
return null;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
function isBase64(str) {
|
51
|
+
if (typeof str !== "string") return false;
|
52
|
+
// remove whitespace and check
|
53
|
+
str = str.trim();
|
54
|
+
// base64 should only contain A-Z, a-z, 0-9, +, /, and possibly = at the end
|
55
|
+
return /^[A-Za-z0-9+/]+={0,2}$/.test(str) && str.length % 4 === 0;
|
56
|
+
}
|
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
@@ -21,6 +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
25
|
|
25
26
|
const eventEmitter = (globalThis.steamUserEventEmitter = new EventEmitter());
|
26
27
|
|
@@ -6299,43 +6300,77 @@ export default class SteamUser {
|
|
6299
6300
|
return null;
|
6300
6301
|
}
|
6301
6302
|
|
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
|
-
});
|
6303
|
+
const list = parseMarketListings(data.results_html);
|
6334
6304
|
const assets = Object.values(data.assets["730"]?.["2"] || {});
|
6335
6305
|
return { list, assets, success: true };
|
6336
6306
|
}
|
6337
6307
|
|
6338
|
-
|
6308
|
+
/**
|
6309
|
+
* @typedef {Object} MarketHistoryResponse
|
6310
|
+
* @property {boolean} success Whether the request was successful.
|
6311
|
+
* @property {number} pagesize Number of items per page.
|
6312
|
+
* @property {number} total_count Total count of market entries available.
|
6313
|
+
* @property {number} start Starting index of the results.
|
6314
|
+
* @property {Array<Asset>} assets Related inventory assets for the transactions.
|
6315
|
+
* @property {string} hovers Raw JavaScript code for web asset hovers.
|
6316
|
+
* @property {string} results_html Raw HTML snippet of market history.
|
6317
|
+
* @property {Array<MarketHistoryListing>} list Simplified, parsed list of market actions (buys/sells).
|
6318
|
+
* // Optionally, you may add below for pre-parsed hover info
|
6319
|
+
* // @property {Array<HoverItem>} [hoverItems] Parsed item hover info (if extracted from hovers).
|
6320
|
+
*/
|
6321
|
+
|
6322
|
+
/**
|
6323
|
+
* @typedef {Object} Asset
|
6324
|
+
* @property {number} currency
|
6325
|
+
* @property {number} appid
|
6326
|
+
* @property {string} contextid
|
6327
|
+
* @property {string} id
|
6328
|
+
* @property {string} classid
|
6329
|
+
* @property {string} instanceid
|
6330
|
+
* @property {string} amount
|
6331
|
+
* @property {number} status
|
6332
|
+
* @property {string} original_amount
|
6333
|
+
* @property {string} unowned_id
|
6334
|
+
* @property {string} unowned_contextid
|
6335
|
+
* @property {string} background_color
|
6336
|
+
* @property {string} icon_url
|
6337
|
+
* @property {Array<AssetDescription>} descriptions
|
6338
|
+
* @property {number} tradable
|
6339
|
+
* @property {Array<AssetAction>} [actions]
|
6340
|
+
* @property {string} name
|
6341
|
+
* @property {string} name_color
|
6342
|
+
* @property {string} type
|
6343
|
+
* @property {string} market_name
|
6344
|
+
* @property {string} market_hash_name
|
6345
|
+
* @property {Array<AssetAction>} [market_actions]
|
6346
|
+
* @property {number} commodity
|
6347
|
+
* @property {number} market_tradable_restriction
|
6348
|
+
* @property {number} market_marketable_restriction
|
6349
|
+
* @property {number} marketable
|
6350
|
+
* @property {string} app_icon
|
6351
|
+
* @property {number} owner
|
6352
|
+
*/
|
6353
|
+
|
6354
|
+
/**
|
6355
|
+
* @typedef {Object} AssetDescription
|
6356
|
+
* @property {string} type Description style or block type (e.g. "html").
|
6357
|
+
* @property {string} value The description's content.
|
6358
|
+
* @property {string} name Field/tag name.
|
6359
|
+
* @property {string} [color] (Optional) Hex color string for this description entry.
|
6360
|
+
*/
|
6361
|
+
|
6362
|
+
/**
|
6363
|
+
* @typedef {Object} AssetAction
|
6364
|
+
* @property {string} link URL or steam protocol link.
|
6365
|
+
* @property {string} name Action button text/description.
|
6366
|
+
*/
|
6367
|
+
|
6368
|
+
/**
|
6369
|
+
* Retrieves the user's Steam Market transaction history, including assets, listing info, and hover selectors.
|
6370
|
+
*
|
6371
|
+
* @returns {MarketHistoryResponse} The market history data structure.
|
6372
|
+
*/
|
6373
|
+
async getMarketHistory({ start = 0, count = 100 } = {}) {
|
6339
6374
|
const result = await this._httpRequestAjax({
|
6340
6375
|
url: `market/myhistory/render/?query=&start=${start}&count=${count}`,
|
6341
6376
|
});
|
@@ -6349,56 +6384,10 @@ export default class SteamUser {
|
|
6349
6384
|
return null;
|
6350
6385
|
}
|
6351
6386
|
|
6352
|
-
const hovers =
|
6387
|
+
const hovers = extractAssetItemsFromHovers(data.hovers);
|
6353
6388
|
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);
|
6389
|
+
const assestByListingId = hovers.reduce((map, item) => (item.listingId && item.assetid && (map[item.listingId] = item.assetid), map), {});
|
6390
|
+
const list = parseMarketHistoryListings(data.results_html, assestByListingId);
|
6402
6391
|
const assets = Object.values(assetById);
|
6403
6392
|
return { ...data, list, assets, success: true };
|
6404
6393
|
}
|
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
|
+
}
|