end of day

This commit is contained in:
2025-12-28 18:35:22 -08:00
parent 53c48319ea
commit c48868a35a
4 changed files with 198 additions and 343 deletions
+1
View File
@@ -0,0 +1 @@
@sugoidogo:registry=https://gitea.sugoidogo.com/api/packages/sugoidogo/npm/
+67 -246
View File
@@ -9,13 +9,18 @@
"version": "0.2.0",
"license": "LGPL-3.0-or-later",
"dependencies": {
"@sugoidogo/js-util": "^0.3.0",
"commander": "^14.0.2",
"confbox": "^0.2.2",
"curseforge-v2": "^1.5.0",
"dotenv": "^17.2.3",
"md5": "^2.3.0",
"murmur2": "^1.0.2",
"native-file-system-adapter": "file:../native-file-system-adapter/native-file-system-adapter-3.0.1.tgz",
"workerless": "^0.1.0"
},
"devDependencies": {
"@types/md5": "^2.3.6",
"@types/node": "^24.10.1",
"@types/web": "^0.0.294",
"bun": "^1.3.3",
@@ -1343,25 +1348,10 @@
"dev": true,
"license": "MIT"
},
"node_modules/@sindresorhus/is": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.1.tgz",
"integrity": "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@speed-highlight/core": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.12.tgz",
"integrity": "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==",
"dev": true,
"license": "CC0-1.0"
"node_modules/@sugoidogo/js-util": {
"version": "0.3.0",
"resolved": "https://gitea.sugoidogo.com/api/packages/sugoidogo/npm/%40sugoidogo%2Fjs-util/-/0.3.0/js-util-0.3.0.tgz",
"integrity": "sha512-ld9PHyjS03zSglQJzvS8CNXcDONDyvRygEEpkAggNywVbf5eGfoj7nimdNSLo2arA7Xs1s4/lHC2F1aWjRe+WQ=="
},
"node_modules/@types/hast": {
"version": "3.0.4",
@@ -1373,6 +1363,13 @@
"@types/unist": "*"
}
},
"node_modules/@types/md5": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.3.6.tgz",
"integrity": "sha512-WD69gNXtRBnpknfZcb4TRQ0XJQbUPZcai/Qdhmka3sxUR3Et8NrXoeAoknG/LghYHTf4ve795rInVYHBTQdNVA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
@@ -1427,6 +1424,23 @@
"dev": true,
"license": "Python-2.0"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1485,51 +1499,6 @@
"@oven/bun-windows-x64-baseline": "1.3.3"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/commander": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
@@ -1545,20 +1514,6 @@
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/deno": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/deno/-/deno-2.5.6.tgz",
@@ -1600,6 +1555,20 @@
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -1613,110 +1582,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/error-stack-parser-es": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
"integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/esbuild": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz",
"integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.0",
"@esbuild/android-arm": "0.27.0",
"@esbuild/android-arm64": "0.27.0",
"@esbuild/android-x64": "0.27.0",
"@esbuild/darwin-arm64": "0.27.0",
"@esbuild/darwin-x64": "0.27.0",
"@esbuild/freebsd-arm64": "0.27.0",
"@esbuild/freebsd-x64": "0.27.0",
"@esbuild/linux-arm": "0.27.0",
"@esbuild/linux-arm64": "0.27.0",
"@esbuild/linux-ia32": "0.27.0",
"@esbuild/linux-loong64": "0.27.0",
"@esbuild/linux-mips64el": "0.27.0",
"@esbuild/linux-ppc64": "0.27.0",
"@esbuild/linux-riscv64": "0.27.0",
"@esbuild/linux-s390x": "0.27.0",
"@esbuild/linux-x64": "0.27.0",
"@esbuild/netbsd-arm64": "0.27.0",
"@esbuild/netbsd-x64": "0.27.0",
"@esbuild/openbsd-arm64": "0.27.0",
"@esbuild/openbsd-x64": "0.27.0",
"@esbuild/openharmony-arm64": "0.27.0",
"@esbuild/sunos-x64": "0.27.0",
"@esbuild/win32-arm64": "0.27.0",
"@esbuild/win32-ia32": "0.27.0",
"@esbuild/win32-x64": "0.27.0"
}
},
"node_modules/exit-hook": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
"integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"dev": true,
"license": "MIT"
},
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
@@ -1752,6 +1617,26 @@
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"license": "BSD-3-Clause",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
@@ -1759,46 +1644,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"dev": true,
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/miniflare": {
"version": "4.20251217.0",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20251217.0.tgz",
"integrity": "sha512-8xsTQbPS6YV+ABZl9qiJIbsum6hbpbhqiyKpOVdzZrhK+1N8EFpT8R6aBZff7kezGmxYZSntjgjqTwJmj3JLgA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "0.8.1",
"acorn": "8.14.0",
"acorn-walk": "8.3.2",
"exit-hook": "2.2.1",
"glob-to-regexp": "0.4.1",
"sharp": "^0.33.5",
"stoppable": "1.1.0",
"undici": "7.14.0",
"workerd": "1.20251217.0",
"ws": "8.18.0",
"youch": "4.1.0-beta.10",
"zod": "3.22.3"
},
"bin": {
"miniflare": "bootstrap.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -1821,20 +1666,6 @@
"integrity": "sha512-r9dVsfnmykIYovQH9oP2JeZu5/DYa9rZpSQw2FWR/VDoI0oxm/u21NF46bFH5h+x2Xv4q5+jHzsX95N6m0UO3Q==",
"license": "MIT"
},
"node_modules/path-to-regexp": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
"integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
"dev": true,
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
@@ -2002,16 +1833,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/unenv": {
"version": "2.0.0-rc.24",
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
"integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"pathe": "^2.0.3"
}
},
"node_modules/web-worker": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz",
+5
View File
@@ -29,13 +29,18 @@
"docs": "typedoc src/lib/packwiz.ts --tsconfig src/lib/tsconfig.json"
},
"dependencies": {
"@sugoidogo/js-util": "^0.3.0",
"commander": "^14.0.2",
"confbox": "^0.2.2",
"curseforge-v2": "^1.5.0",
"dotenv": "^17.2.3",
"md5": "^2.3.0",
"murmur2": "^1.0.2",
"native-file-system-adapter": "file:../native-file-system-adapter/native-file-system-adapter-3.0.1.tgz",
"workerless": "^0.1.0"
},
"devDependencies": {
"@types/md5": "^2.3.6",
"@types/node": "^24.10.1",
"@types/web": "^0.0.294",
"bun": "^1.3.3",
+125 -97
View File
@@ -305,130 +305,125 @@ export interface Mod {
}
}
}
/** Return type of all exported functions, describing how to apply the function result */
export interface Result {
/** files to be deleted */
delete?: Path[]
/** files to be written with TOML data */
write?: { [key: Path]: Mod | Index | Pack }
}
import WorkerlessPool from 'workerless'
import { parseTOML } from 'confbox'
import { parseTOML, stringifyTOML } from 'confbox'
import { CFV2Client } from 'curseforge-v2'
import { forAsync } from '@sugoidogo/js-util'
// copied from https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Non-cryptographic_uses_of_subtle_crypto#hashing_a_file
async function sha1(arrayBuffer: BufferSource) {
const hashAsArrayBuffer = await crypto.subtle.digest("SHA-1", arrayBuffer);
const uint8ViewOfHash = new Uint8Array(hashAsArrayBuffer);
// @ts-ignore
if (uint8ViewOfHash.toHex) {
export async function getHash(data: Uint8Array<ArrayBuffer>, format:HashFormat): Promise<Hash> {
if (format.startsWith('sha')) { // copied from https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Non-cryptographic_uses_of_subtle_crypto#hashing_a_file
const hashAsArrayBuffer = await crypto.subtle.digest("SHA-" + format.substring(3), data);
const uint8ViewOfHash = new Uint8Array(hashAsArrayBuffer);
// @ts-ignore
return uint8ViewOfHash.toHex() as string;
if (uint8ViewOfHash.toHex) {
// @ts-ignore
return uint8ViewOfHash.toHex() as string;
}
const hashAsString = Array.from(uint8ViewOfHash)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return hashAsString;
}
const hashAsString = Array.from(uint8ViewOfHash)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return hashAsString;
}
async function fetch_toml(url: string, options?: RequestInit) {
const response = await fetch_ok(url, options)
const text = await response.text()
return parseTOML(text) as any
}
async function fetch_json(url: string, options?: RequestInit) {
const response = await fetch_ok(url, options)
const json = await response.json()
return json as any
}
async function fetch_bytes(url: string, options?: RequestInit) {
const response = await fetch_ok(url, options)
const bytes = await response.bytes()
return bytes
}
async function fetch_ok(url: string, options?: RequestInit) {
try {
new URL(url)
} catch {
// @ts-expect-error
const fs = await import('node:fs/promises')
const body = await fs.readFile(url)
return new Response(body)
if (format === "murmur2") {
const { default: murmurHash } = await import('murmur2')
return murmurHash(data, 1, true).toString()
}
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`${url}: ${response.statusText}`, { 'cause': response })
if (format === "md5") {
const md5 = await import('md5')
return md5(data)
}
return response
throw new Error('unknown hash format: '+format)
}
function dirname(path: string) {
if (!path.startsWith('http') && !path.startsWith('./')) path = './' + path
function splitPath(path: string) {
path=path.replaceAll('\\','/')
const path_segments = path.split('/')
path_segments.pop()
path = path_segments.join('/')
return path
const basename = path_segments.pop()
const dirname = path_segments.join('/')
return {dirname,basename}
}
async function getDirectoryHandle(dir: FileSystemDirectoryHandle, path: string) {
if (!path) {
return dir
}
path = path.replaceAll('\\', '/')
const path_segments = path.split('/')
for (const segment of path_segments) {
dir = await dir.getDirectoryHandle(segment)
}
return dir
}
async function getFileHandle(dir: FileSystemDirectoryHandle, path: string, options?:FileSystemGetFileOptions) {
const { dirname, basename } = splitPath(path)
dir = await getDirectoryHandle(dir, dirname)
return dir.getFileHandle(basename,options)
}
async function removeEntry(dir: FileSystemDirectoryHandle, path: string) {
const { dirname, basename } = splitPath(path)
dir = await getDirectoryHandle(dir, dirname)
return dir.removeEntry(basename)
}
/**
* detect files availible on curseforge
* @param pack_url url or path to the pack.toml file
* @param cfApiKey curseforge api key
* @param size_min minimum size for a file to be checked
*/
export async function cfDetect(pack_url: string, cfApiKey: string, size_min = 4096): Promise<Result> {
export async function cfDetect(pack_dir:FileSystemDirectoryHandle, cfApiKey: string, pack_file_name='pack.toml', size_min = 4096): Promise<void> {
console.log('loading pack')
const pack = await fetch_toml(pack_url) as Pack
const pack_dir = dirname(pack_url)
const index_url = `${pack_dir}/${pack.index.file}`
const pack_file_handle = await getFileHandle(pack_dir,pack_file_name)
const pack:Pack = await pack_file_handle.getFile()
.then(file => file.text())
.then(text => parseTOML(text))
console.log('loading index')
const index = await fetch_toml(index_url) as Index
const index_dir = dirname(index_url)
const { dirname: index_dir_name, basename: index_file_name } = splitPath(pack.index.file)
let index_dir: FileSystemDirectoryHandle;
if (!index_dir_name) index_dir = pack_dir
else index_dir = await getDirectoryHandle(pack_dir,index_dir_name)
const index_file_handle = await getFileHandle(index_dir,index_file_name)
const index:Index = await index_file_handle.getFile()
.then(file => file.text())
.then(text => parseTOML(text))
if (!index.files) {
console.warn(`${index_url} has no files indexed`)
return {}
console.warn(`modpack has no files indexed`)
return
}
const file_hash_map = new Map<number, Path>()
console.log('hashing files')
const workerlessPool = new WorkerlessPool()
await Promise.all(index.files.map(async file => {
if (file.metafile) return
const file_url = `${index_dir}/${file.file}`
const file_data = await fetch_bytes(file_url)
if (file_data.length < size_min) return
const hash = await workerlessPool.run(async function (buffer: Uint8Array) {
const murmurHash = await import('murmur2').then(module => module.default)
return murmurHash(buffer, 1, true)
}, file_data)
file_hash_map.set(hash, file_url)
}))
await forAsync(index.files, async function (entry) {
if (entry.metafile) return
const file = await getFileHandle(index_dir, entry.file)
.then(handle => handle.getFile())
if (file.size < size_min) return
const file_data = await file.arrayBuffer().then(buffer => new Uint8Array(buffer))
const hash = await workerlessPool.run(getHash, file_data, "murmur2").then(hash => Number(hash))
file_hash_map.set(hash, entry.file)
})
workerlessPool.terminate()
console.log('requesting matches from curseforge')
const response = await fetch_json('https://api.curseforge.com/v1/fingerprints', {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
'x-api-key': cfApiKey
},
body: JSON.stringify({ fingerprints: Array.from(file_hash_map.keys()) })
})
const result: Result = { delete: [], write: {} }
for (const match of response.data.exactMatches) {
let file_url = file_hash_map.get(match.file.fileFingerprint)
if (!file_url) {
const response = await new CFV2Client({ apiKey: cfApiKey }).getFingerprintMatches({ 'fingerprints': Array.from(file_hash_map.keys()) })
const textEncoder = new TextEncoder()
let resultCount=0
await forAsync(Object.values(response.data.data.exactMatches),async function(match){
let file_path = file_hash_map.get(match.file.fileFingerprint)
if (!file_path) {
for (const module of match.file.modules) {
file_url = file_hash_map.get(module.fingerprint)
file_path = file_hash_map.get(module.fingerprint)
}
if (file_url) {
console.debug("skipping module match, this is probably a false positive", file_url, match.file)
if (file_path) {
console.debug("skipping module match, this is probably a false positive", file_path, match.file)
} else {
console.debug("curseforge returned a result we didn't ask for, skipping", match.file)
}
continue
return
}
const { dirname: file_dir_name, basename: file_name } = splitPath(file_path)
for (const hash of match.file.hashes) {
if (hash.algo === 1) {
const mod: Mod = {
@@ -446,14 +441,47 @@ export async function cfDetect(pack_url: string, cfApiKey: string, size_min = 40
}
}
}
result.delete.push(file_url)
result.write[index_dir+'/'+match.file.displayName.toLowerCase().replaceAll(' ', '-')] = mod
const new_file_path = file_dir_name + '/' + encodeURI(match.file.displayName.toLowerCase().replaceAll(' ', '-') + '.pw.toml')
const new_file_data = textEncoder.encode(stringifyTOML(mod))
const new_file_hash = await getHash(new_file_data, index['hash-format'])
await getFileHandle(index_dir,new_file_path,{'create':true})
.then(handle => handle.createWritable())
.then(stream => {
stream.write(new_file_data)
stream.close()
index.files.push({
"file": new_file_path,
"hash": new_file_hash.toString(),
"metafile": true
})
})
await removeEntry(index_dir,file_path)
.then(() => {
for (let i = 0; i < index.files.length; i++){
if (index.files[i].file != file_path) continue
index.files.splice(i, 1)
return
}
throw new Error("can't find removed file in index")
})
resultCount++
break
}
}
}
console.log(`found ${result.delete.length} matching files`)
return result
})
const index_file_data=textEncoder.encode(stringifyTOML(index))
const index_file_hash = await getHash(index_file_data, pack.index['hash-format'])
await index_file_handle.createWritable()
.then(async stream => {
await stream.write(index_file_data)
await stream.close()
pack.index.hash = index_file_hash.toString()
return pack_file_handle.createWritable()
}).then(async stream => {
await stream.write(stringifyTOML(pack))
await stream.close()
})
console.log(`found ${resultCount} matching files`)
}
/**
* detect files availible on modrinth