6699 lines
202 KiB
TypeScript
6699 lines
202 KiB
TypeScript
// deno-fmt-ignore-file
|
||
// deno-lint-ignore-file
|
||
// This code was bundled using `deno bundle` and it's not recommended to edit it manually
|
||
// https://raw.githubusercontent.com/spacemeowx2/s3si.ts/main/s3si.ts
|
||
|
||
class APIError extends Error {
|
||
response;
|
||
json;
|
||
constructor({ response , json , message }){
|
||
super(message);
|
||
this.response = response;
|
||
this.json = json;
|
||
}
|
||
}
|
||
const AGENT_NAME = "s3si.ts";
|
||
const S3SI_VERSION = "0.1.21";
|
||
const NSOAPP_VERSION = "2.3.1";
|
||
const WEB_VIEW_VERSION = "1.0.0-5644e7a2";
|
||
const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts";
|
||
const USERAGENT = `${AGENT_NAME}/${S3SI_VERSION} (${S3SI_LINK})`;
|
||
const DEFAULT_APP_USER_AGENT = "Mozilla/5.0 (Linux; Android 11; Pixel 5) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/94.0.4606.61 Mobile Safari/537.36";
|
||
const SPLATNET3_ENDPOINT = "https://api.lp1.av5ja.srv.nintendo.net/api/graphql";
|
||
const S3S_NAMESPACE = "b3a2dbf5-2c09-4792-b78c-00b548b70aeb";
|
||
const S3SI_NAMESPACE = "63941e1c-e32e-4b56-9a1d-f6fbe19ef6e1";
|
||
const SPLATNET3_STATINK_MAP = {
|
||
RULE: {
|
||
TURF_WAR: "nawabari",
|
||
AREA: "area",
|
||
LOFT: "yagura",
|
||
GOAL: "hoko",
|
||
CLAM: "asari",
|
||
TRI_COLOR: "nawabari"
|
||
},
|
||
RESULT: {
|
||
WIN: "win",
|
||
LOSE: "lose",
|
||
DEEMED_LOSE: "lose",
|
||
EXEMPTED_LOSE: "exempted_lose",
|
||
DRAW: "draw"
|
||
},
|
||
DRAGON: {
|
||
NORMAL: undefined,
|
||
DECUPLE: "10x",
|
||
DRAGON: "100x",
|
||
DOUBLE_DRAGON: "333x"
|
||
}
|
||
};
|
||
const CONTROL_CHARS = /[\x00-\x1F\x7F]/;
|
||
const COOKIE_NAME_BLOCKED = /[()<>@,;:\\"/[\]?={}]/;
|
||
const COOKIE_OCTET_BLOCKED = /[\s",;\\]/;
|
||
const COOKIE_OCTET = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/;
|
||
const TERMINATORS = [
|
||
"\n",
|
||
"\r",
|
||
"\0"
|
||
];
|
||
function isSameDomainOrSubdomain(domainA, domainB) {
|
||
if (!domainA || !domainB) {
|
||
return false;
|
||
}
|
||
let longerDomain;
|
||
let shorterDomain;
|
||
if (domainB.length > domainA.length) {
|
||
longerDomain = domainB;
|
||
shorterDomain = domainA;
|
||
} else {
|
||
longerDomain = domainA;
|
||
shorterDomain = domainB;
|
||
}
|
||
const indexOfDomain = longerDomain.indexOf(shorterDomain);
|
||
if (indexOfDomain === -1) {
|
||
return false;
|
||
} else if (indexOfDomain > 0) {
|
||
if (longerDomain.charAt(indexOfDomain - 1) !== ".") {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
function trimTerminator(str) {
|
||
if (str === undefined || str === "") return str;
|
||
for(let t = 0; t < TERMINATORS.length; t++){
|
||
const terminatorIdx = str.indexOf(TERMINATORS[t]);
|
||
if (terminatorIdx !== -1) {
|
||
str = str.substr(0, terminatorIdx);
|
||
}
|
||
}
|
||
return str;
|
||
}
|
||
function isValidName(name) {
|
||
if (!name) {
|
||
return false;
|
||
}
|
||
if (CONTROL_CHARS.test(name) || COOKIE_NAME_BLOCKED.test(name)) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
function trimWrappingDoubleQuotes(val) {
|
||
if (val.length >= 2 && val.at(0) === '"' && val.at(-1) === '"') {
|
||
return val.slice(1, -1);
|
||
}
|
||
return val;
|
||
}
|
||
function isValidValue(val) {
|
||
if (val === "") {
|
||
return true;
|
||
}
|
||
if (!val) {
|
||
return false;
|
||
}
|
||
if (CONTROL_CHARS.test(val) || COOKIE_OCTET_BLOCKED.test(val) || !COOKIE_OCTET.test(val)) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
function parseURL(input) {
|
||
let copyUrl;
|
||
if (input instanceof Request) {
|
||
copyUrl = input.url;
|
||
} else if (input instanceof URL) {
|
||
copyUrl = input.toString();
|
||
} else {
|
||
copyUrl = input;
|
||
}
|
||
copyUrl = copyUrl.replace(/^\./, "");
|
||
if (!copyUrl.includes("://")) {
|
||
copyUrl = "http://" + copyUrl;
|
||
}
|
||
return new URL(copyUrl);
|
||
}
|
||
class Cookie {
|
||
name;
|
||
value;
|
||
path;
|
||
domain;
|
||
expires;
|
||
maxAge;
|
||
secure;
|
||
httpOnly;
|
||
sameSite;
|
||
creationDate = Date.now();
|
||
creationIndex;
|
||
static cookiesCreated = 0;
|
||
constructor(options){
|
||
if (options) {
|
||
this.name = options.name;
|
||
this.value = options.value;
|
||
this.path = options.path;
|
||
this.domain = options.domain;
|
||
this.expires = options.expires;
|
||
this.maxAge = options.maxAge;
|
||
this.secure = options.secure;
|
||
this.httpOnly = options.httpOnly;
|
||
this.sameSite = options.sameSite;
|
||
if (options.creationDate) {
|
||
this.creationDate = options.creationDate;
|
||
}
|
||
}
|
||
Object.defineProperty(this, "creationIndex", {
|
||
configurable: false,
|
||
enumerable: false,
|
||
writable: true,
|
||
value: ++Cookie.cookiesCreated
|
||
});
|
||
}
|
||
static from(cookieStr) {
|
||
const options = {
|
||
name: undefined,
|
||
value: undefined,
|
||
path: undefined,
|
||
domain: undefined,
|
||
expires: undefined,
|
||
maxAge: undefined,
|
||
secure: undefined,
|
||
httpOnly: undefined,
|
||
sameSite: undefined,
|
||
creationDate: Date.now()
|
||
};
|
||
const unparsed = cookieStr.slice().trim();
|
||
const attrAndValueList = unparsed.split(";");
|
||
const keyValuePairString = trimTerminator(attrAndValueList.shift() || "").trim();
|
||
const keyValuePairEqualsIndex = keyValuePairString.indexOf("=");
|
||
if (keyValuePairEqualsIndex < 0) {
|
||
return new Cookie();
|
||
}
|
||
const name = keyValuePairString.slice(0, keyValuePairEqualsIndex);
|
||
const value = trimWrappingDoubleQuotes(keyValuePairString.slice(keyValuePairEqualsIndex + 1));
|
||
if (!(isValidName(name) && isValidValue(value))) {
|
||
return new Cookie();
|
||
}
|
||
options.name = name;
|
||
options.value = value;
|
||
while(attrAndValueList.length){
|
||
const cookieAV = attrAndValueList.shift()?.trim();
|
||
if (!cookieAV) {
|
||
continue;
|
||
}
|
||
const avSeperatorIndex = cookieAV.indexOf("=");
|
||
let attrKey, attrValue;
|
||
if (avSeperatorIndex === -1) {
|
||
attrKey = cookieAV;
|
||
attrValue = "";
|
||
} else {
|
||
attrKey = cookieAV.substr(0, avSeperatorIndex);
|
||
attrValue = cookieAV.substr(avSeperatorIndex + 1);
|
||
}
|
||
attrKey = attrKey.trim().toLowerCase();
|
||
if (attrValue) {
|
||
attrValue = attrValue.trim();
|
||
}
|
||
switch(attrKey){
|
||
case "expires":
|
||
if (attrValue) {
|
||
const expires = new Date(attrValue).getTime();
|
||
if (expires && !isNaN(expires)) {
|
||
options.expires = expires;
|
||
}
|
||
}
|
||
break;
|
||
case "max-age":
|
||
if (attrValue) {
|
||
const maxAge = parseInt(attrValue, 10);
|
||
if (!isNaN(maxAge)) {
|
||
options.maxAge = maxAge;
|
||
}
|
||
}
|
||
break;
|
||
case "domain":
|
||
if (attrValue) {
|
||
const domain = parseURL(attrValue).host;
|
||
if (domain) {
|
||
options.domain = domain;
|
||
}
|
||
}
|
||
break;
|
||
case "path":
|
||
if (attrValue) {
|
||
options.path = attrValue.startsWith("/") ? attrValue : "/" + attrValue;
|
||
}
|
||
break;
|
||
case "secure":
|
||
options.secure = true;
|
||
break;
|
||
case "httponly":
|
||
options.httpOnly = true;
|
||
break;
|
||
case "samesite":
|
||
{
|
||
const lowerCasedSameSite = attrValue.toLowerCase();
|
||
switch(lowerCasedSameSite){
|
||
case "strict":
|
||
options.sameSite = "Strict";
|
||
break;
|
||
case "lax":
|
||
options.sameSite = "Lax";
|
||
break;
|
||
case "none":
|
||
options.sameSite = "None";
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
return new Cookie(options);
|
||
}
|
||
isValid() {
|
||
return isValidName(this.name) && isValidValue(this.value);
|
||
}
|
||
canSendTo(url) {
|
||
const urlObj = parseURL(url);
|
||
if (this.secure && urlObj.protocol !== "https:") {
|
||
return false;
|
||
}
|
||
if (this.sameSite === "None" && !this.secure) return false;
|
||
if (this.path) {
|
||
if (this.path === urlObj.pathname) {
|
||
return true;
|
||
}
|
||
if (urlObj.pathname.startsWith(this.path) && this.path[this.path.length - 1] === "/") {
|
||
return true;
|
||
}
|
||
if (this.path.length < urlObj.pathname.length && urlObj.pathname.startsWith(this.path) && urlObj.pathname[this.path.length] === "/") {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
if (this.domain) {
|
||
const host = urlObj.host;
|
||
if (isSameDomainOrSubdomain(this.domain, host)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
getCookieString() {
|
||
return `${this.name || ""}=${this.value || ""}`;
|
||
}
|
||
setDomain(url) {
|
||
this.domain = parseURL(url).host;
|
||
}
|
||
setPath(url) {
|
||
const uriPath = parseURL(url).pathname;
|
||
if (!uriPath || uriPath[0] !== "/") {
|
||
this.path = "/";
|
||
} else {
|
||
const rightmostSlashIdx = uriPath.lastIndexOf("/");
|
||
if (rightmostSlashIdx <= 0) {
|
||
this.path = "/";
|
||
} else {
|
||
this.path = uriPath.slice(0, rightmostSlashIdx);
|
||
}
|
||
}
|
||
}
|
||
setExpires(exp) {
|
||
if (exp instanceof Date) {
|
||
this.expires = exp.getTime();
|
||
} else if (typeof exp === "number" && exp >= 0) {
|
||
this.expires = exp;
|
||
}
|
||
}
|
||
isExpired() {
|
||
if (this.maxAge !== undefined) {
|
||
if (Date.now() - this.creationDate >= this.maxAge * 1000) {
|
||
return true;
|
||
}
|
||
}
|
||
if (this.expires !== undefined) {
|
||
if (Date.now() - this.expires >= 0) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
toString() {
|
||
let str = this.getCookieString();
|
||
if (this.expires && this.expires !== Infinity) {
|
||
str += "; Expires=" + new Date(this.expires).toUTCString();
|
||
}
|
||
if (this.maxAge && this.maxAge !== Infinity) {
|
||
str += `; Max-Age=${this.maxAge}`;
|
||
}
|
||
if (this.domain) {
|
||
str += `; Domain=${this.domain}`;
|
||
}
|
||
if (this.path) {
|
||
str += `; Path=${this.path}`;
|
||
}
|
||
if (this.secure) {
|
||
str += "; Secure";
|
||
}
|
||
if (this.httpOnly) {
|
||
str += "; HttpOnly";
|
||
}
|
||
if (this.sameSite) {
|
||
str += `; SameSite=${this.sameSite}`;
|
||
}
|
||
return str;
|
||
}
|
||
clone() {
|
||
return new Cookie(JSON.parse(JSON.stringify(this)));
|
||
}
|
||
}
|
||
const strictMatchProps = [
|
||
"value",
|
||
"secure",
|
||
"httpOnly",
|
||
"maxAge",
|
||
"expires",
|
||
"sameSite"
|
||
];
|
||
function cookieMatches(options, comparedWith, strictMatch = false) {
|
||
if (options.path !== undefined && !comparedWith.path?.startsWith(options.path)) {
|
||
return false;
|
||
}
|
||
if (options.domain) {
|
||
if (!isSameDomainOrSubdomain(options.domain, comparedWith.domain)) {
|
||
return false;
|
||
}
|
||
}
|
||
if (options.name !== undefined && options.name !== comparedWith.name) {
|
||
return false;
|
||
}
|
||
if (strictMatch && strictMatchProps.some((propKey)=>options[propKey] !== undefined && options[propKey] !== comparedWith[propKey])) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
function cookieCompare(a, b) {
|
||
let cmp = 0;
|
||
const aPathLen = a.path?.length || 0;
|
||
const bPathLen = b.path?.length || 0;
|
||
cmp = bPathLen - aPathLen;
|
||
if (cmp !== 0) {
|
||
return cmp;
|
||
}
|
||
const aTime = a.creationDate || 2147483647000;
|
||
const bTime = b.creationDate || 2147483647000;
|
||
cmp = aTime - bTime;
|
||
if (cmp !== 0) {
|
||
return cmp;
|
||
}
|
||
cmp = a.creationIndex - b.creationIndex;
|
||
return cmp;
|
||
}
|
||
class CookieJar {
|
||
cookies = Array();
|
||
constructor(cookies){
|
||
this.replaceCookies(cookies);
|
||
}
|
||
setCookie(cookie, url) {
|
||
let cookieObj;
|
||
if (typeof cookie === "string") {
|
||
cookieObj = Cookie.from(cookie);
|
||
} else {
|
||
cookieObj = cookie;
|
||
}
|
||
if (url) {
|
||
if (!cookieObj.domain) {
|
||
cookieObj.setDomain(url);
|
||
}
|
||
if (!cookieObj.path) {
|
||
cookieObj.setPath(url);
|
||
}
|
||
}
|
||
if (!cookieObj.isValid()) {
|
||
return;
|
||
}
|
||
const foundCookie = this.getCookie(cookieObj);
|
||
if (foundCookie) {
|
||
const indexOfCookie = this.cookies.indexOf(foundCookie);
|
||
if (!cookieObj.isExpired()) {
|
||
this.cookies.splice(indexOfCookie, 1, cookieObj);
|
||
} else {
|
||
this.cookies.splice(indexOfCookie, 1);
|
||
}
|
||
} else if (!cookieObj.isExpired()) {
|
||
this.cookies.push(cookieObj);
|
||
}
|
||
this.cookies.sort(cookieCompare);
|
||
}
|
||
getCookie(options) {
|
||
const strictMatch = typeof options.isValid !== "function";
|
||
for (const [index, cookie] of this.cookies.entries()){
|
||
if (cookieMatches(options, cookie, strictMatch)) {
|
||
if (!cookie.isExpired()) {
|
||
return cookie;
|
||
} else {
|
||
this.cookies.splice(index, 1);
|
||
return undefined;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
getCookies(options) {
|
||
if (options) {
|
||
const matchedCookies = [];
|
||
const removeCookies = [];
|
||
for (const cookie of this.cookies){
|
||
if (cookieMatches(options, cookie)) {
|
||
if (!cookie.isExpired()) {
|
||
matchedCookies.push(cookie);
|
||
} else {
|
||
removeCookies.push(cookie);
|
||
}
|
||
}
|
||
}
|
||
if (removeCookies.length) {
|
||
this.cookies = this.cookies.filter((cookie)=>!removeCookies.includes(cookie));
|
||
}
|
||
return matchedCookies;
|
||
} else {
|
||
return this.cookies;
|
||
}
|
||
}
|
||
getCookieString(url) {
|
||
const searchCookie = new Cookie();
|
||
searchCookie.setDomain(url);
|
||
const cookiesToSend = this.getCookies(searchCookie).filter((cookie)=>{
|
||
return cookie.canSendTo(parseURL(url));
|
||
}).map((c)=>c.getCookieString()).join("; ");
|
||
return cookiesToSend;
|
||
}
|
||
toJSON() {
|
||
return this.cookies;
|
||
}
|
||
removeCookie(options) {
|
||
for (const [index, cookie] of this.cookies.entries()){
|
||
if (cookieMatches(options, cookie)) {
|
||
return this.cookies.splice(index, 1)[0];
|
||
}
|
||
}
|
||
}
|
||
removeCookies(options) {
|
||
if (options) {
|
||
const deletedCookies = [];
|
||
this.cookies = this.cookies.filter((cookie)=>{
|
||
if (cookieMatches(options, cookie)) {
|
||
deletedCookies.push(cookie);
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
return deletedCookies.length ? deletedCookies : undefined;
|
||
} else {
|
||
this.cookies = [];
|
||
}
|
||
}
|
||
replaceCookies(cookies) {
|
||
if (cookies?.length) {
|
||
if (typeof cookies[0].isValid === "function") {
|
||
this.cookies = cookies;
|
||
} else {
|
||
this.cookies = [];
|
||
for (const option of cookies){
|
||
this.cookies.push(new Cookie(option));
|
||
}
|
||
}
|
||
} else {
|
||
this.cookies = [];
|
||
}
|
||
}
|
||
}
|
||
function wrapFetch(options) {
|
||
const { cookieJar =new CookieJar() , fetch: fetch1 = globalThis.fetch } = options || {};
|
||
async function wrappedFetch(input, init) {
|
||
if (!input) {
|
||
return await fetch1(input);
|
||
}
|
||
const cookieString = cookieJar.getCookieString(input);
|
||
let interceptedInit;
|
||
if (init) {
|
||
interceptedInit = init;
|
||
} else if (input instanceof Request) {
|
||
interceptedInit = input;
|
||
} else {
|
||
interceptedInit = {};
|
||
}
|
||
if (!(interceptedInit.headers instanceof Headers)) {
|
||
interceptedInit.headers = new Headers(interceptedInit.headers || {});
|
||
}
|
||
interceptedInit.headers.set("cookie", cookieString);
|
||
const response = await fetch1(input, interceptedInit);
|
||
response.headers.forEach((value, key)=>{
|
||
if (key.toLowerCase() === "set-cookie") {
|
||
cookieJar.setCookie(value, input);
|
||
}
|
||
});
|
||
return response;
|
||
}
|
||
return wrappedFetch;
|
||
}
|
||
const base64abc = [
|
||
"A",
|
||
"B",
|
||
"C",
|
||
"D",
|
||
"E",
|
||
"F",
|
||
"G",
|
||
"H",
|
||
"I",
|
||
"J",
|
||
"K",
|
||
"L",
|
||
"M",
|
||
"N",
|
||
"O",
|
||
"P",
|
||
"Q",
|
||
"R",
|
||
"S",
|
||
"T",
|
||
"U",
|
||
"V",
|
||
"W",
|
||
"X",
|
||
"Y",
|
||
"Z",
|
||
"a",
|
||
"b",
|
||
"c",
|
||
"d",
|
||
"e",
|
||
"f",
|
||
"g",
|
||
"h",
|
||
"i",
|
||
"j",
|
||
"k",
|
||
"l",
|
||
"m",
|
||
"n",
|
||
"o",
|
||
"p",
|
||
"q",
|
||
"r",
|
||
"s",
|
||
"t",
|
||
"u",
|
||
"v",
|
||
"w",
|
||
"x",
|
||
"y",
|
||
"z",
|
||
"0",
|
||
"1",
|
||
"2",
|
||
"3",
|
||
"4",
|
||
"5",
|
||
"6",
|
||
"7",
|
||
"8",
|
||
"9",
|
||
"+",
|
||
"/"
|
||
];
|
||
function encode(data) {
|
||
const uint8 = typeof data === "string" ? new TextEncoder().encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data);
|
||
let result = "", i;
|
||
const l = uint8.length;
|
||
for(i = 2; i < l; i += 3){
|
||
result += base64abc[uint8[i - 2] >> 2];
|
||
result += base64abc[(uint8[i - 2] & 0x03) << 4 | uint8[i - 1] >> 4];
|
||
result += base64abc[(uint8[i - 1] & 0x0f) << 2 | uint8[i] >> 6];
|
||
result += base64abc[uint8[i] & 0x3f];
|
||
}
|
||
if (i === l + 1) {
|
||
result += base64abc[uint8[i - 2] >> 2];
|
||
result += base64abc[(uint8[i - 2] & 0x03) << 4];
|
||
result += "==";
|
||
}
|
||
if (i === l) {
|
||
result += base64abc[uint8[i - 2] >> 2];
|
||
result += base64abc[(uint8[i - 2] & 0x03) << 4 | uint8[i - 1] >> 4];
|
||
result += base64abc[(uint8[i - 1] & 0x0f) << 2];
|
||
result += "=";
|
||
}
|
||
return result;
|
||
}
|
||
function decode(b64) {
|
||
const binString = atob(b64);
|
||
const size = binString.length;
|
||
const bytes = new Uint8Array(size);
|
||
for(let i = 0; i < size; i++){
|
||
bytes[i] = binString.charCodeAt(i);
|
||
}
|
||
return bytes;
|
||
}
|
||
const mod = {
|
||
encode: encode,
|
||
decode: decode
|
||
};
|
||
class DenoStdInternalError extends Error {
|
||
constructor(message){
|
||
super(message);
|
||
this.name = "DenoStdInternalError";
|
||
}
|
||
}
|
||
function assert(expr, msg = "") {
|
||
if (!expr) {
|
||
throw new DenoStdInternalError(msg);
|
||
}
|
||
}
|
||
const { hasOwn } = Object;
|
||
function get(obj, key) {
|
||
if (hasOwn(obj, key)) {
|
||
return obj[key];
|
||
}
|
||
}
|
||
function getForce(obj, key) {
|
||
const v = get(obj, key);
|
||
assert(v != null);
|
||
return v;
|
||
}
|
||
function isNumber(x) {
|
||
if (typeof x === "number") return true;
|
||
if (/^0x[0-9a-f]+$/i.test(String(x))) return true;
|
||
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(String(x));
|
||
}
|
||
function hasKey(obj, keys) {
|
||
let o = obj;
|
||
keys.slice(0, -1).forEach((key)=>{
|
||
o = get(o, key) ?? {};
|
||
});
|
||
const key = keys[keys.length - 1];
|
||
return hasOwn(o, key);
|
||
}
|
||
function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean = false , default: defaults = {} , stopEarly =false , string =[] , collect =[] , negatable =[] , unknown =(i)=>i } = {}) {
|
||
const flags = {
|
||
bools: {},
|
||
strings: {},
|
||
unknownFn: unknown,
|
||
allBools: false,
|
||
collect: {},
|
||
negatable: {}
|
||
};
|
||
if (__boolean !== undefined) {
|
||
if (typeof __boolean === "boolean") {
|
||
flags.allBools = !!__boolean;
|
||
} else {
|
||
const booleanArgs = typeof __boolean === "string" ? [
|
||
__boolean
|
||
] : __boolean;
|
||
for (const key of booleanArgs.filter(Boolean)){
|
||
flags.bools[key] = true;
|
||
}
|
||
}
|
||
}
|
||
const aliases = {};
|
||
if (alias !== undefined) {
|
||
for(const key1 in alias){
|
||
const val = getForce(alias, key1);
|
||
if (typeof val === "string") {
|
||
aliases[key1] = [
|
||
val
|
||
];
|
||
} else {
|
||
aliases[key1] = val;
|
||
}
|
||
for (const alias1 of getForce(aliases, key1)){
|
||
aliases[alias1] = [
|
||
key1
|
||
].concat(aliases[key1].filter((y)=>alias1 !== y));
|
||
}
|
||
}
|
||
}
|
||
if (string !== undefined) {
|
||
const stringArgs = typeof string === "string" ? [
|
||
string
|
||
] : string;
|
||
for (const key2 of stringArgs.filter(Boolean)){
|
||
flags.strings[key2] = true;
|
||
const alias2 = get(aliases, key2);
|
||
if (alias2) {
|
||
for (const al of alias2){
|
||
flags.strings[al] = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (collect !== undefined) {
|
||
const collectArgs = typeof collect === "string" ? [
|
||
collect
|
||
] : collect;
|
||
for (const key3 of collectArgs.filter(Boolean)){
|
||
flags.collect[key3] = true;
|
||
const alias3 = get(aliases, key3);
|
||
if (alias3) {
|
||
for (const al1 of alias3){
|
||
flags.collect[al1] = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (negatable !== undefined) {
|
||
const negatableArgs = typeof negatable === "string" ? [
|
||
negatable
|
||
] : negatable;
|
||
for (const key4 of negatableArgs.filter(Boolean)){
|
||
flags.negatable[key4] = true;
|
||
const alias4 = get(aliases, key4);
|
||
if (alias4) {
|
||
for (const al2 of alias4){
|
||
flags.negatable[al2] = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
const argv = {
|
||
_: []
|
||
};
|
||
function argDefined(key, arg) {
|
||
return flags.allBools && /^--[^=]+$/.test(arg) || get(flags.bools, key) || !!get(flags.strings, key) || !!get(aliases, key);
|
||
}
|
||
function setKey(obj, name, value, collect = true) {
|
||
let o = obj;
|
||
const keys = name.split(".");
|
||
keys.slice(0, -1).forEach(function(key) {
|
||
if (get(o, key) === undefined) {
|
||
o[key] = {};
|
||
}
|
||
o = get(o, key);
|
||
});
|
||
const key = keys[keys.length - 1];
|
||
const collectable = collect && !!get(flags.collect, name);
|
||
if (!collectable) {
|
||
o[key] = value;
|
||
} else if (get(o, key) === undefined) {
|
||
o[key] = [
|
||
value
|
||
];
|
||
} else if (Array.isArray(get(o, key))) {
|
||
o[key].push(value);
|
||
} else {
|
||
o[key] = [
|
||
get(o, key),
|
||
value
|
||
];
|
||
}
|
||
}
|
||
function setArg(key, val, arg = undefined, collect) {
|
||
if (arg && flags.unknownFn && !argDefined(key, arg)) {
|
||
if (flags.unknownFn(arg, key, val) === false) return;
|
||
}
|
||
const value = !get(flags.strings, key) && isNumber(val) ? Number(val) : val;
|
||
setKey(argv, key, value, collect);
|
||
const alias = get(aliases, key);
|
||
if (alias) {
|
||
for (const x of alias){
|
||
setKey(argv, x, value, collect);
|
||
}
|
||
}
|
||
}
|
||
function aliasIsBoolean(key) {
|
||
return getForce(aliases, key).some((x)=>typeof get(flags.bools, x) === "boolean");
|
||
}
|
||
let notFlags = [];
|
||
if (args.includes("--")) {
|
||
notFlags = args.slice(args.indexOf("--") + 1);
|
||
args = args.slice(0, args.indexOf("--"));
|
||
}
|
||
for(let i = 0; i < args.length; i++){
|
||
const arg = args[i];
|
||
if (/^--.+=/.test(arg)) {
|
||
const m = arg.match(/^--([^=]+)=(.*)$/s);
|
||
assert(m != null);
|
||
const [, key5, value] = m;
|
||
if (flags.bools[key5]) {
|
||
const booleanValue = value !== "false";
|
||
setArg(key5, booleanValue, arg);
|
||
} else {
|
||
setArg(key5, value, arg);
|
||
}
|
||
} else if (/^--no-.+/.test(arg) && get(flags.negatable, arg.replace(/^--no-/, ""))) {
|
||
const m1 = arg.match(/^--no-(.+)/);
|
||
assert(m1 != null);
|
||
setArg(m1[1], false, arg, false);
|
||
} else if (/^--.+/.test(arg)) {
|
||
const m2 = arg.match(/^--(.+)/);
|
||
assert(m2 != null);
|
||
const [, key6] = m2;
|
||
const next = args[i + 1];
|
||
if (next !== undefined && !/^-/.test(next) && !get(flags.bools, key6) && !flags.allBools && (get(aliases, key6) ? !aliasIsBoolean(key6) : true)) {
|
||
setArg(key6, next, arg);
|
||
i++;
|
||
} else if (/^(true|false)$/.test(next)) {
|
||
setArg(key6, next === "true", arg);
|
||
i++;
|
||
} else {
|
||
setArg(key6, get(flags.strings, key6) ? "" : true, arg);
|
||
}
|
||
} else if (/^-[^-]+/.test(arg)) {
|
||
const letters = arg.slice(1, -1).split("");
|
||
let broken = false;
|
||
for(let j = 0; j < letters.length; j++){
|
||
const next1 = arg.slice(j + 2);
|
||
if (next1 === "-") {
|
||
setArg(letters[j], next1, arg);
|
||
continue;
|
||
}
|
||
if (/[A-Za-z]/.test(letters[j]) && /=/.test(next1)) {
|
||
setArg(letters[j], next1.split(/=(.+)/)[1], arg);
|
||
broken = true;
|
||
break;
|
||
}
|
||
if (/[A-Za-z]/.test(letters[j]) && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next1)) {
|
||
setArg(letters[j], next1, arg);
|
||
broken = true;
|
||
break;
|
||
}
|
||
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
|
||
setArg(letters[j], arg.slice(j + 2), arg);
|
||
broken = true;
|
||
break;
|
||
} else {
|
||
setArg(letters[j], get(flags.strings, letters[j]) ? "" : true, arg);
|
||
}
|
||
}
|
||
const [key7] = arg.slice(-1);
|
||
if (!broken && key7 !== "-") {
|
||
if (args[i + 1] && !/^(-|--)[^-]/.test(args[i + 1]) && !get(flags.bools, key7) && (get(aliases, key7) ? !aliasIsBoolean(key7) : true)) {
|
||
setArg(key7, args[i + 1], arg);
|
||
i++;
|
||
} else if (args[i + 1] && /^(true|false)$/.test(args[i + 1])) {
|
||
setArg(key7, args[i + 1] === "true", arg);
|
||
i++;
|
||
} else {
|
||
setArg(key7, get(flags.strings, key7) ? "" : true, arg);
|
||
}
|
||
}
|
||
} else {
|
||
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
|
||
argv._.push(flags.strings["_"] ?? !isNumber(arg) ? arg : Number(arg));
|
||
}
|
||
if (stopEarly) {
|
||
argv._.push(...args.slice(i + 1));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
for (const [key8, value1] of Object.entries(defaults)){
|
||
if (!hasKey(argv, key8.split("."))) {
|
||
setKey(argv, key8, value1);
|
||
if (aliases[key8]) {
|
||
for (const x of aliases[key8]){
|
||
setKey(argv, x, value1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
for (const key9 of Object.keys(flags.bools)){
|
||
if (!hasKey(argv, key9.split("."))) {
|
||
const value2 = get(flags.collect, key9) ? [] : false;
|
||
setKey(argv, key9, value2, false);
|
||
}
|
||
}
|
||
for (const key10 of Object.keys(flags.strings)){
|
||
if (!hasKey(argv, key10.split(".")) && get(flags.collect, key10)) {
|
||
setKey(argv, key10, [], false);
|
||
}
|
||
}
|
||
if (doubleDash) {
|
||
argv["--"] = [];
|
||
for (const key11 of notFlags){
|
||
argv["--"].push(key11);
|
||
}
|
||
} else {
|
||
for (const key12 of notFlags){
|
||
argv._.push(key12);
|
||
}
|
||
}
|
||
return argv;
|
||
}
|
||
const mod1 = {
|
||
parse: parse
|
||
};
|
||
class BytesList {
|
||
#len = 0;
|
||
#chunks = [];
|
||
constructor(){}
|
||
size() {
|
||
return this.#len;
|
||
}
|
||
add(value, start = 0, end = value.byteLength) {
|
||
if (value.byteLength === 0 || end - start === 0) {
|
||
return;
|
||
}
|
||
checkRange(start, end, value.byteLength);
|
||
this.#chunks.push({
|
||
value,
|
||
end,
|
||
start,
|
||
offset: this.#len
|
||
});
|
||
this.#len += end - start;
|
||
}
|
||
shift(n) {
|
||
if (n === 0) {
|
||
return;
|
||
}
|
||
if (this.#len <= n) {
|
||
this.#chunks = [];
|
||
this.#len = 0;
|
||
return;
|
||
}
|
||
const idx = this.getChunkIndex(n);
|
||
this.#chunks.splice(0, idx);
|
||
const [chunk] = this.#chunks;
|
||
if (chunk) {
|
||
const diff = n - chunk.offset;
|
||
chunk.start += diff;
|
||
}
|
||
let offset = 0;
|
||
for (const chunk1 of this.#chunks){
|
||
chunk1.offset = offset;
|
||
offset += chunk1.end - chunk1.start;
|
||
}
|
||
this.#len = offset;
|
||
}
|
||
getChunkIndex(pos) {
|
||
let max = this.#chunks.length;
|
||
let min = 0;
|
||
while(true){
|
||
const i = min + Math.floor((max - min) / 2);
|
||
if (i < 0 || this.#chunks.length <= i) {
|
||
return -1;
|
||
}
|
||
const { offset , start , end } = this.#chunks[i];
|
||
const len = end - start;
|
||
if (offset <= pos && pos < offset + len) {
|
||
return i;
|
||
} else if (offset + len <= pos) {
|
||
min = i + 1;
|
||
} else {
|
||
max = i - 1;
|
||
}
|
||
}
|
||
}
|
||
get(i) {
|
||
if (i < 0 || this.#len <= i) {
|
||
throw new Error("out of range");
|
||
}
|
||
const idx = this.getChunkIndex(i);
|
||
const { value , offset , start } = this.#chunks[idx];
|
||
return value[start + i - offset];
|
||
}
|
||
*iterator(start = 0) {
|
||
const startIdx = this.getChunkIndex(start);
|
||
if (startIdx < 0) return;
|
||
const first = this.#chunks[startIdx];
|
||
let firstOffset = start - first.offset;
|
||
for(let i = startIdx; i < this.#chunks.length; i++){
|
||
const chunk = this.#chunks[i];
|
||
for(let j = chunk.start + firstOffset; j < chunk.end; j++){
|
||
yield chunk.value[j];
|
||
}
|
||
firstOffset = 0;
|
||
}
|
||
}
|
||
slice(start, end = this.#len) {
|
||
if (end === start) {
|
||
return new Uint8Array();
|
||
}
|
||
checkRange(start, end, this.#len);
|
||
const result = new Uint8Array(end - start);
|
||
const startIdx = this.getChunkIndex(start);
|
||
const endIdx = this.getChunkIndex(end - 1);
|
||
let written = 0;
|
||
for(let i = startIdx; i < endIdx; i++){
|
||
const chunk = this.#chunks[i];
|
||
const len = chunk.end - chunk.start;
|
||
result.set(chunk.value.subarray(chunk.start, chunk.end), written);
|
||
written += len;
|
||
}
|
||
const last = this.#chunks[endIdx];
|
||
const rest = end - start - written;
|
||
result.set(last.value.subarray(last.start, last.start + rest), written);
|
||
return result;
|
||
}
|
||
concat() {
|
||
const result = new Uint8Array(this.#len);
|
||
let sum = 0;
|
||
for (const { value , start , end } of this.#chunks){
|
||
result.set(value.subarray(start, end), sum);
|
||
sum += end - start;
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
function checkRange(start, end, len) {
|
||
if (start < 0 || len < start || end < 0 || len < end || end < start) {
|
||
throw new Error("invalid range");
|
||
}
|
||
}
|
||
function concat(...buf) {
|
||
let length = 0;
|
||
for (const b of buf){
|
||
length += b.length;
|
||
}
|
||
const output = new Uint8Array(length);
|
||
let index = 0;
|
||
for (const b1 of buf){
|
||
output.set(b1, index);
|
||
index += b1.length;
|
||
}
|
||
return output;
|
||
}
|
||
function copy(src, dst, off = 0) {
|
||
off = Math.max(0, Math.min(off, dst.byteLength));
|
||
const dstBytesAvailable = dst.byteLength - off;
|
||
if (src.byteLength > dstBytesAvailable) {
|
||
src = src.subarray(0, dstBytesAvailable);
|
||
}
|
||
dst.set(src, off);
|
||
return src.byteLength;
|
||
}
|
||
const MIN_READ = 32 * 1024;
|
||
const MAX_SIZE = 2 ** 32 - 2;
|
||
class Buffer {
|
||
#buf;
|
||
#off = 0;
|
||
constructor(ab){
|
||
this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab);
|
||
}
|
||
bytes(options = {
|
||
copy: true
|
||
}) {
|
||
if (options.copy === false) return this.#buf.subarray(this.#off);
|
||
return this.#buf.slice(this.#off);
|
||
}
|
||
empty() {
|
||
return this.#buf.byteLength <= this.#off;
|
||
}
|
||
get length() {
|
||
return this.#buf.byteLength - this.#off;
|
||
}
|
||
get capacity() {
|
||
return this.#buf.buffer.byteLength;
|
||
}
|
||
truncate(n) {
|
||
if (n === 0) {
|
||
this.reset();
|
||
return;
|
||
}
|
||
if (n < 0 || n > this.length) {
|
||
throw Error("bytes.Buffer: truncation out of range");
|
||
}
|
||
this.#reslice(this.#off + n);
|
||
}
|
||
reset() {
|
||
this.#reslice(0);
|
||
this.#off = 0;
|
||
}
|
||
#tryGrowByReslice(n) {
|
||
const l = this.#buf.byteLength;
|
||
if (n <= this.capacity - l) {
|
||
this.#reslice(l + n);
|
||
return l;
|
||
}
|
||
return -1;
|
||
}
|
||
#reslice(len) {
|
||
assert(len <= this.#buf.buffer.byteLength);
|
||
this.#buf = new Uint8Array(this.#buf.buffer, 0, len);
|
||
}
|
||
readSync(p) {
|
||
if (this.empty()) {
|
||
this.reset();
|
||
if (p.byteLength === 0) {
|
||
return 0;
|
||
}
|
||
return null;
|
||
}
|
||
const nread = copy(this.#buf.subarray(this.#off), p);
|
||
this.#off += nread;
|
||
return nread;
|
||
}
|
||
read(p) {
|
||
const rr = this.readSync(p);
|
||
return Promise.resolve(rr);
|
||
}
|
||
writeSync(p) {
|
||
const m = this.#grow(p.byteLength);
|
||
return copy(p, this.#buf, m);
|
||
}
|
||
write(p) {
|
||
const n = this.writeSync(p);
|
||
return Promise.resolve(n);
|
||
}
|
||
#grow(n1) {
|
||
const m = this.length;
|
||
if (m === 0 && this.#off !== 0) {
|
||
this.reset();
|
||
}
|
||
const i = this.#tryGrowByReslice(n1);
|
||
if (i >= 0) {
|
||
return i;
|
||
}
|
||
const c = this.capacity;
|
||
if (n1 <= Math.floor(c / 2) - m) {
|
||
copy(this.#buf.subarray(this.#off), this.#buf);
|
||
} else if (c + n1 > MAX_SIZE) {
|
||
throw new Error("The buffer cannot be grown beyond the maximum size.");
|
||
} else {
|
||
const buf = new Uint8Array(Math.min(2 * c + n1, MAX_SIZE));
|
||
copy(this.#buf.subarray(this.#off), buf);
|
||
this.#buf = buf;
|
||
}
|
||
this.#off = 0;
|
||
this.#reslice(Math.min(m + n1, MAX_SIZE));
|
||
return m;
|
||
}
|
||
grow(n) {
|
||
if (n < 0) {
|
||
throw Error("Buffer.grow: negative count");
|
||
}
|
||
const m = this.#grow(n);
|
||
this.#reslice(m);
|
||
}
|
||
async readFrom(r) {
|
||
let n = 0;
|
||
const tmp = new Uint8Array(MIN_READ);
|
||
while(true){
|
||
const shouldGrow = this.capacity - this.length < MIN_READ;
|
||
const buf = shouldGrow ? tmp : new Uint8Array(this.#buf.buffer, this.length);
|
||
const nread = await r.read(buf);
|
||
if (nread === null) {
|
||
return n;
|
||
}
|
||
if (shouldGrow) this.writeSync(buf.subarray(0, nread));
|
||
else this.#reslice(this.length + nread);
|
||
n += nread;
|
||
}
|
||
}
|
||
readFromSync(r) {
|
||
let n = 0;
|
||
const tmp = new Uint8Array(MIN_READ);
|
||
while(true){
|
||
const shouldGrow = this.capacity - this.length < MIN_READ;
|
||
const buf = shouldGrow ? tmp : new Uint8Array(this.#buf.buffer, this.length);
|
||
const nread = r.readSync(buf);
|
||
if (nread === null) {
|
||
return n;
|
||
}
|
||
if (shouldGrow) this.writeSync(buf.subarray(0, nread));
|
||
else this.#reslice(this.length + nread);
|
||
n += nread;
|
||
}
|
||
}
|
||
}
|
||
const MIN_BUF_SIZE = 16;
|
||
const CR = "\r".charCodeAt(0);
|
||
const LF = "\n".charCodeAt(0);
|
||
class BufferFullError extends Error {
|
||
name;
|
||
constructor(partial){
|
||
super("Buffer full");
|
||
this.partial = partial;
|
||
this.name = "BufferFullError";
|
||
}
|
||
partial;
|
||
}
|
||
class PartialReadError extends Error {
|
||
name = "PartialReadError";
|
||
partial;
|
||
constructor(){
|
||
super("Encountered UnexpectedEof, data only partially read");
|
||
}
|
||
}
|
||
class BufReader {
|
||
#buf;
|
||
#rd;
|
||
#r = 0;
|
||
#w = 0;
|
||
#eof = false;
|
||
static create(r, size = 4096) {
|
||
return r instanceof BufReader ? r : new BufReader(r, size);
|
||
}
|
||
constructor(rd, size = 4096){
|
||
if (size < 16) {
|
||
size = MIN_BUF_SIZE;
|
||
}
|
||
this.#reset(new Uint8Array(size), rd);
|
||
}
|
||
size() {
|
||
return this.#buf.byteLength;
|
||
}
|
||
buffered() {
|
||
return this.#w - this.#r;
|
||
}
|
||
#fill = async ()=>{
|
||
if (this.#r > 0) {
|
||
this.#buf.copyWithin(0, this.#r, this.#w);
|
||
this.#w -= this.#r;
|
||
this.#r = 0;
|
||
}
|
||
if (this.#w >= this.#buf.byteLength) {
|
||
throw Error("bufio: tried to fill full buffer");
|
||
}
|
||
for(let i = 100; i > 0; i--){
|
||
const rr = await this.#rd.read(this.#buf.subarray(this.#w));
|
||
if (rr === null) {
|
||
this.#eof = true;
|
||
return;
|
||
}
|
||
assert(rr >= 0, "negative read");
|
||
this.#w += rr;
|
||
if (rr > 0) {
|
||
return;
|
||
}
|
||
}
|
||
throw new Error(`No progress after ${100} read() calls`);
|
||
};
|
||
reset(r) {
|
||
this.#reset(this.#buf, r);
|
||
}
|
||
#reset = (buf, rd)=>{
|
||
this.#buf = buf;
|
||
this.#rd = rd;
|
||
this.#eof = false;
|
||
};
|
||
async read(p) {
|
||
let rr = p.byteLength;
|
||
if (p.byteLength === 0) return rr;
|
||
if (this.#r === this.#w) {
|
||
if (p.byteLength >= this.#buf.byteLength) {
|
||
const rr1 = await this.#rd.read(p);
|
||
const nread = rr1 ?? 0;
|
||
assert(nread >= 0, "negative read");
|
||
return rr1;
|
||
}
|
||
this.#r = 0;
|
||
this.#w = 0;
|
||
rr = await this.#rd.read(this.#buf);
|
||
if (rr === 0 || rr === null) return rr;
|
||
assert(rr >= 0, "negative read");
|
||
this.#w += rr;
|
||
}
|
||
const copied = copy(this.#buf.subarray(this.#r, this.#w), p, 0);
|
||
this.#r += copied;
|
||
return copied;
|
||
}
|
||
async readFull(p) {
|
||
let bytesRead = 0;
|
||
while(bytesRead < p.length){
|
||
try {
|
||
const rr = await this.read(p.subarray(bytesRead));
|
||
if (rr === null) {
|
||
if (bytesRead === 0) {
|
||
return null;
|
||
} else {
|
||
throw new PartialReadError();
|
||
}
|
||
}
|
||
bytesRead += rr;
|
||
} catch (err) {
|
||
if (err instanceof PartialReadError) {
|
||
err.partial = p.subarray(0, bytesRead);
|
||
} else if (err instanceof Error) {
|
||
const e = new PartialReadError();
|
||
e.partial = p.subarray(0, bytesRead);
|
||
e.stack = err.stack;
|
||
e.message = err.message;
|
||
e.cause = err.cause;
|
||
throw err;
|
||
}
|
||
throw err;
|
||
}
|
||
}
|
||
return p;
|
||
}
|
||
async readByte() {
|
||
while(this.#r === this.#w){
|
||
if (this.#eof) return null;
|
||
await this.#fill();
|
||
}
|
||
const c = this.#buf[this.#r];
|
||
this.#r++;
|
||
return c;
|
||
}
|
||
async readString(delim) {
|
||
if (delim.length !== 1) {
|
||
throw new Error("Delimiter should be a single character");
|
||
}
|
||
const buffer = await this.readSlice(delim.charCodeAt(0));
|
||
if (buffer === null) return null;
|
||
return new TextDecoder().decode(buffer);
|
||
}
|
||
async readLine() {
|
||
let line = null;
|
||
try {
|
||
line = await this.readSlice(LF);
|
||
} catch (err) {
|
||
let partial;
|
||
if (err instanceof PartialReadError) {
|
||
partial = err.partial;
|
||
assert(partial instanceof Uint8Array, "bufio: caught error from `readSlice()` without `partial` property");
|
||
}
|
||
if (!(err instanceof BufferFullError)) {
|
||
throw err;
|
||
}
|
||
partial = err.partial;
|
||
if (!this.#eof && partial && partial.byteLength > 0 && partial[partial.byteLength - 1] === CR) {
|
||
assert(this.#r > 0, "bufio: tried to rewind past start of buffer");
|
||
this.#r--;
|
||
partial = partial.subarray(0, partial.byteLength - 1);
|
||
}
|
||
if (partial) {
|
||
return {
|
||
line: partial,
|
||
more: !this.#eof
|
||
};
|
||
}
|
||
}
|
||
if (line === null) {
|
||
return null;
|
||
}
|
||
if (line.byteLength === 0) {
|
||
return {
|
||
line,
|
||
more: false
|
||
};
|
||
}
|
||
if (line[line.byteLength - 1] == LF) {
|
||
let drop = 1;
|
||
if (line.byteLength > 1 && line[line.byteLength - 2] === CR) {
|
||
drop = 2;
|
||
}
|
||
line = line.subarray(0, line.byteLength - drop);
|
||
}
|
||
return {
|
||
line,
|
||
more: false
|
||
};
|
||
}
|
||
async readSlice(delim) {
|
||
let s = 0;
|
||
let slice;
|
||
while(true){
|
||
let i = this.#buf.subarray(this.#r + s, this.#w).indexOf(delim);
|
||
if (i >= 0) {
|
||
i += s;
|
||
slice = this.#buf.subarray(this.#r, this.#r + i + 1);
|
||
this.#r += i + 1;
|
||
break;
|
||
}
|
||
if (this.#eof) {
|
||
if (this.#r === this.#w) {
|
||
return null;
|
||
}
|
||
slice = this.#buf.subarray(this.#r, this.#w);
|
||
this.#r = this.#w;
|
||
break;
|
||
}
|
||
if (this.buffered() >= this.#buf.byteLength) {
|
||
this.#r = this.#w;
|
||
const oldbuf = this.#buf;
|
||
const newbuf = this.#buf.slice(0);
|
||
this.#buf = newbuf;
|
||
throw new BufferFullError(oldbuf);
|
||
}
|
||
s = this.#w - this.#r;
|
||
try {
|
||
await this.#fill();
|
||
} catch (err) {
|
||
if (err instanceof PartialReadError) {
|
||
err.partial = slice;
|
||
} else if (err instanceof Error) {
|
||
const e = new PartialReadError();
|
||
e.partial = slice;
|
||
e.stack = err.stack;
|
||
e.message = err.message;
|
||
e.cause = err.cause;
|
||
throw err;
|
||
}
|
||
throw err;
|
||
}
|
||
}
|
||
return slice;
|
||
}
|
||
async peek(n) {
|
||
if (n < 0) {
|
||
throw Error("negative count");
|
||
}
|
||
let avail = this.#w - this.#r;
|
||
while(avail < n && avail < this.#buf.byteLength && !this.#eof){
|
||
try {
|
||
await this.#fill();
|
||
} catch (err) {
|
||
if (err instanceof PartialReadError) {
|
||
err.partial = this.#buf.subarray(this.#r, this.#w);
|
||
} else if (err instanceof Error) {
|
||
const e = new PartialReadError();
|
||
e.partial = this.#buf.subarray(this.#r, this.#w);
|
||
e.stack = err.stack;
|
||
e.message = err.message;
|
||
e.cause = err.cause;
|
||
throw err;
|
||
}
|
||
throw err;
|
||
}
|
||
avail = this.#w - this.#r;
|
||
}
|
||
if (avail === 0 && this.#eof) {
|
||
return null;
|
||
} else if (avail < n && this.#eof) {
|
||
return this.#buf.subarray(this.#r, this.#r + avail);
|
||
} else if (avail < n) {
|
||
throw new BufferFullError(this.#buf.subarray(this.#r, this.#w));
|
||
}
|
||
return this.#buf.subarray(this.#r, this.#r + n);
|
||
}
|
||
}
|
||
class AbstractBufBase {
|
||
buf;
|
||
usedBufferBytes = 0;
|
||
err = null;
|
||
constructor(buf){
|
||
this.buf = buf;
|
||
}
|
||
size() {
|
||
return this.buf.byteLength;
|
||
}
|
||
available() {
|
||
return this.buf.byteLength - this.usedBufferBytes;
|
||
}
|
||
buffered() {
|
||
return this.usedBufferBytes;
|
||
}
|
||
}
|
||
class BufWriter extends AbstractBufBase {
|
||
#writer;
|
||
static create(writer, size = 4096) {
|
||
return writer instanceof BufWriter ? writer : new BufWriter(writer, size);
|
||
}
|
||
constructor(writer, size = 4096){
|
||
super(new Uint8Array(size <= 0 ? 4096 : size));
|
||
this.#writer = writer;
|
||
}
|
||
reset(w) {
|
||
this.err = null;
|
||
this.usedBufferBytes = 0;
|
||
this.#writer = w;
|
||
}
|
||
async flush() {
|
||
if (this.err !== null) throw this.err;
|
||
if (this.usedBufferBytes === 0) return;
|
||
try {
|
||
const p = this.buf.subarray(0, this.usedBufferBytes);
|
||
let nwritten = 0;
|
||
while(nwritten < p.length){
|
||
nwritten += await this.#writer.write(p.subarray(nwritten));
|
||
}
|
||
} catch (e) {
|
||
if (e instanceof Error) {
|
||
this.err = e;
|
||
}
|
||
throw e;
|
||
}
|
||
this.buf = new Uint8Array(this.buf.length);
|
||
this.usedBufferBytes = 0;
|
||
}
|
||
async write(data) {
|
||
if (this.err !== null) throw this.err;
|
||
if (data.length === 0) return 0;
|
||
let totalBytesWritten = 0;
|
||
let numBytesWritten = 0;
|
||
while(data.byteLength > this.available()){
|
||
if (this.buffered() === 0) {
|
||
try {
|
||
numBytesWritten = await this.#writer.write(data);
|
||
} catch (e) {
|
||
if (e instanceof Error) {
|
||
this.err = e;
|
||
}
|
||
throw e;
|
||
}
|
||
} else {
|
||
numBytesWritten = copy(data, this.buf, this.usedBufferBytes);
|
||
this.usedBufferBytes += numBytesWritten;
|
||
await this.flush();
|
||
}
|
||
totalBytesWritten += numBytesWritten;
|
||
data = data.subarray(numBytesWritten);
|
||
}
|
||
numBytesWritten = copy(data, this.buf, this.usedBufferBytes);
|
||
this.usedBufferBytes += numBytesWritten;
|
||
totalBytesWritten += numBytesWritten;
|
||
return totalBytesWritten;
|
||
}
|
||
}
|
||
class BufWriterSync extends AbstractBufBase {
|
||
#writer;
|
||
static create(writer, size = 4096) {
|
||
return writer instanceof BufWriterSync ? writer : new BufWriterSync(writer, size);
|
||
}
|
||
constructor(writer, size = 4096){
|
||
super(new Uint8Array(size <= 0 ? 4096 : size));
|
||
this.#writer = writer;
|
||
}
|
||
reset(w) {
|
||
this.err = null;
|
||
this.usedBufferBytes = 0;
|
||
this.#writer = w;
|
||
}
|
||
flush() {
|
||
if (this.err !== null) throw this.err;
|
||
if (this.usedBufferBytes === 0) return;
|
||
try {
|
||
const p = this.buf.subarray(0, this.usedBufferBytes);
|
||
let nwritten = 0;
|
||
while(nwritten < p.length){
|
||
nwritten += this.#writer.writeSync(p.subarray(nwritten));
|
||
}
|
||
} catch (e) {
|
||
if (e instanceof Error) {
|
||
this.err = e;
|
||
}
|
||
throw e;
|
||
}
|
||
this.buf = new Uint8Array(this.buf.length);
|
||
this.usedBufferBytes = 0;
|
||
}
|
||
writeSync(data) {
|
||
if (this.err !== null) throw this.err;
|
||
if (data.length === 0) return 0;
|
||
let totalBytesWritten = 0;
|
||
let numBytesWritten = 0;
|
||
while(data.byteLength > this.available()){
|
||
if (this.buffered() === 0) {
|
||
try {
|
||
numBytesWritten = this.#writer.writeSync(data);
|
||
} catch (e) {
|
||
if (e instanceof Error) {
|
||
this.err = e;
|
||
}
|
||
throw e;
|
||
}
|
||
} else {
|
||
numBytesWritten = copy(data, this.buf, this.usedBufferBytes);
|
||
this.usedBufferBytes += numBytesWritten;
|
||
this.flush();
|
||
}
|
||
totalBytesWritten += numBytesWritten;
|
||
data = data.subarray(numBytesWritten);
|
||
}
|
||
numBytesWritten = copy(data, this.buf, this.usedBufferBytes);
|
||
this.usedBufferBytes += numBytesWritten;
|
||
totalBytesWritten += numBytesWritten;
|
||
return totalBytesWritten;
|
||
}
|
||
}
|
||
function createLPS(pat) {
|
||
const lps = new Uint8Array(pat.length);
|
||
lps[0] = 0;
|
||
let prefixEnd = 0;
|
||
let i = 1;
|
||
while(i < lps.length){
|
||
if (pat[i] == pat[prefixEnd]) {
|
||
prefixEnd++;
|
||
lps[i] = prefixEnd;
|
||
i++;
|
||
} else if (prefixEnd === 0) {
|
||
lps[i] = 0;
|
||
i++;
|
||
} else {
|
||
prefixEnd = lps[prefixEnd - 1];
|
||
}
|
||
}
|
||
return lps;
|
||
}
|
||
async function* readDelim(reader, delim) {
|
||
const delimLen = delim.length;
|
||
const delimLPS = createLPS(delim);
|
||
const chunks = new BytesList();
|
||
const bufSize = Math.max(1024, delimLen + 1);
|
||
let inspectIndex = 0;
|
||
let matchIndex = 0;
|
||
while(true){
|
||
const inspectArr = new Uint8Array(bufSize);
|
||
const result = await reader.read(inspectArr);
|
||
if (result === null) {
|
||
yield chunks.concat();
|
||
return;
|
||
} else if (result < 0) {
|
||
return;
|
||
}
|
||
chunks.add(inspectArr, 0, result);
|
||
let localIndex = 0;
|
||
while(inspectIndex < chunks.size()){
|
||
if (inspectArr[localIndex] === delim[matchIndex]) {
|
||
inspectIndex++;
|
||
localIndex++;
|
||
matchIndex++;
|
||
if (matchIndex === delimLen) {
|
||
const matchEnd = inspectIndex - delimLen;
|
||
const readyBytes = chunks.slice(0, matchEnd);
|
||
yield readyBytes;
|
||
chunks.shift(inspectIndex);
|
||
inspectIndex = 0;
|
||
matchIndex = 0;
|
||
}
|
||
} else {
|
||
if (matchIndex === 0) {
|
||
inspectIndex++;
|
||
localIndex++;
|
||
} else {
|
||
matchIndex = delimLPS[matchIndex - 1];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
async function* readStringDelim(reader, delim, decoderOpts) {
|
||
const encoder = new TextEncoder();
|
||
const decoder = new TextDecoder(decoderOpts?.encoding, decoderOpts);
|
||
for await (const chunk of readDelim(reader, encoder.encode(delim))){
|
||
yield decoder.decode(chunk);
|
||
}
|
||
}
|
||
async function* readLines(reader, decoderOpts) {
|
||
const bufReader = new BufReader(reader);
|
||
let chunks = [];
|
||
const decoder = new TextDecoder(decoderOpts?.encoding, decoderOpts);
|
||
while(true){
|
||
const res = await bufReader.readLine();
|
||
if (!res) {
|
||
if (chunks.length > 0) {
|
||
yield decoder.decode(concat(...chunks));
|
||
}
|
||
break;
|
||
}
|
||
chunks.push(res.line);
|
||
if (!res.more) {
|
||
yield decoder.decode(concat(...chunks));
|
||
chunks = [];
|
||
}
|
||
}
|
||
}
|
||
class StringReader extends Buffer {
|
||
constructor(s){
|
||
super(new TextEncoder().encode(s).buffer);
|
||
}
|
||
}
|
||
class MultiReader {
|
||
#readers;
|
||
#currentIndex = 0;
|
||
constructor(readers){
|
||
this.#readers = [
|
||
...readers
|
||
];
|
||
}
|
||
async read(p) {
|
||
const r = this.#readers[this.#currentIndex];
|
||
if (!r) return null;
|
||
const result = await r.read(p);
|
||
if (result === null) {
|
||
this.#currentIndex++;
|
||
return 0;
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
class LimitedReader {
|
||
constructor(reader, limit){
|
||
this.reader = reader;
|
||
this.limit = limit;
|
||
}
|
||
async read(p) {
|
||
if (this.limit <= 0) {
|
||
return null;
|
||
}
|
||
if (p.length > this.limit) {
|
||
p = p.subarray(0, this.limit);
|
||
}
|
||
const n = await this.reader.read(p);
|
||
if (n == null) {
|
||
return null;
|
||
}
|
||
this.limit -= n;
|
||
return n;
|
||
}
|
||
reader;
|
||
limit;
|
||
}
|
||
const DEFAULT_BUFFER_SIZE = 32 * 1024;
|
||
async function copyN(r, dest, size) {
|
||
let bytesRead = 0;
|
||
let buf = new Uint8Array(DEFAULT_BUFFER_SIZE);
|
||
while(bytesRead < size){
|
||
if (size - bytesRead < DEFAULT_BUFFER_SIZE) {
|
||
buf = new Uint8Array(size - bytesRead);
|
||
}
|
||
const result = await r.read(buf);
|
||
const nread = result ?? 0;
|
||
bytesRead += nread;
|
||
if (nread > 0) {
|
||
let n = 0;
|
||
while(n < nread){
|
||
n += await dest.write(buf.slice(n, nread));
|
||
}
|
||
assert(n === nread, "could not write");
|
||
}
|
||
if (result === null) {
|
||
break;
|
||
}
|
||
}
|
||
return bytesRead;
|
||
}
|
||
async function readShort(buf) {
|
||
const high = await buf.readByte();
|
||
if (high === null) return null;
|
||
const low = await buf.readByte();
|
||
if (low === null) throw new Deno.errors.UnexpectedEof();
|
||
return high << 8 | low;
|
||
}
|
||
async function readInt(buf) {
|
||
const high = await readShort(buf);
|
||
if (high === null) return null;
|
||
const low = await readShort(buf);
|
||
if (low === null) throw new Deno.errors.UnexpectedEof();
|
||
return high << 16 | low;
|
||
}
|
||
const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
|
||
async function readLong(buf) {
|
||
const high = await readInt(buf);
|
||
if (high === null) return null;
|
||
const low = await readInt(buf);
|
||
if (low === null) throw new Deno.errors.UnexpectedEof();
|
||
const big = BigInt(high) << 32n | BigInt(low);
|
||
if (big > MAX_SAFE_INTEGER) {
|
||
throw new RangeError("Long value too big to be represented as a JavaScript number.");
|
||
}
|
||
return Number(big);
|
||
}
|
||
function sliceLongToBytes(d, dest = Array.from({
|
||
length: 8
|
||
})) {
|
||
let big = BigInt(d);
|
||
for(let i = 0; i < 8; i++){
|
||
dest[7 - i] = Number(big & 0xffn);
|
||
big >>= 8n;
|
||
}
|
||
return dest;
|
||
}
|
||
const decoder = new TextDecoder();
|
||
class StringWriter {
|
||
#chunks;
|
||
#byteLength;
|
||
#cache;
|
||
constructor(base = ""){
|
||
this.base = base;
|
||
this.#chunks = [];
|
||
this.#byteLength = 0;
|
||
const c = new TextEncoder().encode(base);
|
||
this.#chunks.push(c);
|
||
this.#byteLength += c.byteLength;
|
||
}
|
||
write(p) {
|
||
return Promise.resolve(this.writeSync(p));
|
||
}
|
||
writeSync(p) {
|
||
this.#chunks.push(new Uint8Array(p));
|
||
this.#byteLength += p.byteLength;
|
||
this.#cache = undefined;
|
||
return p.byteLength;
|
||
}
|
||
toString() {
|
||
if (this.#cache) {
|
||
return this.#cache;
|
||
}
|
||
const buf = new Uint8Array(this.#byteLength);
|
||
let offs = 0;
|
||
for (const chunk of this.#chunks){
|
||
buf.set(chunk, offs);
|
||
offs += chunk.byteLength;
|
||
}
|
||
this.#cache = decoder.decode(buf);
|
||
return this.#cache;
|
||
}
|
||
base;
|
||
}
|
||
const mod2 = {
|
||
copyN: copyN,
|
||
readInt: readInt,
|
||
readLong: readLong,
|
||
readShort: readShort,
|
||
sliceLongToBytes: sliceLongToBytes,
|
||
Buffer,
|
||
BufferFullError,
|
||
PartialReadError,
|
||
BufReader,
|
||
BufWriter,
|
||
BufWriterSync,
|
||
readDelim,
|
||
readStringDelim,
|
||
readLines,
|
||
StringReader,
|
||
MultiReader,
|
||
LimitedReader,
|
||
StringWriter
|
||
};
|
||
function bytesToUuid(bytes) {
|
||
const bits = [
|
||
...bytes
|
||
].map((bit)=>{
|
||
const s = bit.toString(16);
|
||
return bit < 0x10 ? "0" + s : s;
|
||
});
|
||
return [
|
||
...bits.slice(0, 4),
|
||
"-",
|
||
...bits.slice(4, 6),
|
||
"-",
|
||
...bits.slice(6, 8),
|
||
"-",
|
||
...bits.slice(8, 10),
|
||
"-",
|
||
...bits.slice(10, 16)
|
||
].join("");
|
||
}
|
||
function uuidToBytes(uuid) {
|
||
const bytes = [];
|
||
uuid.replace(/[a-fA-F0-9]{2}/g, (hex)=>{
|
||
bytes.push(parseInt(hex, 16));
|
||
return "";
|
||
});
|
||
return bytes;
|
||
}
|
||
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||
function validate(id) {
|
||
return UUID_RE.test(id);
|
||
}
|
||
let _nodeId;
|
||
let _clockseq;
|
||
let _lastMSecs = 0;
|
||
let _lastNSecs = 0;
|
||
function generate(options, buf, offset) {
|
||
let i = buf && offset || 0;
|
||
const b = buf ?? [];
|
||
options ??= {};
|
||
let { node =_nodeId , clockseq =_clockseq } = options;
|
||
if (node === undefined || clockseq === undefined) {
|
||
const seedBytes = options.random ?? options.rng ?? crypto.getRandomValues(new Uint8Array(16));
|
||
if (node === undefined) {
|
||
node = _nodeId = [
|
||
seedBytes[0] | 0x01,
|
||
seedBytes[1],
|
||
seedBytes[2],
|
||
seedBytes[3],
|
||
seedBytes[4],
|
||
seedBytes[5]
|
||
];
|
||
}
|
||
if (clockseq === undefined) {
|
||
clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff;
|
||
}
|
||
}
|
||
let { msecs =new Date().getTime() , nsecs =_lastNSecs + 1 } = options;
|
||
const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000;
|
||
if (dt < 0 && options.clockseq === undefined) {
|
||
clockseq = clockseq + 1 & 0x3fff;
|
||
}
|
||
if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
|
||
nsecs = 0;
|
||
}
|
||
if (nsecs > 10000) {
|
||
throw new Error("Can't create more than 10M uuids/sec");
|
||
}
|
||
_lastMSecs = msecs;
|
||
_lastNSecs = nsecs;
|
||
_clockseq = clockseq;
|
||
msecs += 12219292800000;
|
||
const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
|
||
b[i++] = tl >>> 24 & 0xff;
|
||
b[i++] = tl >>> 16 & 0xff;
|
||
b[i++] = tl >>> 8 & 0xff;
|
||
b[i++] = tl & 0xff;
|
||
const tmh = msecs / 0x100000000 * 10000 & 0xfffffff;
|
||
b[i++] = tmh >>> 8 & 0xff;
|
||
b[i++] = tmh & 0xff;
|
||
b[i++] = tmh >>> 24 & 0xf | 0x10;
|
||
b[i++] = tmh >>> 16 & 0xff;
|
||
b[i++] = clockseq >>> 8 | 0x80;
|
||
b[i++] = clockseq & 0xff;
|
||
for(let n = 0; n < 6; ++n){
|
||
b[i + n] = node[n];
|
||
}
|
||
return buf ?? bytesToUuid(b);
|
||
}
|
||
const mod3 = {
|
||
validate: validate,
|
||
generate: generate
|
||
};
|
||
const UUID_RE1 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||
function validate1(id) {
|
||
return UUID_RE1.test(id);
|
||
}
|
||
const mod4 = {
|
||
validate: validate1
|
||
};
|
||
const UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||
function validate2(id) {
|
||
return UUID_RE2.test(id);
|
||
}
|
||
async function generate1(namespace, data) {
|
||
const space = uuidToBytes(namespace);
|
||
assert(space.length === 16, "namespace must be a valid UUID");
|
||
const toHash = concat(new Uint8Array(space), data);
|
||
const buffer = await crypto.subtle.digest("sha-1", toHash);
|
||
const bytes = new Uint8Array(buffer);
|
||
bytes[6] = bytes[6] & 0x0f | 0x50;
|
||
bytes[8] = bytes[8] & 0x3f | 0x80;
|
||
return bytesToUuid(bytes);
|
||
}
|
||
const mod5 = {
|
||
validate: validate2,
|
||
generate: generate1
|
||
};
|
||
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
||
function isNil(id) {
|
||
return id === NIL_UUID;
|
||
}
|
||
function validate3(uuid) {
|
||
return /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i.test(uuid);
|
||
}
|
||
function version(uuid) {
|
||
if (!validate3(uuid)) {
|
||
throw new TypeError("Invalid UUID");
|
||
}
|
||
return parseInt(uuid[14], 16);
|
||
}
|
||
const mod6 = {
|
||
v1: mod3,
|
||
v4: mod4,
|
||
v5: mod5,
|
||
NIL_UUID: NIL_UUID,
|
||
isNil: isNil,
|
||
validate: validate3,
|
||
version: version
|
||
};
|
||
function utf8Count(str) {
|
||
const strLength = str.length;
|
||
let byteLength = 0;
|
||
let pos = 0;
|
||
while(pos < strLength){
|
||
let value = str.charCodeAt(pos++);
|
||
if ((value & 0xffffff80) === 0) {
|
||
byteLength++;
|
||
continue;
|
||
} else if ((value & 0xfffff800) === 0) {
|
||
byteLength += 2;
|
||
} else {
|
||
if (value >= 0xd800 && value <= 0xdbff) {
|
||
if (pos < strLength) {
|
||
const extra = str.charCodeAt(pos);
|
||
if ((extra & 0xfc00) === 0xdc00) {
|
||
++pos;
|
||
value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
|
||
}
|
||
}
|
||
}
|
||
if ((value & 0xffff0000) === 0) {
|
||
byteLength += 3;
|
||
} else {
|
||
byteLength += 4;
|
||
}
|
||
}
|
||
}
|
||
return byteLength;
|
||
}
|
||
function utf8EncodeJs(str, output, outputOffset) {
|
||
const strLength = str.length;
|
||
let offset = outputOffset;
|
||
let pos = 0;
|
||
while(pos < strLength){
|
||
let value = str.charCodeAt(pos++);
|
||
if ((value & 0xffffff80) === 0) {
|
||
output[offset++] = value;
|
||
continue;
|
||
} else if ((value & 0xfffff800) === 0) {
|
||
output[offset++] = value >> 6 & 0x1f | 0xc0;
|
||
} else {
|
||
if (value >= 0xd800 && value <= 0xdbff) {
|
||
if (pos < strLength) {
|
||
const extra = str.charCodeAt(pos);
|
||
if ((extra & 0xfc00) === 0xdc00) {
|
||
++pos;
|
||
value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
|
||
}
|
||
}
|
||
}
|
||
if ((value & 0xffff0000) === 0) {
|
||
output[offset++] = value >> 12 & 0x0f | 0xe0;
|
||
output[offset++] = value >> 6 & 0x3f | 0x80;
|
||
} else {
|
||
output[offset++] = value >> 18 & 0x07 | 0xf0;
|
||
output[offset++] = value >> 12 & 0x3f | 0x80;
|
||
output[offset++] = value >> 6 & 0x3f | 0x80;
|
||
}
|
||
}
|
||
output[offset++] = value & 0x3f | 0x80;
|
||
}
|
||
}
|
||
const sharedTextEncoder = new TextEncoder();
|
||
function utf8EncodeTEencodeInto(str, output, outputOffset) {
|
||
sharedTextEncoder.encodeInto(str, output.subarray(outputOffset));
|
||
}
|
||
const utf8EncodeTE = utf8EncodeTEencodeInto;
|
||
function utf8DecodeJs(bytes, inputOffset, byteLength) {
|
||
let offset = inputOffset;
|
||
const end = offset + byteLength;
|
||
const units = [];
|
||
let result = "";
|
||
while(offset < end){
|
||
const byte1 = bytes[offset++];
|
||
if ((byte1 & 0x80) === 0) {
|
||
units.push(byte1);
|
||
} else if ((byte1 & 0xe0) === 0xc0) {
|
||
const byte2 = bytes[offset++] & 0x3f;
|
||
units.push((byte1 & 0x1f) << 6 | byte2);
|
||
} else if ((byte1 & 0xf0) === 0xe0) {
|
||
const byte21 = bytes[offset++] & 0x3f;
|
||
const byte3 = bytes[offset++] & 0x3f;
|
||
units.push((byte1 & 0x1f) << 12 | byte21 << 6 | byte3);
|
||
} else if ((byte1 & 0xf8) === 0xf0) {
|
||
const byte22 = bytes[offset++] & 0x3f;
|
||
const byte31 = bytes[offset++] & 0x3f;
|
||
const byte4 = bytes[offset++] & 0x3f;
|
||
let unit = (byte1 & 0x07) << 0x12 | byte22 << 0x0c | byte31 << 0x06 | byte4;
|
||
if (unit > 0xffff) {
|
||
unit -= 0x10000;
|
||
units.push(unit >>> 10 & 0x3ff | 0xd800);
|
||
unit = 0xdc00 | unit & 0x3ff;
|
||
}
|
||
units.push(unit);
|
||
} else {
|
||
units.push(byte1);
|
||
}
|
||
if (units.length >= 0x1_000) {
|
||
result += String.fromCharCode(...units);
|
||
units.length = 0;
|
||
}
|
||
}
|
||
if (units.length > 0) {
|
||
result += String.fromCharCode(...units);
|
||
}
|
||
return result;
|
||
}
|
||
const sharedTextDecoder = new TextDecoder();
|
||
function utf8DecodeTD(bytes, inputOffset, byteLength) {
|
||
const stringBytes = bytes.subarray(inputOffset, inputOffset + byteLength);
|
||
return sharedTextDecoder.decode(stringBytes);
|
||
}
|
||
class ExtData {
|
||
constructor(type, data){
|
||
this.type = type;
|
||
this.data = data;
|
||
}
|
||
type;
|
||
data;
|
||
}
|
||
function setUint64(view, offset, value) {
|
||
const high = value / 0x1_0000_0000;
|
||
const low = value;
|
||
view.setUint32(offset, high);
|
||
view.setUint32(offset + 4, low);
|
||
}
|
||
function setInt64(view, offset, value) {
|
||
const high = Math.floor(value / 0x1_0000_0000);
|
||
const low = value;
|
||
view.setUint32(offset, high);
|
||
view.setUint32(offset + 4, low);
|
||
}
|
||
function getInt64(view, offset) {
|
||
const high = view.getInt32(offset);
|
||
const low = view.getUint32(offset + 4);
|
||
return high * 0x1_0000_0000 + low;
|
||
}
|
||
function getUint64(view, offset) {
|
||
const high = view.getUint32(offset);
|
||
const low = view.getUint32(offset + 4);
|
||
return high * 0x1_0000_0000 + low;
|
||
}
|
||
const EXT_TIMESTAMP = -1;
|
||
const TIMESTAMP32_MAX_SEC = 0x100000000 - 1;
|
||
const TIMESTAMP64_MAX_SEC = 0x400000000 - 1;
|
||
function encodeTimeSpecToTimestamp({ sec , nsec }) {
|
||
if (sec >= 0 && nsec >= 0 && sec <= TIMESTAMP64_MAX_SEC) {
|
||
if (nsec === 0 && sec <= TIMESTAMP32_MAX_SEC) {
|
||
const rv = new Uint8Array(4);
|
||
const view = new DataView(rv.buffer);
|
||
view.setUint32(0, sec);
|
||
return rv;
|
||
} else {
|
||
const secHigh = sec / 0x100000000;
|
||
const secLow = sec & 0xffffffff;
|
||
const rv1 = new Uint8Array(8);
|
||
const view1 = new DataView(rv1.buffer);
|
||
view1.setUint32(0, nsec << 2 | secHigh & 0x3);
|
||
view1.setUint32(4, secLow);
|
||
return rv1;
|
||
}
|
||
} else {
|
||
const rv2 = new Uint8Array(12);
|
||
const view2 = new DataView(rv2.buffer);
|
||
view2.setUint32(0, nsec);
|
||
setInt64(view2, 4, sec);
|
||
return rv2;
|
||
}
|
||
}
|
||
function encodeDateToTimeSpec(date) {
|
||
const msec = date.getTime();
|
||
const sec = Math.floor(msec / 1e3);
|
||
const nsec = (msec - sec * 1e3) * 1e6;
|
||
const nsecInSec = Math.floor(nsec / 1e9);
|
||
return {
|
||
sec: sec + nsecInSec,
|
||
nsec: nsec - nsecInSec * 1e9
|
||
};
|
||
}
|
||
function encodeTimestampExtension(object) {
|
||
if (object instanceof Date) {
|
||
const timeSpec = encodeDateToTimeSpec(object);
|
||
return encodeTimeSpecToTimestamp(timeSpec);
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
function decodeTimestampToTimeSpec(data) {
|
||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
||
switch(data.byteLength){
|
||
case 4:
|
||
{
|
||
const sec = view.getUint32(0);
|
||
return {
|
||
sec,
|
||
nsec: 0
|
||
};
|
||
}
|
||
case 8:
|
||
{
|
||
const nsec30AndSecHigh2 = view.getUint32(0);
|
||
const secLow32 = view.getUint32(4);
|
||
const sec1 = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
|
||
const nsec1 = nsec30AndSecHigh2 >>> 2;
|
||
return {
|
||
sec: sec1,
|
||
nsec: nsec1
|
||
};
|
||
}
|
||
case 12:
|
||
{
|
||
const sec2 = getInt64(view, 4);
|
||
const nsec2 = view.getUint32(0);
|
||
return {
|
||
sec: sec2,
|
||
nsec: nsec2
|
||
};
|
||
}
|
||
default:
|
||
throw new Error(`Unrecognized data size for timestamp: ${data.length}`);
|
||
}
|
||
}
|
||
function decodeTimestampExtension(data) {
|
||
const timeSpec = decodeTimestampToTimeSpec(data);
|
||
return new Date(timeSpec.sec * 1e3 + timeSpec.nsec / 1e6);
|
||
}
|
||
const timestampExtension = {
|
||
type: EXT_TIMESTAMP,
|
||
encode: encodeTimestampExtension,
|
||
decode: decodeTimestampExtension
|
||
};
|
||
class ExtensionCodec {
|
||
static defaultCodec = new ExtensionCodec();
|
||
__brand;
|
||
builtInEncoders = [];
|
||
builtInDecoders = [];
|
||
encoders = [];
|
||
decoders = [];
|
||
constructor(){
|
||
this.register(timestampExtension);
|
||
}
|
||
register({ type , encode , decode }) {
|
||
if (type >= 0) {
|
||
this.encoders[type] = encode;
|
||
this.decoders[type] = decode;
|
||
} else {
|
||
const index = 1 + type;
|
||
this.builtInEncoders[index] = encode;
|
||
this.builtInDecoders[index] = decode;
|
||
}
|
||
}
|
||
tryToEncode(object, context) {
|
||
for(let i = 0; i < this.builtInEncoders.length; i++){
|
||
const encoder = this.builtInEncoders[i];
|
||
if (encoder != null) {
|
||
const data = encoder(object, context);
|
||
if (data != null) {
|
||
const type = -1 - i;
|
||
return new ExtData(type, data);
|
||
}
|
||
}
|
||
}
|
||
for(let i1 = 0; i1 < this.encoders.length; i1++){
|
||
const encoder1 = this.encoders[i1];
|
||
if (encoder1 != null) {
|
||
const data1 = encoder1(object, context);
|
||
if (data1 != null) {
|
||
const type1 = i1;
|
||
return new ExtData(type1, data1);
|
||
}
|
||
}
|
||
}
|
||
if (object instanceof ExtData) {
|
||
return object;
|
||
}
|
||
return null;
|
||
}
|
||
decode(data, type, context) {
|
||
const decoder = type < 0 ? this.builtInDecoders[-1 - type] : this.decoders[type];
|
||
if (decoder) {
|
||
return decoder(data, type, context);
|
||
} else {
|
||
return new ExtData(type, data);
|
||
}
|
||
}
|
||
}
|
||
function ensureUint8Array(buffer) {
|
||
if (buffer instanceof Uint8Array) {
|
||
return buffer;
|
||
} else if (ArrayBuffer.isView(buffer)) {
|
||
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||
} else if (buffer instanceof ArrayBuffer) {
|
||
return new Uint8Array(buffer);
|
||
} else {
|
||
return Uint8Array.from(buffer);
|
||
}
|
||
}
|
||
function createDataView(buffer) {
|
||
if (buffer instanceof ArrayBuffer) {
|
||
return new DataView(buffer);
|
||
}
|
||
const bufferView = ensureUint8Array(buffer);
|
||
return new DataView(bufferView.buffer, bufferView.byteOffset, bufferView.byteLength);
|
||
}
|
||
class Encoder {
|
||
pos;
|
||
view;
|
||
bytes;
|
||
constructor(extensionCodec = ExtensionCodec.defaultCodec, context = undefined, maxDepth = 100, initialBufferSize = 2048, sortKeys = false, forceFloat32 = false, ignoreUndefined = false){
|
||
this.extensionCodec = extensionCodec;
|
||
this.context = context;
|
||
this.maxDepth = maxDepth;
|
||
this.initialBufferSize = initialBufferSize;
|
||
this.sortKeys = sortKeys;
|
||
this.forceFloat32 = forceFloat32;
|
||
this.ignoreUndefined = ignoreUndefined;
|
||
this.pos = 0;
|
||
this.view = new DataView(new ArrayBuffer(this.initialBufferSize));
|
||
this.bytes = new Uint8Array(this.view.buffer);
|
||
}
|
||
getUint8Array() {
|
||
return this.bytes.subarray(0, this.pos);
|
||
}
|
||
reinitializeState() {
|
||
this.pos = 0;
|
||
}
|
||
encode(object) {
|
||
this.reinitializeState();
|
||
this.doEncode(object, 1);
|
||
return this.getUint8Array();
|
||
}
|
||
doEncode(object, depth) {
|
||
if (depth > this.maxDepth) {
|
||
throw new Error(`Too deep objects in depth ${depth}`);
|
||
}
|
||
if (object == null) {
|
||
this.encodeNil();
|
||
} else if (typeof object === "boolean") {
|
||
this.encodeBoolean(object);
|
||
} else if (typeof object === "number") {
|
||
this.encodeNumber(object);
|
||
} else if (typeof object === "string") {
|
||
this.encodeString(object);
|
||
} else {
|
||
this.encodeObject(object, depth);
|
||
}
|
||
}
|
||
ensureBufferSizeToWrite(sizeToWrite) {
|
||
const requiredSize = this.pos + sizeToWrite;
|
||
if (this.view.byteLength < requiredSize) {
|
||
this.resizeBuffer(requiredSize * 2);
|
||
}
|
||
}
|
||
resizeBuffer(newSize) {
|
||
const newBuffer = new ArrayBuffer(newSize);
|
||
const newBytes = new Uint8Array(newBuffer);
|
||
const newView = new DataView(newBuffer);
|
||
newBytes.set(this.bytes);
|
||
this.view = newView;
|
||
this.bytes = newBytes;
|
||
}
|
||
encodeNil() {
|
||
this.writeU8(0xc0);
|
||
}
|
||
encodeBoolean(object) {
|
||
if (object === false) {
|
||
this.writeU8(0xc2);
|
||
} else {
|
||
this.writeU8(0xc3);
|
||
}
|
||
}
|
||
encodeNumber(object) {
|
||
if (Number.isSafeInteger(object)) {
|
||
if (object >= 0) {
|
||
if (object < 0x80) {
|
||
this.writeU8(object);
|
||
} else if (object < 0x100) {
|
||
this.writeU8(0xcc);
|
||
this.writeU8(object);
|
||
} else if (object < 0x10000) {
|
||
this.writeU8(0xcd);
|
||
this.writeU16(object);
|
||
} else if (object < 0x100000000) {
|
||
this.writeU8(0xce);
|
||
this.writeU32(object);
|
||
} else {
|
||
this.writeU8(0xcf);
|
||
this.writeU64(object);
|
||
}
|
||
} else {
|
||
if (object >= -0x20) {
|
||
this.writeU8(0xe0 | object + 0x20);
|
||
} else if (object >= -0x80) {
|
||
this.writeU8(0xd0);
|
||
this.writeI8(object);
|
||
} else if (object >= -0x8000) {
|
||
this.writeU8(0xd1);
|
||
this.writeI16(object);
|
||
} else if (object >= -0x80000000) {
|
||
this.writeU8(0xd2);
|
||
this.writeI32(object);
|
||
} else {
|
||
this.writeU8(0xd3);
|
||
this.writeI64(object);
|
||
}
|
||
}
|
||
} else {
|
||
if (this.forceFloat32) {
|
||
this.writeU8(0xca);
|
||
this.writeF32(object);
|
||
} else {
|
||
this.writeU8(0xcb);
|
||
this.writeF64(object);
|
||
}
|
||
}
|
||
}
|
||
writeStringHeader(byteLength) {
|
||
if (byteLength < 32) {
|
||
this.writeU8(0xa0 + byteLength);
|
||
} else if (byteLength < 0x100) {
|
||
this.writeU8(0xd9);
|
||
this.writeU8(byteLength);
|
||
} else if (byteLength < 0x10000) {
|
||
this.writeU8(0xda);
|
||
this.writeU16(byteLength);
|
||
} else if (byteLength < 0x100000000) {
|
||
this.writeU8(0xdb);
|
||
this.writeU32(byteLength);
|
||
} else {
|
||
throw new Error(`Too long string: ${byteLength} bytes in UTF-8`);
|
||
}
|
||
}
|
||
encodeString(object) {
|
||
const maxHeaderSize = 1 + 4;
|
||
const strLength = object.length;
|
||
if (strLength > 200) {
|
||
const byteLength = utf8Count(object);
|
||
this.ensureBufferSizeToWrite(maxHeaderSize + byteLength);
|
||
this.writeStringHeader(byteLength);
|
||
utf8EncodeTE(object, this.bytes, this.pos);
|
||
this.pos += byteLength;
|
||
} else {
|
||
const byteLength1 = utf8Count(object);
|
||
this.ensureBufferSizeToWrite(maxHeaderSize + byteLength1);
|
||
this.writeStringHeader(byteLength1);
|
||
utf8EncodeJs(object, this.bytes, this.pos);
|
||
this.pos += byteLength1;
|
||
}
|
||
}
|
||
encodeObject(object, depth) {
|
||
const ext = this.extensionCodec.tryToEncode(object, this.context);
|
||
if (ext != null) {
|
||
this.encodeExtension(ext);
|
||
} else if (Array.isArray(object)) {
|
||
this.encodeArray(object, depth);
|
||
} else if (ArrayBuffer.isView(object)) {
|
||
this.encodeBinary(object);
|
||
} else if (typeof object === "object") {
|
||
this.encodeMap(object, depth);
|
||
} else {
|
||
throw new Error(`Unrecognized object: ${Object.prototype.toString.apply(object)}`);
|
||
}
|
||
}
|
||
encodeBinary(object) {
|
||
const size = object.byteLength;
|
||
if (size < 0x100) {
|
||
this.writeU8(0xc4);
|
||
this.writeU8(size);
|
||
} else if (size < 0x10000) {
|
||
this.writeU8(0xc5);
|
||
this.writeU16(size);
|
||
} else if (size < 0x100000000) {
|
||
this.writeU8(0xc6);
|
||
this.writeU32(size);
|
||
} else {
|
||
throw new Error(`Too large binary: ${size}`);
|
||
}
|
||
const bytes = ensureUint8Array(object);
|
||
this.writeU8a(bytes);
|
||
}
|
||
encodeArray(object, depth) {
|
||
const size = object.length;
|
||
if (size < 16) {
|
||
this.writeU8(0x90 + size);
|
||
} else if (size < 0x10000) {
|
||
this.writeU8(0xdc);
|
||
this.writeU16(size);
|
||
} else if (size < 0x100000000) {
|
||
this.writeU8(0xdd);
|
||
this.writeU32(size);
|
||
} else {
|
||
throw new Error(`Too large array: ${size}`);
|
||
}
|
||
for (const item of object){
|
||
this.doEncode(item, depth + 1);
|
||
}
|
||
}
|
||
countWithoutUndefined(object, keys) {
|
||
let count = 0;
|
||
for (const key of keys){
|
||
if (object[key] !== undefined) {
|
||
count++;
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
encodeMap(object, depth) {
|
||
const keys = Object.keys(object);
|
||
if (this.sortKeys) {
|
||
keys.sort();
|
||
}
|
||
const size = this.ignoreUndefined ? this.countWithoutUndefined(object, keys) : keys.length;
|
||
if (size < 16) {
|
||
this.writeU8(0x80 + size);
|
||
} else if (size < 0x10000) {
|
||
this.writeU8(0xde);
|
||
this.writeU16(size);
|
||
} else if (size < 0x100000000) {
|
||
this.writeU8(0xdf);
|
||
this.writeU32(size);
|
||
} else {
|
||
throw new Error(`Too large map object: ${size}`);
|
||
}
|
||
for (const key of keys){
|
||
const value = object[key];
|
||
if (!(this.ignoreUndefined && value === undefined)) {
|
||
this.encodeString(key);
|
||
this.doEncode(value, depth + 1);
|
||
}
|
||
}
|
||
}
|
||
encodeExtension(ext) {
|
||
const size = ext.data.length;
|
||
if (size === 1) {
|
||
this.writeU8(0xd4);
|
||
} else if (size === 2) {
|
||
this.writeU8(0xd5);
|
||
} else if (size === 4) {
|
||
this.writeU8(0xd6);
|
||
} else if (size === 8) {
|
||
this.writeU8(0xd7);
|
||
} else if (size === 16) {
|
||
this.writeU8(0xd8);
|
||
} else if (size < 0x100) {
|
||
this.writeU8(0xc7);
|
||
this.writeU8(size);
|
||
} else if (size < 0x10000) {
|
||
this.writeU8(0xc8);
|
||
this.writeU16(size);
|
||
} else if (size < 0x100000000) {
|
||
this.writeU8(0xc9);
|
||
this.writeU32(size);
|
||
} else {
|
||
throw new Error(`Too large extension object: ${size}`);
|
||
}
|
||
this.writeI8(ext.type);
|
||
this.writeU8a(ext.data);
|
||
}
|
||
writeU8(value) {
|
||
this.ensureBufferSizeToWrite(1);
|
||
this.view.setUint8(this.pos, value);
|
||
this.pos++;
|
||
}
|
||
writeU8a(values) {
|
||
const size = values.length;
|
||
this.ensureBufferSizeToWrite(size);
|
||
this.bytes.set(values, this.pos);
|
||
this.pos += size;
|
||
}
|
||
writeI8(value) {
|
||
this.ensureBufferSizeToWrite(1);
|
||
this.view.setInt8(this.pos, value);
|
||
this.pos++;
|
||
}
|
||
writeU16(value) {
|
||
this.ensureBufferSizeToWrite(2);
|
||
this.view.setUint16(this.pos, value);
|
||
this.pos += 2;
|
||
}
|
||
writeI16(value) {
|
||
this.ensureBufferSizeToWrite(2);
|
||
this.view.setInt16(this.pos, value);
|
||
this.pos += 2;
|
||
}
|
||
writeU32(value) {
|
||
this.ensureBufferSizeToWrite(4);
|
||
this.view.setUint32(this.pos, value);
|
||
this.pos += 4;
|
||
}
|
||
writeI32(value) {
|
||
this.ensureBufferSizeToWrite(4);
|
||
this.view.setInt32(this.pos, value);
|
||
this.pos += 4;
|
||
}
|
||
writeF32(value) {
|
||
this.ensureBufferSizeToWrite(4);
|
||
this.view.setFloat32(this.pos, value);
|
||
this.pos += 4;
|
||
}
|
||
writeF64(value) {
|
||
this.ensureBufferSizeToWrite(8);
|
||
this.view.setFloat64(this.pos, value);
|
||
this.pos += 8;
|
||
}
|
||
writeU64(value) {
|
||
this.ensureBufferSizeToWrite(8);
|
||
setUint64(this.view, this.pos, value);
|
||
this.pos += 8;
|
||
}
|
||
writeI64(value) {
|
||
this.ensureBufferSizeToWrite(8);
|
||
setInt64(this.view, this.pos, value);
|
||
this.pos += 8;
|
||
}
|
||
extensionCodec;
|
||
context;
|
||
maxDepth;
|
||
initialBufferSize;
|
||
sortKeys;
|
||
forceFloat32;
|
||
ignoreUndefined;
|
||
}
|
||
const defaultEncodeOptions = {};
|
||
function encode1(value, options = defaultEncodeOptions) {
|
||
const encoder = new Encoder(options.extensionCodec, options.context, options.maxDepth, options.initialBufferSize, options.sortKeys, options.forceFloat32, options.ignoreUndefined);
|
||
return encoder.encode(value);
|
||
}
|
||
function prettyByte(__byte) {
|
||
return `${__byte < 0 ? "-" : ""}0x${Math.abs(__byte).toString(16).padStart(2, "0")}`;
|
||
}
|
||
class CachedKeyDecoder {
|
||
hit;
|
||
miss;
|
||
caches;
|
||
constructor(maxKeyLength = 16, maxLengthPerKey = 16){
|
||
this.maxKeyLength = maxKeyLength;
|
||
this.maxLengthPerKey = maxLengthPerKey;
|
||
this.hit = 0;
|
||
this.miss = 0;
|
||
this.caches = [];
|
||
for(let i = 0; i < this.maxKeyLength; i++){
|
||
this.caches.push([]);
|
||
}
|
||
}
|
||
canBeCached(byteLength) {
|
||
return byteLength > 0 && byteLength <= this.maxKeyLength;
|
||
}
|
||
get(bytes, inputOffset, byteLength) {
|
||
const records = this.caches[byteLength - 1];
|
||
const recordsLength = records.length;
|
||
FIND_CHUNK: for(let i = 0; i < recordsLength; i++){
|
||
const record = records[i];
|
||
const recordBytes = record.bytes;
|
||
for(let j = 0; j < byteLength; j++){
|
||
if (recordBytes[j] !== bytes[inputOffset + j]) {
|
||
continue FIND_CHUNK;
|
||
}
|
||
}
|
||
return record.value;
|
||
}
|
||
return null;
|
||
}
|
||
store(bytes, value) {
|
||
const records = this.caches[bytes.length - 1];
|
||
const record = {
|
||
bytes,
|
||
value
|
||
};
|
||
if (records.length >= this.maxLengthPerKey) {
|
||
records[Math.random() * records.length | 0] = record;
|
||
} else {
|
||
records.push(record);
|
||
}
|
||
}
|
||
decode(bytes, inputOffset, byteLength) {
|
||
const cachedValue = this.get(bytes, inputOffset, byteLength);
|
||
if (cachedValue != null) {
|
||
this.hit++;
|
||
return cachedValue;
|
||
}
|
||
this.miss++;
|
||
const value = utf8DecodeJs(bytes, inputOffset, byteLength);
|
||
const slicedCopyOfBytes = Uint8Array.prototype.slice.call(bytes, inputOffset, inputOffset + byteLength);
|
||
this.store(slicedCopyOfBytes, value);
|
||
return value;
|
||
}
|
||
maxKeyLength;
|
||
maxLengthPerKey;
|
||
}
|
||
var State;
|
||
(function(State) {
|
||
State[State["ARRAY"] = 0] = "ARRAY";
|
||
State[State["MAP_KEY"] = 1] = "MAP_KEY";
|
||
State[State["MAP_VALUE"] = 2] = "MAP_VALUE";
|
||
})(State || (State = {}));
|
||
const isValidMapKeyType = (key)=>{
|
||
const keyType = typeof key;
|
||
return keyType === "string" || keyType === "number";
|
||
};
|
||
const HEAD_BYTE_REQUIRED = -1;
|
||
const EMPTY_VIEW = new DataView(new ArrayBuffer(0));
|
||
const EMPTY_BYTES = new Uint8Array(EMPTY_VIEW.buffer);
|
||
const DataViewIndexOutOfBoundsError = (()=>{
|
||
try {
|
||
EMPTY_VIEW.getInt8(0);
|
||
} catch (e) {
|
||
return e.constructor;
|
||
}
|
||
throw new Error("never reached");
|
||
})();
|
||
const MORE_DATA = new DataViewIndexOutOfBoundsError("Insufficient data");
|
||
const sharedCachedKeyDecoder = new CachedKeyDecoder();
|
||
class Decoder {
|
||
totalPos;
|
||
pos;
|
||
view;
|
||
bytes;
|
||
headByte;
|
||
stack;
|
||
constructor(extensionCodec = ExtensionCodec.defaultCodec, context = undefined, maxStrLength = 0xffff_ffff, maxBinLength = 0xffff_ffff, maxArrayLength = 0xffff_ffff, maxMapLength = 0xffff_ffff, maxExtLength = 0xffff_ffff, keyDecoder = sharedCachedKeyDecoder){
|
||
this.extensionCodec = extensionCodec;
|
||
this.context = context;
|
||
this.maxStrLength = maxStrLength;
|
||
this.maxBinLength = maxBinLength;
|
||
this.maxArrayLength = maxArrayLength;
|
||
this.maxMapLength = maxMapLength;
|
||
this.maxExtLength = maxExtLength;
|
||
this.keyDecoder = keyDecoder;
|
||
this.totalPos = 0;
|
||
this.pos = 0;
|
||
this.view = EMPTY_VIEW;
|
||
this.bytes = EMPTY_BYTES;
|
||
this.headByte = HEAD_BYTE_REQUIRED;
|
||
this.stack = [];
|
||
}
|
||
reinitializeState() {
|
||
this.totalPos = 0;
|
||
this.headByte = HEAD_BYTE_REQUIRED;
|
||
}
|
||
setBuffer(buffer) {
|
||
this.bytes = ensureUint8Array(buffer);
|
||
this.view = createDataView(this.bytes);
|
||
this.pos = 0;
|
||
}
|
||
appendBuffer(buffer) {
|
||
buffer = ensureUint8Array(buffer).slice();
|
||
if (this.headByte === HEAD_BYTE_REQUIRED && !this.hasRemaining()) {
|
||
this.setBuffer(buffer);
|
||
} else {
|
||
const remainingData = this.bytes.subarray(this.pos);
|
||
const newData = ensureUint8Array(buffer);
|
||
const concated = new Uint8Array(remainingData.length + newData.length);
|
||
concated.set(remainingData);
|
||
concated.set(newData, remainingData.length);
|
||
this.setBuffer(concated);
|
||
}
|
||
}
|
||
hasRemaining(size = 1) {
|
||
return this.view.byteLength - this.pos >= size;
|
||
}
|
||
createNoExtraBytesError(posToShow) {
|
||
const { view , pos } = this;
|
||
return new RangeError(`Extra ${view.byteLength - pos} of ${view.byteLength} byte(s) found at buffer[${posToShow}]`);
|
||
}
|
||
decode(buffer) {
|
||
this.reinitializeState();
|
||
this.setBuffer(buffer);
|
||
return this.doDecodeSingleSync();
|
||
}
|
||
doDecodeSingleSync() {
|
||
const object = this.doDecodeSync();
|
||
if (this.hasRemaining()) {
|
||
throw this.createNoExtraBytesError(this.pos);
|
||
}
|
||
return object;
|
||
}
|
||
async decodeAsync(stream) {
|
||
let decoded = false;
|
||
let object;
|
||
for await (const buffer of stream){
|
||
if (decoded) {
|
||
throw this.createNoExtraBytesError(this.totalPos);
|
||
}
|
||
this.appendBuffer(buffer);
|
||
try {
|
||
object = this.doDecodeSync();
|
||
decoded = true;
|
||
} catch (e) {
|
||
if (!(e instanceof DataViewIndexOutOfBoundsError)) {
|
||
throw e;
|
||
}
|
||
}
|
||
this.totalPos += this.pos;
|
||
}
|
||
if (decoded) {
|
||
if (this.hasRemaining()) {
|
||
throw this.createNoExtraBytesError(this.totalPos);
|
||
}
|
||
return object;
|
||
}
|
||
const { headByte , pos , totalPos } = this;
|
||
throw new RangeError(`Insufficient data in parcing ${prettyByte(headByte)} at ${totalPos} (${pos} in the current buffer)`);
|
||
}
|
||
decodeArrayStream(stream) {
|
||
return this.decodeMultiAsync(stream, true);
|
||
}
|
||
decodeStream(stream) {
|
||
return this.decodeMultiAsync(stream, false);
|
||
}
|
||
async *decodeMultiAsync(stream, isArray) {
|
||
let isArrayHeaderRequired = isArray;
|
||
let arrayItemsLeft = -1;
|
||
for await (const buffer of stream){
|
||
if (isArray && arrayItemsLeft === 0) {
|
||
throw this.createNoExtraBytesError(this.totalPos);
|
||
}
|
||
this.appendBuffer(buffer);
|
||
if (isArrayHeaderRequired) {
|
||
arrayItemsLeft = this.readArraySize();
|
||
isArrayHeaderRequired = false;
|
||
this.complete();
|
||
}
|
||
try {
|
||
while(true){
|
||
yield this.doDecodeSync();
|
||
if (--arrayItemsLeft === 0) {
|
||
break;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
if (!(e instanceof DataViewIndexOutOfBoundsError)) {
|
||
throw e;
|
||
}
|
||
}
|
||
this.totalPos += this.pos;
|
||
}
|
||
}
|
||
doDecodeSync() {
|
||
DECODE: while(true){
|
||
const headByte = this.readHeadByte();
|
||
let object;
|
||
if (headByte >= 0xe0) {
|
||
object = headByte - 0x100;
|
||
} else if (headByte < 0xc0) {
|
||
if (headByte < 0x80) {
|
||
object = headByte;
|
||
} else if (headByte < 0x90) {
|
||
const size = headByte - 0x80;
|
||
if (size !== 0) {
|
||
this.pushMapState(size);
|
||
this.complete();
|
||
continue DECODE;
|
||
} else {
|
||
object = {};
|
||
}
|
||
} else if (headByte < 0xa0) {
|
||
const size1 = headByte - 0x90;
|
||
if (size1 !== 0) {
|
||
this.pushArrayState(size1);
|
||
this.complete();
|
||
continue DECODE;
|
||
} else {
|
||
object = [];
|
||
}
|
||
} else {
|
||
const byteLength = headByte - 0xa0;
|
||
object = this.decodeUtf8String(byteLength, 0);
|
||
}
|
||
} else if (headByte === 0xc0) {
|
||
object = null;
|
||
} else if (headByte === 0xc2) {
|
||
object = false;
|
||
} else if (headByte === 0xc3) {
|
||
object = true;
|
||
} else if (headByte === 0xca) {
|
||
object = this.readF32();
|
||
} else if (headByte === 0xcb) {
|
||
object = this.readF64();
|
||
} else if (headByte === 0xcc) {
|
||
object = this.readU8();
|
||
} else if (headByte === 0xcd) {
|
||
object = this.readU16();
|
||
} else if (headByte === 0xce) {
|
||
object = this.readU32();
|
||
} else if (headByte === 0xcf) {
|
||
object = this.readU64();
|
||
} else if (headByte === 0xd0) {
|
||
object = this.readI8();
|
||
} else if (headByte === 0xd1) {
|
||
object = this.readI16();
|
||
} else if (headByte === 0xd2) {
|
||
object = this.readI32();
|
||
} else if (headByte === 0xd3) {
|
||
object = this.readI64();
|
||
} else if (headByte === 0xd9) {
|
||
const byteLength1 = this.lookU8();
|
||
object = this.decodeUtf8String(byteLength1, 1);
|
||
} else if (headByte === 0xda) {
|
||
const byteLength2 = this.lookU16();
|
||
object = this.decodeUtf8String(byteLength2, 2);
|
||
} else if (headByte === 0xdb) {
|
||
const byteLength3 = this.lookU32();
|
||
object = this.decodeUtf8String(byteLength3, 4);
|
||
} else if (headByte === 0xdc) {
|
||
const size2 = this.readU16();
|
||
if (size2 !== 0) {
|
||
this.pushArrayState(size2);
|
||
this.complete();
|
||
continue DECODE;
|
||
} else {
|
||
object = [];
|
||
}
|
||
} else if (headByte === 0xdd) {
|
||
const size3 = this.readU32();
|
||
if (size3 !== 0) {
|
||
this.pushArrayState(size3);
|
||
this.complete();
|
||
continue DECODE;
|
||
} else {
|
||
object = [];
|
||
}
|
||
} else if (headByte === 0xde) {
|
||
const size4 = this.readU16();
|
||
if (size4 !== 0) {
|
||
this.pushMapState(size4);
|
||
this.complete();
|
||
continue DECODE;
|
||
} else {
|
||
object = {};
|
||
}
|
||
} else if (headByte === 0xdf) {
|
||
const size5 = this.readU32();
|
||
if (size5 !== 0) {
|
||
this.pushMapState(size5);
|
||
this.complete();
|
||
continue DECODE;
|
||
} else {
|
||
object = {};
|
||
}
|
||
} else if (headByte === 0xc4) {
|
||
const size6 = this.lookU8();
|
||
object = this.decodeBinary(size6, 1);
|
||
} else if (headByte === 0xc5) {
|
||
const size7 = this.lookU16();
|
||
object = this.decodeBinary(size7, 2);
|
||
} else if (headByte === 0xc6) {
|
||
const size8 = this.lookU32();
|
||
object = this.decodeBinary(size8, 4);
|
||
} else if (headByte === 0xd4) {
|
||
object = this.decodeExtension(1, 0);
|
||
} else if (headByte === 0xd5) {
|
||
object = this.decodeExtension(2, 0);
|
||
} else if (headByte === 0xd6) {
|
||
object = this.decodeExtension(4, 0);
|
||
} else if (headByte === 0xd7) {
|
||
object = this.decodeExtension(8, 0);
|
||
} else if (headByte === 0xd8) {
|
||
object = this.decodeExtension(16, 0);
|
||
} else if (headByte === 0xc7) {
|
||
const size9 = this.lookU8();
|
||
object = this.decodeExtension(size9, 1);
|
||
} else if (headByte === 0xc8) {
|
||
const size10 = this.lookU16();
|
||
object = this.decodeExtension(size10, 2);
|
||
} else if (headByte === 0xc9) {
|
||
const size11 = this.lookU32();
|
||
object = this.decodeExtension(size11, 4);
|
||
} else {
|
||
throw new Error(`Unrecognized type byte: ${prettyByte(headByte)}`);
|
||
}
|
||
this.complete();
|
||
const stack = this.stack;
|
||
while(stack.length > 0){
|
||
const state = stack[stack.length - 1];
|
||
if (state.type === 0) {
|
||
state.array[state.position] = object;
|
||
state.position++;
|
||
if (state.position === state.size) {
|
||
stack.pop();
|
||
object = state.array;
|
||
} else {
|
||
continue DECODE;
|
||
}
|
||
} else if (state.type === 1) {
|
||
if (!isValidMapKeyType(object)) {
|
||
throw new Error("The type of key must be string or number but " + typeof object);
|
||
}
|
||
state.key = object;
|
||
state.type = 2;
|
||
continue DECODE;
|
||
} else {
|
||
state.map[state.key] = object;
|
||
state.readCount++;
|
||
if (state.readCount === state.size) {
|
||
stack.pop();
|
||
object = state.map;
|
||
} else {
|
||
state.key = null;
|
||
state.type = 1;
|
||
continue DECODE;
|
||
}
|
||
}
|
||
}
|
||
return object;
|
||
}
|
||
}
|
||
readHeadByte() {
|
||
if (this.headByte === HEAD_BYTE_REQUIRED) {
|
||
this.headByte = this.readU8();
|
||
}
|
||
return this.headByte;
|
||
}
|
||
complete() {
|
||
this.headByte = HEAD_BYTE_REQUIRED;
|
||
}
|
||
readArraySize() {
|
||
const headByte = this.readHeadByte();
|
||
switch(headByte){
|
||
case 0xdc:
|
||
return this.readU16();
|
||
case 0xdd:
|
||
return this.readU32();
|
||
default:
|
||
{
|
||
if (headByte < 0xa0) {
|
||
return headByte - 0x90;
|
||
} else {
|
||
throw new Error(`Unrecognized array type byte: ${prettyByte(headByte)}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
pushMapState(size) {
|
||
if (size > this.maxMapLength) {
|
||
throw new Error(`Max length exceeded: map length (${size}) > maxMapLengthLength (${this.maxMapLength})`);
|
||
}
|
||
this.stack.push({
|
||
type: 1,
|
||
size,
|
||
key: null,
|
||
readCount: 0,
|
||
map: {}
|
||
});
|
||
}
|
||
pushArrayState(size) {
|
||
if (size > this.maxArrayLength) {
|
||
throw new Error(`Max length exceeded: array length (${size}) > maxArrayLength (${this.maxArrayLength})`);
|
||
}
|
||
this.stack.push({
|
||
type: 0,
|
||
size,
|
||
array: new Array(size),
|
||
position: 0
|
||
});
|
||
}
|
||
decodeUtf8String(byteLength, headerOffset) {
|
||
if (byteLength > this.maxStrLength) {
|
||
throw new Error(`Max length exceeded: UTF-8 byte length (${byteLength}) > maxStrLength (${this.maxStrLength})`);
|
||
}
|
||
if (this.bytes.byteLength < this.pos + headerOffset + byteLength) {
|
||
throw MORE_DATA;
|
||
}
|
||
const offset = this.pos + headerOffset;
|
||
let object;
|
||
if (this.stateIsMapKey() && this.keyDecoder?.canBeCached(byteLength)) {
|
||
object = this.keyDecoder.decode(this.bytes, offset, byteLength);
|
||
} else if (byteLength > 200) {
|
||
object = utf8DecodeTD(this.bytes, offset, byteLength);
|
||
} else {
|
||
object = utf8DecodeJs(this.bytes, offset, byteLength);
|
||
}
|
||
this.pos += headerOffset + byteLength;
|
||
return object;
|
||
}
|
||
stateIsMapKey() {
|
||
if (this.stack.length > 0) {
|
||
const state = this.stack[this.stack.length - 1];
|
||
return state.type === 1;
|
||
}
|
||
return false;
|
||
}
|
||
decodeBinary(byteLength, headOffset) {
|
||
if (byteLength > this.maxBinLength) {
|
||
throw new Error(`Max length exceeded: bin length (${byteLength}) > maxBinLength (${this.maxBinLength})`);
|
||
}
|
||
if (!this.hasRemaining(byteLength + headOffset)) {
|
||
throw MORE_DATA;
|
||
}
|
||
const offset = this.pos + headOffset;
|
||
const object = this.bytes.subarray(offset, offset + byteLength);
|
||
this.pos += headOffset + byteLength;
|
||
return object;
|
||
}
|
||
decodeExtension(size, headOffset) {
|
||
if (size > this.maxExtLength) {
|
||
throw new Error(`Max length exceeded: ext length (${size}) > maxExtLength (${this.maxExtLength})`);
|
||
}
|
||
const extType = this.view.getInt8(this.pos + headOffset);
|
||
const data = this.decodeBinary(size, headOffset + 1);
|
||
return this.extensionCodec.decode(data, extType, this.context);
|
||
}
|
||
lookU8() {
|
||
return this.view.getUint8(this.pos);
|
||
}
|
||
lookU16() {
|
||
return this.view.getUint16(this.pos);
|
||
}
|
||
lookU32() {
|
||
return this.view.getUint32(this.pos);
|
||
}
|
||
readU8() {
|
||
const value = this.view.getUint8(this.pos);
|
||
this.pos++;
|
||
return value;
|
||
}
|
||
readI8() {
|
||
const value = this.view.getInt8(this.pos);
|
||
this.pos++;
|
||
return value;
|
||
}
|
||
readU16() {
|
||
const value = this.view.getUint16(this.pos);
|
||
this.pos += 2;
|
||
return value;
|
||
}
|
||
readI16() {
|
||
const value = this.view.getInt16(this.pos);
|
||
this.pos += 2;
|
||
return value;
|
||
}
|
||
readU32() {
|
||
const value = this.view.getUint32(this.pos);
|
||
this.pos += 4;
|
||
return value;
|
||
}
|
||
readI32() {
|
||
const value = this.view.getInt32(this.pos);
|
||
this.pos += 4;
|
||
return value;
|
||
}
|
||
readU64() {
|
||
const value = getUint64(this.view, this.pos);
|
||
this.pos += 8;
|
||
return value;
|
||
}
|
||
readI64() {
|
||
const value = getInt64(this.view, this.pos);
|
||
this.pos += 8;
|
||
return value;
|
||
}
|
||
readF32() {
|
||
const value = this.view.getFloat32(this.pos);
|
||
this.pos += 4;
|
||
return value;
|
||
}
|
||
readF64() {
|
||
const value = this.view.getFloat64(this.pos);
|
||
this.pos += 8;
|
||
return value;
|
||
}
|
||
extensionCodec;
|
||
context;
|
||
maxStrLength;
|
||
maxBinLength;
|
||
maxArrayLength;
|
||
maxMapLength;
|
||
maxExtLength;
|
||
keyDecoder;
|
||
}
|
||
const defaultDecodeOptions = {};
|
||
function decode1(buffer, options = defaultDecodeOptions) {
|
||
const decoder = new Decoder(options.extensionCodec, options.context, options.maxStrLength, options.maxBinLength, options.maxArrayLength, options.maxMapLength, options.maxExtLength);
|
||
return decoder.decode(buffer);
|
||
}
|
||
function isAsyncIterable(object) {
|
||
return object[Symbol.asyncIterator] != null;
|
||
}
|
||
function assertNonNull(value) {
|
||
if (value == null) {
|
||
throw new Error("Assertion Failure: value must not be null nor undefined");
|
||
}
|
||
}
|
||
async function* asyncIterableFromStream(stream) {
|
||
const reader = stream.getReader();
|
||
try {
|
||
while(true){
|
||
const { done , value } = await reader.read();
|
||
if (done) {
|
||
return;
|
||
}
|
||
assertNonNull(value);
|
||
yield value;
|
||
}
|
||
} finally{
|
||
reader.releaseLock();
|
||
}
|
||
}
|
||
function ensureAsyncIterabe(streamLike) {
|
||
if (isAsyncIterable(streamLike)) {
|
||
return streamLike;
|
||
} else {
|
||
return asyncIterableFromStream(streamLike);
|
||
}
|
||
}
|
||
function decodeAsync(streamLike, options = defaultDecodeOptions) {
|
||
const stream = ensureAsyncIterabe(streamLike);
|
||
const decoder = new Decoder(options.extensionCodec, options.context, options.maxStrLength, options.maxBinLength, options.maxArrayLength, options.maxMapLength, options.maxExtLength);
|
||
return decoder.decodeAsync(stream);
|
||
}
|
||
function decodeArrayStream(streamLike, options = defaultDecodeOptions) {
|
||
const stream = ensureAsyncIterabe(streamLike);
|
||
const decoder = new Decoder(options.extensionCodec, options.context, options.maxStrLength, options.maxBinLength, options.maxArrayLength, options.maxMapLength, options.maxExtLength);
|
||
return decoder.decodeArrayStream(stream);
|
||
}
|
||
function decodeStream(streamLike, options = defaultDecodeOptions) {
|
||
const stream = ensureAsyncIterabe(streamLike);
|
||
const decoder = new Decoder(options.extensionCodec, options.context, options.maxStrLength, options.maxBinLength, options.maxArrayLength, options.maxMapLength, options.maxExtLength);
|
||
return decoder.decodeStream(stream);
|
||
}
|
||
const mod7 = {
|
||
encode: encode1,
|
||
decode: decode1,
|
||
decodeArrayStream: decodeArrayStream,
|
||
decodeAsync: decodeAsync,
|
||
decodeStream: decodeStream,
|
||
Decoder: Decoder,
|
||
Encoder: Encoder,
|
||
ExtensionCodec: ExtensionCodec,
|
||
ExtData: ExtData,
|
||
decodeTimestampExtension: decodeTimestampExtension,
|
||
decodeTimestampToTimeSpec: decodeTimestampToTimeSpec,
|
||
encodeDateToTimeSpec: encodeDateToTimeSpec,
|
||
encodeTimeSpecToTimestamp: encodeTimeSpecToTimestamp,
|
||
encodeTimestampExtension: encodeTimestampExtension,
|
||
EXT_TIMESTAMP: EXT_TIMESTAMP
|
||
};
|
||
const osType = (()=>{
|
||
const { Deno: Deno1 } = globalThis;
|
||
if (typeof Deno1?.build?.os === "string") {
|
||
return Deno1.build.os;
|
||
}
|
||
const { navigator } = globalThis;
|
||
if (navigator?.appVersion?.includes?.("Win")) {
|
||
return "windows";
|
||
}
|
||
return "linux";
|
||
})();
|
||
const isWindows = osType === "windows";
|
||
const CHAR_FORWARD_SLASH = 47;
|
||
function assertPath(path) {
|
||
if (typeof path !== "string") {
|
||
throw new TypeError(`Path must be a string. Received ${JSON.stringify(path)}`);
|
||
}
|
||
}
|
||
function isPosixPathSeparator(code) {
|
||
return code === 47;
|
||
}
|
||
function isPathSeparator(code) {
|
||
return isPosixPathSeparator(code) || code === 92;
|
||
}
|
||
function isWindowsDeviceRoot(code) {
|
||
return code >= 97 && code <= 122 || code >= 65 && code <= 90;
|
||
}
|
||
function normalizeString(path, allowAboveRoot, separator, isPathSeparator) {
|
||
let res = "";
|
||
let lastSegmentLength = 0;
|
||
let lastSlash = -1;
|
||
let dots = 0;
|
||
let code;
|
||
for(let i = 0, len = path.length; i <= len; ++i){
|
||
if (i < len) code = path.charCodeAt(i);
|
||
else if (isPathSeparator(code)) break;
|
||
else code = CHAR_FORWARD_SLASH;
|
||
if (isPathSeparator(code)) {
|
||
if (lastSlash === i - 1 || dots === 1) {} else if (lastSlash !== i - 1 && dots === 2) {
|
||
if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
|
||
if (res.length > 2) {
|
||
const lastSlashIndex = res.lastIndexOf(separator);
|
||
if (lastSlashIndex === -1) {
|
||
res = "";
|
||
lastSegmentLength = 0;
|
||
} else {
|
||
res = res.slice(0, lastSlashIndex);
|
||
lastSegmentLength = res.length - 1 - res.lastIndexOf(separator);
|
||
}
|
||
lastSlash = i;
|
||
dots = 0;
|
||
continue;
|
||
} else if (res.length === 2 || res.length === 1) {
|
||
res = "";
|
||
lastSegmentLength = 0;
|
||
lastSlash = i;
|
||
dots = 0;
|
||
continue;
|
||
}
|
||
}
|
||
if (allowAboveRoot) {
|
||
if (res.length > 0) res += `${separator}..`;
|
||
else res = "..";
|
||
lastSegmentLength = 2;
|
||
}
|
||
} else {
|
||
if (res.length > 0) res += separator + path.slice(lastSlash + 1, i);
|
||
else res = path.slice(lastSlash + 1, i);
|
||
lastSegmentLength = i - lastSlash - 1;
|
||
}
|
||
lastSlash = i;
|
||
dots = 0;
|
||
} else if (code === 46 && dots !== -1) {
|
||
++dots;
|
||
} else {
|
||
dots = -1;
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
function _format(sep, pathObject) {
|
||
const dir = pathObject.dir || pathObject.root;
|
||
const base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
|
||
if (!dir) return base;
|
||
if (dir === pathObject.root) return dir + base;
|
||
return dir + sep + base;
|
||
}
|
||
const WHITESPACE_ENCODINGS = {
|
||
"\u0009": "%09",
|
||
"\u000A": "%0A",
|
||
"\u000B": "%0B",
|
||
"\u000C": "%0C",
|
||
"\u000D": "%0D",
|
||
"\u0020": "%20"
|
||
};
|
||
function encodeWhitespace(string) {
|
||
return string.replaceAll(/[\s]/g, (c)=>{
|
||
return WHITESPACE_ENCODINGS[c] ?? c;
|
||
});
|
||
}
|
||
const sep = "\\";
|
||
const delimiter = ";";
|
||
function resolve(...pathSegments) {
|
||
let resolvedDevice = "";
|
||
let resolvedTail = "";
|
||
let resolvedAbsolute = false;
|
||
for(let i = pathSegments.length - 1; i >= -1; i--){
|
||
let path;
|
||
const { Deno: Deno1 } = globalThis;
|
||
if (i >= 0) {
|
||
path = pathSegments[i];
|
||
} else if (!resolvedDevice) {
|
||
if (typeof Deno1?.cwd !== "function") {
|
||
throw new TypeError("Resolved a drive-letter-less path without a CWD.");
|
||
}
|
||
path = Deno1.cwd();
|
||
} else {
|
||
if (typeof Deno1?.env?.get !== "function" || typeof Deno1?.cwd !== "function") {
|
||
throw new TypeError("Resolved a relative path without a CWD.");
|
||
}
|
||
path = Deno1.cwd();
|
||
if (path === undefined || path.slice(0, 3).toLowerCase() !== `${resolvedDevice.toLowerCase()}\\`) {
|
||
path = `${resolvedDevice}\\`;
|
||
}
|
||
}
|
||
assertPath(path);
|
||
const len = path.length;
|
||
if (len === 0) continue;
|
||
let rootEnd = 0;
|
||
let device = "";
|
||
let isAbsolute = false;
|
||
const code = path.charCodeAt(0);
|
||
if (len > 1) {
|
||
if (isPathSeparator(code)) {
|
||
isAbsolute = true;
|
||
if (isPathSeparator(path.charCodeAt(1))) {
|
||
let j = 2;
|
||
let last = j;
|
||
for(; j < len; ++j){
|
||
if (isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j < len && j !== last) {
|
||
const firstPart = path.slice(last, j);
|
||
last = j;
|
||
for(; j < len; ++j){
|
||
if (!isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j < len && j !== last) {
|
||
last = j;
|
||
for(; j < len; ++j){
|
||
if (isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j === len) {
|
||
device = `\\\\${firstPart}\\${path.slice(last)}`;
|
||
rootEnd = j;
|
||
} else if (j !== last) {
|
||
device = `\\\\${firstPart}\\${path.slice(last, j)}`;
|
||
rootEnd = j;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
rootEnd = 1;
|
||
}
|
||
} else if (isWindowsDeviceRoot(code)) {
|
||
if (path.charCodeAt(1) === 58) {
|
||
device = path.slice(0, 2);
|
||
rootEnd = 2;
|
||
if (len > 2) {
|
||
if (isPathSeparator(path.charCodeAt(2))) {
|
||
isAbsolute = true;
|
||
rootEnd = 3;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else if (isPathSeparator(code)) {
|
||
rootEnd = 1;
|
||
isAbsolute = true;
|
||
}
|
||
if (device.length > 0 && resolvedDevice.length > 0 && device.toLowerCase() !== resolvedDevice.toLowerCase()) {
|
||
continue;
|
||
}
|
||
if (resolvedDevice.length === 0 && device.length > 0) {
|
||
resolvedDevice = device;
|
||
}
|
||
if (!resolvedAbsolute) {
|
||
resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`;
|
||
resolvedAbsolute = isAbsolute;
|
||
}
|
||
if (resolvedAbsolute && resolvedDevice.length > 0) break;
|
||
}
|
||
resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, "\\", isPathSeparator);
|
||
return resolvedDevice + (resolvedAbsolute ? "\\" : "") + resolvedTail || ".";
|
||
}
|
||
function normalize(path) {
|
||
assertPath(path);
|
||
const len = path.length;
|
||
if (len === 0) return ".";
|
||
let rootEnd = 0;
|
||
let device;
|
||
let isAbsolute = false;
|
||
const code = path.charCodeAt(0);
|
||
if (len > 1) {
|
||
if (isPathSeparator(code)) {
|
||
isAbsolute = true;
|
||
if (isPathSeparator(path.charCodeAt(1))) {
|
||
let j = 2;
|
||
let last = j;
|
||
for(; j < len; ++j){
|
||
if (isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j < len && j !== last) {
|
||
const firstPart = path.slice(last, j);
|
||
last = j;
|
||
for(; j < len; ++j){
|
||
if (!isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j < len && j !== last) {
|
||
last = j;
|
||
for(; j < len; ++j){
|
||
if (isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j === len) {
|
||
return `\\\\${firstPart}\\${path.slice(last)}\\`;
|
||
} else if (j !== last) {
|
||
device = `\\\\${firstPart}\\${path.slice(last, j)}`;
|
||
rootEnd = j;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
rootEnd = 1;
|
||
}
|
||
} else if (isWindowsDeviceRoot(code)) {
|
||
if (path.charCodeAt(1) === 58) {
|
||
device = path.slice(0, 2);
|
||
rootEnd = 2;
|
||
if (len > 2) {
|
||
if (isPathSeparator(path.charCodeAt(2))) {
|
||
isAbsolute = true;
|
||
rootEnd = 3;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else if (isPathSeparator(code)) {
|
||
return "\\";
|
||
}
|
||
let tail;
|
||
if (rootEnd < len) {
|
||
tail = normalizeString(path.slice(rootEnd), !isAbsolute, "\\", isPathSeparator);
|
||
} else {
|
||
tail = "";
|
||
}
|
||
if (tail.length === 0 && !isAbsolute) tail = ".";
|
||
if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) {
|
||
tail += "\\";
|
||
}
|
||
if (device === undefined) {
|
||
if (isAbsolute) {
|
||
if (tail.length > 0) return `\\${tail}`;
|
||
else return "\\";
|
||
} else if (tail.length > 0) {
|
||
return tail;
|
||
} else {
|
||
return "";
|
||
}
|
||
} else if (isAbsolute) {
|
||
if (tail.length > 0) return `${device}\\${tail}`;
|
||
else return `${device}\\`;
|
||
} else if (tail.length > 0) {
|
||
return device + tail;
|
||
} else {
|
||
return device;
|
||
}
|
||
}
|
||
function isAbsolute(path) {
|
||
assertPath(path);
|
||
const len = path.length;
|
||
if (len === 0) return false;
|
||
const code = path.charCodeAt(0);
|
||
if (isPathSeparator(code)) {
|
||
return true;
|
||
} else if (isWindowsDeviceRoot(code)) {
|
||
if (len > 2 && path.charCodeAt(1) === 58) {
|
||
if (isPathSeparator(path.charCodeAt(2))) return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
function join(...paths) {
|
||
const pathsCount = paths.length;
|
||
if (pathsCount === 0) return ".";
|
||
let joined;
|
||
let firstPart = null;
|
||
for(let i = 0; i < pathsCount; ++i){
|
||
const path = paths[i];
|
||
assertPath(path);
|
||
if (path.length > 0) {
|
||
if (joined === undefined) joined = firstPart = path;
|
||
else joined += `\\${path}`;
|
||
}
|
||
}
|
||
if (joined === undefined) return ".";
|
||
let needsReplace = true;
|
||
let slashCount = 0;
|
||
assert(firstPart != null);
|
||
if (isPathSeparator(firstPart.charCodeAt(0))) {
|
||
++slashCount;
|
||
const firstLen = firstPart.length;
|
||
if (firstLen > 1) {
|
||
if (isPathSeparator(firstPart.charCodeAt(1))) {
|
||
++slashCount;
|
||
if (firstLen > 2) {
|
||
if (isPathSeparator(firstPart.charCodeAt(2))) ++slashCount;
|
||
else {
|
||
needsReplace = false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (needsReplace) {
|
||
for(; slashCount < joined.length; ++slashCount){
|
||
if (!isPathSeparator(joined.charCodeAt(slashCount))) break;
|
||
}
|
||
if (slashCount >= 2) joined = `\\${joined.slice(slashCount)}`;
|
||
}
|
||
return normalize(joined);
|
||
}
|
||
function relative(from, to) {
|
||
assertPath(from);
|
||
assertPath(to);
|
||
if (from === to) return "";
|
||
const fromOrig = resolve(from);
|
||
const toOrig = resolve(to);
|
||
if (fromOrig === toOrig) return "";
|
||
from = fromOrig.toLowerCase();
|
||
to = toOrig.toLowerCase();
|
||
if (from === to) return "";
|
||
let fromStart = 0;
|
||
let fromEnd = from.length;
|
||
for(; fromStart < fromEnd; ++fromStart){
|
||
if (from.charCodeAt(fromStart) !== 92) break;
|
||
}
|
||
for(; fromEnd - 1 > fromStart; --fromEnd){
|
||
if (from.charCodeAt(fromEnd - 1) !== 92) break;
|
||
}
|
||
const fromLen = fromEnd - fromStart;
|
||
let toStart = 0;
|
||
let toEnd = to.length;
|
||
for(; toStart < toEnd; ++toStart){
|
||
if (to.charCodeAt(toStart) !== 92) break;
|
||
}
|
||
for(; toEnd - 1 > toStart; --toEnd){
|
||
if (to.charCodeAt(toEnd - 1) !== 92) break;
|
||
}
|
||
const toLen = toEnd - toStart;
|
||
const length = fromLen < toLen ? fromLen : toLen;
|
||
let lastCommonSep = -1;
|
||
let i = 0;
|
||
for(; i <= length; ++i){
|
||
if (i === length) {
|
||
if (toLen > length) {
|
||
if (to.charCodeAt(toStart + i) === 92) {
|
||
return toOrig.slice(toStart + i + 1);
|
||
} else if (i === 2) {
|
||
return toOrig.slice(toStart + i);
|
||
}
|
||
}
|
||
if (fromLen > length) {
|
||
if (from.charCodeAt(fromStart + i) === 92) {
|
||
lastCommonSep = i;
|
||
} else if (i === 2) {
|
||
lastCommonSep = 3;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
const fromCode = from.charCodeAt(fromStart + i);
|
||
const toCode = to.charCodeAt(toStart + i);
|
||
if (fromCode !== toCode) break;
|
||
else if (fromCode === 92) lastCommonSep = i;
|
||
}
|
||
if (i !== length && lastCommonSep === -1) {
|
||
return toOrig;
|
||
}
|
||
let out = "";
|
||
if (lastCommonSep === -1) lastCommonSep = 0;
|
||
for(i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i){
|
||
if (i === fromEnd || from.charCodeAt(i) === 92) {
|
||
if (out.length === 0) out += "..";
|
||
else out += "\\..";
|
||
}
|
||
}
|
||
if (out.length > 0) {
|
||
return out + toOrig.slice(toStart + lastCommonSep, toEnd);
|
||
} else {
|
||
toStart += lastCommonSep;
|
||
if (toOrig.charCodeAt(toStart) === 92) ++toStart;
|
||
return toOrig.slice(toStart, toEnd);
|
||
}
|
||
}
|
||
function toNamespacedPath(path) {
|
||
if (typeof path !== "string") return path;
|
||
if (path.length === 0) return "";
|
||
const resolvedPath = resolve(path);
|
||
if (resolvedPath.length >= 3) {
|
||
if (resolvedPath.charCodeAt(0) === 92) {
|
||
if (resolvedPath.charCodeAt(1) === 92) {
|
||
const code = resolvedPath.charCodeAt(2);
|
||
if (code !== 63 && code !== 46) {
|
||
return `\\\\?\\UNC\\${resolvedPath.slice(2)}`;
|
||
}
|
||
}
|
||
} else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0))) {
|
||
if (resolvedPath.charCodeAt(1) === 58 && resolvedPath.charCodeAt(2) === 92) {
|
||
return `\\\\?\\${resolvedPath}`;
|
||
}
|
||
}
|
||
}
|
||
return path;
|
||
}
|
||
function dirname(path) {
|
||
assertPath(path);
|
||
const len = path.length;
|
||
if (len === 0) return ".";
|
||
let rootEnd = -1;
|
||
let end = -1;
|
||
let matchedSlash = true;
|
||
let offset = 0;
|
||
const code = path.charCodeAt(0);
|
||
if (len > 1) {
|
||
if (isPathSeparator(code)) {
|
||
rootEnd = offset = 1;
|
||
if (isPathSeparator(path.charCodeAt(1))) {
|
||
let j = 2;
|
||
let last = j;
|
||
for(; j < len; ++j){
|
||
if (isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j < len && j !== last) {
|
||
last = j;
|
||
for(; j < len; ++j){
|
||
if (!isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j < len && j !== last) {
|
||
last = j;
|
||
for(; j < len; ++j){
|
||
if (isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j === len) {
|
||
return path;
|
||
}
|
||
if (j !== last) {
|
||
rootEnd = offset = j + 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else if (isWindowsDeviceRoot(code)) {
|
||
if (path.charCodeAt(1) === 58) {
|
||
rootEnd = offset = 2;
|
||
if (len > 2) {
|
||
if (isPathSeparator(path.charCodeAt(2))) rootEnd = offset = 3;
|
||
}
|
||
}
|
||
}
|
||
} else if (isPathSeparator(code)) {
|
||
return path;
|
||
}
|
||
for(let i = len - 1; i >= offset; --i){
|
||
if (isPathSeparator(path.charCodeAt(i))) {
|
||
if (!matchedSlash) {
|
||
end = i;
|
||
break;
|
||
}
|
||
} else {
|
||
matchedSlash = false;
|
||
}
|
||
}
|
||
if (end === -1) {
|
||
if (rootEnd === -1) return ".";
|
||
else end = rootEnd;
|
||
}
|
||
return path.slice(0, end);
|
||
}
|
||
function basename(path, ext = "") {
|
||
if (ext !== undefined && typeof ext !== "string") {
|
||
throw new TypeError('"ext" argument must be a string');
|
||
}
|
||
assertPath(path);
|
||
let start = 0;
|
||
let end = -1;
|
||
let matchedSlash = true;
|
||
let i;
|
||
if (path.length >= 2) {
|
||
const drive = path.charCodeAt(0);
|
||
if (isWindowsDeviceRoot(drive)) {
|
||
if (path.charCodeAt(1) === 58) start = 2;
|
||
}
|
||
}
|
||
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
|
||
if (ext.length === path.length && ext === path) return "";
|
||
let extIdx = ext.length - 1;
|
||
let firstNonSlashEnd = -1;
|
||
for(i = path.length - 1; i >= start; --i){
|
||
const code = path.charCodeAt(i);
|
||
if (isPathSeparator(code)) {
|
||
if (!matchedSlash) {
|
||
start = i + 1;
|
||
break;
|
||
}
|
||
} else {
|
||
if (firstNonSlashEnd === -1) {
|
||
matchedSlash = false;
|
||
firstNonSlashEnd = i + 1;
|
||
}
|
||
if (extIdx >= 0) {
|
||
if (code === ext.charCodeAt(extIdx)) {
|
||
if (--extIdx === -1) {
|
||
end = i;
|
||
}
|
||
} else {
|
||
extIdx = -1;
|
||
end = firstNonSlashEnd;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (start === end) end = firstNonSlashEnd;
|
||
else if (end === -1) end = path.length;
|
||
return path.slice(start, end);
|
||
} else {
|
||
for(i = path.length - 1; i >= start; --i){
|
||
if (isPathSeparator(path.charCodeAt(i))) {
|
||
if (!matchedSlash) {
|
||
start = i + 1;
|
||
break;
|
||
}
|
||
} else if (end === -1) {
|
||
matchedSlash = false;
|
||
end = i + 1;
|
||
}
|
||
}
|
||
if (end === -1) return "";
|
||
return path.slice(start, end);
|
||
}
|
||
}
|
||
function extname(path) {
|
||
assertPath(path);
|
||
let start = 0;
|
||
let startDot = -1;
|
||
let startPart = 0;
|
||
let end = -1;
|
||
let matchedSlash = true;
|
||
let preDotState = 0;
|
||
if (path.length >= 2 && path.charCodeAt(1) === 58 && isWindowsDeviceRoot(path.charCodeAt(0))) {
|
||
start = startPart = 2;
|
||
}
|
||
for(let i = path.length - 1; i >= start; --i){
|
||
const code = path.charCodeAt(i);
|
||
if (isPathSeparator(code)) {
|
||
if (!matchedSlash) {
|
||
startPart = i + 1;
|
||
break;
|
||
}
|
||
continue;
|
||
}
|
||
if (end === -1) {
|
||
matchedSlash = false;
|
||
end = i + 1;
|
||
}
|
||
if (code === 46) {
|
||
if (startDot === -1) startDot = i;
|
||
else if (preDotState !== 1) preDotState = 1;
|
||
} else if (startDot !== -1) {
|
||
preDotState = -1;
|
||
}
|
||
}
|
||
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
||
return "";
|
||
}
|
||
return path.slice(startDot, end);
|
||
}
|
||
function format(pathObject) {
|
||
if (pathObject === null || typeof pathObject !== "object") {
|
||
throw new TypeError(`The "pathObject" argument must be of type Object. Received type ${typeof pathObject}`);
|
||
}
|
||
return _format("\\", pathObject);
|
||
}
|
||
function parse1(path) {
|
||
assertPath(path);
|
||
const ret = {
|
||
root: "",
|
||
dir: "",
|
||
base: "",
|
||
ext: "",
|
||
name: ""
|
||
};
|
||
const len = path.length;
|
||
if (len === 0) return ret;
|
||
let rootEnd = 0;
|
||
let code = path.charCodeAt(0);
|
||
if (len > 1) {
|
||
if (isPathSeparator(code)) {
|
||
rootEnd = 1;
|
||
if (isPathSeparator(path.charCodeAt(1))) {
|
||
let j = 2;
|
||
let last = j;
|
||
for(; j < len; ++j){
|
||
if (isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j < len && j !== last) {
|
||
last = j;
|
||
for(; j < len; ++j){
|
||
if (!isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j < len && j !== last) {
|
||
last = j;
|
||
for(; j < len; ++j){
|
||
if (isPathSeparator(path.charCodeAt(j))) break;
|
||
}
|
||
if (j === len) {
|
||
rootEnd = j;
|
||
} else if (j !== last) {
|
||
rootEnd = j + 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else if (isWindowsDeviceRoot(code)) {
|
||
if (path.charCodeAt(1) === 58) {
|
||
rootEnd = 2;
|
||
if (len > 2) {
|
||
if (isPathSeparator(path.charCodeAt(2))) {
|
||
if (len === 3) {
|
||
ret.root = ret.dir = path;
|
||
return ret;
|
||
}
|
||
rootEnd = 3;
|
||
}
|
||
} else {
|
||
ret.root = ret.dir = path;
|
||
return ret;
|
||
}
|
||
}
|
||
}
|
||
} else if (isPathSeparator(code)) {
|
||
ret.root = ret.dir = path;
|
||
return ret;
|
||
}
|
||
if (rootEnd > 0) ret.root = path.slice(0, rootEnd);
|
||
let startDot = -1;
|
||
let startPart = rootEnd;
|
||
let end = -1;
|
||
let matchedSlash = true;
|
||
let i = path.length - 1;
|
||
let preDotState = 0;
|
||
for(; i >= rootEnd; --i){
|
||
code = path.charCodeAt(i);
|
||
if (isPathSeparator(code)) {
|
||
if (!matchedSlash) {
|
||
startPart = i + 1;
|
||
break;
|
||
}
|
||
continue;
|
||
}
|
||
if (end === -1) {
|
||
matchedSlash = false;
|
||
end = i + 1;
|
||
}
|
||
if (code === 46) {
|
||
if (startDot === -1) startDot = i;
|
||
else if (preDotState !== 1) preDotState = 1;
|
||
} else if (startDot !== -1) {
|
||
preDotState = -1;
|
||
}
|
||
}
|
||
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
||
if (end !== -1) {
|
||
ret.base = ret.name = path.slice(startPart, end);
|
||
}
|
||
} else {
|
||
ret.name = path.slice(startPart, startDot);
|
||
ret.base = path.slice(startPart, end);
|
||
ret.ext = path.slice(startDot, end);
|
||
}
|
||
if (startPart > 0 && startPart !== rootEnd) {
|
||
ret.dir = path.slice(0, startPart - 1);
|
||
} else ret.dir = ret.root;
|
||
return ret;
|
||
}
|
||
function fromFileUrl(url) {
|
||
url = url instanceof URL ? url : new URL(url);
|
||
if (url.protocol != "file:") {
|
||
throw new TypeError("Must be a file URL.");
|
||
}
|
||
let path = decodeURIComponent(url.pathname.replace(/\//g, "\\").replace(/%(?![0-9A-Fa-f]{2})/g, "%25")).replace(/^\\*([A-Za-z]:)(\\|$)/, "$1\\");
|
||
if (url.hostname != "") {
|
||
path = `\\\\${url.hostname}${path}`;
|
||
}
|
||
return path;
|
||
}
|
||
function toFileUrl(path) {
|
||
if (!isAbsolute(path)) {
|
||
throw new TypeError("Must be an absolute path.");
|
||
}
|
||
const [, hostname, pathname] = path.match(/^(?:[/\\]{2}([^/\\]+)(?=[/\\](?:[^/\\]|$)))?(.*)/);
|
||
const url = new URL("file:///");
|
||
url.pathname = encodeWhitespace(pathname.replace(/%/g, "%25"));
|
||
if (hostname != null && hostname != "localhost") {
|
||
url.hostname = hostname;
|
||
if (!url.hostname) {
|
||
throw new TypeError("Invalid hostname.");
|
||
}
|
||
}
|
||
return url;
|
||
}
|
||
const mod8 = {
|
||
sep: sep,
|
||
delimiter: delimiter,
|
||
resolve: resolve,
|
||
normalize: normalize,
|
||
isAbsolute: isAbsolute,
|
||
join: join,
|
||
relative: relative,
|
||
toNamespacedPath: toNamespacedPath,
|
||
dirname: dirname,
|
||
basename: basename,
|
||
extname: extname,
|
||
format: format,
|
||
parse: parse1,
|
||
fromFileUrl: fromFileUrl,
|
||
toFileUrl: toFileUrl
|
||
};
|
||
const sep1 = "/";
|
||
const delimiter1 = ":";
|
||
function resolve1(...pathSegments) {
|
||
let resolvedPath = "";
|
||
let resolvedAbsolute = false;
|
||
for(let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--){
|
||
let path;
|
||
if (i >= 0) path = pathSegments[i];
|
||
else {
|
||
const { Deno: Deno1 } = globalThis;
|
||
if (typeof Deno1?.cwd !== "function") {
|
||
throw new TypeError("Resolved a relative path without a CWD.");
|
||
}
|
||
path = Deno1.cwd();
|
||
}
|
||
assertPath(path);
|
||
if (path.length === 0) {
|
||
continue;
|
||
}
|
||
resolvedPath = `${path}/${resolvedPath}`;
|
||
resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;
|
||
}
|
||
resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, "/", isPosixPathSeparator);
|
||
if (resolvedAbsolute) {
|
||
if (resolvedPath.length > 0) return `/${resolvedPath}`;
|
||
else return "/";
|
||
} else if (resolvedPath.length > 0) return resolvedPath;
|
||
else return ".";
|
||
}
|
||
function normalize1(path) {
|
||
assertPath(path);
|
||
if (path.length === 0) return ".";
|
||
const isAbsolute = path.charCodeAt(0) === 47;
|
||
const trailingSeparator = path.charCodeAt(path.length - 1) === 47;
|
||
path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator);
|
||
if (path.length === 0 && !isAbsolute) path = ".";
|
||
if (path.length > 0 && trailingSeparator) path += "/";
|
||
if (isAbsolute) return `/${path}`;
|
||
return path;
|
||
}
|
||
function isAbsolute1(path) {
|
||
assertPath(path);
|
||
return path.length > 0 && path.charCodeAt(0) === 47;
|
||
}
|
||
function join1(...paths) {
|
||
if (paths.length === 0) return ".";
|
||
let joined;
|
||
for(let i = 0, len = paths.length; i < len; ++i){
|
||
const path = paths[i];
|
||
assertPath(path);
|
||
if (path.length > 0) {
|
||
if (!joined) joined = path;
|
||
else joined += `/${path}`;
|
||
}
|
||
}
|
||
if (!joined) return ".";
|
||
return normalize1(joined);
|
||
}
|
||
function relative1(from, to) {
|
||
assertPath(from);
|
||
assertPath(to);
|
||
if (from === to) return "";
|
||
from = resolve1(from);
|
||
to = resolve1(to);
|
||
if (from === to) return "";
|
||
let fromStart = 1;
|
||
const fromEnd = from.length;
|
||
for(; fromStart < fromEnd; ++fromStart){
|
||
if (from.charCodeAt(fromStart) !== 47) break;
|
||
}
|
||
const fromLen = fromEnd - fromStart;
|
||
let toStart = 1;
|
||
const toEnd = to.length;
|
||
for(; toStart < toEnd; ++toStart){
|
||
if (to.charCodeAt(toStart) !== 47) break;
|
||
}
|
||
const toLen = toEnd - toStart;
|
||
const length = fromLen < toLen ? fromLen : toLen;
|
||
let lastCommonSep = -1;
|
||
let i = 0;
|
||
for(; i <= length; ++i){
|
||
if (i === length) {
|
||
if (toLen > length) {
|
||
if (to.charCodeAt(toStart + i) === 47) {
|
||
return to.slice(toStart + i + 1);
|
||
} else if (i === 0) {
|
||
return to.slice(toStart + i);
|
||
}
|
||
} else if (fromLen > length) {
|
||
if (from.charCodeAt(fromStart + i) === 47) {
|
||
lastCommonSep = i;
|
||
} else if (i === 0) {
|
||
lastCommonSep = 0;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
const fromCode = from.charCodeAt(fromStart + i);
|
||
const toCode = to.charCodeAt(toStart + i);
|
||
if (fromCode !== toCode) break;
|
||
else if (fromCode === 47) lastCommonSep = i;
|
||
}
|
||
let out = "";
|
||
for(i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i){
|
||
if (i === fromEnd || from.charCodeAt(i) === 47) {
|
||
if (out.length === 0) out += "..";
|
||
else out += "/..";
|
||
}
|
||
}
|
||
if (out.length > 0) return out + to.slice(toStart + lastCommonSep);
|
||
else {
|
||
toStart += lastCommonSep;
|
||
if (to.charCodeAt(toStart) === 47) ++toStart;
|
||
return to.slice(toStart);
|
||
}
|
||
}
|
||
function toNamespacedPath1(path) {
|
||
return path;
|
||
}
|
||
function dirname1(path) {
|
||
assertPath(path);
|
||
if (path.length === 0) return ".";
|
||
const hasRoot = path.charCodeAt(0) === 47;
|
||
let end = -1;
|
||
let matchedSlash = true;
|
||
for(let i = path.length - 1; i >= 1; --i){
|
||
if (path.charCodeAt(i) === 47) {
|
||
if (!matchedSlash) {
|
||
end = i;
|
||
break;
|
||
}
|
||
} else {
|
||
matchedSlash = false;
|
||
}
|
||
}
|
||
if (end === -1) return hasRoot ? "/" : ".";
|
||
if (hasRoot && end === 1) return "//";
|
||
return path.slice(0, end);
|
||
}
|
||
function basename1(path, ext = "") {
|
||
if (ext !== undefined && typeof ext !== "string") {
|
||
throw new TypeError('"ext" argument must be a string');
|
||
}
|
||
assertPath(path);
|
||
let start = 0;
|
||
let end = -1;
|
||
let matchedSlash = true;
|
||
let i;
|
||
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
|
||
if (ext.length === path.length && ext === path) return "";
|
||
let extIdx = ext.length - 1;
|
||
let firstNonSlashEnd = -1;
|
||
for(i = path.length - 1; i >= 0; --i){
|
||
const code = path.charCodeAt(i);
|
||
if (code === 47) {
|
||
if (!matchedSlash) {
|
||
start = i + 1;
|
||
break;
|
||
}
|
||
} else {
|
||
if (firstNonSlashEnd === -1) {
|
||
matchedSlash = false;
|
||
firstNonSlashEnd = i + 1;
|
||
}
|
||
if (extIdx >= 0) {
|
||
if (code === ext.charCodeAt(extIdx)) {
|
||
if (--extIdx === -1) {
|
||
end = i;
|
||
}
|
||
} else {
|
||
extIdx = -1;
|
||
end = firstNonSlashEnd;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (start === end) end = firstNonSlashEnd;
|
||
else if (end === -1) end = path.length;
|
||
return path.slice(start, end);
|
||
} else {
|
||
for(i = path.length - 1; i >= 0; --i){
|
||
if (path.charCodeAt(i) === 47) {
|
||
if (!matchedSlash) {
|
||
start = i + 1;
|
||
break;
|
||
}
|
||
} else if (end === -1) {
|
||
matchedSlash = false;
|
||
end = i + 1;
|
||
}
|
||
}
|
||
if (end === -1) return "";
|
||
return path.slice(start, end);
|
||
}
|
||
}
|
||
function extname1(path) {
|
||
assertPath(path);
|
||
let startDot = -1;
|
||
let startPart = 0;
|
||
let end = -1;
|
||
let matchedSlash = true;
|
||
let preDotState = 0;
|
||
for(let i = path.length - 1; i >= 0; --i){
|
||
const code = path.charCodeAt(i);
|
||
if (code === 47) {
|
||
if (!matchedSlash) {
|
||
startPart = i + 1;
|
||
break;
|
||
}
|
||
continue;
|
||
}
|
||
if (end === -1) {
|
||
matchedSlash = false;
|
||
end = i + 1;
|
||
}
|
||
if (code === 46) {
|
||
if (startDot === -1) startDot = i;
|
||
else if (preDotState !== 1) preDotState = 1;
|
||
} else if (startDot !== -1) {
|
||
preDotState = -1;
|
||
}
|
||
}
|
||
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
||
return "";
|
||
}
|
||
return path.slice(startDot, end);
|
||
}
|
||
function format1(pathObject) {
|
||
if (pathObject === null || typeof pathObject !== "object") {
|
||
throw new TypeError(`The "pathObject" argument must be of type Object. Received type ${typeof pathObject}`);
|
||
}
|
||
return _format("/", pathObject);
|
||
}
|
||
function parse2(path) {
|
||
assertPath(path);
|
||
const ret = {
|
||
root: "",
|
||
dir: "",
|
||
base: "",
|
||
ext: "",
|
||
name: ""
|
||
};
|
||
if (path.length === 0) return ret;
|
||
const isAbsolute = path.charCodeAt(0) === 47;
|
||
let start;
|
||
if (isAbsolute) {
|
||
ret.root = "/";
|
||
start = 1;
|
||
} else {
|
||
start = 0;
|
||
}
|
||
let startDot = -1;
|
||
let startPart = 0;
|
||
let end = -1;
|
||
let matchedSlash = true;
|
||
let i = path.length - 1;
|
||
let preDotState = 0;
|
||
for(; i >= start; --i){
|
||
const code = path.charCodeAt(i);
|
||
if (code === 47) {
|
||
if (!matchedSlash) {
|
||
startPart = i + 1;
|
||
break;
|
||
}
|
||
continue;
|
||
}
|
||
if (end === -1) {
|
||
matchedSlash = false;
|
||
end = i + 1;
|
||
}
|
||
if (code === 46) {
|
||
if (startDot === -1) startDot = i;
|
||
else if (preDotState !== 1) preDotState = 1;
|
||
} else if (startDot !== -1) {
|
||
preDotState = -1;
|
||
}
|
||
}
|
||
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
||
if (end !== -1) {
|
||
if (startPart === 0 && isAbsolute) {
|
||
ret.base = ret.name = path.slice(1, end);
|
||
} else {
|
||
ret.base = ret.name = path.slice(startPart, end);
|
||
}
|
||
}
|
||
} else {
|
||
if (startPart === 0 && isAbsolute) {
|
||
ret.name = path.slice(1, startDot);
|
||
ret.base = path.slice(1, end);
|
||
} else {
|
||
ret.name = path.slice(startPart, startDot);
|
||
ret.base = path.slice(startPart, end);
|
||
}
|
||
ret.ext = path.slice(startDot, end);
|
||
}
|
||
if (startPart > 0) ret.dir = path.slice(0, startPart - 1);
|
||
else if (isAbsolute) ret.dir = "/";
|
||
return ret;
|
||
}
|
||
function fromFileUrl1(url) {
|
||
url = url instanceof URL ? url : new URL(url);
|
||
if (url.protocol != "file:") {
|
||
throw new TypeError("Must be a file URL.");
|
||
}
|
||
return decodeURIComponent(url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"));
|
||
}
|
||
function toFileUrl1(path) {
|
||
if (!isAbsolute1(path)) {
|
||
throw new TypeError("Must be an absolute path.");
|
||
}
|
||
const url = new URL("file:///");
|
||
url.pathname = encodeWhitespace(path.replace(/%/g, "%25").replace(/\\/g, "%5C"));
|
||
return url;
|
||
}
|
||
const mod9 = {
|
||
sep: sep1,
|
||
delimiter: delimiter1,
|
||
resolve: resolve1,
|
||
normalize: normalize1,
|
||
isAbsolute: isAbsolute1,
|
||
join: join1,
|
||
relative: relative1,
|
||
toNamespacedPath: toNamespacedPath1,
|
||
dirname: dirname1,
|
||
basename: basename1,
|
||
extname: extname1,
|
||
format: format1,
|
||
parse: parse2,
|
||
fromFileUrl: fromFileUrl1,
|
||
toFileUrl: toFileUrl1
|
||
};
|
||
const SEP = isWindows ? "\\" : "/";
|
||
const SEP_PATTERN = isWindows ? /[\\/]+/ : /\/+/;
|
||
function common(paths, sep = SEP) {
|
||
const [first = "", ...remaining] = paths;
|
||
if (first === "" || remaining.length === 0) {
|
||
return first.substring(0, first.lastIndexOf(sep) + 1);
|
||
}
|
||
const parts = first.split(sep);
|
||
let endOfPrefix = parts.length;
|
||
for (const path of remaining){
|
||
const compare = path.split(sep);
|
||
for(let i = 0; i < endOfPrefix; i++){
|
||
if (compare[i] !== parts[i]) {
|
||
endOfPrefix = i;
|
||
}
|
||
}
|
||
if (endOfPrefix === 0) {
|
||
return "";
|
||
}
|
||
}
|
||
const prefix = parts.slice(0, endOfPrefix).join(sep);
|
||
return prefix.endsWith(sep) ? prefix : `${prefix}${sep}`;
|
||
}
|
||
const path = isWindows ? mod8 : mod9;
|
||
const { join: join2 , normalize: normalize2 } = path;
|
||
const regExpEscapeChars = [
|
||
"!",
|
||
"$",
|
||
"(",
|
||
")",
|
||
"*",
|
||
"+",
|
||
".",
|
||
"=",
|
||
"?",
|
||
"[",
|
||
"\\",
|
||
"^",
|
||
"{",
|
||
"|"
|
||
];
|
||
const rangeEscapeChars = [
|
||
"-",
|
||
"\\",
|
||
"]"
|
||
];
|
||
function globToRegExp(glob, { extended =true , globstar: globstarOption = true , os =osType , caseInsensitive =false } = {}) {
|
||
if (glob == "") {
|
||
return /(?!)/;
|
||
}
|
||
const sep = os == "windows" ? "(?:\\\\|/)+" : "/+";
|
||
const sepMaybe = os == "windows" ? "(?:\\\\|/)*" : "/*";
|
||
const seps = os == "windows" ? [
|
||
"\\",
|
||
"/"
|
||
] : [
|
||
"/"
|
||
];
|
||
const globstar = os == "windows" ? "(?:[^\\\\/]*(?:\\\\|/|$)+)*" : "(?:[^/]*(?:/|$)+)*";
|
||
const wildcard = os == "windows" ? "[^\\\\/]*" : "[^/]*";
|
||
const escapePrefix = os == "windows" ? "`" : "\\";
|
||
let newLength = glob.length;
|
||
for(; newLength > 1 && seps.includes(glob[newLength - 1]); newLength--);
|
||
glob = glob.slice(0, newLength);
|
||
let regExpString = "";
|
||
for(let j = 0; j < glob.length;){
|
||
let segment = "";
|
||
const groupStack = [];
|
||
let inRange = false;
|
||
let inEscape = false;
|
||
let endsWithSep = false;
|
||
let i = j;
|
||
for(; i < glob.length && !seps.includes(glob[i]); i++){
|
||
if (inEscape) {
|
||
inEscape = false;
|
||
const escapeChars = inRange ? rangeEscapeChars : regExpEscapeChars;
|
||
segment += escapeChars.includes(glob[i]) ? `\\${glob[i]}` : glob[i];
|
||
continue;
|
||
}
|
||
if (glob[i] == escapePrefix) {
|
||
inEscape = true;
|
||
continue;
|
||
}
|
||
if (glob[i] == "[") {
|
||
if (!inRange) {
|
||
inRange = true;
|
||
segment += "[";
|
||
if (glob[i + 1] == "!") {
|
||
i++;
|
||
segment += "^";
|
||
} else if (glob[i + 1] == "^") {
|
||
i++;
|
||
segment += "\\^";
|
||
}
|
||
continue;
|
||
} else if (glob[i + 1] == ":") {
|
||
let k = i + 1;
|
||
let value = "";
|
||
while(glob[k + 1] != null && glob[k + 1] != ":"){
|
||
value += glob[k + 1];
|
||
k++;
|
||
}
|
||
if (glob[k + 1] == ":" && glob[k + 2] == "]") {
|
||
i = k + 2;
|
||
if (value == "alnum") segment += "\\dA-Za-z";
|
||
else if (value == "alpha") segment += "A-Za-z";
|
||
else if (value == "ascii") segment += "\x00-\x7F";
|
||
else if (value == "blank") segment += "\t ";
|
||
else if (value == "cntrl") segment += "\x00-\x1F\x7F";
|
||
else if (value == "digit") segment += "\\d";
|
||
else if (value == "graph") segment += "\x21-\x7E";
|
||
else if (value == "lower") segment += "a-z";
|
||
else if (value == "print") segment += "\x20-\x7E";
|
||
else if (value == "punct") {
|
||
segment += "!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_‘{|}~";
|
||
} else if (value == "space") segment += "\\s\v";
|
||
else if (value == "upper") segment += "A-Z";
|
||
else if (value == "word") segment += "\\w";
|
||
else if (value == "xdigit") segment += "\\dA-Fa-f";
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
if (glob[i] == "]" && inRange) {
|
||
inRange = false;
|
||
segment += "]";
|
||
continue;
|
||
}
|
||
if (inRange) {
|
||
if (glob[i] == "\\") {
|
||
segment += `\\\\`;
|
||
} else {
|
||
segment += glob[i];
|
||
}
|
||
continue;
|
||
}
|
||
if (glob[i] == ")" && groupStack.length > 0 && groupStack[groupStack.length - 1] != "BRACE") {
|
||
segment += ")";
|
||
const type = groupStack.pop();
|
||
if (type == "!") {
|
||
segment += wildcard;
|
||
} else if (type != "@") {
|
||
segment += type;
|
||
}
|
||
continue;
|
||
}
|
||
if (glob[i] == "|" && groupStack.length > 0 && groupStack[groupStack.length - 1] != "BRACE") {
|
||
segment += "|";
|
||
continue;
|
||
}
|
||
if (glob[i] == "+" && extended && glob[i + 1] == "(") {
|
||
i++;
|
||
groupStack.push("+");
|
||
segment += "(?:";
|
||
continue;
|
||
}
|
||
if (glob[i] == "@" && extended && glob[i + 1] == "(") {
|
||
i++;
|
||
groupStack.push("@");
|
||
segment += "(?:";
|
||
continue;
|
||
}
|
||
if (glob[i] == "?") {
|
||
if (extended && glob[i + 1] == "(") {
|
||
i++;
|
||
groupStack.push("?");
|
||
segment += "(?:";
|
||
} else {
|
||
segment += ".";
|
||
}
|
||
continue;
|
||
}
|
||
if (glob[i] == "!" && extended && glob[i + 1] == "(") {
|
||
i++;
|
||
groupStack.push("!");
|
||
segment += "(?!";
|
||
continue;
|
||
}
|
||
if (glob[i] == "{") {
|
||
groupStack.push("BRACE");
|
||
segment += "(?:";
|
||
continue;
|
||
}
|
||
if (glob[i] == "}" && groupStack[groupStack.length - 1] == "BRACE") {
|
||
groupStack.pop();
|
||
segment += ")";
|
||
continue;
|
||
}
|
||
if (glob[i] == "," && groupStack[groupStack.length - 1] == "BRACE") {
|
||
segment += "|";
|
||
continue;
|
||
}
|
||
if (glob[i] == "*") {
|
||
if (extended && glob[i + 1] == "(") {
|
||
i++;
|
||
groupStack.push("*");
|
||
segment += "(?:";
|
||
} else {
|
||
const prevChar = glob[i - 1];
|
||
let numStars = 1;
|
||
while(glob[i + 1] == "*"){
|
||
i++;
|
||
numStars++;
|
||
}
|
||
const nextChar = glob[i + 1];
|
||
if (globstarOption && numStars == 2 && [
|
||
...seps,
|
||
undefined
|
||
].includes(prevChar) && [
|
||
...seps,
|
||
undefined
|
||
].includes(nextChar)) {
|
||
segment += globstar;
|
||
endsWithSep = true;
|
||
} else {
|
||
segment += wildcard;
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
segment += regExpEscapeChars.includes(glob[i]) ? `\\${glob[i]}` : glob[i];
|
||
}
|
||
if (groupStack.length > 0 || inRange || inEscape) {
|
||
segment = "";
|
||
for (const c of glob.slice(j, i)){
|
||
segment += regExpEscapeChars.includes(c) ? `\\${c}` : c;
|
||
endsWithSep = false;
|
||
}
|
||
}
|
||
regExpString += segment;
|
||
if (!endsWithSep) {
|
||
regExpString += i < glob.length ? sep : sepMaybe;
|
||
endsWithSep = true;
|
||
}
|
||
while(seps.includes(glob[i]))i++;
|
||
if (!(i > j)) {
|
||
throw new Error("Assertion failure: i > j (potential infinite loop)");
|
||
}
|
||
j = i;
|
||
}
|
||
regExpString = `^${regExpString}$`;
|
||
return new RegExp(regExpString, caseInsensitive ? "i" : "");
|
||
}
|
||
function isGlob(str) {
|
||
const chars = {
|
||
"{": "}",
|
||
"(": ")",
|
||
"[": "]"
|
||
};
|
||
const regex = /\\(.)|(^!|\*|\?|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/;
|
||
if (str === "") {
|
||
return false;
|
||
}
|
||
let match;
|
||
while(match = regex.exec(str)){
|
||
if (match[2]) return true;
|
||
let idx = match.index + match[0].length;
|
||
const open = match[1];
|
||
const close = open ? chars[open] : null;
|
||
if (open && close) {
|
||
const n = str.indexOf(close, idx);
|
||
if (n !== -1) {
|
||
idx = n + 1;
|
||
}
|
||
}
|
||
str = str.slice(idx);
|
||
}
|
||
return false;
|
||
}
|
||
function normalizeGlob(glob, { globstar =false } = {}) {
|
||
if (glob.match(/\0/g)) {
|
||
throw new Error(`Glob contains invalid characters: "${glob}"`);
|
||
}
|
||
if (!globstar) {
|
||
return normalize2(glob);
|
||
}
|
||
const s = SEP_PATTERN.source;
|
||
const badParentPattern = new RegExp(`(?<=(${s}|^)\\*\\*${s})\\.\\.(?=${s}|$)`, "g");
|
||
return normalize2(glob.replace(badParentPattern, "\0")).replace(/\0/g, "..");
|
||
}
|
||
function joinGlobs(globs, { extended =true , globstar =false } = {}) {
|
||
if (!globstar || globs.length == 0) {
|
||
return join2(...globs);
|
||
}
|
||
if (globs.length === 0) return ".";
|
||
let joined;
|
||
for (const glob of globs){
|
||
const path = glob;
|
||
if (path.length > 0) {
|
||
if (!joined) joined = path;
|
||
else joined += `${SEP}${path}`;
|
||
}
|
||
}
|
||
if (!joined) return ".";
|
||
return normalizeGlob(joined, {
|
||
extended,
|
||
globstar
|
||
});
|
||
}
|
||
const path1 = isWindows ? mod8 : mod9;
|
||
const { basename: basename2 , delimiter: delimiter2 , dirname: dirname2 , extname: extname2 , format: format2 , fromFileUrl: fromFileUrl2 , isAbsolute: isAbsolute2 , join: join3 , normalize: normalize3 , parse: parse3 , relative: relative2 , resolve: resolve2 , sep: sep2 , toFileUrl: toFileUrl2 , toNamespacedPath: toNamespacedPath2 } = path1;
|
||
const mod10 = {
|
||
SEP: SEP,
|
||
SEP_PATTERN: SEP_PATTERN,
|
||
win32: mod8,
|
||
posix: mod9,
|
||
basename: basename2,
|
||
delimiter: delimiter2,
|
||
dirname: dirname2,
|
||
extname: extname2,
|
||
format: format2,
|
||
fromFileUrl: fromFileUrl2,
|
||
isAbsolute: isAbsolute2,
|
||
join: join3,
|
||
normalize: normalize3,
|
||
parse: parse3,
|
||
relative: relative2,
|
||
resolve: resolve2,
|
||
sep: sep2,
|
||
toFileUrl: toFileUrl2,
|
||
toNamespacedPath: toNamespacedPath2,
|
||
common,
|
||
globToRegExp,
|
||
isGlob,
|
||
normalizeGlob,
|
||
joinGlobs
|
||
};
|
||
const { Deno: Deno1 } = globalThis;
|
||
const noColor = typeof Deno1?.noColor === "boolean" ? Deno1.noColor : true;
|
||
let enabled = !noColor;
|
||
function code(open, close) {
|
||
return {
|
||
open: `\x1b[${open.join(";")}m`,
|
||
close: `\x1b[${close}m`,
|
||
regexp: new RegExp(`\\x1b\\[${close}m`, "g")
|
||
};
|
||
}
|
||
function run(str, code) {
|
||
return enabled ? `${code.open}${str.replace(code.regexp, code.open)}${code.close}` : str;
|
||
}
|
||
function bgGreen(str) {
|
||
return run(str, code([
|
||
42
|
||
], 49));
|
||
}
|
||
function bgWhite(str) {
|
||
return run(str, code([
|
||
47
|
||
], 49));
|
||
}
|
||
new RegExp([
|
||
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
||
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
|
||
].join("|"), "g");
|
||
function writeAllSync(w, arr) {
|
||
let nwritten = 0;
|
||
while(nwritten < arr.length){
|
||
nwritten += w.writeSync(arr.subarray(nwritten));
|
||
}
|
||
}
|
||
const isTTY = Deno.stdout && Deno.isatty(Deno.stdout.rid);
|
||
const isWindow = Deno.build.os === "windows";
|
||
class MultiProgressBar {
|
||
width;
|
||
complete;
|
||
incomplete;
|
||
clear;
|
||
interval;
|
||
display;
|
||
#end = false;
|
||
#startIndex = 0;
|
||
#lastRows = 0;
|
||
#strs = [];
|
||
lastStr = "";
|
||
start = Date.now();
|
||
lastRender = 0;
|
||
encoder = new TextEncoder();
|
||
constructor({ title ="" , width =50 , complete =bgGreen(" ") , incomplete =bgWhite(" ") , clear =false , interval , display } = {}){
|
||
if (title != "") {
|
||
this.#strs.push(title);
|
||
this.#startIndex = 1;
|
||
}
|
||
this.width = width;
|
||
this.complete = complete;
|
||
this.incomplete = incomplete;
|
||
this.clear = clear;
|
||
this.interval = interval ?? 16;
|
||
this.display = display ?? ":bar :text :percent :time :completed/:total";
|
||
}
|
||
render(bars) {
|
||
if (this.#end || !isTTY) return;
|
||
const now = Date.now();
|
||
const ms = now - this.lastRender;
|
||
this.lastRender = now;
|
||
const time = ((now - this.start) / 1000).toFixed(1) + "s";
|
||
let end = true;
|
||
let index = this.#startIndex;
|
||
for (const { completed , total =100 , text ="" , ...options } of bars){
|
||
if (completed < 0) {
|
||
throw new Error(`completed must greater than or equal to 0`);
|
||
}
|
||
if (!Number.isInteger(total)) throw new Error(`total must be 'number'`);
|
||
if (completed > total && this.#strs[index] != undefined) continue;
|
||
end = false;
|
||
const percent = (completed / total * 100).toFixed(2) + "%";
|
||
const eta = completed == 0 ? "-" : (completed >= total ? 0 : (total / completed - 1) * (now - this.start) / 1000).toFixed(1) + "s";
|
||
let str = this.display.replace(":text", text).replace(":time", time).replace(":eta", eta).replace(":percent", percent).replace(":completed", completed + "").replace(":total", total + "");
|
||
let availableSpace = Math.max(0, this.ttyColumns - str.replace(":bar", "").length);
|
||
if (availableSpace && isWindow) availableSpace -= 1;
|
||
const width = Math.min(this.width, availableSpace);
|
||
const completeLength = Math.round(width * completed / total);
|
||
const complete = new Array(completeLength).fill(options.complete ?? this.complete).join("");
|
||
const incomplete = new Array(width - completeLength).fill(options.incomplete ?? this.incomplete).join("");
|
||
str = str.replace(":bar", complete + incomplete);
|
||
if (this.#strs[index] && str.length < this.#strs[index].length) {
|
||
str += " ".repeat(this.#strs[index].length - str.length);
|
||
}
|
||
this.#strs[index++] = str;
|
||
}
|
||
if (ms < this.interval && end == false) return;
|
||
const renderStr = this.#strs.join("\n");
|
||
if (renderStr !== this.lastStr) {
|
||
this.resetScreen();
|
||
this.write(renderStr);
|
||
this.lastStr = renderStr;
|
||
this.#lastRows = this.#strs.length;
|
||
}
|
||
if (end) this.end();
|
||
}
|
||
end() {
|
||
this.#end = true;
|
||
if (this.clear) {
|
||
this.resetScreen();
|
||
} else {
|
||
this.breakLine();
|
||
}
|
||
this.showCursor();
|
||
}
|
||
console(message) {
|
||
this.resetScreen();
|
||
this.write(`${message}`);
|
||
this.breakLine();
|
||
this.write(this.lastStr);
|
||
}
|
||
write(msg) {
|
||
msg = `${msg}\x1b[?25l`;
|
||
this.stdoutWrite(msg);
|
||
}
|
||
resetScreen() {
|
||
if (this.#lastRows > 0) {
|
||
this.stdoutWrite("\x1b[" + (this.#lastRows - 1) + "A\r\x1b[?0J");
|
||
}
|
||
}
|
||
get ttyColumns() {
|
||
return 100;
|
||
}
|
||
breakLine() {
|
||
this.stdoutWrite("\r\n");
|
||
}
|
||
stdoutWrite(msg) {
|
||
writeAllSync(Deno.stdout, this.encoder.encode(msg));
|
||
}
|
||
showCursor() {
|
||
this.stdoutWrite("\x1b[?25h");
|
||
}
|
||
}
|
||
const isTTY1 = Deno.stdout && Deno.isatty(Deno.stdout.rid);
|
||
const isWindow1 = Deno.build.os === "windows";
|
||
var Direction;
|
||
(function(Direction) {
|
||
Direction[Direction["left"] = 0] = "left";
|
||
Direction[Direction["right"] = 1] = "right";
|
||
Direction[Direction["all"] = 2] = "all";
|
||
})(Direction || (Direction = {}));
|
||
class ProgressBar {
|
||
title;
|
||
total;
|
||
width;
|
||
complete;
|
||
preciseBar;
|
||
incomplete;
|
||
clear;
|
||
interval;
|
||
display;
|
||
isCompleted = false;
|
||
lastStr = "";
|
||
start = Date.now();
|
||
lastRender = 0;
|
||
encoder = new TextEncoder();
|
||
constructor({ title ="" , total , width =50 , complete =bgGreen(" ") , preciseBar =[] , incomplete =bgWhite(" ") , clear =false , interval =16 , display } = {}){
|
||
this.title = title;
|
||
this.total = total;
|
||
this.width = width;
|
||
this.complete = complete;
|
||
this.preciseBar = preciseBar.concat(complete);
|
||
this.incomplete = incomplete;
|
||
this.clear = clear;
|
||
this.interval = interval;
|
||
this.display = display ?? ":title :percent :bar :time :completed/:total";
|
||
}
|
||
render(completed, options = {}) {
|
||
if (this.isCompleted || !isTTY1) return;
|
||
if (completed < 0) {
|
||
throw new Error(`completed must greater than or equal to 0`);
|
||
}
|
||
const total = options.total ?? this.total ?? 100;
|
||
const now = Date.now();
|
||
const ms = now - this.lastRender;
|
||
if (ms < this.interval && completed < total) return;
|
||
this.lastRender = now;
|
||
const time = ((now - this.start) / 1000).toFixed(1) + "s";
|
||
const eta = completed == 0 ? "-" : (completed >= total ? 0 : (total / completed - 1) * (now - this.start) / 1000).toFixed(1) + "s";
|
||
const percent = (completed / total * 100).toFixed(2) + "%";
|
||
let str = this.display.replace(":title", options.title ?? this.title).replace(":time", time).replace(":eta", eta).replace(":percent", percent).replace(":completed", completed + "").replace(":total", total + "");
|
||
let availableSpace = Math.max(0, this.ttyColumns - str.replace(":bar", "").length);
|
||
if (availableSpace && isWindow1) availableSpace -= 1;
|
||
const width = Math.min(this.width, availableSpace);
|
||
const finished = completed >= total;
|
||
const preciseBar = options.preciseBar ?? this.preciseBar;
|
||
const precision = preciseBar.length > 1;
|
||
const completeLength = width * completed / total;
|
||
const roundedCompleteLength = Math.floor(completeLength);
|
||
let precise = "";
|
||
if (precision) {
|
||
const preciseLength = completeLength - roundedCompleteLength;
|
||
precise = finished ? "" : preciseBar[Math.floor(preciseBar.length * preciseLength)];
|
||
}
|
||
const complete = new Array(roundedCompleteLength).fill(options.complete ?? this.complete).join("");
|
||
const incomplete = new Array(Math.max(width - roundedCompleteLength - (precision ? 1 : 0), 0)).fill(options.incomplete ?? this.incomplete).join("");
|
||
str = str.replace(":bar", complete + precise + incomplete);
|
||
if (str.length < this.lastStr.length) {
|
||
str += " ".repeat(this.lastStr.length - str.length);
|
||
}
|
||
if (str !== this.lastStr) {
|
||
this.write(str);
|
||
this.lastStr = str;
|
||
}
|
||
if (finished) this.end();
|
||
}
|
||
end() {
|
||
this.isCompleted = true;
|
||
if (this.clear) {
|
||
this.stdoutWrite("\r");
|
||
this.clearLine();
|
||
} else {
|
||
this.breakLine();
|
||
}
|
||
this.showCursor();
|
||
}
|
||
console(message) {
|
||
this.clearLine();
|
||
this.write(`${message}`);
|
||
this.breakLine();
|
||
this.write(this.lastStr);
|
||
}
|
||
write(msg) {
|
||
msg = `\r${msg}\x1b[?25l`;
|
||
this.stdoutWrite(msg);
|
||
}
|
||
get ttyColumns() {
|
||
return 100;
|
||
}
|
||
breakLine() {
|
||
this.stdoutWrite("\r\n");
|
||
}
|
||
stdoutWrite(msg) {
|
||
writeAllSync(Deno.stdout, this.encoder.encode(msg));
|
||
}
|
||
clearLine(direction = 2) {
|
||
switch(direction){
|
||
case 2:
|
||
this.stdoutWrite("\x1b[2K");
|
||
break;
|
||
case 0:
|
||
this.stdoutWrite("\x1b[1K");
|
||
break;
|
||
case 1:
|
||
this.stdoutWrite("\x1b[0K");
|
||
break;
|
||
}
|
||
}
|
||
showCursor() {
|
||
this.stdoutWrite("\x1b[?25h");
|
||
}
|
||
}
|
||
class Semaphore {
|
||
tasks = [];
|
||
count;
|
||
constructor(count){
|
||
this.count = count;
|
||
}
|
||
schedule() {
|
||
if (this.count > 0 && this.tasks.length > 0) {
|
||
this.count--;
|
||
const next = this.tasks.shift();
|
||
if (next === undefined) {
|
||
throw "Unexpected undefined value in tasks list";
|
||
}
|
||
next();
|
||
}
|
||
}
|
||
get length() {
|
||
return this.tasks.length;
|
||
}
|
||
acquire() {
|
||
return new Promise((resolve)=>{
|
||
const task = ()=>{
|
||
let released = false;
|
||
resolve(()=>{
|
||
if (!released) {
|
||
released = true;
|
||
this.count++;
|
||
this.schedule();
|
||
}
|
||
});
|
||
};
|
||
this.tasks.push(task);
|
||
queueMicrotask(this.schedule.bind(this));
|
||
});
|
||
}
|
||
async use(fn) {
|
||
const release = await this.acquire();
|
||
try {
|
||
const res = await fn();
|
||
release();
|
||
return res;
|
||
} catch (err) {
|
||
release();
|
||
throw err;
|
||
}
|
||
}
|
||
}
|
||
class Mutex extends Semaphore {
|
||
constructor(){
|
||
super(1);
|
||
}
|
||
}
|
||
const stdinLines = mod2.readLines(Deno.stdin);
|
||
async function readline({ skipEmpty =true } = {}) {
|
||
for await (const line of stdinLines){
|
||
if (!skipEmpty || line !== "") {
|
||
return line;
|
||
}
|
||
}
|
||
throw new Error("EOF");
|
||
}
|
||
function urlBase64Encode(data) {
|
||
return mod.encode(data).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
||
}
|
||
async function retry(f, times = 2) {
|
||
let lastError;
|
||
for(let i = 0; i < times; i++){
|
||
try {
|
||
return await f();
|
||
} catch (e) {
|
||
lastError = e;
|
||
}
|
||
}
|
||
throw lastError;
|
||
}
|
||
const GLOBAL_CACHE = {};
|
||
function cache(f, { key =f.name , expireIn =3600 } = {}) {
|
||
return async ()=>{
|
||
const cached = GLOBAL_CACHE[key];
|
||
if (cached && cached.ts + expireIn * 1000 > Date.now()) {
|
||
return cached.value;
|
||
}
|
||
const value = await f();
|
||
GLOBAL_CACHE[key] = {
|
||
ts: Date.now(),
|
||
value
|
||
};
|
||
return value;
|
||
};
|
||
}
|
||
async function showError(env, p) {
|
||
try {
|
||
return await p;
|
||
} catch (e) {
|
||
if (e instanceof APIError) {
|
||
env.logger.error(`\n\nAPIError: ${e.message}`, "\nResponse: ", e.response, "\nBody: ", e.json);
|
||
} else {
|
||
env.logger.error(e);
|
||
}
|
||
throw e;
|
||
}
|
||
}
|
||
function gameId(id, namespace = S3S_NAMESPACE) {
|
||
const fullId = mod.decode(id);
|
||
const tsUuid = fullId.slice(fullId.length - 52, fullId.length);
|
||
return mod6.v5.generate(namespace, tsUuid);
|
||
}
|
||
function parseHistoryDetailId(id) {
|
||
const plainText = new TextDecoder().decode(mod.decode(id));
|
||
const vsRE = /VsHistoryDetail-([a-z0-9-]+):(\w+):(\d{8}T\d{6})_([0-9a-f-]{36})/;
|
||
const coopRE = /CoopHistoryDetail-([a-z0-9-]+):(\d{8}T\d{6})_([0-9a-f-]{36})/;
|
||
if (vsRE.test(plainText)) {
|
||
const [, uid, listType, timestamp, uuid] = plainText.match(vsRE);
|
||
return {
|
||
type: "VsHistoryDetail",
|
||
uid,
|
||
listType,
|
||
timestamp,
|
||
uuid
|
||
};
|
||
} else if (coopRE.test(plainText)) {
|
||
const [, uid1, timestamp1, uuid1] = plainText.match(coopRE);
|
||
return {
|
||
type: "CoopHistoryDetail",
|
||
uid: uid1,
|
||
timestamp: timestamp1,
|
||
uuid: uuid1
|
||
};
|
||
} else {
|
||
throw new Error(`Invalid ID: ${plainText}`);
|
||
}
|
||
}
|
||
const delay = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
||
async function loginManually({ newFetcher , prompts: { promptLogin } }) {
|
||
const fetch1 = newFetcher();
|
||
const state = urlBase64Encode(random(36));
|
||
const authCodeVerifier = urlBase64Encode(random(32));
|
||
const authCvHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(authCodeVerifier));
|
||
const authCodeChallenge = urlBase64Encode(authCvHash);
|
||
const body = {
|
||
"state": state,
|
||
"redirect_uri": "npf71b963c1b7b6d119://auth",
|
||
"client_id": "71b963c1b7b6d119",
|
||
"scope": "openid user user.birthday user.mii user.screenName",
|
||
"response_type": "session_token_code",
|
||
"session_token_code_challenge": authCodeChallenge,
|
||
"session_token_code_challenge_method": "S256",
|
||
"theme": "login_form"
|
||
};
|
||
const url = "https://accounts.nintendo.com/connect/1.0.0/authorize?" + new URLSearchParams(body);
|
||
const res = await fetch1.get({
|
||
url,
|
||
headers: {
|
||
"Host": "accounts.nintendo.com",
|
||
"Connection": "keep-alive",
|
||
"Cache-Control": "max-age=0",
|
||
"Upgrade-Insecure-Requests": "1",
|
||
"User-Agent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Mobile Safari/537.36",
|
||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8n",
|
||
"DNT": "1",
|
||
"Accept-Encoding": "gzip,deflate,br"
|
||
}
|
||
});
|
||
const login = (await promptLogin(res.url)).trim();
|
||
if (!login) {
|
||
throw new Error("No login URL provided");
|
||
}
|
||
const loginURL = new URL(login);
|
||
const params = new URLSearchParams(loginURL.hash.substring(1));
|
||
const sessionTokenCode = params.get("session_token_code");
|
||
if (!sessionTokenCode) {
|
||
throw new Error("No session token code provided");
|
||
}
|
||
const sessionToken = await getSessionToken({
|
||
fetch: fetch1,
|
||
sessionTokenCode,
|
||
authCodeVerifier
|
||
});
|
||
if (!sessionToken) {
|
||
throw new Error("No session token found");
|
||
}
|
||
return sessionToken;
|
||
}
|
||
async function getGToken({ fApi , sessionToken , env }) {
|
||
const fetch1 = env.newFetcher();
|
||
const idResp = await fetch1.post({
|
||
url: "https://accounts.nintendo.com/connect/1.0.0/api/token",
|
||
headers: {
|
||
"Host": "accounts.nintendo.com",
|
||
"Accept-Encoding": "gzip",
|
||
"Content-Type": "application/json",
|
||
"Accept": "application/json",
|
||
"Connection": "Keep-Alive",
|
||
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.1.2)"
|
||
},
|
||
body: JSON.stringify({
|
||
"client_id": "71b963c1b7b6d119",
|
||
"session_token": sessionToken,
|
||
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer-session-token"
|
||
})
|
||
});
|
||
const idRespJson = await idResp.json();
|
||
const { access_token: accessToken , id_token: idToken } = idRespJson;
|
||
if (!accessToken || !idToken) {
|
||
throw new APIError({
|
||
response: idResp,
|
||
json: idRespJson,
|
||
message: "No access_token or id_token found"
|
||
});
|
||
}
|
||
const uiResp = await fetch1.get({
|
||
url: "https://api.accounts.nintendo.com/2.0.0/users/me",
|
||
headers: {
|
||
"User-Agent": "NASDKAPI; Android",
|
||
"Content-Type": "application/json",
|
||
"Accept": "application/json",
|
||
"Authorization": `Bearer ${accessToken}`,
|
||
"Host": "api.accounts.nintendo.com",
|
||
"Connection": "Keep-Alive",
|
||
"Accept-Encoding": "gzip"
|
||
}
|
||
});
|
||
const uiRespJson = await uiResp.json();
|
||
const { nickname , birthday , language , country } = uiRespJson;
|
||
const getIdToken2 = async (idToken)=>{
|
||
const { f , request_id: requestId , timestamp } = await callImink({
|
||
fApi,
|
||
step: 1,
|
||
idToken,
|
||
env
|
||
});
|
||
const resp = await fetch1.post({
|
||
url: "https://api-lp1.znc.srv.nintendo.net/v3/Account/Login",
|
||
headers: {
|
||
"X-Platform": "Android",
|
||
"X-ProductVersion": NSOAPP_VERSION,
|
||
"Content-Type": "application/json; charset=utf-8",
|
||
"Connection": "Keep-Alive",
|
||
"Accept-Encoding": "gzip",
|
||
"User-Agent": `com.nintendo.znca/${NSOAPP_VERSION}(Android/7.1.2)`
|
||
},
|
||
body: JSON.stringify({
|
||
parameter: {
|
||
"f": f,
|
||
"language": language,
|
||
"naBirthday": birthday,
|
||
"naCountry": country,
|
||
"naIdToken": idToken,
|
||
"requestId": requestId,
|
||
"timestamp": timestamp
|
||
}
|
||
})
|
||
});
|
||
const respJson = await resp.json();
|
||
const idToken2 = respJson?.result?.webApiServerCredential?.accessToken;
|
||
if (!idToken2) {
|
||
throw new APIError({
|
||
response: resp,
|
||
json: respJson,
|
||
message: "No idToken2 found"
|
||
});
|
||
}
|
||
return idToken2;
|
||
};
|
||
const getGToken = async (idToken)=>{
|
||
const { f , request_id: requestId , timestamp } = await callImink({
|
||
step: 2,
|
||
idToken,
|
||
fApi,
|
||
env
|
||
});
|
||
const resp = await fetch1.post({
|
||
url: "https://api-lp1.znc.srv.nintendo.net/v2/Game/GetWebServiceToken",
|
||
headers: {
|
||
"X-Platform": "Android",
|
||
"X-ProductVersion": NSOAPP_VERSION,
|
||
"Authorization": `Bearer ${idToken}`,
|
||
"Content-Type": "application/json; charset=utf-8",
|
||
"Accept-Encoding": "gzip",
|
||
"User-Agent": `com.nintendo.znca/${NSOAPP_VERSION}(Android/7.1.2)`
|
||
},
|
||
body: JSON.stringify({
|
||
parameter: {
|
||
"f": f,
|
||
"id": 4834290508791808,
|
||
"registrationToken": idToken,
|
||
"requestId": requestId,
|
||
"timestamp": timestamp
|
||
}
|
||
})
|
||
});
|
||
const respJson = await resp.json();
|
||
const webServiceToken = respJson?.result?.accessToken;
|
||
if (!webServiceToken) {
|
||
throw new APIError({
|
||
response: resp,
|
||
json: respJson,
|
||
message: "No webServiceToken found"
|
||
});
|
||
}
|
||
return webServiceToken;
|
||
};
|
||
const idToken2 = await retry(()=>getIdToken2(idToken));
|
||
const webServiceToken = await retry(()=>getGToken(idToken2));
|
||
return {
|
||
webServiceToken,
|
||
nickname,
|
||
userCountry: country,
|
||
userLang: language
|
||
};
|
||
}
|
||
async function getBulletToken({ webServiceToken , appUserAgent =DEFAULT_APP_USER_AGENT , userLang , userCountry , env }) {
|
||
const { post } = env.newFetcher({
|
||
cookies: [
|
||
{
|
||
name: "_gtoken",
|
||
value: webServiceToken,
|
||
domain: "api.lp1.av5ja.srv.nintendo.net"
|
||
}
|
||
]
|
||
});
|
||
const resp = await post({
|
||
url: "https://api.lp1.av5ja.srv.nintendo.net/api/bullet_tokens",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
"Accept-Language": userLang,
|
||
"User-Agent": appUserAgent,
|
||
"X-Web-View-Ver": WEB_VIEW_VERSION,
|
||
"X-NACOUNTRY": userCountry,
|
||
"Accept": "*/*",
|
||
"Origin": "https://api.lp1.av5ja.srv.nintendo.net",
|
||
"X-Requested-With": "com.nintendo.znca"
|
||
}
|
||
});
|
||
if (resp.status == 401) {
|
||
throw new APIError({
|
||
response: resp,
|
||
message: "Unauthorized error (ERROR_INVALID_GAME_WEB_TOKEN). Cannot fetch tokens at this time."
|
||
});
|
||
}
|
||
if (resp.status == 403) {
|
||
throw new APIError({
|
||
response: resp,
|
||
message: "Forbidden error (ERROR_OBSOLETE_VERSION). Cannot fetch tokens at this time."
|
||
});
|
||
}
|
||
if (resp.status == 204) {
|
||
throw new APIError({
|
||
response: resp,
|
||
message: "Cannot access SplatNet 3 without having played online."
|
||
});
|
||
}
|
||
if (resp.status !== 201) {
|
||
throw new APIError({
|
||
response: resp,
|
||
message: "Not 201"
|
||
});
|
||
}
|
||
const respJson = await resp.json();
|
||
const { bulletToken } = respJson;
|
||
if (typeof bulletToken !== "string") {
|
||
throw new APIError({
|
||
response: resp,
|
||
json: respJson,
|
||
message: "No bulletToken found"
|
||
});
|
||
}
|
||
return bulletToken;
|
||
}
|
||
function random(size) {
|
||
return crypto.getRandomValues(new Uint8Array(size)).buffer;
|
||
}
|
||
async function getSessionToken({ fetch: fetch1 , sessionTokenCode , authCodeVerifier }) {
|
||
const resp = await fetch1.post({
|
||
url: "https://accounts.nintendo.com/connect/1.0.0/api/session_token",
|
||
headers: {
|
||
"User-Agent": `OnlineLounge/${NSOAPP_VERSION} NASDKAPI Android`,
|
||
"Accept-Language": "en-US",
|
||
"Accept": "application/json",
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
"Host": "accounts.nintendo.com",
|
||
"Connection": "Keep-Alive",
|
||
"Accept-Encoding": "gzip"
|
||
},
|
||
body: new URLSearchParams({
|
||
"client_id": "71b963c1b7b6d119",
|
||
"session_token_code": sessionTokenCode,
|
||
"session_token_code_verifier": authCodeVerifier
|
||
})
|
||
});
|
||
const json = await resp.json();
|
||
if (json.error) {
|
||
throw new APIError({
|
||
response: resp,
|
||
json,
|
||
message: "Error getting session token"
|
||
});
|
||
}
|
||
return json["session_token"];
|
||
}
|
||
async function callImink({ fApi , step , idToken , env }) {
|
||
const { post } = env.newFetcher();
|
||
const resp = await post({
|
||
url: fApi,
|
||
headers: {
|
||
"User-Agent": USERAGENT,
|
||
"Content-Type": "application/json; charset=utf-8"
|
||
},
|
||
body: JSON.stringify({
|
||
"token": idToken,
|
||
"hashMethod": step
|
||
})
|
||
});
|
||
return await resp.json();
|
||
}
|
||
const DEFAULT_ENV = {
|
||
prompts: {
|
||
promptLogin: async (url)=>{
|
||
console.log("Navigate to this URL in your browser:");
|
||
console.log(url);
|
||
console.log('Log in, right click the "Select this account" button, copy the link address, and paste it below:');
|
||
return await readline();
|
||
},
|
||
prompt: async (tips)=>{
|
||
console.log(tips);
|
||
return await readline();
|
||
}
|
||
},
|
||
logger: {
|
||
debug: console.debug,
|
||
log: console.log,
|
||
warn: console.warn,
|
||
error: console.error
|
||
},
|
||
newFetcher: ({ cookies } = {})=>{
|
||
const cookieJar = new CookieJar(cookies);
|
||
const fetch1 = wrapFetch({
|
||
cookieJar
|
||
});
|
||
return {
|
||
async get ({ url , headers }) {
|
||
return await fetch1(url, {
|
||
method: "GET",
|
||
headers
|
||
});
|
||
},
|
||
async post ({ url , body , headers }) {
|
||
return await fetch1(url, {
|
||
method: "POST",
|
||
headers,
|
||
body
|
||
});
|
||
}
|
||
};
|
||
}
|
||
};
|
||
const DEFAULT_STATE = {
|
||
cacheDir: "./cache",
|
||
fGen: "https://api.imink.app/f",
|
||
fileExportPath: "./export",
|
||
monitorInterval: 500
|
||
};
|
||
class FileStateBackend {
|
||
constructor(path){
|
||
this.path = path;
|
||
}
|
||
async read() {
|
||
const data = await Deno.readTextFile(this.path);
|
||
const json = JSON.parse(data);
|
||
return json;
|
||
}
|
||
async write(newState) {
|
||
const data = JSON.stringify(newState, undefined, 2);
|
||
const swapPath = `${this.path}.swap`;
|
||
await Deno.writeTextFile(swapPath, data);
|
||
await Deno.rename(swapPath, this.path);
|
||
}
|
||
path;
|
||
}
|
||
class Profile {
|
||
_state;
|
||
stateBackend;
|
||
env;
|
||
constructor({ stateBackend , env =DEFAULT_ENV }){
|
||
this.stateBackend = stateBackend;
|
||
this.env = env;
|
||
}
|
||
get state() {
|
||
if (!this._state) {
|
||
throw new Error("state is not initialized");
|
||
}
|
||
return this._state;
|
||
}
|
||
async writeState(newState) {
|
||
this._state = newState;
|
||
await this.stateBackend.write(newState);
|
||
}
|
||
async readState() {
|
||
try {
|
||
const json = await this.stateBackend.read();
|
||
this._state = {
|
||
...DEFAULT_STATE,
|
||
...json
|
||
};
|
||
} catch (e) {
|
||
this.env.logger.warn(`Failed to read config file, create new config file. (${e})`);
|
||
await this.writeState(DEFAULT_STATE);
|
||
}
|
||
}
|
||
}
|
||
var Queries;
|
||
(function(Queries) {
|
||
Queries["HomeQuery"] = "dba47124d5ec3090c97ba17db5d2f4b3";
|
||
Queries["LatestBattleHistoriesQuery"] = "7d8b560e31617e981cf7c8aa1ca13a00";
|
||
Queries["RegularBattleHistoriesQuery"] = "f6e7e0277e03ff14edfef3b41f70cd33";
|
||
Queries["BankaraBattleHistoriesQuery"] = "c1553ac75de0a3ea497cdbafaa93e95b";
|
||
Queries["PrivateBattleHistoriesQuery"] = "38e0529de8bc77189504d26c7a14e0b8";
|
||
Queries["VsHistoryDetailQuery"] = "2b085984f729cd51938fc069ceef784a";
|
||
Queries["CoopHistoryQuery"] = "817618ce39bcf5570f52a97d73301b30";
|
||
Queries["CoopHistoryDetailQuery"] = "f3799a033f0a7ad4b1b396f9a3bafb1e";
|
||
Queries["myOutfitCommonDataFilteringConditionQuery"] = "d02ab22c9dccc440076055c8baa0fa7a";
|
||
Queries["myOutfitCommonDataEquipmentsQuery"] = "d29cd0c2b5e6bac90dd5b817914832f8";
|
||
})(Queries || (Queries = {}));
|
||
var BattleListType;
|
||
(function(BattleListType) {
|
||
BattleListType[BattleListType["Latest"] = 0] = "Latest";
|
||
BattleListType[BattleListType["Regular"] = 1] = "Regular";
|
||
BattleListType[BattleListType["Bankara"] = 2] = "Bankara";
|
||
BattleListType[BattleListType["Private"] = 3] = "Private";
|
||
BattleListType[BattleListType["Coop"] = 4] = "Coop";
|
||
})(BattleListType || (BattleListType = {}));
|
||
class Splatnet3 {
|
||
profile;
|
||
env;
|
||
constructor({ profile , env =DEFAULT_ENV }){
|
||
this.profile = profile;
|
||
this.env = env;
|
||
}
|
||
async request(query, ...rest) {
|
||
const doRequest = async ()=>{
|
||
const state = this.profile.state;
|
||
const variables = rest?.[0] ?? {};
|
||
const body = {
|
||
extensions: {
|
||
persistedQuery: {
|
||
sha256Hash: query,
|
||
version: 1
|
||
}
|
||
},
|
||
variables
|
||
};
|
||
const { post } = this.env.newFetcher();
|
||
const resp = await post({
|
||
url: SPLATNET3_ENDPOINT,
|
||
headers: {
|
||
"Authorization": `Bearer ${state.loginState?.bulletToken}`,
|
||
"Accept-Language": state.userLang ?? "en-US",
|
||
"User-Agent": state.appUserAgent ?? DEFAULT_APP_USER_AGENT,
|
||
"X-Web-View-Ver": WEB_VIEW_VERSION,
|
||
"Content-Type": "application/json",
|
||
"Accept": "*/*",
|
||
"Origin": "https://api.lp1.av5ja.srv.nintendo.net",
|
||
"X-Requested-With": "com.nintendo.znca",
|
||
"Referer": `https://api.lp1.av5ja.srv.nintendo.net/?lang=${state.userLang}&na_country=${state.userCountry}&na_lang=${state.userLang}`,
|
||
"Accept-Encoding": "gzip, deflate",
|
||
"Cookie": `_gtoken: ${state.loginState?.gToken}`
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
if (resp.status !== 200) {
|
||
throw new APIError({
|
||
response: resp,
|
||
message: "Splatnet3 request failed"
|
||
});
|
||
}
|
||
const json = await resp.json();
|
||
if ("errors" in json) {
|
||
throw new APIError({
|
||
response: resp,
|
||
json,
|
||
message: `Splatnet3 request failed(${json.errors?.[0].message})`
|
||
});
|
||
}
|
||
return json.data;
|
||
};
|
||
try {
|
||
return await doRequest();
|
||
} catch (e) {
|
||
if (isTokenExpired(e)) {
|
||
await this.fetchToken();
|
||
return await doRequest();
|
||
}
|
||
throw e;
|
||
}
|
||
}
|
||
async fetchToken() {
|
||
const state = this.profile.state;
|
||
const sessionToken = state.loginState?.sessionToken;
|
||
if (!sessionToken) {
|
||
throw new Error("Session token is not set.");
|
||
}
|
||
const { webServiceToken , userCountry , userLang } = await getGToken({
|
||
fApi: state.fGen,
|
||
sessionToken,
|
||
env: this.env
|
||
});
|
||
const bulletToken = await getBulletToken({
|
||
webServiceToken,
|
||
userLang,
|
||
userCountry,
|
||
appUserAgent: state.appUserAgent,
|
||
env: this.env
|
||
});
|
||
await this.profile.writeState({
|
||
...state,
|
||
loginState: {
|
||
...state.loginState,
|
||
gToken: webServiceToken,
|
||
bulletToken
|
||
},
|
||
userLang: state.userLang ?? userLang,
|
||
userCountry: state.userCountry ?? userCountry
|
||
});
|
||
}
|
||
BATTLE_LIST_TYPE_MAP = {
|
||
[BattleListType.Latest]: ()=>this.request(Queries.LatestBattleHistoriesQuery).then((r)=>getIdsFromGroups(r.latestBattleHistories)),
|
||
[BattleListType.Regular]: ()=>this.request(Queries.RegularBattleHistoriesQuery).then((r)=>getIdsFromGroups(r.regularBattleHistories)),
|
||
[BattleListType.Bankara]: ()=>this.request(Queries.BankaraBattleHistoriesQuery).then((r)=>getIdsFromGroups(r.bankaraBattleHistories)),
|
||
[BattleListType.Private]: ()=>this.request(Queries.PrivateBattleHistoriesQuery).then((r)=>getIdsFromGroups(r.privateBattleHistories)),
|
||
[BattleListType.Coop]: ()=>this.request(Queries.CoopHistoryQuery).then((r)=>getIdsFromGroups(r.coopResult))
|
||
};
|
||
async checkToken() {
|
||
const state = this.profile.state;
|
||
if (!state.loginState?.sessionToken || !state.loginState?.bulletToken || !state.loginState?.gToken) {
|
||
return false;
|
||
}
|
||
try {
|
||
await this.request(Queries.HomeQuery);
|
||
return true;
|
||
} catch (_e) {
|
||
return false;
|
||
}
|
||
}
|
||
async getBattleList(battleListType = BattleListType.Latest) {
|
||
return await this.BATTLE_LIST_TYPE_MAP[battleListType]();
|
||
}
|
||
getBattleDetail(id) {
|
||
return this.request(Queries.VsHistoryDetailQuery, {
|
||
vsResultId: id
|
||
});
|
||
}
|
||
getCoopDetail(id) {
|
||
return this.request(Queries.CoopHistoryDetailQuery, {
|
||
coopHistoryDetailId: id
|
||
});
|
||
}
|
||
async getBankaraBattleHistories() {
|
||
const resp = await this.request(Queries.BankaraBattleHistoriesQuery);
|
||
return resp;
|
||
}
|
||
async getCoopHistories() {
|
||
const resp = await this.request(Queries.CoopHistoryQuery);
|
||
return resp;
|
||
}
|
||
async getGearPower() {
|
||
const resp = await this.request(Queries.myOutfitCommonDataFilteringConditionQuery);
|
||
return resp;
|
||
}
|
||
async getLatestBattleHistoriesQuery() {
|
||
const resp = await this.request(Queries.LatestBattleHistoriesQuery);
|
||
return resp;
|
||
}
|
||
async getGears() {
|
||
const resp = await this.request(Queries.myOutfitCommonDataEquipmentsQuery);
|
||
return resp;
|
||
}
|
||
}
|
||
function getIdsFromGroups({ historyGroups }) {
|
||
return historyGroups.nodes.flatMap((i)=>i.historyDetails.nodes).map((i)=>i.id);
|
||
}
|
||
function isTokenExpired(e) {
|
||
if (e instanceof APIError) {
|
||
return e.response.status === 401;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
class MemoryCache {
|
||
cache = {};
|
||
async read(key) {
|
||
return this.cache[key];
|
||
}
|
||
async write(key, value) {
|
||
this.cache[key] = value;
|
||
}
|
||
}
|
||
class FileCache {
|
||
constructor(path){
|
||
this.path = path;
|
||
}
|
||
async getPath(key) {
|
||
await Deno.mkdir(this.path, {
|
||
recursive: true
|
||
});
|
||
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key));
|
||
const hashHex = Array.from(new Uint8Array(hash)).map((b)=>b.toString(16).padStart(2, "0")).join("");
|
||
return mod10.join(this.path, hashHex);
|
||
}
|
||
async read(key) {
|
||
const path = await this.getPath(key);
|
||
try {
|
||
const data = await Deno.readTextFile(path);
|
||
return JSON.parse(data);
|
||
} catch (e) {
|
||
if (e instanceof Deno.errors.NotFound) {
|
||
return undefined;
|
||
}
|
||
throw e;
|
||
}
|
||
}
|
||
async write(key, value) {
|
||
const path = await this.getPath(key);
|
||
const encoder = new TextEncoder();
|
||
const data = encoder.encode(JSON.stringify(value));
|
||
const swapPath = `${path}.swap`;
|
||
await Deno.writeFile(swapPath, data);
|
||
await Deno.rename(swapPath, path);
|
||
}
|
||
path;
|
||
}
|
||
function b64Number(id) {
|
||
const text = new TextDecoder().decode(mod.decode(id));
|
||
const [_, num] = text.split("-");
|
||
return parseInt(num);
|
||
}
|
||
const FETCH_LOCK = new Mutex();
|
||
async function _getAbility() {
|
||
const release = await FETCH_LOCK.acquire();
|
||
try {
|
||
const resp = await fetch("https://stat.ink/api/v3/ability?full=1");
|
||
const json = await resp.json();
|
||
return json;
|
||
} finally{
|
||
release();
|
||
}
|
||
}
|
||
async function _getStage() {
|
||
const resp = await fetch("https://stat.ink/api/v3/stage");
|
||
const json = await resp.json();
|
||
return json;
|
||
}
|
||
const getAbility = cache(_getAbility);
|
||
const getStage = cache(_getStage);
|
||
class StatInkExporter {
|
||
name = "stat.ink";
|
||
statInkApiKey;
|
||
uploadMode;
|
||
constructor({ statInkApiKey , uploadMode }){
|
||
if (statInkApiKey.length !== 43) {
|
||
throw new Error("Invalid stat.ink API key");
|
||
}
|
||
this.statInkApiKey = statInkApiKey;
|
||
this.uploadMode = uploadMode;
|
||
}
|
||
requestHeaders() {
|
||
return {
|
||
"User-Agent": USERAGENT,
|
||
"Authorization": `Bearer ${this.statInkApiKey}`
|
||
};
|
||
}
|
||
isTriColor({ vsMode }) {
|
||
return vsMode.mode === "FEST" && b64Number(vsMode.id) === 8;
|
||
}
|
||
async exportGame(game) {
|
||
if (game.type === "CoopInfo" || this.isTriColor(game.detail)) {
|
||
return {};
|
||
}
|
||
const body = await this.mapBattle(game);
|
||
const resp = await fetch("https://stat.ink/api/v3/battle", {
|
||
method: "POST",
|
||
headers: {
|
||
...this.requestHeaders(),
|
||
"Content-Type": "application/x-msgpack"
|
||
},
|
||
body: mod7.encode(body)
|
||
});
|
||
const json = await resp.json().catch(()=>({}));
|
||
if (resp.status !== 200 && resp.status !== 201) {
|
||
throw new APIError({
|
||
response: resp,
|
||
message: "Failed to export battle",
|
||
json
|
||
});
|
||
}
|
||
if (json.error) {
|
||
throw new APIError({
|
||
response: resp,
|
||
message: "Failed to export battle",
|
||
json
|
||
});
|
||
}
|
||
return {
|
||
url: json.url
|
||
};
|
||
}
|
||
async notExported({ list }) {
|
||
const uuid = await (await fetch("https://stat.ink/api/v3/s3s/uuid-list", {
|
||
headers: this.requestHeaders()
|
||
})).json();
|
||
const out = [];
|
||
for (const id of list){
|
||
const s3sId = await gameId(id, S3S_NAMESPACE);
|
||
const s3siId = await gameId(id, S3SI_NAMESPACE);
|
||
if (!uuid.includes(s3sId) && !uuid.includes(s3siId)) {
|
||
out.push(id);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
mapLobby(vsDetail) {
|
||
const { mode: vsMode } = vsDetail.vsMode;
|
||
if (vsMode === "REGULAR") {
|
||
return "regular";
|
||
} else if (vsMode === "BANKARA") {
|
||
const { mode } = vsDetail.bankaraMatch ?? {
|
||
mode: "UNKNOWN"
|
||
};
|
||
const map = {
|
||
OPEN: "bankara_open",
|
||
CHALLENGE: "bankara_challenge",
|
||
UNKNOWN: ""
|
||
};
|
||
const result = map[mode];
|
||
if (result) {
|
||
return result;
|
||
}
|
||
} else if (vsMode === "PRIVATE") {
|
||
return "private";
|
||
} else if (vsMode === "FEST") {
|
||
const modeId = b64Number(vsDetail.vsMode.id);
|
||
if (modeId === 6) {
|
||
return "splatfest_open";
|
||
} else if (modeId === 7) {
|
||
return "splatfest_challenge";
|
||
} else if (modeId === 8) {
|
||
throw new Error("Tri-color battle is not supported");
|
||
}
|
||
}
|
||
throw new TypeError(`Unknown vsMode ${vsMode}`);
|
||
}
|
||
async mapStage({ vsStage }) {
|
||
const id = b64Number(vsStage.id).toString();
|
||
const stage = await getStage();
|
||
const result = stage.find((s)=>s.aliases.includes(id));
|
||
if (!result) {
|
||
throw new Error("Unknown stage: " + vsStage.name);
|
||
}
|
||
return result.key;
|
||
}
|
||
async mapGears({ headGear , clothingGear , shoesGear }) {
|
||
const amap = (await getAbility()).map((i)=>({
|
||
...i,
|
||
names: Object.values(i.name)
|
||
}));
|
||
const mapAbility = ({ name })=>{
|
||
const result = amap.find((a)=>a.names.includes(name));
|
||
if (!result) {
|
||
return null;
|
||
}
|
||
return result.key;
|
||
};
|
||
const mapGear = ({ primaryGearPower , additionalGearPowers })=>{
|
||
const primary = mapAbility(primaryGearPower);
|
||
if (!primary) {
|
||
throw new Error("Unknown ability: " + primaryGearPower.name);
|
||
}
|
||
return {
|
||
primary_ability: primary,
|
||
secondary_abilities: additionalGearPowers.map(mapAbility)
|
||
};
|
||
};
|
||
return {
|
||
headgear: mapGear(headGear),
|
||
clothing: mapGear(clothingGear),
|
||
shoes: mapGear(shoesGear)
|
||
};
|
||
}
|
||
mapPlayer = async (player, index)=>{
|
||
const result = {
|
||
me: player.isMyself ? "yes" : "no",
|
||
rank_in_team: index + 1,
|
||
name: player.name,
|
||
number: player.nameId ?? undefined,
|
||
splashtag_title: player.byname,
|
||
weapon: b64Number(player.weapon.id).toString(),
|
||
inked: player.paint,
|
||
gears: await this.mapGears(player),
|
||
disconnected: player.result ? "no" : "yes"
|
||
};
|
||
if (player.result) {
|
||
result.kill_or_assist = player.result.kill;
|
||
result.assist = player.result.assist;
|
||
result.kill = result.kill_or_assist - result.assist;
|
||
result.death = player.result.death;
|
||
result.special = player.result.special;
|
||
}
|
||
return result;
|
||
};
|
||
async mapBattle({ challengeProgress , bankaraMatchChallenge , listNode , detail: vsDetail , rankBeforeState , rankState }) {
|
||
const { knockout , vsRule: { rule } , myTeam , otherTeams , bankaraMatch , festMatch , playedTime } = vsDetail;
|
||
const self = vsDetail.myTeam.players.find((i)=>i.isMyself);
|
||
if (!self) {
|
||
throw new Error("Self not found");
|
||
}
|
||
const startedAt = Math.floor(new Date(playedTime).getTime() / 1000);
|
||
const result = {
|
||
uuid: await gameId(vsDetail.id),
|
||
lobby: this.mapLobby(vsDetail),
|
||
rule: SPLATNET3_STATINK_MAP.RULE[vsDetail.vsRule.rule],
|
||
stage: await this.mapStage(vsDetail),
|
||
result: SPLATNET3_STATINK_MAP.RESULT[vsDetail.judgement],
|
||
weapon: b64Number(self.weapon.id).toString(),
|
||
inked: self.paint,
|
||
rank_in_team: vsDetail.myTeam.players.indexOf(self) + 1,
|
||
medals: vsDetail.awards.map((i)=>i.name),
|
||
our_team_players: await Promise.all(myTeam.players.map(this.mapPlayer)),
|
||
their_team_players: await Promise.all(otherTeams.flatMap((i)=>i.players).map(this.mapPlayer)),
|
||
agent: AGENT_NAME,
|
||
agent_version: S3SI_VERSION,
|
||
agent_variables: {
|
||
"Upload Mode": this.uploadMode
|
||
},
|
||
automated: "yes",
|
||
start_at: startedAt,
|
||
end_at: startedAt + vsDetail.duration
|
||
};
|
||
if (self.result) {
|
||
result.kill_or_assist = self.result.kill;
|
||
result.assist = self.result.assist;
|
||
result.kill = result.kill_or_assist - result.assist;
|
||
result.death = self.result.death;
|
||
result.special = self.result.special;
|
||
}
|
||
if (festMatch) {
|
||
result.fest_dragon = SPLATNET3_STATINK_MAP.DRAGON[festMatch.dragonMatchType];
|
||
result.clout_change = festMatch.contribution;
|
||
result.fest_power = festMatch.myFestPower ?? undefined;
|
||
}
|
||
if (rule === "TURF_WAR") {
|
||
result.our_team_percent = (myTeam?.result?.paintRatio ?? 0) * 100;
|
||
result.their_team_percent = (otherTeams?.[0]?.result?.paintRatio ?? 0) * 100;
|
||
result.our_team_inked = myTeam.players.reduce((acc, i)=>acc + i.paint, 0);
|
||
result.their_team_inked = otherTeams?.[0].players.reduce((acc, i)=>acc + i.paint, 0);
|
||
}
|
||
if (knockout) {
|
||
result.knockout = knockout === "NEITHER" ? "no" : "yes";
|
||
}
|
||
result.our_team_count = myTeam?.result?.score ?? undefined;
|
||
result.their_team_count = otherTeams?.[0]?.result?.score ?? undefined;
|
||
result.rank_exp_change = bankaraMatch?.earnedUdemaePoint ?? undefined;
|
||
if (listNode) {
|
||
[result.rank_before, result.rank_before_s_plus] = parseUdemae(listNode.udemae);
|
||
}
|
||
if (bankaraMatchChallenge && challengeProgress) {
|
||
result.rank_up_battle = bankaraMatchChallenge.isPromo ? "yes" : "no";
|
||
if (challengeProgress.index === 0 && bankaraMatchChallenge.udemaeAfter) {
|
||
[result.rank_after, result.rank_after_s_plus] = parseUdemae(bankaraMatchChallenge.udemaeAfter);
|
||
result.rank_exp_change = bankaraMatchChallenge.earnedUdemaePoint ?? undefined;
|
||
} else {
|
||
result.rank_after = result.rank_before;
|
||
result.rank_after_s_plus = result.rank_before_s_plus;
|
||
}
|
||
result.challenge_win = challengeProgress.winCount;
|
||
result.challenge_lose = challengeProgress.loseCount;
|
||
}
|
||
if (rankBeforeState && rankState) {
|
||
result.rank_before_exp = rankBeforeState.rankPoint;
|
||
result.rank_after_exp = rankState.rankPoint;
|
||
if (!bankaraMatchChallenge?.isUdemaeUp && result.rank_exp_change === undefined) {
|
||
result.rank_exp_change = result.rank_after_exp - result.rank_before_exp;
|
||
}
|
||
if (!result.rank_after) {
|
||
[result.rank_after, result.rank_after_s_plus] = parseUdemae(rankState.rank);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
function parseUdemae(udemae) {
|
||
const [rank, rankNum] = udemae.split(/([0-9]+)/);
|
||
return [
|
||
rank.toLowerCase(),
|
||
rankNum === undefined ? undefined : parseInt(rankNum)
|
||
];
|
||
}
|
||
function replacer(key, value) {
|
||
return [
|
||
"url",
|
||
"maskImageUrl",
|
||
"overlayImageUrl"
|
||
].includes(key) ? undefined : value;
|
||
}
|
||
class FileExporter {
|
||
name;
|
||
constructor(exportPath){
|
||
this.exportPath = exportPath;
|
||
this.name = "file";
|
||
}
|
||
getFilenameById(id) {
|
||
const { uid , timestamp } = parseHistoryDetailId(id);
|
||
return `${uid}_${timestamp}Z.json`;
|
||
}
|
||
async exportGame(info) {
|
||
await Deno.mkdir(this.exportPath, {
|
||
recursive: true
|
||
});
|
||
const filename = this.getFilenameById(info.detail.id);
|
||
const filepath = mod10.join(this.exportPath, filename);
|
||
const body = {
|
||
type: info.type === "VsInfo" ? "VS" : "COOP",
|
||
nsoVersion: NSOAPP_VERSION,
|
||
s3siVersion: S3SI_VERSION,
|
||
exportTime: new Date().toISOString(),
|
||
data: info
|
||
};
|
||
await Deno.writeTextFile(filepath, JSON.stringify(body, replacer));
|
||
return {
|
||
url: filepath
|
||
};
|
||
}
|
||
async notExported({ list }) {
|
||
const out = [];
|
||
for (const id of list){
|
||
const filename = this.getFilenameById(id);
|
||
const filepath = mod10.join(this.exportPath, filename);
|
||
const isFile = await Deno.stat(filepath).then((f)=>f.isFile).catch(()=>false);
|
||
if (!isFile) {
|
||
out.push(id);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
exportPath;
|
||
}
|
||
const splusParams = ()=>{
|
||
const out = [];
|
||
for(let i = 0; i < 50; i++){
|
||
const level = i % 10;
|
||
const item = {
|
||
rank: `S+${i}`,
|
||
pointRange: [
|
||
300 + level * 350,
|
||
300 + (level + 1) * 350
|
||
],
|
||
charge: 160
|
||
};
|
||
if (level === 9) {
|
||
item.promotion = true;
|
||
}
|
||
out.push(item);
|
||
}
|
||
out.push({
|
||
rank: "S+50",
|
||
pointRange: [
|
||
0,
|
||
9999
|
||
],
|
||
charge: 160
|
||
});
|
||
return out;
|
||
};
|
||
const RANK_PARAMS = [
|
||
{
|
||
rank: "C-",
|
||
pointRange: [
|
||
0,
|
||
200
|
||
],
|
||
charge: 0
|
||
},
|
||
{
|
||
rank: "C",
|
||
pointRange: [
|
||
200,
|
||
400
|
||
],
|
||
charge: 20
|
||
},
|
||
{
|
||
rank: "C+",
|
||
pointRange: [
|
||
400,
|
||
600
|
||
],
|
||
charge: 40,
|
||
promotion: true
|
||
},
|
||
{
|
||
rank: "B-",
|
||
pointRange: [
|
||
100,
|
||
350
|
||
],
|
||
charge: 55
|
||
},
|
||
{
|
||
rank: "B",
|
||
pointRange: [
|
||
350,
|
||
600
|
||
],
|
||
charge: 70
|
||
},
|
||
{
|
||
rank: "B+",
|
||
pointRange: [
|
||
600,
|
||
850
|
||
],
|
||
charge: 85,
|
||
promotion: true
|
||
},
|
||
{
|
||
rank: "A-",
|
||
pointRange: [
|
||
200,
|
||
500
|
||
],
|
||
charge: 100
|
||
},
|
||
{
|
||
rank: "A",
|
||
pointRange: [
|
||
500,
|
||
800
|
||
],
|
||
charge: 110
|
||
},
|
||
{
|
||
rank: "A+",
|
||
pointRange: [
|
||
800,
|
||
1100
|
||
],
|
||
charge: 120,
|
||
promotion: true
|
||
},
|
||
{
|
||
rank: "S",
|
||
pointRange: [
|
||
300,
|
||
1000
|
||
],
|
||
charge: 150,
|
||
promotion: true
|
||
},
|
||
...splusParams()
|
||
];
|
||
function addRank(state, delta) {
|
||
const { rank , rankPoint } = state;
|
||
const { gameId , rankAfter , isPromotion , isRankUp , isChallengeFirst } = delta;
|
||
const rankIndex = RANK_PARAMS.findIndex((r)=>r.rank === rank);
|
||
if (rankIndex === -1) {
|
||
throw new Error(`Rank not found: ${rank}`);
|
||
}
|
||
const rankParam = RANK_PARAMS[rankIndex];
|
||
if (isChallengeFirst) {
|
||
return {
|
||
gameId,
|
||
rank,
|
||
rankPoint: rankPoint - rankParam.charge
|
||
};
|
||
}
|
||
if (rankIndex === RANK_PARAMS.length - 1) {
|
||
return {
|
||
gameId,
|
||
rank,
|
||
rankPoint: Math.min(rankPoint + delta.rankPoint, rankParam.pointRange[1])
|
||
};
|
||
}
|
||
if (isPromotion && isRankUp) {
|
||
const nextRankParam = RANK_PARAMS[rankIndex + 1];
|
||
return {
|
||
gameId,
|
||
rank: nextRankParam.rank,
|
||
rankPoint: nextRankParam.pointRange[0]
|
||
};
|
||
}
|
||
return {
|
||
gameId,
|
||
rank: rankAfter ?? rank,
|
||
rankPoint: rankPoint + delta.rankPoint
|
||
};
|
||
}
|
||
const battleTime = (id)=>{
|
||
const { timestamp } = parseHistoryDetailId(id);
|
||
const dateStr = timestamp.replace(/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/, "$1-$2-$3T$4:$5:$6Z");
|
||
return new Date(dateStr);
|
||
};
|
||
function generateDeltaList(state, flatten) {
|
||
const index = flatten.findIndex((i)=>i.gameId === state.gameId);
|
||
if (index === -1) {
|
||
return;
|
||
}
|
||
const unProcessed = flatten.slice(index);
|
||
const deltaList = [];
|
||
let beforeGameId = state.gameId;
|
||
for (const i of unProcessed.slice(1)){
|
||
if (!i.detail.bankaraMatch) {
|
||
throw new TypeError("bankaraMatch must be defined");
|
||
}
|
||
let delta = {
|
||
beforeGameId,
|
||
gameId: i.gameId,
|
||
rankPoint: 0,
|
||
isPromotion: false,
|
||
isRankUp: false,
|
||
isChallengeFirst: false
|
||
};
|
||
beforeGameId = i.gameId;
|
||
if (i.bankaraMatchChallenge) {
|
||
if (i.index === 0 && i.bankaraMatchChallenge.state !== "INPROGRESS") {
|
||
delta = {
|
||
...delta,
|
||
rankAfter: i.bankaraMatchChallenge.udemaeAfter ?? undefined,
|
||
rankPoint: i.bankaraMatchChallenge.earnedUdemaePoint ?? 0,
|
||
isPromotion: i.bankaraMatchChallenge.isPromo ?? false,
|
||
isRankUp: i.bankaraMatchChallenge.isUdemaeUp ?? false,
|
||
isChallengeFirst: false
|
||
};
|
||
} else if (i.index === i.groupLength - 1) {
|
||
delta = {
|
||
...delta,
|
||
isChallengeFirst: true
|
||
};
|
||
}
|
||
} else {
|
||
delta = {
|
||
...delta,
|
||
rankAfter: i.detail.udemae,
|
||
rankPoint: i.detail.bankaraMatch?.earnedUdemaePoint ?? 0
|
||
};
|
||
}
|
||
deltaList.push(delta);
|
||
}
|
||
return deltaList;
|
||
}
|
||
function getRankState(i) {
|
||
const rank = i.detail.udemae;
|
||
const param = RANK_PARAMS.find((i)=>i.rank === rank);
|
||
if (!param) {
|
||
throw new Error(`Rank not found: ${rank}`);
|
||
}
|
||
return {
|
||
gameId: i.gameId,
|
||
rank,
|
||
rankPoint: -1
|
||
};
|
||
}
|
||
class RankTracker {
|
||
deltaMap;
|
||
constructor(state){
|
||
this.state = state;
|
||
this.deltaMap = new Map();
|
||
}
|
||
async getRankStateById(id) {
|
||
if (!this.state) {
|
||
return;
|
||
}
|
||
const gid = await gameId(id);
|
||
let cur = this.state;
|
||
let before = cur;
|
||
while(cur.gameId !== gid){
|
||
const delta = this.deltaMap.get(cur.gameId);
|
||
if (!delta) {
|
||
return;
|
||
}
|
||
before = cur;
|
||
cur = addRank(cur, delta);
|
||
}
|
||
return {
|
||
before,
|
||
after: cur
|
||
};
|
||
}
|
||
setState(state) {
|
||
this.state = state;
|
||
}
|
||
async updateState(history) {
|
||
const flatten = await Promise.all(history.flatMap(({ historyDetails , bankaraMatchChallenge })=>{
|
||
return historyDetails.nodes.map((j, index)=>({
|
||
time: battleTime(j.id),
|
||
gameId: gameId(j.id),
|
||
bankaraMatchChallenge,
|
||
index,
|
||
groupLength: historyDetails.nodes.length,
|
||
detail: j
|
||
}));
|
||
}).sort((a, b)=>a.time.getTime() - b.time.getTime()).map((i)=>i.gameId.then((gameId)=>({
|
||
...i,
|
||
gameId
|
||
}))));
|
||
const gameIdTime = new Map(flatten.map((i)=>[
|
||
i.gameId,
|
||
i.time
|
||
]));
|
||
let curState;
|
||
const oldestPromotion = flatten.find((i)=>i.bankaraMatchChallenge?.isPromo && i.bankaraMatchChallenge.isUdemaeUp);
|
||
const thisStateTime = gameIdTime.get(this.state?.gameId);
|
||
if (!thisStateTime && !oldestPromotion) {
|
||
return;
|
||
} else if (thisStateTime && !oldestPromotion) {
|
||
curState = this.state;
|
||
} else if (!thisStateTime && oldestPromotion) {
|
||
curState = getRankState(oldestPromotion);
|
||
} else if (thisStateTime && oldestPromotion) {
|
||
if (thisStateTime <= oldestPromotion.time) {
|
||
curState = this.state;
|
||
} else {
|
||
curState = getRankState(oldestPromotion);
|
||
}
|
||
}
|
||
if (!curState) {
|
||
return;
|
||
}
|
||
const deltaList = generateDeltaList(curState, flatten);
|
||
if (!deltaList) {
|
||
return;
|
||
}
|
||
for (const delta of deltaList){
|
||
this.deltaMap.set(delta.beforeGameId, delta);
|
||
curState = addRank(curState, delta);
|
||
}
|
||
return curState;
|
||
}
|
||
state;
|
||
}
|
||
class GameFetcher {
|
||
splatnet;
|
||
cache;
|
||
rankTracker;
|
||
lock = {};
|
||
bankaraLock = new Mutex();
|
||
bankaraHistory;
|
||
coopLock = new Mutex();
|
||
coopHistory;
|
||
constructor({ cache =new MemoryCache() , splatnet , state }){
|
||
this.splatnet = splatnet;
|
||
this.cache = cache;
|
||
this.rankTracker = new RankTracker(state.rankState);
|
||
}
|
||
getLock(id) {
|
||
let cur = this.lock[id];
|
||
if (!cur) {
|
||
cur = new Mutex();
|
||
this.lock[id] = cur;
|
||
}
|
||
return cur;
|
||
}
|
||
setRankState(state) {
|
||
this.rankTracker.setState(state);
|
||
}
|
||
async updateRank() {
|
||
const finalState = await this.rankTracker.updateState(await this.getBankaraHistory());
|
||
return finalState;
|
||
}
|
||
getRankStateById(id) {
|
||
return this.rankTracker.getRankStateById(id);
|
||
}
|
||
getBankaraHistory() {
|
||
return this.bankaraLock.use(async ()=>{
|
||
if (this.bankaraHistory) {
|
||
return this.bankaraHistory;
|
||
}
|
||
const { bankaraBattleHistories: { historyGroups } } = await this.splatnet.getBankaraBattleHistories();
|
||
this.bankaraHistory = historyGroups.nodes;
|
||
return this.bankaraHistory;
|
||
});
|
||
}
|
||
getCoopHistory() {
|
||
return this.coopLock.use(async ()=>{
|
||
if (this.coopHistory) {
|
||
return this.coopHistory;
|
||
}
|
||
const { coopResult: { historyGroups } } = await this.splatnet.getCoopHistories();
|
||
this.coopHistory = historyGroups.nodes;
|
||
return this.coopHistory;
|
||
});
|
||
}
|
||
async getCoopMetaById(id) {
|
||
const coopHistory = await this.getCoopHistory();
|
||
const group = coopHistory.find((i)=>i.historyDetails.nodes.some((i)=>i.id === id));
|
||
if (!group) {
|
||
return {
|
||
type: "CoopInfo",
|
||
listNode: null
|
||
};
|
||
}
|
||
const listNode = group.historyDetails.nodes.find((i)=>i.id === id) ?? null;
|
||
return {
|
||
type: "CoopInfo",
|
||
listNode
|
||
};
|
||
}
|
||
async getBattleMetaById(id) {
|
||
const gid = await gameId(id);
|
||
const bankaraHistory = await this.getBankaraHistory();
|
||
const gameIdMap = new Map();
|
||
for (const i of bankaraHistory){
|
||
for (const j of i.historyDetails.nodes){
|
||
gameIdMap.set(j, await gameId(j.id));
|
||
}
|
||
}
|
||
const group = bankaraHistory.find((i)=>i.historyDetails.nodes.some((i)=>gameIdMap.get(i) === gid));
|
||
if (!group) {
|
||
return {
|
||
type: "VsInfo",
|
||
challengeProgress: null,
|
||
bankaraMatchChallenge: null,
|
||
listNode: null,
|
||
rankState: null,
|
||
rankBeforeState: null
|
||
};
|
||
}
|
||
const { bankaraMatchChallenge } = group;
|
||
const listNode = group.historyDetails.nodes.find((i)=>gameIdMap.get(i) === gid) ?? null;
|
||
const index = group.historyDetails.nodes.indexOf(listNode);
|
||
let challengeProgress = null;
|
||
if (bankaraMatchChallenge) {
|
||
const pastBattles = group.historyDetails.nodes.slice(0, index);
|
||
const { winCount , loseCount } = bankaraMatchChallenge;
|
||
challengeProgress = {
|
||
index,
|
||
winCount: winCount - pastBattles.filter((i)=>i.judgement == "WIN").length,
|
||
loseCount: loseCount - pastBattles.filter((i)=>[
|
||
"LOSE",
|
||
"DEEMED_LOSE"
|
||
].includes(i.judgement)).length
|
||
};
|
||
}
|
||
const { before , after } = await this.rankTracker.getRankStateById(id) ?? {};
|
||
return {
|
||
type: "VsInfo",
|
||
bankaraMatchChallenge,
|
||
listNode,
|
||
challengeProgress,
|
||
rankState: after ?? null,
|
||
rankBeforeState: before ?? null
|
||
};
|
||
}
|
||
cacheDetail(id, getter) {
|
||
const lock = this.getLock(id);
|
||
return lock.use(async ()=>{
|
||
const cached = await this.cache.read(id);
|
||
if (cached) {
|
||
return cached;
|
||
}
|
||
const detail = await getter();
|
||
await this.cache.write(id, detail);
|
||
return detail;
|
||
});
|
||
}
|
||
fetch(type, id) {
|
||
switch(type){
|
||
case "VsInfo":
|
||
return this.fetchBattle(id);
|
||
case "CoopInfo":
|
||
return this.fetchCoop(id);
|
||
default:
|
||
throw new Error(`Unknown game type: ${type}`);
|
||
}
|
||
}
|
||
async fetchBattle(id) {
|
||
const detail = await this.cacheDetail(id, ()=>this.splatnet.getBattleDetail(id).then((r)=>r.vsHistoryDetail));
|
||
const metadata = await this.getBattleMetaById(id);
|
||
const game = {
|
||
...metadata,
|
||
detail
|
||
};
|
||
return game;
|
||
}
|
||
async fetchCoop(id) {
|
||
const detail = await this.cacheDetail(id, ()=>this.splatnet.getCoopDetail(id).then((r)=>r.coopHistoryDetail));
|
||
const metadata = await this.getCoopMetaById(id);
|
||
const game = {
|
||
...metadata,
|
||
detail
|
||
};
|
||
return game;
|
||
}
|
||
}
|
||
const DEFAULT_OPTS = {
|
||
profilePath: "./profile.json",
|
||
exporter: "stat.ink",
|
||
noProgress: false,
|
||
monitor: false,
|
||
env: DEFAULT_ENV
|
||
};
|
||
class App {
|
||
profile;
|
||
env;
|
||
constructor(opts){
|
||
this.opts = opts;
|
||
const stateBackend = opts.stateBackend ?? new FileStateBackend(opts.profilePath);
|
||
this.profile = new Profile({
|
||
stateBackend,
|
||
env: opts.env
|
||
});
|
||
this.env = opts.env;
|
||
}
|
||
getSkipMode() {
|
||
const mode = this.opts.skipMode;
|
||
if (mode === "vs") {
|
||
return [
|
||
"vs"
|
||
];
|
||
} else if (mode === "coop") {
|
||
return [
|
||
"coop"
|
||
];
|
||
}
|
||
return [];
|
||
}
|
||
async getExporters() {
|
||
const state = this.profile.state;
|
||
const exporters = this.opts.exporter.split(",");
|
||
const out = [];
|
||
if (exporters.includes("stat.ink")) {
|
||
if (!state.statInkApiKey) {
|
||
const key = (await this.env.prompts.prompt("stat.ink API key is not set. Please enter below.")).trim();
|
||
if (!key) {
|
||
this.env.logger.error("API key is required.");
|
||
Deno.exit(1);
|
||
}
|
||
await this.profile.writeState({
|
||
...state,
|
||
statInkApiKey: key
|
||
});
|
||
}
|
||
out.push(new StatInkExporter({
|
||
statInkApiKey: this.profile.state.statInkApiKey,
|
||
uploadMode: this.opts.monitor ? "Monitoring" : "Manual"
|
||
}));
|
||
}
|
||
if (exporters.includes("file")) {
|
||
out.push(new FileExporter(state.fileExportPath));
|
||
}
|
||
return out;
|
||
}
|
||
exporterProgress(title) {
|
||
const bar = !this.opts.noProgress ? new MultiProgressBar({
|
||
title,
|
||
display: "[:bar] :text :percent :time eta: :eta :completed/:total"
|
||
}) : undefined;
|
||
const allProgress = {};
|
||
const redraw = (name, progress)=>{
|
||
allProgress[name] = progress;
|
||
if (bar) {
|
||
bar.render(Object.entries(allProgress).map(([name, progress])=>({
|
||
completed: progress.current,
|
||
total: progress.total,
|
||
text: name
|
||
})));
|
||
} else if (progress.currentUrl) {
|
||
this.env.logger.log(`Battle exported to ${progress.currentUrl} (${progress.current}/${progress.total})`);
|
||
}
|
||
};
|
||
const endBar = ()=>{
|
||
bar?.end();
|
||
};
|
||
return {
|
||
redraw,
|
||
endBar
|
||
};
|
||
}
|
||
async exportOnce() {
|
||
const splatnet = new Splatnet3({
|
||
profile: this.profile,
|
||
env: this.env
|
||
});
|
||
const exporters = await this.getExporters();
|
||
const initStats = ()=>Object.fromEntries(exporters.map((e)=>[
|
||
e.name,
|
||
0
|
||
]));
|
||
let stats = initStats();
|
||
const skipMode = this.getSkipMode();
|
||
const errors = [];
|
||
if (skipMode.includes("vs")) {
|
||
this.env.logger.log("Skip exporting VS games.");
|
||
} else {
|
||
this.env.logger.log("Fetching battle list...");
|
||
const gameList = await splatnet.getBattleList();
|
||
const { redraw , endBar } = this.exporterProgress("Export vs games");
|
||
const fetcher = new GameFetcher({
|
||
cache: this.opts.cache ?? new FileCache(this.profile.state.cacheDir),
|
||
state: this.profile.state,
|
||
splatnet
|
||
});
|
||
const finalRankState = await fetcher.updateRank();
|
||
await Promise.all(exporters.map((e)=>showError(this.env, this.exportGameList({
|
||
type: "VsInfo",
|
||
fetcher,
|
||
exporter: e,
|
||
gameList,
|
||
onStep: (progress)=>{
|
||
redraw(e.name, progress);
|
||
stats[e.name] = progress.current;
|
||
}
|
||
}).then((count)=>{
|
||
stats[e.name] = count;
|
||
})).catch((err)=>{
|
||
errors.push(err);
|
||
this.env.logger.error(`\nFailed to export to ${e.name}:`, err);
|
||
})));
|
||
endBar();
|
||
this.printStats(stats);
|
||
if (errors.length > 0) {
|
||
throw errors[0];
|
||
}
|
||
fetcher.setRankState(finalRankState);
|
||
await this.profile.writeState({
|
||
...this.profile.state,
|
||
rankState: finalRankState
|
||
});
|
||
}
|
||
stats = initStats();
|
||
const coopExporter = exporters.filter((e)=>e.name !== "stat.ink");
|
||
if (skipMode.includes("coop") || coopExporter.length === 0) {
|
||
this.env.logger.log("Skip exporting coop games.");
|
||
} else {
|
||
this.env.logger.log("Fetching coop battle list...");
|
||
const coopBattleList = await splatnet.getBattleList(BattleListType.Coop);
|
||
const { redraw: redraw1 , endBar: endBar1 } = this.exporterProgress("Export coop games");
|
||
const fetcher1 = new GameFetcher({
|
||
cache: this.opts.cache ?? new FileCache(this.profile.state.cacheDir),
|
||
state: this.profile.state,
|
||
splatnet
|
||
});
|
||
await Promise.all(coopExporter.map((e)=>showError(this.env, this.exportGameList({
|
||
type: "CoopInfo",
|
||
fetcher: fetcher1,
|
||
exporter: e,
|
||
gameList: coopBattleList,
|
||
onStep: (progress)=>{
|
||
stats[e.name] = progress.current;
|
||
redraw1(e.name, progress);
|
||
}
|
||
}).then((count)=>{
|
||
stats[e.name] = count;
|
||
})).catch((err)=>{
|
||
errors.push(err);
|
||
this.env.logger.error(`\nFailed to export to ${e.name}:`, err);
|
||
})));
|
||
endBar1();
|
||
this.printStats(stats);
|
||
if (errors.length > 0) {
|
||
throw errors[0];
|
||
}
|
||
}
|
||
}
|
||
async monitor() {
|
||
while(true){
|
||
await this.exportOnce();
|
||
await this.countDown(this.profile.state.monitorInterval);
|
||
}
|
||
}
|
||
async countDown(sec) {
|
||
const bar = !this.opts.noProgress ? new MultiProgressBar({
|
||
title: "Killing time...",
|
||
display: "[:bar] :completed/:total"
|
||
}) : undefined;
|
||
for (const i of Array(sec).keys()){
|
||
bar?.render([
|
||
{
|
||
completed: i,
|
||
total: sec
|
||
}
|
||
]);
|
||
await delay(1000);
|
||
}
|
||
bar?.end();
|
||
}
|
||
async run() {
|
||
await this.profile.readState();
|
||
if (!this.profile.state.loginState?.sessionToken) {
|
||
const sessionToken = await loginManually(this.env);
|
||
await this.profile.writeState({
|
||
...this.profile.state,
|
||
loginState: {
|
||
...this.profile.state.loginState,
|
||
sessionToken
|
||
}
|
||
});
|
||
}
|
||
if (this.opts.monitor) {
|
||
await this.monitor();
|
||
} else {
|
||
await this.exportOnce();
|
||
}
|
||
}
|
||
async exportGameList({ type , fetcher , exporter , gameList , onStep }) {
|
||
let exported = 0;
|
||
onStep?.({
|
||
current: 0,
|
||
total: 1
|
||
});
|
||
const workQueue = [
|
||
...await exporter.notExported({
|
||
type,
|
||
list: gameList
|
||
})
|
||
].reverse();
|
||
const step = async (id)=>{
|
||
const detail = await fetcher.fetch(type, id);
|
||
const { url } = await exporter.exportGame(detail);
|
||
exported += 1;
|
||
onStep?.({
|
||
currentUrl: url,
|
||
current: exported,
|
||
total: workQueue.length
|
||
});
|
||
};
|
||
if (workQueue.length > 0) {
|
||
onStep?.({
|
||
current: exported,
|
||
total: workQueue.length
|
||
});
|
||
for (const battle of workQueue){
|
||
await step(battle);
|
||
}
|
||
} else {
|
||
onStep?.({
|
||
current: 1,
|
||
total: 1
|
||
});
|
||
}
|
||
return exported;
|
||
}
|
||
printStats(stats) {
|
||
this.env.logger.log(`Exported ${Object.entries(stats).map(([name, count])=>`${name}: ${count}`).join(", ")}`);
|
||
}
|
||
opts;
|
||
}
|
||
const parseArgs = (args)=>{
|
||
const parsed = mod1.parse(args, {
|
||
string: [
|
||
"profilePath",
|
||
"exporter",
|
||
"skipMode"
|
||
],
|
||
boolean: [
|
||
"help",
|
||
"noProgress",
|
||
"monitor"
|
||
],
|
||
alias: {
|
||
"help": "h",
|
||
"profilePath": [
|
||
"p",
|
||
"profile-path"
|
||
],
|
||
"exporter": [
|
||
"e"
|
||
],
|
||
"noProgress": [
|
||
"n",
|
||
"no-progress"
|
||
],
|
||
"monitor": [
|
||
"m"
|
||
],
|
||
"skipMode": [
|
||
"s",
|
||
"skip-mode"
|
||
]
|
||
}
|
||
});
|
||
return parsed;
|
||
};
|
||
const opts = parseArgs(Deno.args);
|
||
if (opts.help) {
|
||
console.log(`Usage: deno run -A ${Deno.mainModule} [options]
|
||
|
||
Options:
|
||
--profile-path <path>, -p Path to config file (default: ./profile.json)
|
||
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
|
||
Multiple exporters can be separated by commas
|
||
(e.g. "stat.ink,file")
|
||
--no-progress, -n Disable progress bar
|
||
--monitor, -m Monitor mode
|
||
--skip-mode <mode>, -s Skip mode (default: null)
|
||
("vs", "coop")
|
||
--help Show this help message and exit`);
|
||
Deno.exit(0);
|
||
}
|
||
const app = new App({
|
||
...DEFAULT_OPTS,
|
||
...opts
|
||
});
|
||
await showError(app.env, app.run()); |