This commit is contained in:
Lea 2024-06-16 17:31:47 +02:00
parent 6a0152611d
commit 33df529b54
Signed by: Lea
GPG key ID: 1BAFFE8347019C42
3 changed files with 122 additions and 121 deletions

View file

@ -1,124 +1,4 @@
import path from "path"; import { fetchScript, extractDataFromScript } from "./util";
import { fileURLToPath, URL } from "url";
import fs from "fs/promises";
import axios from "axios";
import { createWriteStream } from "fs";
import ivm from "isolated-vm";
const dirname = path.dirname(fileURLToPath(import.meta.url));
type Item = {
itmId: number,
thumbUrl: string,
typeId: 1,
}
type Plist = {
pId: number,
cpId: string, // Used to index into cpList
lyrs: number[],
defItmId: number,
items: Item[],
}[];
type Cplist = {
[key: string]: {
cId: number, // Colour ID
cd: string, // Colour hex code
}[];
}
type CommonImages = {
[key: string]: {
[key: string]: {
[key: string]: {
url: string;
}
}
}
}
const fsExists = async (path: string) => (await fs.stat(path).catch((e) => false)) !== false;
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<void>(r => stream.once('finish', () => { stream.close(); r(); }));
}
/**
* Downloads the maker's HTML file and extracts the script from it,
* unless it is already cached.
*/
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) {
await downloadToPath(`https://picrew.me/en/image_maker/${encodeURIComponent(makerId)}`, htmlPath);
}
if (!scriptExists) {
const text = await fs.readFile(htmlPath, { encoding: "utf8" });
const START_MATCHER = "<script>window.__NUXT__=",
END_MATCHER = "));</script>";
const startIndex = text.indexOf(START_MATCHER),
endIndex = text.indexOf(END_MATCHER);
const script = text.substring(startIndex, endIndex + END_MATCHER.length)
.replace(/^\<script\>/, "")
.replace(/\<\/script\>$/, "")
.replace("window.__NUXT__=", "");
await fs.writeFile(scriptPath, script);
}
}
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 });
const { config, commonImages, picrewData } = res.state;
return {
config: res.state.config as { pList: Plist, cpList: Cplist },
commonImages: res.state.commonImages as CommonImages,
cdnRoot: picrewData.cdnRoot,
};
}
/**
* Download an asset if it's not cached already, then
* return it as buffer.
*/
async function getAssetBuffer(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);
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);
}
return await fs.readFile(filePath);
}
await fetchScript("1904634"); await fetchScript("1904634");
const { config, commonImages, cdnRoot } = await extractDataFromScript("1904634"); const { config, commonImages, cdnRoot } = await extractDataFromScript("1904634");

30
src/types.ts Normal file
View file

@ -0,0 +1,30 @@
export type Item = {
itmId: number,
thumbUrl: string,
typeId: 1,
}
export type Plist = {
pId: number,
cpId: string, // Used to index into cpList
lyrs: number[],
defItmId: number,
items: Item[],
}[];
export type Cplist = {
[key: string]: {
cId: number, // Colour ID
cd: string, // Colour hex code
}[];
}
export type CommonImages = {
[key: string]: {
[key: string]: {
[key: string]: {
url: string;
}
}
}
}

91
src/util.ts Normal file
View file

@ -0,0 +1,91 @@
import path from "path";
import { fileURLToPath, URL } from "url";
import fs from "fs/promises";
import axios from "axios";
import { createWriteStream } from "fs";
import ivm from "isolated-vm";
import { CommonImages, Plist, Cplist } from "./types";
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<void>(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) {
await downloadToPath(`https://picrew.me/en/image_maker/${encodeURIComponent(makerId)}`, htmlPath);
}
if (!scriptExists) {
const text = await fs.readFile(htmlPath, { encoding: "utf8" });
const START_MATCHER = "<script>window.__NUXT__=",
END_MATCHER = "));</script>";
const startIndex = text.indexOf(START_MATCHER),
endIndex = text.indexOf(END_MATCHER);
const script = text.substring(startIndex, endIndex + END_MATCHER.length)
.replace(/^\<script\>/, "")
.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 });
const { config, commonImages, picrewData } = res.state;
return {
config: res.state.config as { pList: Plist, cpList: Cplist },
commonImages: res.state.commonImages as CommonImages,
cdnRoot: picrewData.cdnRoot,
};
}
/**
* Download an asset if it's not cached already, then
* return it as buffer.
*/
export async function getAssetBuffer(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);
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);
}
return await fs.readFile(filePath);
}