Files
metrics/source/plugins/community/splatoon/s3si/index.ts

7364 lines
239 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
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.3.1";
const NSOAPP_VERSION = "2.5.0";
const WEB_VIEW_VERSION = "3.0.0-2857bc50";
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 BATTLE_NAMESPACE = "b3a2dbf5-2c09-4792-b78c-00b548b70aeb";
const COOP_NAMESPACE = "f1911910-605e-11ed-a622-7085c2057a9d";
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: "tricolor"
},
RESULT: {
WIN: "win",
LOSE: "lose",
DEEMED_LOSE: "lose",
EXEMPTED_LOSE: "exempted_lose",
DRAW: "draw"
},
DRAGON: {
NORMAL: undefined,
DECUPLE: "10x",
DRAGON: "100x",
DOUBLE_DRAGON: "333x"
},
COOP_EVENT_MAP: {
1: "rush",
2: "goldie_seeking",
3: "griller",
4: "mothership",
5: "fog",
6: "cohock_charge",
7: "giant_tornado",
8: "mudmouth_eruption"
},
COOP_SPECIAL_MAP: {
"bd327d1b64372dedefd32adb28bea62a5b6152d93aada5d9fc4f669a1955d6d4": "nicedama",
"463eedc60013608666b260c79ac8c352f9795c3d0cce074d3fbbdbd2c054a56d": "hopsonar",
"fa8d49e8c850ee69f0231976208a913384e73dc0a39e6fb00806f6aa3da8a1ee": "megaphone51",
"252059408283fbcb69ca9c18b98effd3b8653ab73b7349c42472281e5a1c38f9": "jetpack",
"680379f8b83e5f9e033b828360827bc2f0e08c34df1abcc23de3d059fe2ac435": "kanitank",
"0785cb4979024a83aaa2196e287e232d5d7e4ac959895a650c30ed00fedbc714": "sameride",
"380e541b5bc5e49d77ff1a616f1343aeba01d500fee36aaddf8f09d74bd3d3bc": "tripletornado"
},
WATER_LEVEL_MAP: {
0: "low",
1: "normal",
2: "high"
}
};
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 =globalThis.fetch } = options || {};
async function wrappedFetch(input, init) {
if (!input) {
return await fetch(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 fetch(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 key in alias){
const val = getForce(alias, key);
if (typeof val === "string") {
aliases[key] = [
val
];
} else {
aliases[key] = val;
}
for (const alias of getForce(aliases, key)){
aliases[alias] = [
key
].concat(aliases[key].filter((y)=>alias !== y));
}
}
}
if (string !== undefined) {
const stringArgs = typeof string === "string" ? [
string
] : string;
for (const key of stringArgs.filter(Boolean)){
flags.strings[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias){
flags.strings[al] = true;
}
}
}
}
if (collect !== undefined) {
const collectArgs = typeof collect === "string" ? [
collect
] : collect;
for (const key of collectArgs.filter(Boolean)){
flags.collect[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias){
flags.collect[al] = true;
}
}
}
}
if (negatable !== undefined) {
const negatableArgs = typeof negatable === "string" ? [
negatable
] : negatable;
for (const key of negatableArgs.filter(Boolean)){
flags.negatable[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias){
flags.negatable[al] = 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 [, key, value] = m;
if (flags.bools[key]) {
const booleanValue = value !== "false";
setArg(key, booleanValue, arg);
} else {
setArg(key, value, arg);
}
} else if (/^--no-.+/.test(arg) && get(flags.negatable, arg.replace(/^--no-/, ""))) {
const m = arg.match(/^--no-(.+)/);
assert(m != null);
setArg(m[1], false, arg, false);
} else if (/^--.+/.test(arg)) {
const m = arg.match(/^--(.+)/);
assert(m != null);
const [, key] = m;
const next = args[i + 1];
if (next !== undefined && !/^-/.test(next) && !get(flags.bools, key) && !flags.allBools && (get(aliases, key) ? !aliasIsBoolean(key) : true)) {
setArg(key, next, arg);
i++;
} else if (/^(true|false)$/.test(next)) {
setArg(key, next === "true", arg);
i++;
} else {
setArg(key, get(flags.strings, key) ? "" : 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 next = arg.slice(j + 2);
if (next === "-") {
setArg(letters[j], next, arg);
continue;
}
if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) {
setArg(letters[j], next.split(/=(.+)/)[1], arg);
broken = true;
break;
}
if (/[A-Za-z]/.test(letters[j]) && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
setArg(letters[j], next, 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 [key] = arg.slice(-1);
if (!broken && key !== "-") {
if (args[i + 1] && !/^(-|--)[^-]/.test(args[i + 1]) && !get(flags.bools, key) && (get(aliases, key) ? !aliasIsBoolean(key) : true)) {
setArg(key, args[i + 1], arg);
i++;
} else if (args[i + 1] && /^(true|false)$/.test(args[i + 1])) {
setArg(key, args[i + 1] === "true", arg);
i++;
} else {
setArg(key, get(flags.strings, key) ? "" : 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 [key, value] of Object.entries(defaults)){
if (!hasKey(argv, key.split("."))) {
setKey(argv, key, value);
if (aliases[key]) {
for (const x of aliases[key]){
setKey(argv, x, value);
}
}
}
}
for (const key of Object.keys(flags.bools)){
if (!hasKey(argv, key.split("."))) {
const value = get(flags.collect, key) ? [] : false;
setKey(argv, key, value, false);
}
}
for (const key of Object.keys(flags.strings)){
if (!hasKey(argv, key.split(".")) && get(flags.collect, key)) {
setKey(argv, key, [], false);
}
}
if (doubleDash) {
argv["--"] = [];
for (const key of notFlags){
argv["--"].push(key);
}
} else {
for (const key of notFlags){
argv._.push(key);
}
}
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 chunk of this.#chunks){
chunk.offset = offset;
offset += chunk.end - chunk.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 b of buf){
output.set(b, index);
index += b.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 rr = await this.#rd.read(p);
const nread = rr ?? 0;
assert(nread >= 0, "negative read");
return rr;
}
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 byte2 = bytes[offset++] & 0x3f;
const byte3 = bytes[offset++] & 0x3f;
units.push((byte1 & 0x1f) << 12 | byte2 << 6 | byte3);
} else if ((byte1 & 0xf8) === 0xf0) {
const byte2 = bytes[offset++] & 0x3f;
const byte3 = bytes[offset++] & 0x3f;
const byte4 = bytes[offset++] & 0x3f;
let unit = (byte1 & 0x07) << 0x12 | byte2 << 0x0c | byte3 << 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 rv = new Uint8Array(8);
const view = new DataView(rv.buffer);
view.setUint32(0, nsec << 2 | secHigh & 0x3);
view.setUint32(4, secLow);
return rv;
}
} else {
const rv = new Uint8Array(12);
const view = new DataView(rv.buffer);
view.setUint32(0, nsec);
setInt64(view, 4, sec);
return rv;
}
}
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 sec = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
const nsec = nsec30AndSecHigh2 >>> 2;
return {
sec,
nsec
};
}
case 12:
{
const sec = getInt64(view, 4);
const nsec = view.getUint32(0);
return {
sec,
nsec
};
}
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 i = 0; i < this.encoders.length; i++){
const encoder = this.encoders[i];
if (encoder != null) {
const data = encoder(object, context);
if (data != null) {
const type = i;
return new ExtData(type, data);
}
}
}
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 byteLength = utf8Count(object);
this.ensureBufferSizeToWrite(maxHeaderSize + byteLength);
this.writeStringHeader(byteLength);
utf8EncodeJs(object, this.bytes, this.pos);
this.pos += byteLength;
}
}
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 size = headByte - 0x90;
if (size !== 0) {
this.pushArrayState(size);
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 byteLength = this.lookU8();
object = this.decodeUtf8String(byteLength, 1);
} else if (headByte === 0xda) {
const byteLength = this.lookU16();
object = this.decodeUtf8String(byteLength, 2);
} else if (headByte === 0xdb) {
const byteLength = this.lookU32();
object = this.decodeUtf8String(byteLength, 4);
} else if (headByte === 0xdc) {
const size = this.readU16();
if (size !== 0) {
this.pushArrayState(size);
this.complete();
continue DECODE;
} else {
object = [];
}
} else if (headByte === 0xdd) {
const size = this.readU32();
if (size !== 0) {
this.pushArrayState(size);
this.complete();
continue DECODE;
} else {
object = [];
}
} else if (headByte === 0xde) {
const size = this.readU16();
if (size !== 0) {
this.pushMapState(size);
this.complete();
continue DECODE;
} else {
object = {};
}
} else if (headByte === 0xdf) {
const size = this.readU32();
if (size !== 0) {
this.pushMapState(size);
this.complete();
continue DECODE;
} else {
object = {};
}
} else if (headByte === 0xc4) {
const size = this.lookU8();
object = this.decodeBinary(size, 1);
} else if (headByte === 0xc5) {
const size = this.lookU16();
object = this.decodeBinary(size, 2);
} else if (headByte === 0xc6) {
const size = this.lookU32();
object = this.decodeBinary(size, 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 size = this.lookU8();
object = this.decodeExtension(size, 1);
} else if (headByte === 0xc8) {
const size = this.lookU16();
object = this.decodeExtension(size, 2);
} else if (headByte === 0xc9) {
const size = this.lookU32();
object = this.decodeExtension(size, 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;
}
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) {
const parsed = parseHistoryDetailId(id);
if (parsed.type === "VsHistoryDetail") {
const content = new TextEncoder().encode(`${parsed.timestamp}_${parsed.uuid}`);
return mod6.v5.generate(BATTLE_NAMESPACE, content);
} else if (parsed.type === "CoopHistoryDetail") {
return mod6.v5.generate(COOP_NAMESPACE, mod.decode(id));
} else {
throw new Error("Unknown type");
}
}
function s3siGameId(id) {
const fullId = mod.decode(id);
const tsUuid = fullId.slice(fullId.length - 52, fullId.length);
return mod6.v5.generate(S3SI_NAMESPACE, tsUuid);
}
function s3sCoopGameId(id) {
const fullId = mod.decode(id);
const tsUuid = fullId.slice(fullId.length - 52, fullId.length);
return mod6.v5.generate(COOP_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 [, uid, timestamp, uuid] = plainText.match(coopRE);
return {
type: "CoopHistoryDetail",
uid,
timestamp,
uuid
};
} else {
throw new Error(`Invalid ID: ${plainText}`);
}
}
const delay = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
function b64Number(id) {
const text = new TextDecoder().decode(mod.decode(id));
const [_, num] = text.split("-");
return parseInt(num);
}
function nonNullable(v) {
return v !== null && v !== undefined;
}
function urlSimplify(url) {
try {
const { pathname } = new URL(url);
return {
pathname
};
} catch (_e) {
return url;
}
}
async function loginSteps({ newFetcher }, step2) {
const fetch = newFetcher();
if (!step2) {
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 fetch.get({
url,
headers: {
"Host": "accounts.nintendo.com",
"Connection": "keep-alive",
"Cache-Control": "max-age=0",
"Upgrade-Insecure-Requests": "1",
"User-Agent": DEFAULT_APP_USER_AGENT,
"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"
}
});
return {
authCodeVerifier,
url: res.url
};
} else {
const { login , authCodeVerifier } = step2;
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,
sessionTokenCode,
authCodeVerifier
});
if (!sessionToken) {
throw new Error("No session token found");
}
return {
sessionToken
};
}
}
async function loginManually(env) {
const { prompts: { promptLogin } } = env;
const step1 = await loginSteps(env);
const { url , authCodeVerifier } = step1;
const login = (await promptLogin(url)).trim();
if (!login) {
throw new Error("No login URL provided");
}
const step2 = await loginSteps(env, {
authCodeVerifier,
login
});
return step2.sessionToken;
}
async function getGToken({ fApi , sessionToken , env }) {
const fetch = env.newFetcher();
const idResp = await fetch.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 fetch.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 fetch.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 fetch.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 , sessionTokenCode , authCodeVerifier }) {
const resp = await fetch.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"
},
body: JSON.stringify({
"token": idToken,
"hash_method": 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 fetch = wrapFetch({
cookieJar
});
return {
async get ({ url , headers }) {
return await fetch(url, {
method: "GET",
headers
});
},
async post ({ url , body , headers }) {
return await fetch(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"] = "22e2fa8294168003c21b00c333c35384";
Queries["LatestBattleHistoriesQuery"] = "4f5f26e64bca394b45345a65a2f383bd";
Queries["RegularBattleHistoriesQuery"] = "d5b795d09e67ce153e622a184b7e7dfa";
Queries["BankaraBattleHistoriesQuery"] = "de4754588109b77dbcb90fbe44b612ee";
Queries["XBattleHistoriesQuery"] = "45c74fefb45a49073207229ca65f0a62";
Queries["PrivateBattleHistoriesQuery"] = "1d6ed57dc8b801863126ad4f351dfb9a";
Queries["VsHistoryDetailQuery"] = "291295ad311b99a6288fc95a5c4cb2d2";
Queries["CoopHistoryQuery"] = "6ed02537e4a65bbb5e7f4f23092f6154";
Queries["CoopHistoryDetailQuery"] = "379f0d9b78b531be53044bcac031b34b";
Queries["myOutfitCommonDataFilteringConditionQuery"] = "d02ab22c9dccc440076055c8baa0fa7a";
Queries["myOutfitCommonDataEquipmentsQuery"] = "d29cd0c2b5e6bac90dd5b817914832f8";
Queries["HistoryRecordQuery"] = "32b6771f94083d8f04848109b7300af5";
Queries["ConfigureAnalyticsQuery"] = "f8ae00773cc412a50dd41a6d9a159ddd";
})(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 = {}));
var SummaryEnum;
(function(SummaryEnum) {
SummaryEnum[SummaryEnum["ConfigureAnalyticsQuery"] = Queries.ConfigureAnalyticsQuery] = "ConfigureAnalyticsQuery";
SummaryEnum[SummaryEnum["HistoryRecordQuery"] = Queries.HistoryRecordQuery] = "HistoryRecordQuery";
SummaryEnum[SummaryEnum["CoopHistoryQuery"] = Queries.CoopHistoryQuery] = "CoopHistoryQuery";
})(SummaryEnum || (SummaryEnum = {}));
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.ConfigureAnalyticsQuery);
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 getXBattleHistories() {
return await this.request(Queries.XBattleHistoriesQuery);
}
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;
}
async getSummary() {
const ConfigureAnalyticsQuery = await this.request(Queries.ConfigureAnalyticsQuery);
const HistoryRecordQuery = await this.request(Queries.HistoryRecordQuery);
const CoopHistoryQuery = await this.request(Queries.CoopHistoryQuery);
const getFirstBattleId = async ()=>{
const latest = await this.request(Queries.LatestBattleHistoriesQuery);
const id = latest?.latestBattleHistories?.historyGroups?.nodes?.[0]?.historyDetails?.nodes?.[0]?.id;
return id;
};
const id = CoopHistoryQuery?.coopResult?.historyGroups?.nodes?.[0]?.historyDetails?.nodes?.[0]?.id ?? await getFirstBattleId();
if (!id) {
throw new Error("No battle id found");
}
const { uid } = parseHistoryDetailId(id);
return {
uid,
ConfigureAnalyticsQuery,
HistoryRecordQuery,
CoopHistoryQuery
};
}
}
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;
}
const COOP_POINT_MAP = {
0: -20,
1: -10,
2: 0,
3: 20
};
async function checkResponse(resp) {
if (Math.floor(resp.status / 100) !== 2) {
const json = await resp.json().catch(()=>undefined);
throw new APIError({
response: resp,
json,
message: "Failed to fetch data from stat.ink"
});
}
}
class StatInkAPI {
statInk;
FETCH_LOCK;
cache;
constructor(statInkApiKey, env){
this.statInkApiKey = statInkApiKey;
this.env = env;
this.statInk = "https://stat.ink";
this.FETCH_LOCK = new Mutex();
this.cache = {};
this._salmonWeaponMap = new Map();
this.getSalmonWeapon = ()=>this._getCached(`${this.statInk}/api/v3/salmon/weapon?full=1`);
this.getWeapon = ()=>this._getCached(`${this.statInk}/api/v3/weapon?full=1`);
this.getAbility = ()=>this._getCached(`${this.statInk}/api/v3/ability?full=1`);
this.getStage = ()=>this._getCached(`${this.statInk}/api/v3/stage`);
if (statInkApiKey.length !== 43) {
throw new Error("Invalid stat.ink API key");
}
}
requestHeaders() {
return {
"User-Agent": USERAGENT,
"Authorization": `Bearer ${this.statInkApiKey}`
};
}
async uuidList(type) {
const fetch = this.env.newFetcher();
const response = await fetch.get({
url: type === "VsInfo" ? `${this.statInk}/api/v3/s3s/uuid-list` : `${this.statInk}/api/v3/salmon/uuid-list`,
headers: this.requestHeaders()
});
await checkResponse(response);
const uuidResult = await response.json();
if (!Array.isArray(uuidResult)) {
throw new APIError({
response,
json: uuidResult,
message: uuidResult.message
});
}
return uuidResult;
}
async postBattle(body) {
const fetch = this.env.newFetcher();
const resp = await fetch.post({
url: `${this.statInk}/api/v3/battle`,
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 json;
}
async postCoop(body) {
const fetch = this.env.newFetcher();
const resp = await fetch.post({
url: `${this.statInk}/api/v3/salmon`,
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 json;
}
async _getCached(url) {
const release = await this.FETCH_LOCK.acquire();
try {
if (this.cache[url]) {
return this.cache[url];
}
const fetch = this.env.newFetcher();
const resp = await fetch.get({
url,
headers: this.requestHeaders()
});
await checkResponse(resp);
const json = await resp.json();
this.cache[url] = json;
return json;
} finally{
release();
}
}
_getAliasName(name) {
const STAT_INK_DOT = "┬À";
const SPLATNET_DOT = "";
if (name.includes(STAT_INK_DOT)) {
return [
name,
name.replaceAll(STAT_INK_DOT, SPLATNET_DOT)
];
} else {
return [
name
];
}
}
_salmonWeaponMap;
async getSalmonWeaponMap() {
if (this._salmonWeaponMap.size === 0) {
const weapons = await this.getSalmonWeapon();
for (const weapon of weapons){
for (const name of Object.values(weapon.name).flatMap((n)=>this._getAliasName(n))){
const prevKey = this._salmonWeaponMap.get(name);
if (prevKey !== undefined && prevKey !== weapon.key) {
console.warn(`Duplicate weapon name: ${name}`);
}
this._salmonWeaponMap.set(name, weapon.key);
}
}
if (this._salmonWeaponMap.size === 0) {
throw new Error("Failed to get salmon weapon map");
}
}
return this._salmonWeaponMap;
}
getSalmonWeapon;
getWeapon;
getAbility;
getStage;
statInkApiKey;
env;
}
class StatInkExporter {
name = "stat.ink";
api;
uploadMode;
constructor({ statInkApiKey , uploadMode , env }){
this.api = new StatInkAPI(statInkApiKey, env);
this.uploadMode = uploadMode;
}
isTriColor({ vsMode }) {
return vsMode.mode === "FEST" && b64Number(vsMode.id) === 8;
}
async exportGame(game) {
if (game.type === "VsInfo") {
const body = await this.mapBattle(game);
const { url } = await this.api.postBattle(body);
return {
status: "success",
url
};
} else {
const body = await this.mapCoop(game);
const { url } = await this.api.postCoop(body);
return {
status: "success",
url
};
}
}
async notExported({ type , list }) {
const uuid = await this.api.uuidList(type);
const out = [];
for (const id of list){
const s3sId = await gameId(id);
const s3siId = await s3siGameId(id);
const s3sCoopId = await s3sCoopGameId(id);
if (!uuid.includes(s3sId) && !uuid.includes(s3siId) && !uuid.includes(s3sCoopId)) {
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) {
return "splatfest_open";
}
} else if (vsMode === "X_MATCH") {
return "xmatch";
}
throw new TypeError(`Unknown vsMode ${vsMode}`);
}
async mapStage({ vsStage }) {
const id = b64Number(vsStage.id).toString();
const stage = await this.api.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 this.api.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),
crown: player.crown ? "yes" : "no",
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.signal = player.result.noroshiTry ?? undefined;
result.special = player.result.special;
}
return result;
};
async mapBattle({ groupInfo , 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);
if (otherTeams.length === 0) {
throw new Error(`Other teams is empty`);
}
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[0].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.signal = self.result.noroshiTry ?? undefined;
result.special = self.result.special;
}
result.our_team_color = this.mapColor(myTeam.color);
result.their_team_color = this.mapColor(otherTeams[0].color);
if (otherTeams.length === 2) {
result.third_team_color = this.mapColor(otherTeams[1].color);
}
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" || rule === "TRI_COLOR") {
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 (myTeam.festTeamName) {
result.our_team_theme = myTeam.festTeamName;
}
if (myTeam.tricolorRole) {
result.our_team_role = myTeam.tricolorRole === "DEFENSE" ? "defender" : "attacker";
}
if (otherTeams[0].festTeamName) {
result.their_team_theme = otherTeams[0].festTeamName;
}
if (otherTeams[0].tricolorRole) {
result.their_team_role = otherTeams[0].tricolorRole === "DEFENSE" ? "defender" : "attacker";
}
if (otherTeams.length === 2) {
result.third_team_players = await Promise.all(otherTeams[1].players.map(this.mapPlayer));
result.third_team_percent = (otherTeams[1]?.result?.paintRatio ?? 0) * 100;
result.third_team_inked = otherTeams[1].players.reduce((acc, i)=>acc + i.paint, 0);
if (otherTeams[1].festTeamName) {
result.third_team_theme = otherTeams[1].festTeamName;
}
if (otherTeams[1].tricolorRole) {
result.third_team_role = otherTeams[1].tricolorRole === "DEFENSE" ? "defender" : "attacker";
}
}
}
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?.udemae) {
[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;
}
}
if (challengeProgress) {
result.challenge_win = challengeProgress.winCount;
result.challenge_lose = challengeProgress.loseCount;
}
if (vsDetail.xMatch) {
result.x_power_before = result.x_power_after = vsDetail.xMatch.lastXPower;
if (groupInfo?.xMatchMeasurement && groupInfo?.xMatchMeasurement.state === "COMPLETED" && challengeProgress?.index === 0) {
result.x_power_after = groupInfo.xMatchMeasurement.xPowerAfter;
}
}
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;
}
mapColor(color) {
const float2hex = (i)=>Math.round(i * 255).toString(16).padStart(2, "0");
const nums = [
color.r,
color.g,
color.b,
color.a
];
return nums.map(float2hex).join("");
}
isRandom(image) {
const RANDOM_FILENAME = "473fffb2442075078d8bb7125744905abdeae651b6a5b7453ae295582e45f7d1";
const url = image?.url;
if (typeof url === "string") {
return url.includes(RANDOM_FILENAME);
} else if (url === undefined || url === null) {
return false;
} else {
return url.pathname.includes(RANDOM_FILENAME);
}
}
async mapCoopWeapon({ name , image }) {
const weaponMap = await this.api.getSalmonWeaponMap();
const weapon = weaponMap.get(name);
if (!weapon) {
if (this.isRandom(image)) {
return null;
}
throw new Error(`Weapon not found: ${name}`);
}
return weapon;
}
mapSpecial({ name , image }) {
const { url } = image;
const imageName = typeof url === "object" ? url.pathname : url ?? "";
const hash = /\/(\w+)_0\.\w+/.exec(imageName)?.[1] ?? "";
const special = SPLATNET3_STATINK_MAP.COOP_SPECIAL_MAP[hash];
if (!special) {
if (this.isRandom(image)) {
return Promise.resolve(undefined);
}
throw new Error(`Special not found: ${name} (${imageName})`);
}
return Promise.resolve(special);
}
async mapCoopPlayer(isMyself, { player , weapons , specialWeapon , defeatEnemyCount , deliverCount , goldenAssistCount , goldenDeliverCount , rescueCount , rescuedCount }) {
const disconnected = [
goldenDeliverCount,
deliverCount,
rescueCount,
rescuedCount,
defeatEnemyCount
].every((v)=>v === 0) || !specialWeapon;
return {
me: isMyself ? "yes" : "no",
name: player.name,
number: player.nameId,
splashtag_title: player.byname,
uniform: b64Number(player.uniform.id).toString(),
special: specialWeapon ? await this.mapSpecial(specialWeapon) : undefined,
weapons: await Promise.all(weapons.map((w)=>this.mapCoopWeapon(w))),
golden_eggs: goldenDeliverCount,
golden_assist: goldenAssistCount,
power_eggs: deliverCount,
rescue: rescueCount,
rescued: rescuedCount,
defeat_boss: defeatEnemyCount,
disconnected: disconnected ? "yes" : "no"
};
}
mapKing(id) {
if (!id) {
return undefined;
}
const nid = b64Number(id).toString();
return nid;
}
async mapWave(wave) {
const event = wave.eventWave ? SPLATNET3_STATINK_MAP.COOP_EVENT_MAP[b64Number(wave.eventWave.id)] : undefined;
const special_uses = (await Promise.all(wave.specialWeapons.map((w)=>this.mapSpecial(w)))).flatMap((key)=>key ? [
key
] : []).reduce((p, key)=>({
...p,
[key]: (p[key] ?? 0) + 1
}), {});
return {
tide: SPLATNET3_STATINK_MAP.WATER_LEVEL_MAP[wave.waterLevel],
event,
golden_quota: wave.deliverNorm,
golden_appearances: wave.goldenPopCount,
golden_delivered: wave.teamDeliverCount,
special_uses
};
}
async mapCoop({ gradeBefore , groupInfo , detail }) {
const { dangerRate , resultWave , bossResult , myResult , memberResults , scale , playedTime , enemyResults , smellMeter , waveResults } = detail;
const startedAt = Math.floor(new Date(playedTime).getTime() / 1000);
const golden_eggs = waveResults.reduce((prev, i)=>prev + i.teamDeliverCount, 0);
const power_eggs = myResult.deliverCount + memberResults.reduce((p, i)=>p + i.deliverCount, 0);
const bosses = Object.fromEntries(enemyResults.map((i)=>[
b64Number(i.enemy.id),
{
appearances: i.popCount,
defeated: i.teamDefeatCount,
defeated_by_me: i.defeatCount
}
]));
const title_after = detail.afterGrade ? b64Number(detail.afterGrade.id).toString() : undefined;
const title_exp_after = detail.afterGradePoint;
let clear_waves;
if (waveResults.length > 0) {
clear_waves = waveResults.filter((i)=>i.waveNumber < 4).length - 1 + (resultWave === 0 ? 1 : 0);
} else {
clear_waves = 0;
}
let title_before = undefined;
let title_exp_before = undefined;
if (gradeBefore) {
title_before = b64Number(gradeBefore.grade.id).toString();
title_exp_before = gradeBefore.gradePoint;
} else {
const expDiff = COOP_POINT_MAP[clear_waves];
if (nonNullable(title_after) && nonNullable(title_exp_after) && nonNullable(expDiff)) {
if (title_exp_after === 40 && expDiff === 20) {} else if (title_exp_after === 40 && expDiff < 0 && title_after !== "8") {} else if (title_exp_after === 999 && expDiff !== 0) {
title_before = title_after;
} else {
if (title_exp_after - expDiff >= 0) {
title_before = title_after;
title_exp_before = title_exp_after - expDiff;
} else {
title_before = (parseInt(title_after) - 1).toString();
}
}
}
}
let fail_reason = null;
if (clear_waves !== 3 && waveResults.length > 0) {
const lastWave = waveResults[waveResults.length - 1];
if (lastWave.teamDeliverCount >= lastWave.deliverNorm) {
fail_reason = "wipe_out";
}
}
const result = {
uuid: await gameId(detail.id),
private: groupInfo?.mode === "PRIVATE_CUSTOM" ? "yes" : "no",
big_run: detail.rule === "BIG_RUN" ? "yes" : "no",
stage: b64Number(detail.coopStage.id).toString(),
danger_rate: dangerRate * 100,
clear_waves,
fail_reason,
king_smell: smellMeter,
king_salmonid: this.mapKing(detail.bossResult?.boss.id),
clear_extra: bossResult?.hasDefeatBoss ? "yes" : "no",
title_before,
title_exp_before,
title_after,
title_exp_after,
golden_eggs,
power_eggs,
gold_scale: scale?.gold,
silver_scale: scale?.silver,
bronze_scale: scale?.bronze,
job_point: detail.jobPoint,
job_score: detail.jobScore,
job_rate: detail.jobRate,
job_bonus: detail.jobBonus,
waves: await Promise.all(waveResults.map((w)=>this.mapWave(w))),
players: await Promise.all([
this.mapCoopPlayer(true, myResult),
...memberResults.map((p)=>this.mapCoopPlayer(false, p))
]),
bosses,
agent: AGENT_NAME,
agent_version: S3SI_VERSION,
agent_variables: {
"Upload Mode": this.uploadMode
},
automated: "yes",
start_at: startedAt
};
return result;
}
}
function parseUdemae(udemae) {
const [rank, rankNum] = udemae.split(/([0-9]+)/);
return [
rank.toLowerCase(),
rankNum === undefined ? undefined : parseInt(rankNum)
];
}
function replacer(key, value) {
if (![
"url",
"maskImageUrl",
"overlayImageUrl"
].includes(key)) {
return value;
}
return typeof value === "string" ? urlSimplify(value) : undefined;
}
class FileExporter {
name;
constructor(exportPath){
this.exportPath = exportPath;
this.name = "file";
}
getFilenameById(id) {
const { uid , timestamp } = parseHistoryDetailId(id);
return `${uid}_${timestamp}Z.json`;
}
async exportedGames({ uid , type , filter }) {
const out = [];
for await (const entry of Deno.readDir(this.exportPath)){
const filename = entry.name;
const [fileUid, timestamp] = filename.split("_", 2);
if (!entry.isFile || fileUid !== uid) {
continue;
}
const filepath = mod10.join(this.exportPath, filename);
const content = await Deno.readTextFile(filepath);
const body = JSON.parse(content);
if (body.type === "SUMMARY") {
continue;
}
if (body.type === "VS" && type === "VsInfo") {
if (filter && !filter(body.data)) {
continue;
}
out.push({
id: body.data.detail.id,
filepath,
timestamp
});
} else if (body.type === "COOP" && type === "CoopInfo") {
if (filter && !filter(body.data)) {
continue;
}
out.push({
id: body.data.detail.id,
filepath,
timestamp
});
}
}
return out.sort((a, b)=>b.timestamp.localeCompare(a.timestamp)).map(({ id , filepath })=>({
id,
getContent: async ()=>{
const content = await Deno.readTextFile(filepath);
const body = JSON.parse(content);
return body.data;
}
}));
}
async exportSummary(summary) {
const filename = `${summary.uid}_summary.json`;
const filepath = mod10.join(this.exportPath, filename);
const body = {
type: "SUMMARY",
nsoVersion: NSOAPP_VERSION,
s3siVersion: S3SI_VERSION,
exportTime: new Date().toISOString(),
data: summary
};
await Deno.writeTextFile(filepath, JSON.stringify(body));
return {
status: "success",
url: filepath
};
}
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 common = {
nsoVersion: NSOAPP_VERSION,
s3siVersion: S3SI_VERSION,
exportTime: new Date().toISOString()
};
const dataType = info.type === "VsInfo" ? {
type: "VS",
data: info
} : {
type: "COOP",
data: info
};
const body = {
...common,
...dataType
};
await Deno.writeTextFile(filepath, JSON.stringify(body, replacer));
return {
status: "success",
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 SEASONS = [
{
id: "season202209",
name: "Drizzle Season 2022",
start: new Date("2022-09-01T00:00:00+00:00"),
end: new Date("2022-12-01T00:00:00+00:00")
},
{
id: "season202212",
name: "Chill Season 2022",
start: new Date("2022-12-01T00:00:00+00:00"),
end: new Date("2023-03-01T00:00:00+00:00")
},
{
id: "season202303",
name: "Fresh Season 2023",
start: new Date("2023-03-01T00:00:00+00:00"),
end: new Date("2023-06-01T00:00:00+00:00")
}
];
const getSeason = (date)=>{
return SEASONS.find((s)=>s.start <= date && date < s.end);
};
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: 180
};
if (level === 9) {
item.promotion = true;
}
out.push(item);
}
out.push({
rank: "S+50",
pointRange: [
0,
9999
],
charge: 180
});
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: 110
},
{
rank: "A",
pointRange: [
500,
800
],
charge: 120
},
{
rank: "A+",
pointRange: [
800,
1100
],
charge: 130,
promotion: true
},
{
rank: "S",
pointRange: [
300,
1000
],
charge: 170,
promotion: true
},
...splusParams()
];
function addRank(state, delta) {
if (!state) {
if (delta.isPromotion && delta.isRankUp) {
state = getRankStateByDelta(delta);
} else {
return;
}
}
if (state.gameId !== delta.before.gameId) {
throw new Error("Invalid state");
}
const { rank , rankPoint } = state;
const { gameId , timestamp , rankAfter , isPromotion , isRankUp , isChallengeFirst } = delta;
if (state.timestamp) {
const oldSeason = getSeason(new Date(state.timestamp * 1000));
if (oldSeason) {
const newSeason = getSeason(new Date(timestamp * 1000));
if (newSeason?.id !== oldSeason.id) {
return;
}
}
}
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 {
before: state,
after: {
gameId,
timestamp,
rank,
rankPoint: rankPoint - rankParam.charge
}
};
}
if (rankIndex === RANK_PARAMS.length - 1) {
return {
before: state,
after: {
timestamp,
gameId,
rank,
rankPoint: Math.min(rankPoint + delta.rankPoint, rankParam.pointRange[1])
}
};
}
if (isPromotion && isRankUp) {
const nextRankParam = RANK_PARAMS[rankIndex + 1];
return {
before: state,
after: {
gameId,
timestamp,
rank: nextRankParam.rank,
rankPoint: nextRankParam.pointRange[0]
}
};
}
return {
before: state,
after: {
gameId,
timestamp,
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 beginPoint(state, flatten) {
if (state) {
const index = flatten.findIndex((i)=>i.gameId === state.gameId);
if (index !== -1) {
return [
flatten[index],
flatten.slice(index)
];
}
}
if (flatten.length === 0) {
throw new Error("flatten must not be empty");
}
return [
flatten[0],
flatten
];
}
function getTimestamp(date) {
return Math.floor(date.getTime() / 1000);
}
function generateDeltaList(state, flatten) {
const [firstItem, unProcessed] = beginPoint(state, flatten);
const deltaList = [];
let before = {
gameId: firstItem.gameId,
timestamp: getTimestamp(firstItem.time)
};
for (const i of unProcessed.slice(1)){
if (!i.detail.bankaraMatch) {
throw new TypeError("bankaraMatch must be defined");
}
let delta = {
before,
gameId: i.gameId,
timestamp: getTimestamp(i.time),
rankPoint: 0,
isPromotion: false,
isRankUp: false,
isChallengeFirst: false
};
before = {
gameId: i.gameId,
timestamp: Math.floor(i.time.getTime() / 1000)
};
if (i.bankaraMatchChallenge) {
if (i.index === 0 && i.bankaraMatchChallenge.state !== "INPROGRESS") {
delta = {
...delta,
rank: i.detail.udemae,
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 {
firstItem,
deltaList
};
}
function getRankStateByDelta(i) {
const rank = i.rank;
const nextRank = i.rankAfter;
const earnedUdemaePoint = i.rankPoint;
if (!rank || !nextRank) {
throw new Error("rank and nextRank must be defined");
}
const param = RANK_PARAMS.find((i)=>i.rank === rank);
const nextParam = RANK_PARAMS.find((i)=>i.rank === nextRank);
if (!param || !nextParam) {
throw new Error(`Rank or nextRank not found: ${rank} ${nextRank}`);
}
const oldRankPoint = nextParam.pointRange[0] - earnedUdemaePoint;
return {
gameId: i.before.gameId,
timestamp: i.before.timestamp,
rank,
rankPoint: oldRankPoint
};
}
class RankTracker {
deltaMap;
stateMap;
constructor(state){
this.state = state;
this.deltaMap = new Map();
this.stateMap = new Map();
}
async getRankStateById(id) {
const gid = await gameId(id);
return this.stateMap.get(gid);
}
setState(state) {
this.state = state;
}
async updateState(history) {
const flatten = await Promise.all(history.flatMap(({ historyDetails , bankaraMatchChallenge })=>{
return historyDetails.nodes.map((j, index)=>({
id: j.id,
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
}))));
let curState = this.state;
const { firstItem , deltaList } = generateDeltaList(curState, flatten);
if (curState && firstItem.gameId !== curState.gameId) {
return;
}
for (const delta of deltaList){
this.deltaMap.set(delta.before.gameId, delta);
const result = addRank(curState, delta);
curState = result?.after;
if (result) {
this.stateMap.set(result.after.gameId, result);
}
}
return curState;
}
state;
}
class GameFetcher {
_splatnet;
cache;
rankTracker;
lock = {};
bankaraLock = new Mutex();
bankaraHistory;
coopLock = new Mutex();
coopHistory;
xMatchLock = new Mutex();
xMatchHistory;
constructor({ cache =new MemoryCache() , splatnet , state }){
this._splatnet = splatnet;
this.cache = cache;
this.rankTracker = new RankTracker(state.rankState);
}
get splatnet() {
if (!this._splatnet) {
throw new Error("splatnet is not set");
}
return this._splatnet;
}
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);
}
getXMatchHistory() {
if (!this._splatnet) {
return [];
}
return this.xMatchLock.use(async ()=>{
if (this.xMatchHistory) {
return this.xMatchHistory;
}
const { xBattleHistories: { historyGroups } } = await this.splatnet.getXBattleHistories();
this.xMatchHistory = historyGroups.nodes;
return this.xMatchHistory;
});
}
getBankaraHistory() {
if (!this._splatnet) {
return [];
}
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() {
if (!this._splatnet) {
return [];
}
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 = this._splatnet ? await this.getCoopHistory() : [];
const group = coopHistory.find((i)=>i.historyDetails.nodes.some((i)=>i.id === id));
if (!group) {
return {
type: "CoopInfo",
listNode: null,
groupInfo: null,
gradeBefore: null
};
}
const { historyDetails , ...groupInfo } = group;
const listNodeIdx = historyDetails.nodes.findIndex((i)=>i.id === id) ?? null;
const listNode = listNodeIdx !== null ? historyDetails.nodes[listNodeIdx] : null;
const listNodeBefore = listNodeIdx !== null ? historyDetails.nodes[listNodeIdx + 1] ?? null : null;
return {
type: "CoopInfo",
listNode,
groupInfo,
gradeBefore: listNodeBefore?.afterGrade && listNodeBefore.afterGradePoint ? {
grade: listNodeBefore.afterGrade,
gradePoint: listNodeBefore.afterGradePoint
} : null
};
}
async getBattleMetaById(id, vsMode) {
const gid = await gameId(id);
const gameIdMap = new Map();
let group = null;
let listNode = null;
if (vsMode === "BANKARA" || vsMode === "X_MATCH") {
const bankaraHistory = vsMode === "BANKARA" ? await this.getBankaraHistory() : await this.getXMatchHistory();
for (const i of bankaraHistory){
for (const j of i.historyDetails.nodes){
gameIdMap.set(j, await gameId(j.id));
}
}
group = bankaraHistory.find((i)=>i.historyDetails.nodes.some((i)=>gameIdMap.get(i) === gid)) ?? null;
}
if (!group) {
return {
type: "VsInfo",
challengeProgress: null,
bankaraMatchChallenge: null,
listNode: null,
rankState: null,
rankBeforeState: null,
groupInfo: null
};
}
const { bankaraMatchChallenge , xMatchMeasurement } = group;
const { historyDetails , ...groupInfo } = group;
listNode = historyDetails.nodes.find((i)=>gameIdMap.get(i) === gid) ?? null;
const index = historyDetails.nodes.indexOf(listNode);
let challengeProgress = null;
const challengeOrMeasurement = bankaraMatchChallenge || xMatchMeasurement;
if (challengeOrMeasurement) {
const pastBattles = historyDetails.nodes.slice(0, index);
const { winCount , loseCount } = challengeOrMeasurement;
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,
groupInfo
};
}
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, detail.vsMode.mode);
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,
withSummary: false,
env: DEFAULT_ENV
};
class StepProgress {
currentUrl;
total;
exported;
done;
skipped;
constructor(){
this.total = 1;
this.exported = 0;
this.done = 0;
this.skipped = {};
}
}
function progress({ total , currentUrl , done }) {
return {
total,
currentUrl,
current: done
};
}
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",
env: this.env
}));
}
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,
new StepProgress()
]));
let stats = initStats();
const skipMode = this.getSkipMode();
const errors = [];
if (skipMode.includes("vs") || exporters.length === 0) {
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,
stepProgress: stats[e.name],
onStep: ()=>{
redraw(e.name, progress(stats[e.name]));
}
})).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();
if (skipMode.includes("coop") || exporters.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 , endBar } = this.exporterProgress("Export coop games");
const fetcher = new GameFetcher({
cache: this.opts.cache ?? new FileCache(this.profile.state.cacheDir),
state: this.profile.state,
splatnet
});
await Promise.all(exporters.map((e)=>showError(this.env, this.exportGameList({
type: "CoopInfo",
fetcher,
exporter: e,
gameList: coopBattleList,
stepProgress: stats[e.name],
onStep: ()=>{
redraw(e.name, progress(stats[e.name]));
}
})).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];
}
}
const summaryExporters = exporters.filter((e)=>e.exportSummary);
if (!this.opts.withSummary || summaryExporters.length === 0) {
this.env.logger.log("Skip exporting summary.");
} else {
this.env.logger.log("Fetching summary...");
const summary = await splatnet.getSummary();
await Promise.all(summaryExporters.map((e)=>showError(this.env, e.exportSummary(summary)).then((result)=>{
if (result.status === "success") {
this.env.logger.log(`Exported summary to ${result.url}`);
} else if (result.status === "skip") {
this.env.logger.log(`Skipped exporting summary to ${e.name}`);
} else {}
}).catch((err)=>{
errors.push(err);
this.env.logger.error(`\nFailed to export to ${e.name}:`, err);
})));
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 , stepProgress , onStep }) {
onStep?.();
const workQueue = [
...await exporter.notExported({
type,
list: gameList
})
].reverse();
const step = async (id)=>{
const detail = await fetcher.fetch(type, id);
const result = await exporter.exportGame(detail);
stepProgress.done += 1;
stepProgress.currentUrl = undefined;
if (result.status === "success") {
stepProgress.exported += 1;
stepProgress.currentUrl = result.url;
} else if (result.status === "skip") {
const { skipped } = stepProgress;
skipped[result.reason] = (skipped[result.reason] ?? 0) + 1;
} else {}
onStep?.();
};
if (workQueue.length > 0) {
stepProgress.total = workQueue.length;
onStep?.();
for (const battle of workQueue){
await step(battle);
}
} else {
stepProgress.done = 1;
onStep?.();
}
return stepProgress;
}
printStats(stats) {
this.env.logger.log(`Exported ${Object.entries(stats).map(([name, { exported }])=>`${name}: ${exported}`).join(", ")}`);
if (Object.values(stats).some((i)=>Object.keys(i.skipped).length > 0)) {
this.env.logger.log(`Skipped ${Object.entries(stats).map(([name, { skipped }])=>Object.entries(skipped).map(([reason, count])=>`${name}: ${reason} (${count})`).join(", "))}`);
}
}
opts;
}
const parseArgs = (args)=>{
const parsed = mod1.parse(args, {
string: [
"profilePath",
"exporter",
"skipMode"
],
boolean: [
"help",
"noProgress",
"monitor",
"withSummary"
],
alias: {
"help": "h",
"profilePath": [
"p",
"profile-path"
],
"exporter": [
"e"
],
"noProgress": [
"n",
"no-progress"
],
"monitor": [
"m"
],
"skipMode": [
"s",
"skip-mode"
],
"withSummary": "with-summary"
}
});
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")
--with-summary Include summary in the output
--help Show this help message and exit`);
Deno.exit(0);
}
const app = new App({
...DEFAULT_OPTS,
...opts
});
await showError(app.env, app.run());