meow
This commit is contained in:
parent
6a0152611d
commit
33df529b54
122
src/index.ts
122
src/index.ts
|
@ -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
30
src/types.ts
Normal 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
91
src/util.ts
Normal 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);
|
||||||
|
}
|
Loading…
Reference in a new issue