@assetpay/steam-mobile 1.0.5 → 1.2.0
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/README.md +242 -4
- package/dist/index.d.mts +408 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1046 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -68,6 +68,7 @@ let EConfirmationType = /* @__PURE__ */ function(EConfirmationType) {
|
|
|
68
68
|
EConfirmationType[EConfirmationType["FeatureOptOut"] = 4] = "FeatureOptOut";
|
|
69
69
|
EConfirmationType[EConfirmationType["PhoneNumberChange"] = 5] = "PhoneNumberChange";
|
|
70
70
|
EConfirmationType[EConfirmationType["AccountRecovery"] = 6] = "AccountRecovery";
|
|
71
|
+
EConfirmationType[EConfirmationType["BuyOrder"] = 12] = "BuyOrder";
|
|
71
72
|
return EConfirmationType;
|
|
72
73
|
}({});
|
|
73
74
|
let EOfferFilter = /* @__PURE__ */ function(EOfferFilter) {
|
|
@@ -306,6 +307,13 @@ var RateLimitError = class extends SteamError {
|
|
|
306
307
|
this.unlockAt = Date.now() + this.retryAfterMs;
|
|
307
308
|
}
|
|
308
309
|
};
|
|
310
|
+
var ThrottledError = class extends SteamError {
|
|
311
|
+
statusCode = 403;
|
|
312
|
+
constructor(message = "Throttled (403)", options) {
|
|
313
|
+
super(message, options);
|
|
314
|
+
this.name = "ThrottledError";
|
|
315
|
+
}
|
|
316
|
+
};
|
|
309
317
|
var EscrowError = class extends SteamError {
|
|
310
318
|
escrowDays;
|
|
311
319
|
constructor(escrowDays, message) {
|
|
@@ -350,6 +358,12 @@ var ConfirmationError = class extends SteamError {
|
|
|
350
358
|
this.name = "ConfirmationError";
|
|
351
359
|
}
|
|
352
360
|
};
|
|
361
|
+
var ConfirmationOutcomeUnknownError = class extends ConfirmationError {
|
|
362
|
+
constructor(message = "Confirmation response unreadable; the outcome is unknown") {
|
|
363
|
+
super(message);
|
|
364
|
+
this.name = "ConfirmationOutcomeUnknownError";
|
|
365
|
+
}
|
|
366
|
+
};
|
|
353
367
|
var FamilyViewError = class extends SteamError {
|
|
354
368
|
constructor(message = "Family View Restricted") {
|
|
355
369
|
super(message);
|
|
@@ -362,6 +376,74 @@ var PrivateInventoryError = class extends SteamError {
|
|
|
362
376
|
this.name = "PrivateInventoryError";
|
|
363
377
|
}
|
|
364
378
|
};
|
|
379
|
+
var MarketConfirmationLimitError = class extends SteamError {
|
|
380
|
+
reason;
|
|
381
|
+
constructor(message = "Too many listings pending confirmation", options) {
|
|
382
|
+
super(message, options);
|
|
383
|
+
this.name = "MarketConfirmationLimitError";
|
|
384
|
+
this.reason = options?.reason ?? "unknown";
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
var MarketItemUnavailableError = class extends SteamError {
|
|
388
|
+
constructor(message, options) {
|
|
389
|
+
super(message, options);
|
|
390
|
+
this.name = "MarketItemUnavailableError";
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
var MarketIneligibleError = class extends SteamError {
|
|
394
|
+
constructor(message = "Steam redirected to the market eligibility check — this account cannot use the market") {
|
|
395
|
+
super(message);
|
|
396
|
+
this.name = "MarketIneligibleError";
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
var CurrencyMismatchError = class extends SteamError {
|
|
400
|
+
expected;
|
|
401
|
+
actual;
|
|
402
|
+
constructor(expected, actual, context) {
|
|
403
|
+
super(`Currency mismatch in ${context}: expected ${expected}, got ${actual ?? "unknown"}`);
|
|
404
|
+
this.name = "CurrencyMismatchError";
|
|
405
|
+
this.expected = expected;
|
|
406
|
+
this.actual = actual;
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
var WalletCurrencyUnknownError = class extends SteamError {
|
|
410
|
+
constructor(context) {
|
|
411
|
+
super(`Wallet currency unknown for ${context} — seed walletCurrency or call getWalletDetails() first`);
|
|
412
|
+
this.name = "WalletCurrencyUnknownError";
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
var BuyOrderError = class extends SteamError {
|
|
416
|
+
stage;
|
|
417
|
+
code;
|
|
418
|
+
mayExist;
|
|
419
|
+
orderExists;
|
|
420
|
+
confirmationId;
|
|
421
|
+
constructor(message, options) {
|
|
422
|
+
super(message, {
|
|
423
|
+
...options.code !== void 0 ? { eresult: options.code } : {},
|
|
424
|
+
body: options.body,
|
|
425
|
+
cause: options.cause
|
|
426
|
+
});
|
|
427
|
+
this.name = "BuyOrderError";
|
|
428
|
+
this.stage = options.stage;
|
|
429
|
+
this.code = options.code;
|
|
430
|
+
this.orderExists = options.orderExists ?? false;
|
|
431
|
+
this.mayExist = options.mayExist || this.orderExists;
|
|
432
|
+
this.confirmationId = options.confirmationId;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
var WalletBalanceLimitError = class extends SteamError {
|
|
436
|
+
constructor(message = "Listing would exceed the maximum wallet balance", options) {
|
|
437
|
+
super(message, options);
|
|
438
|
+
this.name = "WalletBalanceLimitError";
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
var MarketBlockedError = class extends SteamError {
|
|
442
|
+
constructor(message = "This account is currently unable to use the Community Market", options) {
|
|
443
|
+
super(message, options);
|
|
444
|
+
this.name = "MarketBlockedError";
|
|
445
|
+
}
|
|
446
|
+
};
|
|
365
447
|
var LoginError = class extends SteamError {
|
|
366
448
|
extendedErrorMessage;
|
|
367
449
|
isTransient;
|
|
@@ -883,6 +965,12 @@ const IMPIT_BROWSER = {
|
|
|
883
965
|
}
|
|
884
966
|
};
|
|
885
967
|
const BROWSER_ACCEPT$1 = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
|
|
968
|
+
function requestControl(control) {
|
|
969
|
+
const out = {};
|
|
970
|
+
if (control?.signal) out.signal = control.signal;
|
|
971
|
+
if (control?.timeoutMs !== void 0) out.timeoutMs = control.timeoutMs;
|
|
972
|
+
return out;
|
|
973
|
+
}
|
|
886
974
|
function clean(input) {
|
|
887
975
|
if (!input) return void 0;
|
|
888
976
|
const out = {};
|
|
@@ -926,7 +1014,8 @@ var HttpClient = class {
|
|
|
926
1014
|
const res = await this.perform(method, url, opts);
|
|
927
1015
|
const location = firstHeader(res.headers.location);
|
|
928
1016
|
if (res.statusCode >= 300 && res.statusCode < 400 && location?.includes("/market/eligibilitycheck")) {
|
|
929
|
-
await this.perform("GET", location, opts
|
|
1017
|
+
await this.perform("GET", location, requestControl(opts));
|
|
1018
|
+
if (opts.noEligibilityReplay) return res;
|
|
930
1019
|
return this.perform(method, url, opts);
|
|
931
1020
|
}
|
|
932
1021
|
return res;
|
|
@@ -1177,7 +1266,7 @@ function httpError(res, rateLimitWindowMs) {
|
|
|
1177
1266
|
if (res.statusCode >= 300 && res.statusCode <= 399) {
|
|
1178
1267
|
const loc = locationHeader(res);
|
|
1179
1268
|
if (loc.includes("/login")) return new SteamSessionExpiredError();
|
|
1180
|
-
if (loc.includes("eligibilitycheck")) return new
|
|
1269
|
+
if (loc.includes("eligibilitycheck")) return new MarketIneligibleError("Steam redirected to the market eligibility check — this account is limited / not trade-eligible");
|
|
1181
1270
|
}
|
|
1182
1271
|
return new HttpStatusError(res.statusCode, void 0, res.body);
|
|
1183
1272
|
}
|
|
@@ -1292,7 +1381,7 @@ function queryParams(url) {
|
|
|
1292
1381
|
for (const [k, v] of new URL(url).searchParams) out[k] = v;
|
|
1293
1382
|
return out;
|
|
1294
1383
|
}
|
|
1295
|
-
function decodeEntities(s) {
|
|
1384
|
+
function decodeEntities$1(s) {
|
|
1296
1385
|
return s.replace(/"/g, "\"").replace(/�?39;/g, "'").replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
1297
1386
|
}
|
|
1298
1387
|
function parseOpenidForm(html) {
|
|
@@ -1303,14 +1392,14 @@ function parseOpenidForm(html) {
|
|
|
1303
1392
|
for (const tag of (form[2] ?? "").match(/<input\b[^>]*>/gi) ?? []) {
|
|
1304
1393
|
const name = tag.match(/\bname=["']([^"']*)["']/i)?.[1];
|
|
1305
1394
|
if (!name) continue;
|
|
1306
|
-
const value = decodeEntities(tag.match(/\bvalue=["']([^"']*)["']/i)?.[1] ?? "");
|
|
1395
|
+
const value = decodeEntities$1(tag.match(/\bvalue=["']([^"']*)["']/i)?.[1] ?? "");
|
|
1307
1396
|
fields.push({
|
|
1308
1397
|
name,
|
|
1309
1398
|
value
|
|
1310
1399
|
});
|
|
1311
1400
|
}
|
|
1312
1401
|
return {
|
|
1313
|
-
action: new URL(decodeEntities(action), STEAM_OPENID_URL).toString(),
|
|
1402
|
+
action: new URL(decodeEntities$1(action), STEAM_OPENID_URL).toString(),
|
|
1314
1403
|
fields
|
|
1315
1404
|
};
|
|
1316
1405
|
}
|
|
@@ -1472,6 +1561,16 @@ var CommunityNamespace = class {
|
|
|
1472
1561
|
input: { steamid: id }
|
|
1473
1562
|
})).response?.player_level ?? 0;
|
|
1474
1563
|
}
|
|
1564
|
+
async getTwoFactorStatus() {
|
|
1565
|
+
const body = await this.api.call({
|
|
1566
|
+
httpMethod: "POST",
|
|
1567
|
+
iface: "ITwoFactorService",
|
|
1568
|
+
method: "QueryStatus",
|
|
1569
|
+
input: { steamid: this.session.steamID.getSteamID64() }
|
|
1570
|
+
});
|
|
1571
|
+
if (!body.response) throw new SteamError("Malformed two-factor status response", { body });
|
|
1572
|
+
return body.response;
|
|
1573
|
+
}
|
|
1475
1574
|
async ensureApiKey(domain = "assetpay.gg") {
|
|
1476
1575
|
await this.session.getAccessToken();
|
|
1477
1576
|
const res = await this.http.get(`${URLS.community}/dev/apikey?l=english`, { responseType: "text" });
|
|
@@ -1571,8 +1670,11 @@ function xmlValue(xml, tag) {
|
|
|
1571
1670
|
}
|
|
1572
1671
|
//#endregion
|
|
1573
1672
|
//#region src/http/queryTime.ts
|
|
1574
|
-
async function queryServerTimeOffset(http) {
|
|
1575
|
-
const res = await http.post(`${URLS.api}/ITwoFactorService/QueryTime/v1/`, {
|
|
1673
|
+
async function queryServerTimeOffset(http, control) {
|
|
1674
|
+
const res = await http.post(`${URLS.api}/ITwoFactorService/QueryTime/v1/`, {
|
|
1675
|
+
...requestControl(control),
|
|
1676
|
+
responseType: "json"
|
|
1677
|
+
});
|
|
1576
1678
|
if (res.statusCode !== 200) throw httpError(res);
|
|
1577
1679
|
const serverTime = Number(res.body?.response?.server_time);
|
|
1578
1680
|
if (!serverTime) throw new SteamError("Failed to query Steam server time");
|
|
@@ -1602,9 +1704,9 @@ var ConfirmationManager = class {
|
|
|
1602
1704
|
const android = getDeviceID(this.steamID.getSteamID64());
|
|
1603
1705
|
return this.profile.mobileClient === "ios" ? android.replace(/^android:/, "").toUpperCase() : android;
|
|
1604
1706
|
}
|
|
1605
|
-
async getConfirmations(time, key) {
|
|
1707
|
+
async getConfirmations(time, key, control) {
|
|
1606
1708
|
const { tag, k } = splitKey(key, "conf");
|
|
1607
|
-
const body = await this.getlist(k, time, tag);
|
|
1709
|
+
const body = await this.getlist(k, time, tag, control);
|
|
1608
1710
|
if (!body?.success) {
|
|
1609
1711
|
if (body?.needauth) throw new SteamSessionExpiredError();
|
|
1610
1712
|
throw new ConfirmationError(String(body?.message ?? body?.detail ?? "Failed to get confirmation list"));
|
|
@@ -1622,41 +1724,58 @@ var ConfirmationManager = class {
|
|
|
1622
1724
|
icon: String(c.icon ?? "")
|
|
1623
1725
|
}));
|
|
1624
1726
|
}
|
|
1625
|
-
async respondToConfirmation(confID, confKey, time, key, accept) {
|
|
1727
|
+
async respondToConfirmation(confID, confKey, time, key, accept, control) {
|
|
1728
|
+
const ids = Array.isArray(confID) ? confID : [confID];
|
|
1729
|
+
const keys = Array.isArray(confKey) ? confKey : [confKey];
|
|
1730
|
+
if (ids.length !== keys.length) throw new ConfirmationError("confID and confKey must have the same length");
|
|
1626
1731
|
const { tag, k } = splitKey(key, accept ? "allow" : "cancel");
|
|
1627
1732
|
const op = accept ? "allow" : "cancel";
|
|
1628
1733
|
const res = await this.http.post(`${URLS.community}/mobileconf/multiajaxop`, {
|
|
1734
|
+
...requestControl(control),
|
|
1629
1735
|
responseType: "json",
|
|
1630
1736
|
searchParams: {
|
|
1631
1737
|
...this.confParams(k, time, tag),
|
|
1632
1738
|
op
|
|
1633
1739
|
},
|
|
1634
|
-
multipart: [{
|
|
1740
|
+
multipart: ids.flatMap((id, i) => [{
|
|
1635
1741
|
name: "cid[]",
|
|
1636
|
-
value:
|
|
1742
|
+
value: id
|
|
1637
1743
|
}, {
|
|
1638
1744
|
name: "ck[]",
|
|
1639
|
-
value:
|
|
1640
|
-
}]
|
|
1745
|
+
value: keys[i]
|
|
1746
|
+
}])
|
|
1641
1747
|
});
|
|
1642
1748
|
if (res.statusCode !== 200) throw httpError(res);
|
|
1643
1749
|
if (res.body?.success) return;
|
|
1750
|
+
if (res.body === void 0 || res.body === null) throw new ConfirmationOutcomeUnknownError();
|
|
1644
1751
|
throw new ConfirmationError(String(res.body?.message ?? "Could not act on confirmation"));
|
|
1645
1752
|
}
|
|
1646
|
-
async getPending() {
|
|
1753
|
+
async getPending(control) {
|
|
1647
1754
|
const secret = this.requireSecret();
|
|
1648
|
-
const listTime = time(await this.getTimeOffset());
|
|
1755
|
+
const listTime = time(await this.getTimeOffset(control));
|
|
1649
1756
|
const listKey = getConfirmationKey(secret, listTime, "list");
|
|
1650
1757
|
return this.getConfirmations(listTime, {
|
|
1651
1758
|
tag: "list",
|
|
1652
1759
|
key: listKey
|
|
1653
|
-
});
|
|
1760
|
+
}, control);
|
|
1654
1761
|
}
|
|
1655
1762
|
acceptConfirmation(confID, nonce) {
|
|
1656
|
-
return this.
|
|
1763
|
+
return this.actOnConfirmations([{
|
|
1764
|
+
id: confID,
|
|
1765
|
+
key: nonce
|
|
1766
|
+
}], true);
|
|
1657
1767
|
}
|
|
1658
1768
|
rejectConfirmation(confID, nonce) {
|
|
1659
|
-
return this.
|
|
1769
|
+
return this.actOnConfirmations([{
|
|
1770
|
+
id: confID,
|
|
1771
|
+
key: nonce
|
|
1772
|
+
}], false);
|
|
1773
|
+
}
|
|
1774
|
+
acceptConfirmations(confs, control) {
|
|
1775
|
+
return this.actOnConfirmations(confs, true, control);
|
|
1776
|
+
}
|
|
1777
|
+
rejectConfirmations(confs, control) {
|
|
1778
|
+
return this.actOnConfirmations(confs, false, control);
|
|
1660
1779
|
}
|
|
1661
1780
|
async acceptAll() {
|
|
1662
1781
|
const pending = await this.getPending();
|
|
@@ -1668,16 +1787,17 @@ var ConfirmationManager = class {
|
|
|
1668
1787
|
if (!conf) throw new ConfirmationError(`Could not find confirmation for object ${objectID}`);
|
|
1669
1788
|
await this.acceptConfirmation(conf.id, conf.key);
|
|
1670
1789
|
}
|
|
1671
|
-
async
|
|
1790
|
+
async actOnConfirmations(confs, accept, control) {
|
|
1791
|
+
if (confs.length === 0) return;
|
|
1672
1792
|
const secret = this.requireSecret();
|
|
1673
|
-
const offset = await this.getTimeOffset();
|
|
1793
|
+
const offset = await this.getTimeOffset(control);
|
|
1674
1794
|
const time = this.nextConfTime(offset);
|
|
1675
1795
|
const tag = accept ? "accept" : "cancel";
|
|
1676
1796
|
const key = getConfirmationKey(secret, time, tag);
|
|
1677
|
-
await this.respondToConfirmation(
|
|
1797
|
+
await this.respondToConfirmation(confs.map((c) => c.id), confs.map((c) => c.key), time, {
|
|
1678
1798
|
tag,
|
|
1679
1799
|
key
|
|
1680
|
-
}, accept);
|
|
1800
|
+
}, accept, control);
|
|
1681
1801
|
}
|
|
1682
1802
|
requireSecret() {
|
|
1683
1803
|
if (!this.identitySecret) throw new ConfirmationError("identitySecret is required to respond to confirmations");
|
|
@@ -1691,9 +1811,9 @@ var ConfirmationManager = class {
|
|
|
1691
1811
|
if (this.usedConfTimes.length > 60) this.usedConfTimes.splice(0, this.usedConfTimes.length - 60);
|
|
1692
1812
|
return time$1;
|
|
1693
1813
|
}
|
|
1694
|
-
async getTimeOffset() {
|
|
1814
|
+
async getTimeOffset(control) {
|
|
1695
1815
|
if (this.timeOffset !== void 0 && Date.now() - this.timeOffsetAt < TIME_OFFSET_TTL_MS) return this.timeOffset;
|
|
1696
|
-
const offset = await queryServerTimeOffset(this.http);
|
|
1816
|
+
const offset = await queryServerTimeOffset(this.http, control);
|
|
1697
1817
|
this.timeOffset = offset;
|
|
1698
1818
|
this.timeOffsetAt = Date.now();
|
|
1699
1819
|
return offset;
|
|
@@ -1708,8 +1828,9 @@ var ConfirmationManager = class {
|
|
|
1708
1828
|
tag
|
|
1709
1829
|
};
|
|
1710
1830
|
}
|
|
1711
|
-
async getlist(key, time, tag) {
|
|
1831
|
+
async getlist(key, time, tag, control) {
|
|
1712
1832
|
const res = await this.http.get(`${URLS.community}/mobileconf/getlist`, {
|
|
1833
|
+
...requestControl(control),
|
|
1713
1834
|
responseType: "json",
|
|
1714
1835
|
searchParams: this.confParams(key, time, tag)
|
|
1715
1836
|
});
|
|
@@ -1779,6 +1900,7 @@ var WebApiClient = class {
|
|
|
1779
1900
|
console.log(`[http] ${httpMethod} ${url}?${qs.toString()}`);
|
|
1780
1901
|
}
|
|
1781
1902
|
const res = await this.http.request(httpMethod, url, {
|
|
1903
|
+
...requestControl(params),
|
|
1782
1904
|
responseType: "json",
|
|
1783
1905
|
...httpMethod === "GET" ? { searchParams: payload } : { form: payload }
|
|
1784
1906
|
});
|
|
@@ -1877,6 +1999,900 @@ var SteamWebApi = class {
|
|
|
1877
1999
|
return response;
|
|
1878
2000
|
}
|
|
1879
2001
|
};
|
|
2002
|
+
//#endregion
|
|
2003
|
+
//#region src/market/fees.ts
|
|
2004
|
+
const DEFAULT_MARKET_FEE_INFO = Object.freeze({
|
|
2005
|
+
walletFee: true,
|
|
2006
|
+
walletFeePercent: .05,
|
|
2007
|
+
walletFeeMinimum: 1,
|
|
2008
|
+
walletFeeBase: 0,
|
|
2009
|
+
publisherFeePercent: .1
|
|
2010
|
+
});
|
|
2011
|
+
function assertCents(name, value) {
|
|
2012
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative integer number of cents, got ${value}`);
|
|
2013
|
+
}
|
|
2014
|
+
const MAX_FEE_FRACTION = .5;
|
|
2015
|
+
function assertFeeInfo(info) {
|
|
2016
|
+
const fraction = (v) => Number.isFinite(v) && v >= 0 && v < MAX_FEE_FRACTION;
|
|
2017
|
+
const cents = (v) => Number.isSafeInteger(v) && v >= 0;
|
|
2018
|
+
if (typeof info?.walletFee !== "boolean" || !fraction(info.walletFeePercent) || !fraction(info.publisherFeePercent) || !cents(info.walletFeeMinimum) || !cents(info.walletFeeBase)) throw new RangeError(`invalid market fee info: ${JSON.stringify(info)}`);
|
|
2019
|
+
}
|
|
2020
|
+
function feeInfoFromWalletInfo(raw, publisherFeePercent) {
|
|
2021
|
+
const num = (v) => typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
2022
|
+
const info = {
|
|
2023
|
+
walletFee: num(raw.wallet_fee) === 1,
|
|
2024
|
+
walletFeePercent: num(raw.wallet_fee_percent),
|
|
2025
|
+
walletFeeMinimum: num(raw.wallet_fee_minimum),
|
|
2026
|
+
walletFeeBase: num(raw.wallet_fee_base),
|
|
2027
|
+
publisherFeePercent: publisherFeePercent ?? num(raw.wallet_publisher_fee_percent_default)
|
|
2028
|
+
};
|
|
2029
|
+
if (num(raw.wallet_fee) !== 0 && num(raw.wallet_fee) !== 1) return null;
|
|
2030
|
+
try {
|
|
2031
|
+
assertFeeInfo(info);
|
|
2032
|
+
} catch {
|
|
2033
|
+
return null;
|
|
2034
|
+
}
|
|
2035
|
+
return info;
|
|
2036
|
+
}
|
|
2037
|
+
function buyerPaysForSellerReceives(receivedAmount, info) {
|
|
2038
|
+
assertCents("receivedAmount", receivedAmount);
|
|
2039
|
+
assertFeeInfo(info);
|
|
2040
|
+
return amountToSend(receivedAmount, info);
|
|
2041
|
+
}
|
|
2042
|
+
function feesForBuyerPays(amount, info) {
|
|
2043
|
+
assertCents("amount", amount);
|
|
2044
|
+
assertFeeInfo(info);
|
|
2045
|
+
if (!info.walletFee) return {
|
|
2046
|
+
amount,
|
|
2047
|
+
steamFee: 0,
|
|
2048
|
+
publisherFee: 0,
|
|
2049
|
+
fees: 0
|
|
2050
|
+
};
|
|
2051
|
+
let iterations = 0;
|
|
2052
|
+
let estimate = Math.trunc((amount - Math.trunc(info.walletFeeBase)) / (info.walletFeePercent + info.publisherFeePercent + 1));
|
|
2053
|
+
let everUndershot = false;
|
|
2054
|
+
let fees = amountToSend(estimate, info);
|
|
2055
|
+
while (fees.amount !== amount && iterations < 10) {
|
|
2056
|
+
if (fees.amount > amount) {
|
|
2057
|
+
if (everUndershot) {
|
|
2058
|
+
fees = amountToSend(estimate - 1, info);
|
|
2059
|
+
fees.steamFee += amount - fees.amount;
|
|
2060
|
+
fees.fees += amount - fees.amount;
|
|
2061
|
+
fees.amount = amount;
|
|
2062
|
+
break;
|
|
2063
|
+
}
|
|
2064
|
+
estimate--;
|
|
2065
|
+
} else {
|
|
2066
|
+
everUndershot = true;
|
|
2067
|
+
estimate++;
|
|
2068
|
+
}
|
|
2069
|
+
fees = amountToSend(estimate, info);
|
|
2070
|
+
iterations++;
|
|
2071
|
+
}
|
|
2072
|
+
return fees;
|
|
2073
|
+
}
|
|
2074
|
+
function sellerReceivesForBuyerPays(amount, info) {
|
|
2075
|
+
return Math.max(0, amount - feesForBuyerPays(amount, info).fees);
|
|
2076
|
+
}
|
|
2077
|
+
function landingBuyerPays(targetBuyerPays, info) {
|
|
2078
|
+
const receives = sellerReceivesForBuyerPays(targetBuyerPays, info);
|
|
2079
|
+
return receives === 0 ? 0 : buyerPaysForSellerReceives(receives, info).amount;
|
|
2080
|
+
}
|
|
2081
|
+
function amountToSend(receivedAmount, info) {
|
|
2082
|
+
if (!info.walletFee) return {
|
|
2083
|
+
amount: receivedAmount,
|
|
2084
|
+
steamFee: 0,
|
|
2085
|
+
publisherFee: 0,
|
|
2086
|
+
fees: 0
|
|
2087
|
+
};
|
|
2088
|
+
const steamFee = Math.trunc(Math.floor(Math.max(receivedAmount * info.walletFeePercent, info.walletFeeMinimum) + Math.trunc(info.walletFeeBase)));
|
|
2089
|
+
const publisherFee = Math.trunc(Math.floor(info.publisherFeePercent > 0 ? Math.max(receivedAmount * info.publisherFeePercent, 1) : 0));
|
|
2090
|
+
return {
|
|
2091
|
+
amount: Math.trunc(receivedAmount + steamFee + publisherFee),
|
|
2092
|
+
steamFee,
|
|
2093
|
+
publisherFee,
|
|
2094
|
+
fees: steamFee + publisherFee
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
//#endregion
|
|
2098
|
+
//#region src/market/prices.ts
|
|
2099
|
+
function getPriceValueAsInt(strAmount) {
|
|
2100
|
+
if (!strAmount) return 0;
|
|
2101
|
+
let amount = String(strAmount);
|
|
2102
|
+
amount = amount.replace(/,/g, ".");
|
|
2103
|
+
amount = amount.replace(".--", ".00");
|
|
2104
|
+
amount = amount.replace(/[^\d.]/g, "");
|
|
2105
|
+
if (amount.indexOf(".") !== -1) {
|
|
2106
|
+
const segments = amount.split(".");
|
|
2107
|
+
const last = segments[segments.length - 1] ?? "";
|
|
2108
|
+
if (!Number.isNaN(Number(last)) && last.length === 3 && segments[segments.length - 2] !== "0") amount = segments.join("");
|
|
2109
|
+
else amount = `${segments.slice(0, -1).join("")}.${last}`;
|
|
2110
|
+
}
|
|
2111
|
+
const cents = Number.parseFloat(amount) * 100;
|
|
2112
|
+
return Math.max(Math.floor(Number.isNaN(cents) ? 0 : cents + 1e-6), 0);
|
|
2113
|
+
}
|
|
2114
|
+
//#endregion
|
|
2115
|
+
//#region src/market/history.ts
|
|
2116
|
+
function parseMarketHistory(body) {
|
|
2117
|
+
const history = {
|
|
2118
|
+
events: [],
|
|
2119
|
+
sales: [],
|
|
2120
|
+
purchases: [],
|
|
2121
|
+
listingEvents: [],
|
|
2122
|
+
malformed: [],
|
|
2123
|
+
totalCount: body.total_count
|
|
2124
|
+
};
|
|
2125
|
+
if (!body.results_html) return history;
|
|
2126
|
+
const hovers = body.hovers ?? "";
|
|
2127
|
+
const assets = body.assets ?? {};
|
|
2128
|
+
const html = body.results_html.replace(/<!--[\s\S]*?-->/g, "");
|
|
2129
|
+
for (const row of findByClass(html, "market_listing_row", "market_recent_listing_row")) {
|
|
2130
|
+
const dates = findByClass(row.inner, "market_listing_listed_date");
|
|
2131
|
+
const itemName = textOf(row.inner, "market_listing_item_name");
|
|
2132
|
+
const gameName = textOf(row.inner, "market_listing_game_name");
|
|
2133
|
+
const actedOn = dates[0] ? elementText(dates[0]).trim() : "";
|
|
2134
|
+
const listedOn = dates[1] ? elementText(dates[1]).trim() : "";
|
|
2135
|
+
const displayPrice = textOf(row.inner, "market_listing_price").trim();
|
|
2136
|
+
const rowId = attrValue(row.attrs, "id") ?? "";
|
|
2137
|
+
const priceInCents = getPriceValueAsInt(displayPrice);
|
|
2138
|
+
const gainOrLoss = textOf(row.inner, "market_listing_gainorloss").trim();
|
|
2139
|
+
const type = gainOrLoss === "-" ? "sale" : gainOrLoss === "+" ? "purchase" : "listing_event";
|
|
2140
|
+
const idMatch = rowId.match(/^history_row_(\d+)_(\d+)$/);
|
|
2141
|
+
if (!idMatch) continue;
|
|
2142
|
+
if (priceInCents === 0 && (displayPrice || type !== "listing_event")) {
|
|
2143
|
+
history.malformed.push({
|
|
2144
|
+
historyId: rowId,
|
|
2145
|
+
displayPrice,
|
|
2146
|
+
gainOrLoss,
|
|
2147
|
+
reason: displayPrice ? "zero_price" : "missing_price"
|
|
2148
|
+
});
|
|
2149
|
+
continue;
|
|
2150
|
+
}
|
|
2151
|
+
const event = {
|
|
2152
|
+
historyId: rowId,
|
|
2153
|
+
listingid: idMatch[1],
|
|
2154
|
+
eventid: idMatch[2],
|
|
2155
|
+
itemName,
|
|
2156
|
+
gameName,
|
|
2157
|
+
listedOn,
|
|
2158
|
+
actedOn,
|
|
2159
|
+
displayPrice,
|
|
2160
|
+
priceInCents,
|
|
2161
|
+
type,
|
|
2162
|
+
marketName: null,
|
|
2163
|
+
appID: null,
|
|
2164
|
+
contextID: null,
|
|
2165
|
+
assetID: null,
|
|
2166
|
+
classID: null,
|
|
2167
|
+
instanceID: null,
|
|
2168
|
+
unOwnedContextID: null,
|
|
2169
|
+
unOwnedID: null
|
|
2170
|
+
};
|
|
2171
|
+
if (type !== "listing_event") attachAsset(event, rowId, hovers, assets);
|
|
2172
|
+
if (type === "sale") {
|
|
2173
|
+
const sale = {
|
|
2174
|
+
...event,
|
|
2175
|
+
type,
|
|
2176
|
+
receivedAmount: priceInCents
|
|
2177
|
+
};
|
|
2178
|
+
history.sales.push(sale);
|
|
2179
|
+
history.events.push(sale);
|
|
2180
|
+
} else if (type === "purchase") {
|
|
2181
|
+
const purchase = {
|
|
2182
|
+
...event,
|
|
2183
|
+
type,
|
|
2184
|
+
paidAmount: priceInCents
|
|
2185
|
+
};
|
|
2186
|
+
history.purchases.push(purchase);
|
|
2187
|
+
history.events.push(purchase);
|
|
2188
|
+
} else {
|
|
2189
|
+
const listingEvent = {
|
|
2190
|
+
...event,
|
|
2191
|
+
type
|
|
2192
|
+
};
|
|
2193
|
+
history.listingEvents.push(listingEvent);
|
|
2194
|
+
history.events.push(listingEvent);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
return history;
|
|
2198
|
+
}
|
|
2199
|
+
function attachAsset(event, rowId, hovers, assets) {
|
|
2200
|
+
try {
|
|
2201
|
+
const args = hovers.split(`${rowId}_name`)[1].split(")")[0].split(",");
|
|
2202
|
+
event.appID = Number.parseInt(args[1], 10);
|
|
2203
|
+
event.contextID = args[2].split("'")[1] ?? null;
|
|
2204
|
+
event.assetID = args[3].split("'")[1] ?? null;
|
|
2205
|
+
if (event.contextID === null || event.assetID === null) return;
|
|
2206
|
+
const asset = assets[String(event.appID)]?.[event.contextID]?.[event.assetID];
|
|
2207
|
+
if (asset) {
|
|
2208
|
+
event.classID = asset.classid ?? null;
|
|
2209
|
+
event.instanceID = asset.instanceid ?? null;
|
|
2210
|
+
event.unOwnedContextID = asset.unowned_contextid ?? null;
|
|
2211
|
+
event.unOwnedID = asset.unowned_id ?? null;
|
|
2212
|
+
event.marketName = asset.market_hash_name ?? null;
|
|
2213
|
+
}
|
|
2214
|
+
} catch {}
|
|
2215
|
+
}
|
|
2216
|
+
const TAG = /<([a-zA-Z][\w:-]*)((?:"[^"]*"|'[^']*'|[^'">])*)>/g;
|
|
2217
|
+
const VOID_TAGS = /* @__PURE__ */ new Set([
|
|
2218
|
+
"area",
|
|
2219
|
+
"base",
|
|
2220
|
+
"br",
|
|
2221
|
+
"col",
|
|
2222
|
+
"embed",
|
|
2223
|
+
"hr",
|
|
2224
|
+
"img",
|
|
2225
|
+
"input",
|
|
2226
|
+
"link",
|
|
2227
|
+
"meta",
|
|
2228
|
+
"source",
|
|
2229
|
+
"track",
|
|
2230
|
+
"wbr"
|
|
2231
|
+
]);
|
|
2232
|
+
function findByClass(html, ...classes) {
|
|
2233
|
+
const found = [];
|
|
2234
|
+
for (const m of html.matchAll(TAG)) {
|
|
2235
|
+
const attrs = m[2] ?? "";
|
|
2236
|
+
const tokens = (attrValue(attrs, "class") ?? "").split(/\s+/);
|
|
2237
|
+
if (!classes.every((c) => tokens.includes(c))) continue;
|
|
2238
|
+
const tag = m[1].toLowerCase();
|
|
2239
|
+
const start = m.index + m[0].length;
|
|
2240
|
+
const empty = VOID_TAGS.has(tag) || attrs.trimEnd().endsWith("/");
|
|
2241
|
+
found.push({
|
|
2242
|
+
attrs,
|
|
2243
|
+
inner: empty ? "" : html.slice(start, closeIndex(html, tag, start))
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
return found;
|
|
2247
|
+
}
|
|
2248
|
+
function closeIndex(html, tag, from) {
|
|
2249
|
+
const re = new RegExp(`<(/?)${tag}\\b((?:"[^"]*"|'[^']*'|[^'">])*)>`, "gi");
|
|
2250
|
+
re.lastIndex = from;
|
|
2251
|
+
let depth = 1;
|
|
2252
|
+
for (let m = re.exec(html); m; m = re.exec(html)) if (m[1]) {
|
|
2253
|
+
if (--depth === 0) return m.index;
|
|
2254
|
+
} else if (!(m[2] ?? "").trimEnd().endsWith("/")) depth++;
|
|
2255
|
+
return html.length;
|
|
2256
|
+
}
|
|
2257
|
+
const ATTR = /([^\s"'>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
2258
|
+
function attrValue(attrs, name) {
|
|
2259
|
+
for (const m of attrs.matchAll(ATTR)) if (m[1].toLowerCase() === name) return decodeEntities(m[2] ?? m[3] ?? m[4] ?? "");
|
|
2260
|
+
}
|
|
2261
|
+
function textOf(html, className) {
|
|
2262
|
+
return findByClass(html, className).map(elementText).join("");
|
|
2263
|
+
}
|
|
2264
|
+
function elementText(el) {
|
|
2265
|
+
return decodeEntities(el.inner.replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, ""));
|
|
2266
|
+
}
|
|
2267
|
+
const NAMED_ENTITIES = {
|
|
2268
|
+
amp: "&",
|
|
2269
|
+
lt: "<",
|
|
2270
|
+
gt: ">",
|
|
2271
|
+
quot: "\"",
|
|
2272
|
+
apos: "'",
|
|
2273
|
+
nbsp: "\xA0"
|
|
2274
|
+
};
|
|
2275
|
+
function decodeEntities(s) {
|
|
2276
|
+
return s.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (whole, ref) => {
|
|
2277
|
+
if (ref[0] === "#") {
|
|
2278
|
+
const code = ref[1] === "x" || ref[1] === "X" ? Number.parseInt(ref.slice(2), 16) : Number.parseInt(ref.slice(1), 10);
|
|
2279
|
+
return code <= 1114111 ? String.fromCodePoint(code) : whole;
|
|
2280
|
+
}
|
|
2281
|
+
return NAMED_ENTITIES[ref.toLowerCase()] ?? whole;
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
function priceTextCurrency(text) {
|
|
2285
|
+
const t = text.trim();
|
|
2286
|
+
if (t.includes("€")) return t.includes("$") ? void 0 : 3;
|
|
2287
|
+
const dollar = t.indexOf("$");
|
|
2288
|
+
if (dollar === -1 || t.indexOf("$", dollar + 1) !== -1) return void 0;
|
|
2289
|
+
if (/[A-Za-z]$/.test(t.slice(0, dollar)) || /^[A-Za-z]/.test(t.slice(dollar + 1))) return;
|
|
2290
|
+
return 1;
|
|
2291
|
+
}
|
|
2292
|
+
//#endregion
|
|
2293
|
+
//#region src/market/MarketNamespace.ts
|
|
2294
|
+
const LISTINGS_PAGE_SIZE = 100;
|
|
2295
|
+
const MARKET_REFERER = `${URLS.community}/market/`;
|
|
2296
|
+
const LISTING_CURRENCY_BASE = 2e3;
|
|
2297
|
+
const BUY_ORDER_CONFIRMATION_LOOKUPS = [
|
|
2298
|
+
0,
|
|
2299
|
+
1e3,
|
|
2300
|
+
2500
|
|
2301
|
+
];
|
|
2302
|
+
const BUY_ORDER_NOT_PLACED_CODES = /* @__PURE__ */ new Set([
|
|
2303
|
+
15,
|
|
2304
|
+
25,
|
|
2305
|
+
107
|
|
2306
|
+
]);
|
|
2307
|
+
const UNKNOWN_OUTCOME_RE = /hearing back|double check/i;
|
|
2308
|
+
const DUPLICATE_ORDER_CODE = 29;
|
|
2309
|
+
var MarketNamespace = class {
|
|
2310
|
+
http;
|
|
2311
|
+
session;
|
|
2312
|
+
confirmations;
|
|
2313
|
+
api;
|
|
2314
|
+
currency;
|
|
2315
|
+
currencyVerified = false;
|
|
2316
|
+
constructor(http, session, confirmations, api) {
|
|
2317
|
+
this.http = http;
|
|
2318
|
+
this.session = session;
|
|
2319
|
+
this.confirmations = confirmations;
|
|
2320
|
+
this.api = api;
|
|
2321
|
+
}
|
|
2322
|
+
get walletCurrency() {
|
|
2323
|
+
return this.currency;
|
|
2324
|
+
}
|
|
2325
|
+
get walletCurrencyVerified() {
|
|
2326
|
+
return this.currencyVerified;
|
|
2327
|
+
}
|
|
2328
|
+
seedWalletCurrency(currency) {
|
|
2329
|
+
if (!Number.isInteger(currency) || currency <= 0) throw new RangeError(`walletCurrency must be a positive integer, got ${currency}`);
|
|
2330
|
+
if (this.currency !== void 0 && this.currency !== currency) throw new CurrencyMismatchError(this.currency, currency, "seedWalletCurrency");
|
|
2331
|
+
this.currency = currency;
|
|
2332
|
+
}
|
|
2333
|
+
async getWalletDetails(options = {}) {
|
|
2334
|
+
const body = await this.api.call({
|
|
2335
|
+
httpMethod: "POST",
|
|
2336
|
+
iface: "IUserAccountService",
|
|
2337
|
+
method: "GetClientWalletDetails",
|
|
2338
|
+
input: {
|
|
2339
|
+
include_balance_in_usd: 1,
|
|
2340
|
+
include_formatted_balance: 1
|
|
2341
|
+
},
|
|
2342
|
+
...requestControl(options)
|
|
2343
|
+
});
|
|
2344
|
+
if (!body.response) throw new SteamError("Malformed wallet details response", { body });
|
|
2345
|
+
const currency = body.response.currency_code;
|
|
2346
|
+
if (body.response.has_wallet === true && typeof currency === "number" && currency > 0) this.learnCurrency(currency, "wallet details");
|
|
2347
|
+
return body.response;
|
|
2348
|
+
}
|
|
2349
|
+
async getMarketWalletInfo(options = {}) {
|
|
2350
|
+
await this.session.getAccessToken();
|
|
2351
|
+
const res = await this.http.get(`${URLS.community}/market/`, requestControl(options));
|
|
2352
|
+
if (res.statusCode !== 200) throw httpError(res);
|
|
2353
|
+
if (res.body.match(/g_steamID\s*=\s*"(\d+)"/)?.[1] !== this.session.steamID.getSteamID64()) throw new SteamSessionExpiredError("market page was not rendered for this account");
|
|
2354
|
+
const match = res.body.match(/g_rgWalletInfo\s*=\s*(\{[^;]*?\});/);
|
|
2355
|
+
if (!match?.[1]) throw new SteamError("g_rgWalletInfo not found on the market page");
|
|
2356
|
+
let info;
|
|
2357
|
+
try {
|
|
2358
|
+
info = JSON.parse(match[1]);
|
|
2359
|
+
} catch {
|
|
2360
|
+
throw new SteamError("g_rgWalletInfo on the market page is not valid JSON");
|
|
2361
|
+
}
|
|
2362
|
+
const currency = Number(info.wallet_currency);
|
|
2363
|
+
if (this.currency !== void 0 && currency !== this.currency) throw new CurrencyMismatchError(this.currency, currency, "g_rgWalletInfo");
|
|
2364
|
+
return info;
|
|
2365
|
+
}
|
|
2366
|
+
async getMyListings(options = {}) {
|
|
2367
|
+
const currency = this.readCurrency(options.currency, "mylistings");
|
|
2368
|
+
await this.session.getAccessToken();
|
|
2369
|
+
const result = {
|
|
2370
|
+
listings: [],
|
|
2371
|
+
listings_to_confirm: [],
|
|
2372
|
+
listings_on_hold: [],
|
|
2373
|
+
buy_orders: [],
|
|
2374
|
+
num_active_listings: 0,
|
|
2375
|
+
complete: false
|
|
2376
|
+
};
|
|
2377
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2378
|
+
let firstTotal;
|
|
2379
|
+
let totalMoved = false;
|
|
2380
|
+
let buyOrdersMissing = false;
|
|
2381
|
+
let start = 0;
|
|
2382
|
+
for (;;) {
|
|
2383
|
+
const res = await this.http.get(`${URLS.community}/market/mylistings/`, {
|
|
2384
|
+
...requestControl(options),
|
|
2385
|
+
responseType: "json",
|
|
2386
|
+
searchParams: {
|
|
2387
|
+
norender: 1,
|
|
2388
|
+
currency: currency ?? void 0,
|
|
2389
|
+
start,
|
|
2390
|
+
count: LISTINGS_PAGE_SIZE
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
if (res.statusCode !== 200) throw httpError(res);
|
|
2394
|
+
const body = res.body;
|
|
2395
|
+
if (!body?.success) throw new SteamError("Steam returned success=false for /market/mylistings", { body });
|
|
2396
|
+
if (body.num_active_listings !== void 0) {
|
|
2397
|
+
result.num_active_listings = body.num_active_listings;
|
|
2398
|
+
if (firstTotal === void 0) firstTotal = body.num_active_listings;
|
|
2399
|
+
else if (body.num_active_listings !== firstTotal) totalMoved = true;
|
|
2400
|
+
}
|
|
2401
|
+
if (start === 0) {
|
|
2402
|
+
if (Array.isArray(body.buy_orders)) result.buy_orders = body.buy_orders;
|
|
2403
|
+
else buyOrdersMissing = true;
|
|
2404
|
+
if (Array.isArray(body.listings_to_confirm)) result.listings_to_confirm = body.listings_to_confirm;
|
|
2405
|
+
if (Array.isArray(body.listings_on_hold)) result.listings_on_hold = body.listings_on_hold;
|
|
2406
|
+
}
|
|
2407
|
+
const page = body.listings ?? body.results?.listings ?? [];
|
|
2408
|
+
for (const listing of page) {
|
|
2409
|
+
if (seen.has(listing.listingid)) continue;
|
|
2410
|
+
seen.add(listing.listingid);
|
|
2411
|
+
result.listings.push(listing);
|
|
2412
|
+
}
|
|
2413
|
+
start += page.length;
|
|
2414
|
+
if (start >= result.num_active_listings || page.length === 0) break;
|
|
2415
|
+
}
|
|
2416
|
+
result.complete = !totalMoved && !buyOrdersMissing && result.listings.length === result.num_active_listings;
|
|
2417
|
+
if (currency !== null) {
|
|
2418
|
+
for (const order of result.buy_orders) {
|
|
2419
|
+
const actual = Number(order.wallet_currency);
|
|
2420
|
+
if (actual !== currency) throw new CurrencyMismatchError(currency, actual, "buy orders");
|
|
2421
|
+
}
|
|
2422
|
+
const all = [
|
|
2423
|
+
...result.listings,
|
|
2424
|
+
...result.listings_to_confirm,
|
|
2425
|
+
...result.listings_on_hold
|
|
2426
|
+
];
|
|
2427
|
+
for (const listing of all) assertListingCurrency(listing, currency);
|
|
2428
|
+
if (result.buy_orders.length > 0 || all.length > 0) this.confirmCurrency(currency);
|
|
2429
|
+
}
|
|
2430
|
+
return result;
|
|
2431
|
+
}
|
|
2432
|
+
async getMyHistory(options = {}) {
|
|
2433
|
+
const currency = this.readCurrency(options.currency, "myhistory");
|
|
2434
|
+
await this.session.getAccessToken();
|
|
2435
|
+
const res = await this.http.get(`${URLS.community}/market/myhistory`, {
|
|
2436
|
+
...requestControl(options),
|
|
2437
|
+
responseType: "json",
|
|
2438
|
+
searchParams: {
|
|
2439
|
+
count: options.count ?? 500,
|
|
2440
|
+
start: options.start
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
2443
|
+
if (res.statusCode !== 200) throw httpError(res);
|
|
2444
|
+
if (!res.body?.success) throw new SteamError("Steam returned success=false for /market/myhistory", { body: res.body });
|
|
2445
|
+
const history = parseMarketHistory(res.body);
|
|
2446
|
+
if (currency !== null && PINNED_PRICE_FORMATS.has(currency)) {
|
|
2447
|
+
const other = new Set(history.events.filter((e) => e.displayPrice && priceTextCurrency(e.displayPrice) !== currency).map((e) => e.historyId));
|
|
2448
|
+
if (other.size > 0) {
|
|
2449
|
+
for (const e of history.events) {
|
|
2450
|
+
if (!other.has(e.historyId)) continue;
|
|
2451
|
+
history.malformed.push({
|
|
2452
|
+
historyId: e.historyId,
|
|
2453
|
+
displayPrice: e.displayPrice,
|
|
2454
|
+
gainOrLoss: e.type === "sale" ? "-" : e.type === "purchase" ? "+" : "",
|
|
2455
|
+
reason: "other_currency"
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
const keep = (rows) => rows.filter((r) => !other.has(r.historyId));
|
|
2459
|
+
history.events = keep(history.events);
|
|
2460
|
+
history.sales = keep(history.sales);
|
|
2461
|
+
history.purchases = keep(history.purchases);
|
|
2462
|
+
history.listingEvents = keep(history.listingEvents);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
return history;
|
|
2466
|
+
}
|
|
2467
|
+
async getOrderbook(appid, marketHashName, options = {}) {
|
|
2468
|
+
const expected = this.readCurrency(options.expectedCurrency, "orderbook");
|
|
2469
|
+
await this.session.getAccessToken();
|
|
2470
|
+
const qp = encodeURIComponent(JSON.stringify([appid, marketHashName]));
|
|
2471
|
+
const res = await this.http.get(`${URLS.community}/market/orderbook?q=Load&qp=${qp}`, {
|
|
2472
|
+
...requestControl(options),
|
|
2473
|
+
responseType: "json"
|
|
2474
|
+
});
|
|
2475
|
+
if (res.statusCode === 403) throw new ThrottledError(void 0, { body: res.body });
|
|
2476
|
+
if (res.statusCode !== 200) throw httpError(res);
|
|
2477
|
+
const body = res.body;
|
|
2478
|
+
const payload = body && body.success === void 0 && body.data ? body.data : body;
|
|
2479
|
+
if (!payload?.success || !payload.data) throw new SteamError(`Bad orderbook response for ${marketHashName}`, { body });
|
|
2480
|
+
const data = payload.data;
|
|
2481
|
+
if (expected !== null) {
|
|
2482
|
+
if (data.eCurrency !== expected) throw new CurrencyMismatchError(expected, data.eCurrency, `orderbook of ${marketHashName}`);
|
|
2483
|
+
this.confirmCurrency(expected);
|
|
2484
|
+
}
|
|
2485
|
+
return data;
|
|
2486
|
+
}
|
|
2487
|
+
async getPriceHistory(appid, marketHashName, options = {}) {
|
|
2488
|
+
const expected = this.readCurrency(options.expectedCurrency, "pricehistory");
|
|
2489
|
+
await this.session.getAccessToken();
|
|
2490
|
+
const res = await this.http.get(`${URLS.community}/market/pricehistory?appid=${appid}&market_hash_name=${encodeURIComponent(marketHashName)}`, {
|
|
2491
|
+
...requestControl(options),
|
|
2492
|
+
responseType: "json"
|
|
2493
|
+
});
|
|
2494
|
+
if (res.statusCode !== 200) throw httpError(res);
|
|
2495
|
+
if (!res.body?.success) throw new SteamError(`Steam returned success=false for pricehistory of ${marketHashName}`, { body: res.body });
|
|
2496
|
+
if (expected !== null && PINNED_PRICE_FORMATS.has(expected)) {
|
|
2497
|
+
const actual = priceHistoryCurrency(res.body.price_prefix, res.body.price_suffix);
|
|
2498
|
+
if (actual !== expected) throw new CurrencyMismatchError(expected, actual, `pricehistory of ${marketHashName}`);
|
|
2499
|
+
}
|
|
2500
|
+
return Array.isArray(res.body.prices) ? res.body.prices : [];
|
|
2501
|
+
}
|
|
2502
|
+
async getMarketItemDetails(appid, marketHashName, options = {}) {
|
|
2503
|
+
await this.session.getAccessToken();
|
|
2504
|
+
const query = encodeURIComponent(`"${marketHashName}"`);
|
|
2505
|
+
const res = await this.http.get(`${URLS.community}/market/search/render/?query=${query}&start=0&count=10&search_descriptions=0&sort_column=quantity&sort_dir=desc&appid=${appid}&norender=1`, {
|
|
2506
|
+
...requestControl(options),
|
|
2507
|
+
responseType: "json"
|
|
2508
|
+
});
|
|
2509
|
+
if (res.statusCode !== 200) throw httpError(res);
|
|
2510
|
+
const body = res.body;
|
|
2511
|
+
if (!body || typeof body !== "object" || body.success === false) throw new SteamError("Malformed or unsuccessful market search response", { body });
|
|
2512
|
+
return (Array.isArray(body.results) ? body.results : []).find((r) => r.hash_name === marketHashName) ?? null;
|
|
2513
|
+
}
|
|
2514
|
+
async sellItem(options) {
|
|
2515
|
+
this.writeCurrency(options.currency, "sellitem");
|
|
2516
|
+
assertPositiveCents("price", options.price);
|
|
2517
|
+
const amount = options.amount ?? 1;
|
|
2518
|
+
assertPositiveCents("amount", amount);
|
|
2519
|
+
await this.session.getAccessToken();
|
|
2520
|
+
const sessionid = await this.http.getSessionId();
|
|
2521
|
+
const res = await this.http.post(`${URLS.community}/market/sellitem/`, {
|
|
2522
|
+
...requestControl(options),
|
|
2523
|
+
noEligibilityReplay: true,
|
|
2524
|
+
responseType: "json",
|
|
2525
|
+
form: {
|
|
2526
|
+
sessionid,
|
|
2527
|
+
appid: options.appid,
|
|
2528
|
+
contextid: options.contextid ?? "2",
|
|
2529
|
+
assetid: options.assetid,
|
|
2530
|
+
amount,
|
|
2531
|
+
price: options.price
|
|
2532
|
+
},
|
|
2533
|
+
headers: { Referer: MARKET_REFERER }
|
|
2534
|
+
});
|
|
2535
|
+
const body = res.body;
|
|
2536
|
+
if (res.statusCode === 200 && (body?.success === true || body?.success === 1)) return body;
|
|
2537
|
+
if (res.statusCode === 429 || res.statusCode === 401) throw httpError(res);
|
|
2538
|
+
if (body?.message) throw sellItemError(body.message, body);
|
|
2539
|
+
if (res.statusCode !== 200) throw httpError(res);
|
|
2540
|
+
throw new SteamError("Unknown error listing item", {
|
|
2541
|
+
...eresultOf(body),
|
|
2542
|
+
body
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
async cancelListing(listingId, options = {}) {
|
|
2546
|
+
await this.session.getAccessToken();
|
|
2547
|
+
const sessionid = await this.http.getSessionId();
|
|
2548
|
+
const res = await this.http.post(`${URLS.community}/market/removelisting/${listingId}`, {
|
|
2549
|
+
...requestControl(options),
|
|
2550
|
+
noEligibilityReplay: true,
|
|
2551
|
+
form: { sessionid },
|
|
2552
|
+
headers: { Referer: MARKET_REFERER }
|
|
2553
|
+
});
|
|
2554
|
+
if (res.statusCode === 200 || res.statusCode === 302 && redirectsToMarket(res)) return;
|
|
2555
|
+
throw httpError(res);
|
|
2556
|
+
}
|
|
2557
|
+
async createBuyOrder(options) {
|
|
2558
|
+
const currency = this.writeCurrency(options.currency, "createbuyorder");
|
|
2559
|
+
assertPositiveCents("quantity", options.quantity);
|
|
2560
|
+
assertPositiveCents("priceTotal", options.priceTotal);
|
|
2561
|
+
if (options.priceTotal % options.quantity !== 0) throw new RangeError("priceTotal must be the per-unit price times quantity");
|
|
2562
|
+
await this.session.getAccessToken();
|
|
2563
|
+
const sessionid = await this.http.getSessionId();
|
|
2564
|
+
const referer = `${URLS.community}/market/listings/${options.appid}/${encodeURIComponent(options.marketHashName)}`;
|
|
2565
|
+
const form = {
|
|
2566
|
+
sessionid,
|
|
2567
|
+
currency,
|
|
2568
|
+
appid: options.appid,
|
|
2569
|
+
market_hash_name: options.marketHashName,
|
|
2570
|
+
price_total: options.priceTotal,
|
|
2571
|
+
quantity: options.quantity,
|
|
2572
|
+
confirmation: 0
|
|
2573
|
+
};
|
|
2574
|
+
let first;
|
|
2575
|
+
try {
|
|
2576
|
+
first = await this.postBuyOrder(form, referer, options);
|
|
2577
|
+
} catch (err) {
|
|
2578
|
+
if (isNotPlacedError(err)) throw err;
|
|
2579
|
+
throw new BuyOrderError("createbuyorder request failed", {
|
|
2580
|
+
stage: "create",
|
|
2581
|
+
mayExist: true,
|
|
2582
|
+
cause: err
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
if (first.buyOrderId) return first.buyOrderId;
|
|
2586
|
+
const confirmationId = first.confirmationId;
|
|
2587
|
+
if (!confirmationId) throw buyOrderRefusal(first, "create");
|
|
2588
|
+
await this.acceptBuyOrderConfirmation(confirmationId, options);
|
|
2589
|
+
let final;
|
|
2590
|
+
try {
|
|
2591
|
+
final = await this.postBuyOrder({
|
|
2592
|
+
...form,
|
|
2593
|
+
confirmation: confirmationId
|
|
2594
|
+
}, referer, options);
|
|
2595
|
+
} catch (err) {
|
|
2596
|
+
throw new BuyOrderError("createbuyorder finalize request failed", {
|
|
2597
|
+
stage: "finalize",
|
|
2598
|
+
mayExist: true,
|
|
2599
|
+
confirmationId,
|
|
2600
|
+
cause: err
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
if (final.buyOrderId) return final.buyOrderId;
|
|
2604
|
+
throw buyOrderRefusal(final, "finalize", confirmationId);
|
|
2605
|
+
}
|
|
2606
|
+
async cancelBuyOrder(buyOrderId, options = {}) {
|
|
2607
|
+
await this.session.getAccessToken();
|
|
2608
|
+
const sessionid = await this.http.getSessionId();
|
|
2609
|
+
const res = await this.http.post(`${URLS.community}/market/cancelbuyorder/`, {
|
|
2610
|
+
...requestControl(options),
|
|
2611
|
+
noEligibilityReplay: true,
|
|
2612
|
+
responseType: "json",
|
|
2613
|
+
form: {
|
|
2614
|
+
sessionid,
|
|
2615
|
+
buy_orderid: buyOrderId
|
|
2616
|
+
},
|
|
2617
|
+
headers: { Referer: MARKET_REFERER }
|
|
2618
|
+
});
|
|
2619
|
+
if (res.statusCode !== 200) throw httpError(res);
|
|
2620
|
+
const body = res.body ?? {};
|
|
2621
|
+
if (body.success === 1 || body.success === true) return body;
|
|
2622
|
+
throw new SteamError(`cancelbuyorder refused for ${buyOrderId}`, {
|
|
2623
|
+
...eresultOf(body),
|
|
2624
|
+
body
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
async confirmListingIds(listingIds, options = {}) {
|
|
2628
|
+
const wanted = new Set(listingIds.map(String));
|
|
2629
|
+
if (wanted.size === 0) return {
|
|
2630
|
+
confirmed: [],
|
|
2631
|
+
missing: []
|
|
2632
|
+
};
|
|
2633
|
+
await this.session.getAccessToken();
|
|
2634
|
+
const confirmed = (await this.confirmations.getPending(options)).filter((c) => c.type === 3 && wanted.has(c.creator));
|
|
2635
|
+
await this.confirmations.acceptConfirmations(confirmed, options);
|
|
2636
|
+
const found = new Set(confirmed.map((c) => c.creator));
|
|
2637
|
+
return {
|
|
2638
|
+
confirmed,
|
|
2639
|
+
missing: [...wanted].filter((id) => !found.has(id))
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
async confirmListings(expected, options = {}) {
|
|
2643
|
+
if (Object.values(expected).reduce((sum, n) => sum + n, 0) === 0) return {
|
|
2644
|
+
confirmed: [],
|
|
2645
|
+
skipped: []
|
|
2646
|
+
};
|
|
2647
|
+
await this.session.getAccessToken();
|
|
2648
|
+
const listings = (await this.confirmations.getPending(options)).filter((c) => c.type === 3).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
2649
|
+
const remaining = { ...expected };
|
|
2650
|
+
const confirmed = [];
|
|
2651
|
+
const skipped = [];
|
|
2652
|
+
for (const conf of listings) {
|
|
2653
|
+
const name = (remaining[conf.sending] ?? 0) > 0 ? conf.sending : Object.keys(remaining).find((n) => (remaining[n] ?? 0) > 0 && conf.sending !== "" && n.includes(conf.sending));
|
|
2654
|
+
if (name === void 0) {
|
|
2655
|
+
skipped.push(conf);
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
confirmed.push(conf);
|
|
2659
|
+
remaining[name] = (remaining[name] ?? 0) - 1;
|
|
2660
|
+
}
|
|
2661
|
+
await this.confirmations.acceptConfirmations(confirmed, options);
|
|
2662
|
+
return {
|
|
2663
|
+
confirmed,
|
|
2664
|
+
skipped
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
async rejectListings(types = [3], options = {}) {
|
|
2668
|
+
await this.session.getAccessToken();
|
|
2669
|
+
const keep = new Set((options.keepCreators ?? []).map(String));
|
|
2670
|
+
const matched = (await this.confirmations.getPending(options)).filter((c) => types.includes(c.type) && !keep.has(c.creator));
|
|
2671
|
+
await this.confirmations.rejectConfirmations(matched, options);
|
|
2672
|
+
return matched;
|
|
2673
|
+
}
|
|
2674
|
+
async acceptBuyOrderConfirmation(confirmationId, options) {
|
|
2675
|
+
let conf;
|
|
2676
|
+
try {
|
|
2677
|
+
for (const delay of BUY_ORDER_CONFIRMATION_LOOKUPS) {
|
|
2678
|
+
if (delay > 0) await sleep(delay, options.signal);
|
|
2679
|
+
conf = (await this.confirmations.getPending(options)).find((c) => c.creator === confirmationId && c.type === 12);
|
|
2680
|
+
if (conf) break;
|
|
2681
|
+
}
|
|
2682
|
+
} catch (err) {
|
|
2683
|
+
throw new BuyOrderError("could not read the buy order's mobile confirmation", {
|
|
2684
|
+
stage: "confirm",
|
|
2685
|
+
mayExist: false,
|
|
2686
|
+
confirmationId,
|
|
2687
|
+
cause: err
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
if (!conf) throw new BuyOrderError(`no mobile confirmation found for buy order ${confirmationId}`, {
|
|
2691
|
+
stage: "confirm",
|
|
2692
|
+
mayExist: false,
|
|
2693
|
+
confirmationId
|
|
2694
|
+
});
|
|
2695
|
+
try {
|
|
2696
|
+
await this.confirmations.acceptConfirmations([conf], options);
|
|
2697
|
+
} catch (err) {
|
|
2698
|
+
throw new BuyOrderError("accepting the buy order's mobile confirmation failed", {
|
|
2699
|
+
stage: "confirm",
|
|
2700
|
+
mayExist: !(err instanceof ConfirmationError && !(err instanceof ConfirmationOutcomeUnknownError)),
|
|
2701
|
+
confirmationId,
|
|
2702
|
+
cause: err
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
async postBuyOrder(form, referer, control) {
|
|
2707
|
+
const res = await this.http.post(`${URLS.community}/market/createbuyorder/`, {
|
|
2708
|
+
...requestControl(control),
|
|
2709
|
+
noEligibilityReplay: true,
|
|
2710
|
+
form,
|
|
2711
|
+
headers: { Referer: referer }
|
|
2712
|
+
});
|
|
2713
|
+
if (res.statusCode === 429 || res.statusCode === 401) throw httpError(res);
|
|
2714
|
+
const raw = typeof res.body === "string" ? res.body : "";
|
|
2715
|
+
const body = safeJson(raw);
|
|
2716
|
+
const ok = body?.success === 1 || body?.success === true || body?.success === void 0 && body?.wallet_info?.success === 1;
|
|
2717
|
+
return {
|
|
2718
|
+
res,
|
|
2719
|
+
body,
|
|
2720
|
+
buyOrderId: res.statusCode === 200 && ok ? rawIdField(raw, "buy_orderid") ?? void 0 : void 0,
|
|
2721
|
+
confirmationId: rawIdField(raw, "confirmation_id") ?? void 0
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
readCurrency(explicit, context) {
|
|
2725
|
+
if (explicit === null) return null;
|
|
2726
|
+
const known = this.currency;
|
|
2727
|
+
if (explicit !== void 0) {
|
|
2728
|
+
if (known !== void 0 && explicit !== known) throw new CurrencyMismatchError(known, explicit, context);
|
|
2729
|
+
return explicit;
|
|
2730
|
+
}
|
|
2731
|
+
if (known === void 0) throw new WalletCurrencyUnknownError(context);
|
|
2732
|
+
return known;
|
|
2733
|
+
}
|
|
2734
|
+
writeCurrency(explicit, context) {
|
|
2735
|
+
const known = this.currency;
|
|
2736
|
+
if (known === void 0 || !this.currencyVerified) throw new WalletCurrencyUnknownError(context);
|
|
2737
|
+
if (explicit !== void 0 && explicit !== known) throw new CurrencyMismatchError(known, explicit, context);
|
|
2738
|
+
return known;
|
|
2739
|
+
}
|
|
2740
|
+
learnCurrency(currency, context) {
|
|
2741
|
+
if (this.currency !== void 0 && this.currency !== currency) throw new CurrencyMismatchError(this.currency, currency, context);
|
|
2742
|
+
this.currency = currency;
|
|
2743
|
+
this.currencyVerified = true;
|
|
2744
|
+
}
|
|
2745
|
+
confirmCurrency(currency) {
|
|
2746
|
+
if (this.currency === currency) this.currencyVerified = true;
|
|
2747
|
+
}
|
|
2748
|
+
};
|
|
2749
|
+
function summarizeOrderbook(data) {
|
|
2750
|
+
const sells = depth(data.rgCompactSellOrders, "asc");
|
|
2751
|
+
const buys = depth(data.rgCompactBuyOrders, "desc");
|
|
2752
|
+
return {
|
|
2753
|
+
currency: data.eCurrency,
|
|
2754
|
+
lowestAsk: level(data.amtMinSellOrder, sells),
|
|
2755
|
+
askVolumeAtLowest: sells[1] ?? 0,
|
|
2756
|
+
secondLowestAsk: sells.length >= 4 ? sells[2] ?? null : null,
|
|
2757
|
+
highestBid: level(data.amtMaxBuyOrder, buys),
|
|
2758
|
+
bidVolumeAtHighest: buys[1] ?? 0,
|
|
2759
|
+
secondHighestBid: buys.length >= 4 ? buys[2] ?? null : null
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
function wholeCents(v) {
|
|
2763
|
+
if (typeof v === "number") return Number.isSafeInteger(v) ? v : null;
|
|
2764
|
+
if (typeof v === "string" && /^\d+$/.test(v)) {
|
|
2765
|
+
const n = Number(v);
|
|
2766
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
2767
|
+
}
|
|
2768
|
+
return null;
|
|
2769
|
+
}
|
|
2770
|
+
function depth(raw, order) {
|
|
2771
|
+
if (raw === void 0 || raw === null) return [];
|
|
2772
|
+
if (!Array.isArray(raw) || raw.length % 2 !== 0) throw new SteamError("Malformed orderbook depth", { body: raw });
|
|
2773
|
+
const out = [];
|
|
2774
|
+
for (const v of raw) {
|
|
2775
|
+
const n = wholeCents(v);
|
|
2776
|
+
if (n === null || n <= 0) throw new SteamError("Malformed orderbook depth", { body: raw });
|
|
2777
|
+
out.push(n);
|
|
2778
|
+
}
|
|
2779
|
+
for (let i = 2; i < out.length; i += 2) {
|
|
2780
|
+
const prev = out[i - 2];
|
|
2781
|
+
const cur = out[i];
|
|
2782
|
+
if (order === "asc" ? cur <= prev : cur >= prev) throw new SteamError("Orderbook depth is not strictly ordered", { body: raw });
|
|
2783
|
+
}
|
|
2784
|
+
return out;
|
|
2785
|
+
}
|
|
2786
|
+
function level(best, levels) {
|
|
2787
|
+
if (best === null || best === void 0) {
|
|
2788
|
+
if (levels.length > 0) throw new SteamError("Orderbook has depth but no best level");
|
|
2789
|
+
return null;
|
|
2790
|
+
}
|
|
2791
|
+
const value = wholeCents(best);
|
|
2792
|
+
if (value === null || value <= 0) throw new SteamError(`Malformed orderbook best level ${best}`);
|
|
2793
|
+
if (levels.length === 0 || levels[0] !== value) throw new SteamError(`Orderbook best level ${value} disagrees with its depth ${levels[0]}`);
|
|
2794
|
+
return value;
|
|
2795
|
+
}
|
|
2796
|
+
const PINNED_PRICE_FORMATS = /* @__PURE__ */ new Set([1, 3]);
|
|
2797
|
+
function priceHistoryCurrency(prefix, suffix) {
|
|
2798
|
+
const p = typeof prefix === "string" ? prefix.trim() : "";
|
|
2799
|
+
const s = typeof suffix === "string" ? suffix.trim() : "";
|
|
2800
|
+
if (p === "$" && s === "") return 1;
|
|
2801
|
+
if (p === "" && s === "€") return 3;
|
|
2802
|
+
}
|
|
2803
|
+
function assertListingCurrency(listing, currency) {
|
|
2804
|
+
const raw = listing.currencyid;
|
|
2805
|
+
const actual = raw === void 0 || raw === null ? void 0 : Number(raw) - LISTING_CURRENCY_BASE;
|
|
2806
|
+
if (actual !== currency) throw new CurrencyMismatchError(currency, actual, `listing ${listing.listingid}`);
|
|
2807
|
+
}
|
|
2808
|
+
function assertPositiveCents(name, value) {
|
|
2809
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${name} must be a positive integer, got ${value}`);
|
|
2810
|
+
}
|
|
2811
|
+
function isNotPlacedError(err) {
|
|
2812
|
+
return err instanceof RateLimitError || err instanceof SteamSessionExpiredError || err instanceof MarketIneligibleError;
|
|
2813
|
+
}
|
|
2814
|
+
function redirectsToMarket(res) {
|
|
2815
|
+
const location = res.headers.location;
|
|
2816
|
+
if (typeof location !== "string" || !URL.canParse(location, URLS.community)) return false;
|
|
2817
|
+
const { pathname } = new URL(location, URLS.community);
|
|
2818
|
+
return pathname === "/market/" || pathname === "/market";
|
|
2819
|
+
}
|
|
2820
|
+
function eresultOf(body) {
|
|
2821
|
+
const success = body?.success;
|
|
2822
|
+
return typeof success === "number" && success !== 1 ? { eresult: success } : {};
|
|
2823
|
+
}
|
|
2824
|
+
function sellItemError(message, body) {
|
|
2825
|
+
const options = {
|
|
2826
|
+
...eresultOf(body),
|
|
2827
|
+
body
|
|
2828
|
+
};
|
|
2829
|
+
const lower = message.toLowerCase();
|
|
2830
|
+
if (lower.includes("pending confirmation")) {
|
|
2831
|
+
const reason = lower.includes("already have a listing") ? "item_pending" : lower.includes("too many") ? "too_many_pending" : "unknown";
|
|
2832
|
+
return new MarketConfirmationLimitError(message, {
|
|
2833
|
+
...options,
|
|
2834
|
+
reason
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
if (lower.includes("maximum wallet balance")) return new WalletBalanceLimitError(message, options);
|
|
2838
|
+
if (lower.includes("unable to use the community market")) return new MarketBlockedError(message, options);
|
|
2839
|
+
if (lower.includes("no longer in your inventory") || lower.includes("not allowed to be traded")) return new MarketItemUnavailableError(message, options);
|
|
2840
|
+
return new SteamError(message, options);
|
|
2841
|
+
}
|
|
2842
|
+
function buyOrderRefusal(reply, stage, confirmationId) {
|
|
2843
|
+
const { res, body } = reply;
|
|
2844
|
+
const code = typeof body?.success === "number" && body.success !== 1 ? body.success : void 0;
|
|
2845
|
+
const message = typeof body?.message === "string" ? body.message : void 0;
|
|
2846
|
+
if (code === 15 || message?.toLowerCase().includes("unable to use the community market")) return new MarketBlockedError(message, {
|
|
2847
|
+
...code !== void 0 ? { eresult: code } : {},
|
|
2848
|
+
body
|
|
2849
|
+
});
|
|
2850
|
+
if (code === void 0 && message === void 0 && stage === "create") {
|
|
2851
|
+
if (![200, 406].includes(res.statusCode)) {
|
|
2852
|
+
const transport = httpError(res);
|
|
2853
|
+
if (isNotPlacedError(transport)) return transport;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
const unknownOutcome = message !== void 0 && UNKNOWN_OUTCOME_RE.test(message);
|
|
2857
|
+
const notPlaced = code !== void 0 && BUY_ORDER_NOT_PLACED_CODES.has(code) && !unknownOutcome;
|
|
2858
|
+
const orderExists = code === DUPLICATE_ORDER_CODE || message !== void 0 && /already have an active buy order/i.test(message);
|
|
2859
|
+
return new BuyOrderError(message ?? `createbuyorder ${stage} failed (HTTP ${res.statusCode})`, {
|
|
2860
|
+
stage,
|
|
2861
|
+
mayExist: !notPlaced,
|
|
2862
|
+
orderExists,
|
|
2863
|
+
...code !== void 0 ? { code } : {},
|
|
2864
|
+
...confirmationId !== void 0 ? { confirmationId } : {},
|
|
2865
|
+
body: body ?? res.body
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
function safeJson(text) {
|
|
2869
|
+
if (!text) return void 0;
|
|
2870
|
+
try {
|
|
2871
|
+
return JSON.parse(text);
|
|
2872
|
+
} catch {
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
function rawIdField(raw, field) {
|
|
2877
|
+
return raw.match(new RegExp(`"${field}"\\s*:\\s*"?(\\d+)"?`))?.[1] ?? null;
|
|
2878
|
+
}
|
|
2879
|
+
function sleep(ms, signal) {
|
|
2880
|
+
return new Promise((resolve, reject) => {
|
|
2881
|
+
if (signal?.aborted) {
|
|
2882
|
+
reject(signal.reason);
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
const timer = setTimeout(() => {
|
|
2886
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2887
|
+
resolve();
|
|
2888
|
+
}, ms);
|
|
2889
|
+
const onAbort = () => {
|
|
2890
|
+
clearTimeout(timer);
|
|
2891
|
+
reject(signal?.reason);
|
|
2892
|
+
};
|
|
2893
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2894
|
+
});
|
|
2895
|
+
}
|
|
1880
2896
|
/**
|
|
1881
2897
|
* Describes the message CEconItemPreviewDataBlock.
|
|
1882
2898
|
* Use `create(CEconItemPreviewDataBlockSchema)` to create a new message.
|
|
@@ -2961,6 +3977,7 @@ var SteamMobile = class extends EventEmitter {
|
|
|
2961
3977
|
confirmations;
|
|
2962
3978
|
trade;
|
|
2963
3979
|
community;
|
|
3980
|
+
market;
|
|
2964
3981
|
identitySecret;
|
|
2965
3982
|
polling;
|
|
2966
3983
|
proxy;
|
|
@@ -2981,6 +3998,8 @@ var SteamMobile = class extends EventEmitter {
|
|
|
2981
3998
|
this.confirmations = new ConfirmationManager(this.http, this.session.steamID, options.identitySecret, profile);
|
|
2982
3999
|
this.trade = new TradeNamespace(this.api, this.http, this.session, this.confirmations);
|
|
2983
4000
|
this.community = new CommunityNamespace(this.http, this.session, this.confirmations, this.api);
|
|
4001
|
+
this.market = new MarketNamespace(this.http, this.session, this.confirmations, this.api);
|
|
4002
|
+
if (options.walletCurrency !== void 0) this.market.seedWalletCurrency(options.walletCurrency);
|
|
2984
4003
|
this.session.on("refreshToken", (token) => this.emit("refreshToken", token));
|
|
2985
4004
|
this.session.on("sessionExpired", (error) => this.emit("sessionExpired", error));
|
|
2986
4005
|
this.session.on("debug", (message) => this.emit("debug", message));
|
|
@@ -3040,6 +4059,6 @@ var SteamMobile = class extends EventEmitter {
|
|
|
3040
4059
|
}
|
|
3041
4060
|
};
|
|
3042
4061
|
//#endregion
|
|
3043
|
-
export { ANDROID_PROFILE, AccessTokenError, AuthClient, CommunityNamespace, ConfirmationError, ConfirmationManager, CredentialSession, DEFAULT_CONTEXTID, DEFAULT_POLL_FULL_UPDATE_INTERVAL, DEFAULT_POLL_INTERVAL, DEFAULT_POLL_MAX_AGE_MS, DEFAULT_RATE_LIMIT_RETRY_MS, EAuthSessionGuardType, EAuthTokenPlatformType, EAuthTokenRevokeAction, EConfirmationMethod, EConfirmationType, EOfferFilter, EResult, ESessionPersistence, ETokenRenewalType, ETradeOfferState, ETradeStatus, EscrowError, FamilyViewError, HttpClient, HttpStatusError, IOS_PROFILE, ItemServerUnavailableError, LANG, LoginError, NewDeviceError, NoMobileAuthenticatorError, OfferLimitError, OpenIdError, Poller, PrivateInventoryError, ProxyError, RATE_LIMITS, RETRY_AFTER, RateLimitError, SessionManager, SteamError, SteamMobile, SteamSessionExpiredError, SteamWebApi, TERMINAL_AUTH_ERESULTS, TRANSIENT_ERESULTS, TargetCannotTradeError, TradeBanError, TradeNamespace, TradeOffer, URLS, WebApiClient, confirmOpenid, decodeJwt, decodePreviewToken, encodePreviewToken, getTradeHistory, getTradeOffersSummary, getTradeStatus, isTerminalAuthEResult, isTerminalState, isTransientEResult, loginWithCredentials, parseInventory, parseOpenidForm, parsePartnerInventory, resolveMobileProfile, resolveTarget, secondsUntilExpiry, steamOpenidLogin };
|
|
4062
|
+
export { ANDROID_PROFILE, AccessTokenError, AuthClient, BuyOrderError, CommunityNamespace, ConfirmationError, ConfirmationManager, ConfirmationOutcomeUnknownError, CredentialSession, CurrencyMismatchError, DEFAULT_CONTEXTID, DEFAULT_MARKET_FEE_INFO, DEFAULT_POLL_FULL_UPDATE_INTERVAL, DEFAULT_POLL_INTERVAL, DEFAULT_POLL_MAX_AGE_MS, DEFAULT_RATE_LIMIT_RETRY_MS, EAuthSessionGuardType, EAuthTokenPlatformType, EAuthTokenRevokeAction, EConfirmationMethod, EConfirmationType, EOfferFilter, EResult, ESessionPersistence, ETokenRenewalType, ETradeOfferState, ETradeStatus, EscrowError, FamilyViewError, HttpClient, HttpStatusError, IOS_PROFILE, ItemServerUnavailableError, LANG, LoginError, MarketBlockedError, MarketConfirmationLimitError, MarketIneligibleError, MarketItemUnavailableError, MarketNamespace, NewDeviceError, NoMobileAuthenticatorError, OfferLimitError, OpenIdError, Poller, PrivateInventoryError, ProxyError, RATE_LIMITS, RETRY_AFTER, RateLimitError, SessionManager, SteamError, SteamMobile, SteamSessionExpiredError, SteamWebApi, TERMINAL_AUTH_ERESULTS, TRANSIENT_ERESULTS, TargetCannotTradeError, ThrottledError, TradeBanError, TradeNamespace, TradeOffer, URLS, WalletBalanceLimitError, WalletCurrencyUnknownError, WebApiClient, buyerPaysForSellerReceives, confirmOpenid, decodeJwt, decodePreviewToken, encodePreviewToken, feeInfoFromWalletInfo, feesForBuyerPays, getPriceValueAsInt, getTradeHistory, getTradeOffersSummary, getTradeStatus, isTerminalAuthEResult, isTerminalState, isTransientEResult, landingBuyerPays, loginWithCredentials, parseInventory, parseMarketHistory, parseOpenidForm, parsePartnerInventory, priceTextCurrency, resolveMobileProfile, resolveTarget, secondsUntilExpiry, sellerReceivesForBuyerPays, steamOpenidLogin, summarizeOrderbook };
|
|
3044
4063
|
|
|
3045
4064
|
//# sourceMappingURL=index.mjs.map
|