import path from "path"; import { fileURLToPath, URL } from "url"; import fs from "fs/promises"; import axios, { AxiosError } from "axios"; import { createWriteStream } from "fs"; import ivm from "isolated-vm"; import { CommonImages, Config, HttpRespondError, PartsId2Index } from "./types.js"; export const dirname = path.dirname(fileURLToPath(import.meta.url)); export const fsExists = async (path: string) => (await fs.stat(path).catch((e) => false)) !== false; export async function downloadToPath(url: string, path: string) { const stream = createWriteStream(path); const response = await axios.get(url, { responseType: "stream" }); response.data.pipe(stream); await new Promise(r => stream.once('finish', () => { stream.close(); r(); })); } /** * Downloads the maker's HTML file and extracts the script from it, * unless it is already cached. */ export async function fetchScript(makerId: string) { const cacheDir = path.join(dirname, "..", "cache", "picrew", makerId); const htmlPath = path.join(cacheDir, "index.html"); const scriptPath = path.join(cacheDir, "script.js"); await fs.mkdir(cacheDir, { recursive: true }) .catch((e) => { if (e?.code != "EEXIST") throw e; }); // Ignore "already exists" error const htmlExists = await fsExists(htmlPath); const scriptExists = await fsExists(scriptPath); if (!htmlExists) { try { await downloadToPath(`https://picrew.me/en/image_maker/${encodeURIComponent(makerId)}`, htmlPath); } catch(e) { await fs.unlink(htmlPath).catch(() => {}); if (e instanceof AxiosError && e.status == 404) { throw new HttpRespondError(`There is no Picrew maker with ID ${makerId}.`, 400); } else throw e; } } if (!scriptExists) { const text = await fs.readFile(htmlPath, { encoding: "utf8" }); const START_MATCHER = ""; const startIndex = text.indexOf(START_MATCHER), endIndex = text.indexOf(END_MATCHER); const script = text.substring(startIndex, endIndex + END_MATCHER.length) .replace(/^\/, "") .replace(/\<\/script\>$/, "") .replace("window.__NUXT__=", ""); await fs.writeFile(scriptPath, script); } } export async function extractDataFromScript(makerId: string) { const code = await fs.readFile(path.join(dirname, "..", "cache", "picrew", makerId, "script.js"), { encoding: "utf8" }); const isolate = new ivm.Isolate(); const context = await isolate.createContext(); const script = await isolate.compileScript(code); const res = await script.run(context, { copy: true }); return { config: res.state.config as Config, commonImages: res.state.commonImages as CommonImages, cdnRoot: res.state.picrewData.cdnRoot, partsId2Index: res.state.partsId2Index as PartsId2Index, }; } /** * Download an asset if it's not cached already, then * return its file path. */ export async function getAsset(url: string) { // https://cdn.picrew.me/app/image_maker/{makerId}/{pId}/{random_string}.png const [ makerId, cId, filename ] = new URL(url).pathname.split("/").slice(-3); //console.log(`Starting download: ${makerId}/${cId}/${filename}`); const dir = path.join(dirname, "..", "cache", "assets", makerId, cId); const filePath = path.join(dir, filename); await fs.mkdir(dir, { recursive: true }) .catch((e) => { if (e?.code != "EEXIST") throw e; }); // Ignore "already exists" error if (!await fsExists(filePath)) { await downloadToPath(url, filePath); } //console.log(`Finished download: ${makerId}/${cId}/${filename}`); return filePath; } export async function isValidSha256(hash: string) { return /^[0-9A-f]{64}$/.test(hash); }