Files
WebAI2API/patches/camoufox-js@0.8.3.pkgman.patched.js

352 lines
12 KiB
JavaScript

import { execSync } from "node:child_process";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { setTimeout } from "node:timers/promises";
import AdmZip from "adm-zip";
import ProgressBar from "progress";
import { CONSTRAINTS } from "./__version__.js";
import { CamoufoxNotInstalled, FileNotFoundError, MissingRelease, UnsupportedArchitecture, UnsupportedOS, UnsupportedVersion, } from "./exceptions.js";
const ARCH_MAP = {
x64: "x86_64",
ia32: "i686",
arm64: "arm64",
arm: "arm64",
};
const OS_MAP = {
darwin: "mac",
linux: "lin",
win32: "win",
};
if (!(process.platform in OS_MAP)) {
throw new UnsupportedOS(`OS ${process.platform} is not supported`);
}
export const OS_NAME = OS_MAP[process.platform];
// [PATCH] Portable Mode: 优先使用项目目录下的 camoufox 文件夹
const localInstallDir = path.join(process.cwd(), "camoufox");
export const INSTALL_DIR = fs.existsSync(localInstallDir) ? localInstallDir : userCacheDir("camoufox");
//export const INSTALL_DIR = userCacheDir("camoufox");
export const LOCAL_DATA = path.join(import.meta.dirname, "data-files");
export const OS_ARCH_MATRIX = {
win: ["x86_64", "i686"],
mac: ["x86_64", "arm64"],
lin: ["x86_64", "arm64", "i686"],
};
const LAUNCH_FILE = {
win: "camoufox.exe",
mac: "../MacOS/camoufox",
lin: "camoufox-bin",
};
class Version {
release;
version;
sorted_rel;
constructor(release, version) {
this.release = release;
this.version = version;
this.sorted_rel = this.buildSortedRel();
}
buildSortedRel() {
const parts = this.release
.split(".")
.map((x) => Number.isNaN(Number(x)) ? x.charCodeAt(0) - 1024 : Number(x));
while (parts.length < 5) {
parts.push(0);
}
return parts;
}
get fullString() {
return `${this.version}-${this.release}`;
}
equals(other) {
return this.sorted_rel.join(".") === other.sorted_rel.join(".");
}
lessThan(other) {
for (let i = 0; i < this.sorted_rel.length; i++) {
if (this.sorted_rel[i] < other.sorted_rel[i])
return true;
if (this.sorted_rel[i] > other.sorted_rel[i])
return false;
}
return false;
}
isSupported() {
return VERSION_MIN.lessThan(this) && this.lessThan(VERSION_MAX);
}
static fromPath(filePath = INSTALL_DIR) {
const versionPath = path.join(filePath.toString(), "version.json");
if (!fs.existsSync(versionPath)) {
throw new FileNotFoundError(`Version information not found at ${versionPath}. Please run \`camoufox fetch\` to install.`);
}
const versionData = JSON.parse(fs.readFileSync(versionPath, "utf-8"));
return new Version(versionData.release, versionData.version);
}
static isSupportedPath(path) {
return Version.fromPath(path).isSupported();
}
static buildMinMax() {
return [
new Version(CONSTRAINTS.MIN_VERSION),
new Version(CONSTRAINTS.MAX_VERSION),
];
}
}
const [VERSION_MIN, VERSION_MAX] = Version.buildMinMax();
export class GitHubDownloader {
githubRepo;
apiUrl;
constructor(githubRepo) {
this.githubRepo = githubRepo;
this.apiUrl = `https://api.github.com/repos/${githubRepo}/releases`;
}
checkAsset(asset) {
return asset.browser_download_url;
}
missingAssetError() {
throw new MissingRelease(`Could not find a release asset in ${this.githubRepo}.`);
}
async getAsset({ retries } = { retries: 5 }) {
let attempts = 0;
let response;
while (attempts < retries) {
try {
response = await fetch(this.apiUrl);
if (response.ok)
break;
}
catch (e) {
console.error(e, `retrying (${attempts + 1}/${retries})...`);
await setTimeout(5e3);
}
attempts++;
}
if (!response || !response.ok) {
throw new Error(`Failed to fetch releases from ${this.apiUrl} after ${retries} attempts`);
}
const releases = await response.json();
for (const release of releases) {
for (const asset of release.assets) {
const data = this.checkAsset(asset);
if (data) {
return data;
}
}
}
this.missingAssetError();
}
}
export class CamoufoxFetcher extends GitHubDownloader {
arch;
_version_obj;
pattern;
_url;
constructor() {
super("daijro/camoufox");
this.arch = CamoufoxFetcher.getPlatformArch();
this.pattern = new RegExp(`camoufox-(.+)-(.+)-${OS_NAME}\\.${this.arch}\\.zip`);
}
async init() {
await this.fetchLatest();
}
checkAsset(asset) {
const match = asset.name.match(this.pattern);
if (!match)
return null;
const version = new Version(match[2], match[1]);
if (!version.isSupported())
return null;
return [version, asset.browser_download_url];
}
missingAssetError() {
throw new MissingRelease(`No matching release found for ${OS_NAME} ${this.arch} in the supported range: (${CONSTRAINTS.asRange()}). Please update the library.`);
}
static getPlatformArch() {
const platArch = os.arch().toLowerCase();
if (!(platArch in ARCH_MAP)) {
throw new UnsupportedArchitecture(`Architecture ${platArch} is not supported`);
}
const arch = ARCH_MAP[platArch];
if (!OS_ARCH_MATRIX[OS_NAME].includes(arch)) {
throw new UnsupportedArchitecture(`Architecture ${arch} is not supported for ${OS_NAME}`);
}
return arch;
}
async fetchLatest() {
if (this._version_obj)
return;
const releaseData = await this.getAsset();
this._version_obj = releaseData[0];
this._url = releaseData[1];
}
static async downloadFile(url) {
const response = await fetch(url);
return Buffer.from(await response.arrayBuffer());
}
async extractZip(zipFile) {
const zip = new AdmZip(zipFile);
zip.extractAllTo(INSTALL_DIR.toString(), true);
}
static cleanup() {
if (fs.existsSync(INSTALL_DIR)) {
fs.rmSync(INSTALL_DIR, { recursive: true });
return true;
}
return false;
}
setVersion() {
fs.writeFileSync(path.join(INSTALL_DIR.toString(), "version.json"), JSON.stringify({ version: this.version, release: this.release }));
}
async install() {
await this.init();
await CamoufoxFetcher.cleanup();
try {
fs.mkdirSync(INSTALL_DIR, { recursive: true });
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "camoufox-"));
const tempFilePath = path.join(tempDir, "camoufox.zip");
const tempFileStream = fs.createWriteStream(tempFilePath);
await webdl(this.url, "Downloading Camoufox...", true, tempFileStream);
await new Promise((r) => tempFileStream.close(r));
await this.extractZip(tempFilePath);
this.setVersion();
if (OS_NAME !== "win") {
execSync(`chmod -R 755 ${INSTALL_DIR}`);
}
console.log("Camoufox successfully installed.");
}
catch (e) {
console.error(`Error installing Camoufox: ${e}`);
await CamoufoxFetcher.cleanup();
throw e;
}
}
get url() {
if (!this._url) {
throw new Error("Url is not available. Make sure to run fetchLatest first.");
}
return this._url;
}
get version() {
if (!this._version_obj || !this._version_obj.version) {
throw new Error("Version is not available. Make sure to run fetchLatest first.");
}
return this._version_obj.version;
}
get release() {
if (!this._version_obj) {
throw new Error("Release information is not available. Make sure to run the installation first.");
}
return this._version_obj.release;
}
get verstr() {
if (!this._version_obj) {
throw new Error("Version is not available. Make sure to run the installation first.");
}
return this._version_obj.fullString;
}
}
function userCacheDir(appName) {
if (OS_NAME === "win") {
return path.join(os.homedir(), "AppData", "Local", appName, appName, "Cache");
}
else if (OS_NAME === "mac") {
return path.join(os.homedir(), "Library", "Caches", appName);
}
else {
return path.join(os.homedir(), ".cache", appName);
}
}
export function installedVerStr() {
return Version.fromPath().fullString;
}
export function camoufoxPath(downloadIfMissing = true) {
// Ensure the directory exists and is not empty
if (!fs.existsSync(INSTALL_DIR) || fs.readdirSync(INSTALL_DIR).length === 0) {
if (!downloadIfMissing) {
throw new Error(`Camoufox executable not found at ${INSTALL_DIR}`);
}
}
else if (fs.existsSync(INSTALL_DIR) &&
Version.isSupportedPath(INSTALL_DIR)) {
return INSTALL_DIR;
}
else {
if (!downloadIfMissing) {
throw new UnsupportedVersion("Camoufox executable is outdated.");
}
}
// Install and recheck
const fetcher = new CamoufoxFetcher();
fetcher.install().then(() => camoufoxPath());
return INSTALL_DIR;
}
export function getPath(file) {
if (OS_NAME === "mac") {
return path.resolve(camoufoxPath().toString(), "Camoufox.app", "Contents", "Resources", file);
}
return path.join(camoufoxPath().toString(), file);
}
export function launchPath() {
const launchPath = getPath(LAUNCH_FILE[OS_NAME]);
if (!fs.existsSync(launchPath)) {
throw new CamoufoxNotInstalled(`Camoufox is not installed at ${camoufoxPath()}. Please run \`camoufox fetch\` to install.`);
}
return launchPath;
}
export async function webdl(url, desc = "", bar = true, buffer = null, { retries } = { retries: 5 }) {
let attempts = 0;
let response;
while (attempts < retries) {
try {
response = await fetch(url);
if (response.ok)
break;
}
catch (e) {
console.error(e, `retrying (${attempts + 1}/${retries})...`);
await setTimeout(5e3);
}
attempts++;
}
if (!response || !response.ok) {
throw new Error(`Failed to download from ${url} after ${retries} attempts`);
}
const totalSize = parseInt(response.headers.get("content-length") || "0", 10);
const progressBar = bar
? new ProgressBar(`${desc} [:bar] :percent :etas`, {
total: totalSize,
width: 40,
})
: null;
const chunks = [];
for await (const chunk of response.body) {
if (buffer) {
buffer.write(chunk);
}
else {
chunks.push(chunk);
}
if (progressBar) {
progressBar.tick(chunk.length, "X");
}
}
const fileBuffer = Buffer.concat(chunks);
return fileBuffer;
}
export async function unzip(zipFile, extractPath, desc, bar = true) {
const zip = new AdmZip(zipFile);
const zipEntries = zip.getEntries();
if (bar) {
console.log(desc || "Extracting files...");
}
for (const entry of zipEntries) {
if (bar) {
console.log(`Extracting ${entry.entryName}`);
}
zip.extractEntryTo(entry, extractPath, false, true);
}
if (bar) {
console.log("Extraction complete.");
}
}