@assetpay/steam-mobile 1.1.0 → 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 +146 -48
- package/dist/index.d.mts +160 -24
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +607 -81
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -358,6 +358,12 @@ var ConfirmationError = class extends SteamError {
|
|
|
358
358
|
this.name = "ConfirmationError";
|
|
359
359
|
}
|
|
360
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
|
+
};
|
|
361
367
|
var FamilyViewError = class extends SteamError {
|
|
362
368
|
constructor(message = "Family View Restricted") {
|
|
363
369
|
super(message);
|
|
@@ -371,9 +377,59 @@ var PrivateInventoryError = class extends SteamError {
|
|
|
371
377
|
}
|
|
372
378
|
};
|
|
373
379
|
var MarketConfirmationLimitError = class extends SteamError {
|
|
380
|
+
reason;
|
|
374
381
|
constructor(message = "Too many listings pending confirmation", options) {
|
|
375
382
|
super(message, options);
|
|
376
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;
|
|
377
433
|
}
|
|
378
434
|
};
|
|
379
435
|
var WalletBalanceLimitError = class extends SteamError {
|
|
@@ -909,6 +965,12 @@ const IMPIT_BROWSER = {
|
|
|
909
965
|
}
|
|
910
966
|
};
|
|
911
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
|
+
}
|
|
912
974
|
function clean(input) {
|
|
913
975
|
if (!input) return void 0;
|
|
914
976
|
const out = {};
|
|
@@ -952,7 +1014,8 @@ var HttpClient = class {
|
|
|
952
1014
|
const res = await this.perform(method, url, opts);
|
|
953
1015
|
const location = firstHeader(res.headers.location);
|
|
954
1016
|
if (res.statusCode >= 300 && res.statusCode < 400 && location?.includes("/market/eligibilitycheck")) {
|
|
955
|
-
await this.perform("GET", location, opts
|
|
1017
|
+
await this.perform("GET", location, requestControl(opts));
|
|
1018
|
+
if (opts.noEligibilityReplay) return res;
|
|
956
1019
|
return this.perform(method, url, opts);
|
|
957
1020
|
}
|
|
958
1021
|
return res;
|
|
@@ -1203,7 +1266,7 @@ function httpError(res, rateLimitWindowMs) {
|
|
|
1203
1266
|
if (res.statusCode >= 300 && res.statusCode <= 399) {
|
|
1204
1267
|
const loc = locationHeader(res);
|
|
1205
1268
|
if (loc.includes("/login")) return new SteamSessionExpiredError();
|
|
1206
|
-
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");
|
|
1207
1270
|
}
|
|
1208
1271
|
return new HttpStatusError(res.statusCode, void 0, res.body);
|
|
1209
1272
|
}
|
|
@@ -1607,8 +1670,11 @@ function xmlValue(xml, tag) {
|
|
|
1607
1670
|
}
|
|
1608
1671
|
//#endregion
|
|
1609
1672
|
//#region src/http/queryTime.ts
|
|
1610
|
-
async function queryServerTimeOffset(http) {
|
|
1611
|
-
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
|
+
});
|
|
1612
1678
|
if (res.statusCode !== 200) throw httpError(res);
|
|
1613
1679
|
const serverTime = Number(res.body?.response?.server_time);
|
|
1614
1680
|
if (!serverTime) throw new SteamError("Failed to query Steam server time");
|
|
@@ -1638,9 +1704,9 @@ var ConfirmationManager = class {
|
|
|
1638
1704
|
const android = getDeviceID(this.steamID.getSteamID64());
|
|
1639
1705
|
return this.profile.mobileClient === "ios" ? android.replace(/^android:/, "").toUpperCase() : android;
|
|
1640
1706
|
}
|
|
1641
|
-
async getConfirmations(time, key) {
|
|
1707
|
+
async getConfirmations(time, key, control) {
|
|
1642
1708
|
const { tag, k } = splitKey(key, "conf");
|
|
1643
|
-
const body = await this.getlist(k, time, tag);
|
|
1709
|
+
const body = await this.getlist(k, time, tag, control);
|
|
1644
1710
|
if (!body?.success) {
|
|
1645
1711
|
if (body?.needauth) throw new SteamSessionExpiredError();
|
|
1646
1712
|
throw new ConfirmationError(String(body?.message ?? body?.detail ?? "Failed to get confirmation list"));
|
|
@@ -1658,13 +1724,14 @@ var ConfirmationManager = class {
|
|
|
1658
1724
|
icon: String(c.icon ?? "")
|
|
1659
1725
|
}));
|
|
1660
1726
|
}
|
|
1661
|
-
async respondToConfirmation(confID, confKey, time, key, accept) {
|
|
1727
|
+
async respondToConfirmation(confID, confKey, time, key, accept, control) {
|
|
1662
1728
|
const ids = Array.isArray(confID) ? confID : [confID];
|
|
1663
1729
|
const keys = Array.isArray(confKey) ? confKey : [confKey];
|
|
1664
1730
|
if (ids.length !== keys.length) throw new ConfirmationError("confID and confKey must have the same length");
|
|
1665
1731
|
const { tag, k } = splitKey(key, accept ? "allow" : "cancel");
|
|
1666
1732
|
const op = accept ? "allow" : "cancel";
|
|
1667
1733
|
const res = await this.http.post(`${URLS.community}/mobileconf/multiajaxop`, {
|
|
1734
|
+
...requestControl(control),
|
|
1668
1735
|
responseType: "json",
|
|
1669
1736
|
searchParams: {
|
|
1670
1737
|
...this.confParams(k, time, tag),
|
|
@@ -1680,16 +1747,17 @@ var ConfirmationManager = class {
|
|
|
1680
1747
|
});
|
|
1681
1748
|
if (res.statusCode !== 200) throw httpError(res);
|
|
1682
1749
|
if (res.body?.success) return;
|
|
1750
|
+
if (res.body === void 0 || res.body === null) throw new ConfirmationOutcomeUnknownError();
|
|
1683
1751
|
throw new ConfirmationError(String(res.body?.message ?? "Could not act on confirmation"));
|
|
1684
1752
|
}
|
|
1685
|
-
async getPending() {
|
|
1753
|
+
async getPending(control) {
|
|
1686
1754
|
const secret = this.requireSecret();
|
|
1687
|
-
const listTime = time(await this.getTimeOffset());
|
|
1755
|
+
const listTime = time(await this.getTimeOffset(control));
|
|
1688
1756
|
const listKey = getConfirmationKey(secret, listTime, "list");
|
|
1689
1757
|
return this.getConfirmations(listTime, {
|
|
1690
1758
|
tag: "list",
|
|
1691
1759
|
key: listKey
|
|
1692
|
-
});
|
|
1760
|
+
}, control);
|
|
1693
1761
|
}
|
|
1694
1762
|
acceptConfirmation(confID, nonce) {
|
|
1695
1763
|
return this.actOnConfirmations([{
|
|
@@ -1703,11 +1771,11 @@ var ConfirmationManager = class {
|
|
|
1703
1771
|
key: nonce
|
|
1704
1772
|
}], false);
|
|
1705
1773
|
}
|
|
1706
|
-
acceptConfirmations(confs) {
|
|
1707
|
-
return this.actOnConfirmations(confs, true);
|
|
1774
|
+
acceptConfirmations(confs, control) {
|
|
1775
|
+
return this.actOnConfirmations(confs, true, control);
|
|
1708
1776
|
}
|
|
1709
|
-
rejectConfirmations(confs) {
|
|
1710
|
-
return this.actOnConfirmations(confs, false);
|
|
1777
|
+
rejectConfirmations(confs, control) {
|
|
1778
|
+
return this.actOnConfirmations(confs, false, control);
|
|
1711
1779
|
}
|
|
1712
1780
|
async acceptAll() {
|
|
1713
1781
|
const pending = await this.getPending();
|
|
@@ -1719,17 +1787,17 @@ var ConfirmationManager = class {
|
|
|
1719
1787
|
if (!conf) throw new ConfirmationError(`Could not find confirmation for object ${objectID}`);
|
|
1720
1788
|
await this.acceptConfirmation(conf.id, conf.key);
|
|
1721
1789
|
}
|
|
1722
|
-
async actOnConfirmations(confs, accept) {
|
|
1790
|
+
async actOnConfirmations(confs, accept, control) {
|
|
1723
1791
|
if (confs.length === 0) return;
|
|
1724
1792
|
const secret = this.requireSecret();
|
|
1725
|
-
const offset = await this.getTimeOffset();
|
|
1793
|
+
const offset = await this.getTimeOffset(control);
|
|
1726
1794
|
const time = this.nextConfTime(offset);
|
|
1727
1795
|
const tag = accept ? "accept" : "cancel";
|
|
1728
1796
|
const key = getConfirmationKey(secret, time, tag);
|
|
1729
1797
|
await this.respondToConfirmation(confs.map((c) => c.id), confs.map((c) => c.key), time, {
|
|
1730
1798
|
tag,
|
|
1731
1799
|
key
|
|
1732
|
-
}, accept);
|
|
1800
|
+
}, accept, control);
|
|
1733
1801
|
}
|
|
1734
1802
|
requireSecret() {
|
|
1735
1803
|
if (!this.identitySecret) throw new ConfirmationError("identitySecret is required to respond to confirmations");
|
|
@@ -1743,9 +1811,9 @@ var ConfirmationManager = class {
|
|
|
1743
1811
|
if (this.usedConfTimes.length > 60) this.usedConfTimes.splice(0, this.usedConfTimes.length - 60);
|
|
1744
1812
|
return time$1;
|
|
1745
1813
|
}
|
|
1746
|
-
async getTimeOffset() {
|
|
1814
|
+
async getTimeOffset(control) {
|
|
1747
1815
|
if (this.timeOffset !== void 0 && Date.now() - this.timeOffsetAt < TIME_OFFSET_TTL_MS) return this.timeOffset;
|
|
1748
|
-
const offset = await queryServerTimeOffset(this.http);
|
|
1816
|
+
const offset = await queryServerTimeOffset(this.http, control);
|
|
1749
1817
|
this.timeOffset = offset;
|
|
1750
1818
|
this.timeOffsetAt = Date.now();
|
|
1751
1819
|
return offset;
|
|
@@ -1760,8 +1828,9 @@ var ConfirmationManager = class {
|
|
|
1760
1828
|
tag
|
|
1761
1829
|
};
|
|
1762
1830
|
}
|
|
1763
|
-
async getlist(key, time, tag) {
|
|
1831
|
+
async getlist(key, time, tag, control) {
|
|
1764
1832
|
const res = await this.http.get(`${URLS.community}/mobileconf/getlist`, {
|
|
1833
|
+
...requestControl(control),
|
|
1765
1834
|
responseType: "json",
|
|
1766
1835
|
searchParams: this.confParams(key, time, tag)
|
|
1767
1836
|
});
|
|
@@ -1831,6 +1900,7 @@ var WebApiClient = class {
|
|
|
1831
1900
|
console.log(`[http] ${httpMethod} ${url}?${qs.toString()}`);
|
|
1832
1901
|
}
|
|
1833
1902
|
const res = await this.http.request(httpMethod, url, {
|
|
1903
|
+
...requestControl(params),
|
|
1834
1904
|
responseType: "json",
|
|
1835
1905
|
...httpMethod === "GET" ? { searchParams: payload } : { form: payload }
|
|
1836
1906
|
});
|
|
@@ -1930,6 +2000,101 @@ var SteamWebApi = class {
|
|
|
1930
2000
|
}
|
|
1931
2001
|
};
|
|
1932
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
|
|
1933
2098
|
//#region src/market/prices.ts
|
|
1934
2099
|
function getPriceValueAsInt(strAmount) {
|
|
1935
2100
|
if (!strAmount) return 0;
|
|
@@ -1954,6 +2119,7 @@ function parseMarketHistory(body) {
|
|
|
1954
2119
|
sales: [],
|
|
1955
2120
|
purchases: [],
|
|
1956
2121
|
listingEvents: [],
|
|
2122
|
+
malformed: [],
|
|
1957
2123
|
totalCount: body.total_count
|
|
1958
2124
|
};
|
|
1959
2125
|
if (!body.results_html) return history;
|
|
@@ -1969,11 +2135,19 @@ function parseMarketHistory(body) {
|
|
|
1969
2135
|
const displayPrice = textOf(row.inner, "market_listing_price").trim();
|
|
1970
2136
|
const rowId = attrValue(row.attrs, "id") ?? "";
|
|
1971
2137
|
const priceInCents = getPriceValueAsInt(displayPrice);
|
|
1972
|
-
if (priceInCents === 0 && displayPrice) throw new SteamError(`Market history price parsed to 0 cents for "${displayPrice}" in row ${rowId || "?"}`);
|
|
1973
2138
|
const gainOrLoss = textOf(row.inner, "market_listing_gainorloss").trim();
|
|
1974
2139
|
const type = gainOrLoss === "-" ? "sale" : gainOrLoss === "+" ? "purchase" : "listing_event";
|
|
1975
2140
|
const idMatch = rowId.match(/^history_row_(\d+)_(\d+)$/);
|
|
1976
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
|
+
}
|
|
1977
2151
|
const event = {
|
|
1978
2152
|
historyId: rowId,
|
|
1979
2153
|
listingid: idMatch[1],
|
|
@@ -2107,23 +2281,56 @@ function decodeEntities(s) {
|
|
|
2107
2281
|
return NAMED_ENTITIES[ref.toLowerCase()] ?? whole;
|
|
2108
2282
|
});
|
|
2109
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
|
+
}
|
|
2110
2292
|
//#endregion
|
|
2111
2293
|
//#region src/market/MarketNamespace.ts
|
|
2112
2294
|
const LISTINGS_PAGE_SIZE = 100;
|
|
2113
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;
|
|
2114
2309
|
var MarketNamespace = class {
|
|
2115
2310
|
http;
|
|
2116
2311
|
session;
|
|
2117
2312
|
confirmations;
|
|
2118
2313
|
api;
|
|
2119
|
-
|
|
2314
|
+
currency;
|
|
2315
|
+
currencyVerified = false;
|
|
2120
2316
|
constructor(http, session, confirmations, api) {
|
|
2121
2317
|
this.http = http;
|
|
2122
2318
|
this.session = session;
|
|
2123
2319
|
this.confirmations = confirmations;
|
|
2124
2320
|
this.api = api;
|
|
2125
2321
|
}
|
|
2126
|
-
|
|
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 = {}) {
|
|
2127
2334
|
const body = await this.api.call({
|
|
2128
2335
|
httpMethod: "POST",
|
|
2129
2336
|
iface: "IUserAccountService",
|
|
@@ -2131,28 +2338,54 @@ var MarketNamespace = class {
|
|
|
2131
2338
|
input: {
|
|
2132
2339
|
include_balance_in_usd: 1,
|
|
2133
2340
|
include_formatted_balance: 1
|
|
2134
|
-
}
|
|
2341
|
+
},
|
|
2342
|
+
...requestControl(options)
|
|
2135
2343
|
});
|
|
2136
2344
|
if (!body.response) throw new SteamError("Malformed wallet details response", { body });
|
|
2137
2345
|
const currency = body.response.currency_code;
|
|
2138
|
-
if (typeof currency === "number" && currency > 0) this.
|
|
2346
|
+
if (body.response.has_wallet === true && typeof currency === "number" && currency > 0) this.learnCurrency(currency, "wallet details");
|
|
2139
2347
|
return body.response;
|
|
2140
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
|
+
}
|
|
2141
2366
|
async getMyListings(options = {}) {
|
|
2367
|
+
const currency = this.readCurrency(options.currency, "mylistings");
|
|
2142
2368
|
await this.session.getAccessToken();
|
|
2143
2369
|
const result = {
|
|
2144
2370
|
listings: [],
|
|
2145
2371
|
listings_to_confirm: [],
|
|
2372
|
+
listings_on_hold: [],
|
|
2146
2373
|
buy_orders: [],
|
|
2147
|
-
num_active_listings: 0
|
|
2374
|
+
num_active_listings: 0,
|
|
2375
|
+
complete: false
|
|
2148
2376
|
};
|
|
2377
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2378
|
+
let firstTotal;
|
|
2379
|
+
let totalMoved = false;
|
|
2380
|
+
let buyOrdersMissing = false;
|
|
2149
2381
|
let start = 0;
|
|
2150
2382
|
for (;;) {
|
|
2151
2383
|
const res = await this.http.get(`${URLS.community}/market/mylistings/`, {
|
|
2384
|
+
...requestControl(options),
|
|
2152
2385
|
responseType: "json",
|
|
2153
2386
|
searchParams: {
|
|
2154
2387
|
norender: 1,
|
|
2155
|
-
currency:
|
|
2388
|
+
currency: currency ?? void 0,
|
|
2156
2389
|
start,
|
|
2157
2390
|
count: LISTINGS_PAGE_SIZE
|
|
2158
2391
|
}
|
|
@@ -2160,20 +2393,47 @@ var MarketNamespace = class {
|
|
|
2160
2393
|
if (res.statusCode !== 200) throw httpError(res);
|
|
2161
2394
|
const body = res.body;
|
|
2162
2395
|
if (!body?.success) throw new SteamError("Steam returned success=false for /market/mylistings", { body });
|
|
2163
|
-
if (body.num_active_listings !== void 0)
|
|
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
|
+
}
|
|
2164
2401
|
if (start === 0) {
|
|
2165
2402
|
if (Array.isArray(body.buy_orders)) result.buy_orders = body.buy_orders;
|
|
2403
|
+
else buyOrdersMissing = true;
|
|
2166
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;
|
|
2167
2406
|
}
|
|
2168
2407
|
const page = body.listings ?? body.results?.listings ?? [];
|
|
2169
|
-
|
|
2408
|
+
for (const listing of page) {
|
|
2409
|
+
if (seen.has(listing.listingid)) continue;
|
|
2410
|
+
seen.add(listing.listingid);
|
|
2411
|
+
result.listings.push(listing);
|
|
2412
|
+
}
|
|
2170
2413
|
start += page.length;
|
|
2171
|
-
if (start >= result.num_active_listings || page.length === 0)
|
|
2414
|
+
if (start >= result.num_active_listings || page.length === 0) break;
|
|
2172
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;
|
|
2173
2431
|
}
|
|
2174
2432
|
async getMyHistory(options = {}) {
|
|
2433
|
+
const currency = this.readCurrency(options.currency, "myhistory");
|
|
2175
2434
|
await this.session.getAccessToken();
|
|
2176
2435
|
const res = await this.http.get(`${URLS.community}/market/myhistory`, {
|
|
2436
|
+
...requestControl(options),
|
|
2177
2437
|
responseType: "json",
|
|
2178
2438
|
searchParams: {
|
|
2179
2439
|
count: options.count ?? 500,
|
|
@@ -2182,49 +2442,92 @@ var MarketNamespace = class {
|
|
|
2182
2442
|
});
|
|
2183
2443
|
if (res.statusCode !== 200) throw httpError(res);
|
|
2184
2444
|
if (!res.body?.success) throw new SteamError("Steam returned success=false for /market/myhistory", { body: res.body });
|
|
2185
|
-
|
|
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;
|
|
2186
2466
|
}
|
|
2187
2467
|
async getOrderbook(appid, marketHashName, options = {}) {
|
|
2468
|
+
const expected = this.readCurrency(options.expectedCurrency, "orderbook");
|
|
2188
2469
|
await this.session.getAccessToken();
|
|
2189
2470
|
const qp = encodeURIComponent(JSON.stringify([appid, marketHashName]));
|
|
2190
|
-
const res = await this.http.get(`${URLS.community}/market/orderbook?q=Load&qp=${qp}`, {
|
|
2471
|
+
const res = await this.http.get(`${URLS.community}/market/orderbook?q=Load&qp=${qp}`, {
|
|
2472
|
+
...requestControl(options),
|
|
2473
|
+
responseType: "json"
|
|
2474
|
+
});
|
|
2191
2475
|
if (res.statusCode === 403) throw new ThrottledError(void 0, { body: res.body });
|
|
2192
2476
|
if (res.statusCode !== 200) throw httpError(res);
|
|
2193
2477
|
const body = res.body;
|
|
2194
2478
|
const payload = body && body.success === void 0 && body.data ? body.data : body;
|
|
2195
2479
|
if (!payload?.success || !payload.data) throw new SteamError(`Bad orderbook response for ${marketHashName}`, { body });
|
|
2196
2480
|
const data = payload.data;
|
|
2197
|
-
|
|
2198
|
-
|
|
2481
|
+
if (expected !== null) {
|
|
2482
|
+
if (data.eCurrency !== expected) throw new CurrencyMismatchError(expected, data.eCurrency, `orderbook of ${marketHashName}`);
|
|
2483
|
+
this.confirmCurrency(expected);
|
|
2484
|
+
}
|
|
2199
2485
|
return data;
|
|
2200
2486
|
}
|
|
2201
|
-
async getPriceHistory(appid, marketHashName) {
|
|
2487
|
+
async getPriceHistory(appid, marketHashName, options = {}) {
|
|
2488
|
+
const expected = this.readCurrency(options.expectedCurrency, "pricehistory");
|
|
2202
2489
|
await this.session.getAccessToken();
|
|
2203
|
-
const res = await this.http.get(`${URLS.community}/market/pricehistory?appid=${appid}&market_hash_name=${encodeURIComponent(marketHashName)}`, {
|
|
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
|
+
});
|
|
2204
2494
|
if (res.statusCode !== 200) throw httpError(res);
|
|
2205
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
|
+
}
|
|
2206
2500
|
return Array.isArray(res.body.prices) ? res.body.prices : [];
|
|
2207
2501
|
}
|
|
2208
|
-
async getMarketItemDetails(appid, marketHashName) {
|
|
2502
|
+
async getMarketItemDetails(appid, marketHashName, options = {}) {
|
|
2209
2503
|
await this.session.getAccessToken();
|
|
2210
2504
|
const query = encodeURIComponent(`"${marketHashName}"`);
|
|
2211
|
-
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`, {
|
|
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
|
+
});
|
|
2212
2509
|
if (res.statusCode !== 200) throw httpError(res);
|
|
2213
2510
|
const body = res.body;
|
|
2214
|
-
if (!body || typeof body !== "object") throw new SteamError("Malformed market search response", { body });
|
|
2511
|
+
if (!body || typeof body !== "object" || body.success === false) throw new SteamError("Malformed or unsuccessful market search response", { body });
|
|
2215
2512
|
return (Array.isArray(body.results) ? body.results : []).find((r) => r.hash_name === marketHashName) ?? null;
|
|
2216
2513
|
}
|
|
2217
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);
|
|
2218
2519
|
await this.session.getAccessToken();
|
|
2219
2520
|
const sessionid = await this.http.getSessionId();
|
|
2220
2521
|
const res = await this.http.post(`${URLS.community}/market/sellitem/`, {
|
|
2522
|
+
...requestControl(options),
|
|
2523
|
+
noEligibilityReplay: true,
|
|
2221
2524
|
responseType: "json",
|
|
2222
2525
|
form: {
|
|
2223
2526
|
sessionid,
|
|
2224
2527
|
appid: options.appid,
|
|
2225
2528
|
contextid: options.contextid ?? "2",
|
|
2226
2529
|
assetid: options.assetid,
|
|
2227
|
-
amount
|
|
2530
|
+
amount,
|
|
2228
2531
|
price: options.price
|
|
2229
2532
|
},
|
|
2230
2533
|
headers: { Referer: MARKET_REFERER }
|
|
@@ -2239,10 +2542,12 @@ var MarketNamespace = class {
|
|
|
2239
2542
|
body
|
|
2240
2543
|
});
|
|
2241
2544
|
}
|
|
2242
|
-
async cancelListing(listingId) {
|
|
2545
|
+
async cancelListing(listingId, options = {}) {
|
|
2243
2546
|
await this.session.getAccessToken();
|
|
2244
2547
|
const sessionid = await this.http.getSessionId();
|
|
2245
2548
|
const res = await this.http.post(`${URLS.community}/market/removelisting/${listingId}`, {
|
|
2549
|
+
...requestControl(options),
|
|
2550
|
+
noEligibilityReplay: true,
|
|
2246
2551
|
form: { sessionid },
|
|
2247
2552
|
headers: { Referer: MARKET_REFERER }
|
|
2248
2553
|
});
|
|
@@ -2250,8 +2555,10 @@ var MarketNamespace = class {
|
|
|
2250
2555
|
throw httpError(res);
|
|
2251
2556
|
}
|
|
2252
2557
|
async createBuyOrder(options) {
|
|
2253
|
-
const currency = options.currency
|
|
2254
|
-
|
|
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");
|
|
2255
2562
|
await this.session.getAccessToken();
|
|
2256
2563
|
const sessionid = await this.http.getSessionId();
|
|
2257
2564
|
const referer = `${URLS.community}/market/listings/${options.appid}/${encodeURIComponent(options.marketHashName)}`;
|
|
@@ -2264,24 +2571,44 @@ var MarketNamespace = class {
|
|
|
2264
2571
|
quantity: options.quantity,
|
|
2265
2572
|
confirmation: 0
|
|
2266
2573
|
};
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
if (
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
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 = {}) {
|
|
2282
2607
|
await this.session.getAccessToken();
|
|
2283
2608
|
const sessionid = await this.http.getSessionId();
|
|
2284
2609
|
const res = await this.http.post(`${URLS.community}/market/cancelbuyorder/`, {
|
|
2610
|
+
...requestControl(options),
|
|
2611
|
+
noEligibilityReplay: true,
|
|
2285
2612
|
responseType: "json",
|
|
2286
2613
|
form: {
|
|
2287
2614
|
sessionid,
|
|
@@ -2290,15 +2617,35 @@ var MarketNamespace = class {
|
|
|
2290
2617
|
headers: { Referer: MARKET_REFERER }
|
|
2291
2618
|
});
|
|
2292
2619
|
if (res.statusCode !== 200) throw httpError(res);
|
|
2293
|
-
|
|
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
|
+
});
|
|
2294
2626
|
}
|
|
2295
|
-
async
|
|
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 = {}) {
|
|
2296
2643
|
if (Object.values(expected).reduce((sum, n) => sum + n, 0) === 0) return {
|
|
2297
2644
|
confirmed: [],
|
|
2298
2645
|
skipped: []
|
|
2299
2646
|
};
|
|
2300
2647
|
await this.session.getAccessToken();
|
|
2301
|
-
const listings = (await this.confirmations.getPending()).filter((c) => c.type === 3).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
2648
|
+
const listings = (await this.confirmations.getPending(options)).filter((c) => c.type === 3).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
2302
2649
|
const remaining = { ...expected };
|
|
2303
2650
|
const confirmed = [];
|
|
2304
2651
|
const skipped = [];
|
|
@@ -2311,28 +2658,159 @@ var MarketNamespace = class {
|
|
|
2311
2658
|
confirmed.push(conf);
|
|
2312
2659
|
remaining[name] = (remaining[name] ?? 0) - 1;
|
|
2313
2660
|
}
|
|
2314
|
-
await this.confirmations.acceptConfirmations(confirmed);
|
|
2661
|
+
await this.confirmations.acceptConfirmations(confirmed, options);
|
|
2315
2662
|
return {
|
|
2316
2663
|
confirmed,
|
|
2317
2664
|
skipped
|
|
2318
2665
|
};
|
|
2319
2666
|
}
|
|
2320
|
-
async rejectListings(types = [3]) {
|
|
2667
|
+
async rejectListings(types = [3], options = {}) {
|
|
2321
2668
|
await this.session.getAccessToken();
|
|
2322
|
-
const
|
|
2323
|
-
await this.confirmations.
|
|
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);
|
|
2324
2672
|
return matched;
|
|
2325
2673
|
}
|
|
2326
|
-
async
|
|
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) {
|
|
2327
2707
|
const res = await this.http.post(`${URLS.community}/market/createbuyorder/`, {
|
|
2328
|
-
|
|
2708
|
+
...requestControl(control),
|
|
2709
|
+
noEligibilityReplay: true,
|
|
2329
2710
|
form,
|
|
2330
2711
|
headers: { Referer: referer }
|
|
2331
2712
|
});
|
|
2332
2713
|
if (res.statusCode === 429 || res.statusCode === 401) throw httpError(res);
|
|
2333
|
-
|
|
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;
|
|
2334
2747
|
}
|
|
2335
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
|
+
}
|
|
2336
2814
|
function redirectsToMarket(res) {
|
|
2337
2815
|
const location = res.headers.location;
|
|
2338
2816
|
if (typeof location !== "string" || !URL.canParse(location, URLS.community)) return false;
|
|
@@ -2349,24 +2827,71 @@ function sellItemError(message, body) {
|
|
|
2349
2827
|
body
|
|
2350
2828
|
};
|
|
2351
2829
|
const lower = message.toLowerCase();
|
|
2352
|
-
if (lower.includes("pending confirmation"))
|
|
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
|
+
}
|
|
2353
2837
|
if (lower.includes("maximum wallet balance")) return new WalletBalanceLimitError(message, options);
|
|
2354
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);
|
|
2355
2840
|
return new SteamError(message, options);
|
|
2356
2841
|
}
|
|
2357
|
-
function
|
|
2358
|
-
const body =
|
|
2359
|
-
const
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
const body = res.body;
|
|
2364
|
-
const eresult = eresultOf(body);
|
|
2365
|
-
if (!body?.message && eresult.eresult === void 0 && ![200, 406].includes(res.statusCode)) return httpError(res);
|
|
2366
|
-
return new SteamError(body?.message || fallback, {
|
|
2367
|
-
...eresult,
|
|
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 } : {},
|
|
2368
2848
|
body
|
|
2369
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
|
+
});
|
|
2370
2895
|
}
|
|
2371
2896
|
/**
|
|
2372
2897
|
* Describes the message CEconItemPreviewDataBlock.
|
|
@@ -3474,6 +3999,7 @@ var SteamMobile = class extends EventEmitter {
|
|
|
3474
3999
|
this.trade = new TradeNamespace(this.api, this.http, this.session, this.confirmations);
|
|
3475
4000
|
this.community = new CommunityNamespace(this.http, this.session, this.confirmations, this.api);
|
|
3476
4001
|
this.market = new MarketNamespace(this.http, this.session, this.confirmations, this.api);
|
|
4002
|
+
if (options.walletCurrency !== void 0) this.market.seedWalletCurrency(options.walletCurrency);
|
|
3477
4003
|
this.session.on("refreshToken", (token) => this.emit("refreshToken", token));
|
|
3478
4004
|
this.session.on("sessionExpired", (error) => this.emit("sessionExpired", error));
|
|
3479
4005
|
this.session.on("debug", (message) => this.emit("debug", message));
|
|
@@ -3533,6 +4059,6 @@ var SteamMobile = class extends EventEmitter {
|
|
|
3533
4059
|
}
|
|
3534
4060
|
};
|
|
3535
4061
|
//#endregion
|
|
3536
|
-
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, MarketBlockedError, MarketConfirmationLimitError, 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, WebApiClient, confirmOpenid, decodeJwt, decodePreviewToken, encodePreviewToken, getPriceValueAsInt, getTradeHistory, getTradeOffersSummary, getTradeStatus, isTerminalAuthEResult, isTerminalState, isTransientEResult, loginWithCredentials, parseInventory, parseMarketHistory, 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 };
|
|
3537
4063
|
|
|
3538
4064
|
//# sourceMappingURL=index.mjs.map
|