Update MPVPage

This commit is contained in:
2023-05-20 11:50:20 +08:00
parent f13387163c
commit 228ff6dcc1
5 changed files with 255 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
import { Config } from "./config.ts";
import { load_gallery_metadata } from "./page/GalleryMetadata.ts";
import { load_gallery_page } from "./page/GalleryPage.ts";
import { load_mpv_page } from "./page/MPVPage.ts";
import { load_single_page } from "./page/SinglePage.ts";
export type GID = [number, string];
@@ -27,6 +28,11 @@ export class Client {
) {
return this.request(url, "POST", options);
}
is_eh(hostname: string) {
return hostname == "e-hentai.org" || hostname == "exhentai.org" ||
hostname.endsWith(".e-hentai.org") ||
hostname.endsWith(".exhentai.org");
}
request(
url: string | Request | URL,
method: string | undefined = undefined,
@@ -37,23 +43,16 @@ export class Client {
if (this.cookies) {
if (typeof url === "string") {
const u = new URL(url);
if (
u.hostname == "e-hentai.org" || u.hostname == "exhentai.org"
) {
if (this.is_eh(u.hostname)) {
headers.set("Cookie", this.cookies);
}
} else if (url instanceof Request) {
const u = new URL(url.url);
if (
u.hostname == "e-hentai.org" || u.hostname == "exhentai.org"
) {
if (this.is_eh(u.hostname)) {
headers.set("Cookie", this.cookies);
}
} else if (url instanceof URL) {
if (
url.hostname == "e-hentai.org" ||
url.hostname == "exhentai.org"
) {
if (this.is_eh(url.hostname)) {
headers.set("Cookie", this.cookies);
}
}
@@ -64,10 +63,17 @@ export class Client {
}
return fetch(url);
} else {
return fetch(
url,
Object.assign({ headers, method: method || "GET" }, options),
);
const d = Object.assign({ method: method || "GET" }, options);
if (d.headers) {
const nheaders = new Headers(d.headers);
for (const v of headers) {
nheaders.set(v[0], v[1]);
}
d.headers = nheaders;
} else {
d.headers = headers;
}
return fetch(url, d);
}
}
/**
@@ -122,4 +128,20 @@ export class Client {
}
return load_gallery_page(await re.text(), this);
}
/**
* Fetch a Multi-Page Viewer page
* @param gid Gallery ID
* @param token Token
* @returns
*/
async fetchMPVPage(gid: number, token: string) {
const url = `https://${this.host}/mpv/${gid}/${token}/`;
const re = await this.get(url);
if (re.status != 200) {
throw new Error(
`Fetch ${url} failed, status ${re.status} ${re.statusText}`,
);
}
return load_mpv_page(await re.text(), this);
}
}

2
db.ts
View File

@@ -1,4 +1,4 @@
const { DB } = await import("sqlite/mod.ts");
import { DB } from "sqlite/mod.ts";
import { SemVer } from "std/semver/mod.ts";
import { join } from "std/path/mod.ts";
import { SqliteError } from "sqlite/mod.ts";

186
page/MPVPage.ts Normal file
View File

@@ -0,0 +1,186 @@
import { DOMParser } from "deno_dom/deno-dom-wasm-noinit.ts";
import { Client } from "../client.ts";
import { initDOMParser } from "../utils.ts";
export type MPVRawImage = {
/**Image name*/
n: string;
/**Page token*/
k: string;
/**Thumbnail*/
t: string;
};
export type MPVDispatchData = {
/**width x height :: file size*/
d: string;
/**Image*/
i: string;
/**URL to load full image. full url is `${base_url}${lf}` */
lf: string;
/**Forum token url. full url is `${base_url}r/${ll}`*/
ll: string;
/**File search url. full url is `${base_url}${lo}` */
lo: string;
/**`ori` or `Download original ${width} x ${height} ${file_size} source`*/
o: string;
/**Reload token*/
s: string;
/**Width*/
xres: string;
/**Height*/
yres: string;
};
class MPVImage {
base;
/**Page number*/
index;
#mpv;
data: MPVDispatchData | undefined;
constructor(base: MPVRawImage, index: number, mpv: MPVPage) {
this.base = base;
this.index = index;
this.#mpv = mpv;
}
get name() {
return this.base.n;
}
get page_number() {
return this.index;
}
get page_token() {
return this.base.k;
}
get thumbnail() {
return this.base.t;
}
get xres() {
const xres = this.data?.xres;
if (!xres) return undefined;
return parseInt(xres);
}
get yres() {
const yres = this.data?.yres;
if (!yres) return undefined;
return parseInt(yres);
}
async load() {
if (this.data === undefined) {
this.data = await this.#mpv.image_dispatch(
this.index,
this.page_token,
);
}
}
}
class MPVPage {
dom;
doc;
client;
#meta_script: string | undefined;
#api_url: string | undefined;
#gid: number | undefined;
#mpvkey: string | undefined;
#pagecount: number | undefined;
#imagelist: MPVImage[] | undefined;
#base_url: string | undefined;
constructor(html: string, client: Client) {
const dom = (new DOMParser()).parseFromString(html, "text/html");
if (!dom) {
throw Error("Failed to parse HTML document.");
}
this.dom = dom;
const doc = this.dom.documentElement;
if (!doc) {
throw Error("HTML document don't have a document element.");
}
this.doc = doc;
this.client = client;
}
get api_url() {
if (this.#api_url === undefined) {
const api_url: string = eval(`${this.meta_script};api_url`);
this.#api_url = api_url;
return api_url;
} else return this.#api_url;
}
get base_url() {
if (this.#base_url === undefined) {
const base_url: string = eval(`${this.meta_script};base_url`);
this.#base_url = base_url;
return base_url;
} else return this.#base_url;
}
async image_dispatch(
page: number,
imgkey: string,
nl: string | undefined = undefined,
): Promise<MPVDispatchData> {
const param: Record<string, unknown> = {
method: "imagedispatch",
gid: this.gid,
page,
imgkey,
mpvkey: this.mpvkey,
};
if (nl) param.nl = nl;
const re = await this.client.post(this.api_url, {
body: JSON.stringify(param),
headers: { "content-type": "application/json" },
});
if (re.status != 200) {
throw new Error(
`Fetch ${this.api_url} failed, status ${re.status} ${re.statusText}`,
);
}
return re.json();
}
get gid() {
if (this.#gid === undefined) {
const gid: number = eval(`${this.meta_script};gid`);
this.#gid = gid;
return gid;
} else return this.#gid;
}
get imagelist() {
if (this.#imagelist === undefined) {
const t: MPVRawImage[] = eval(`${this.meta_script};imagelist`);
const imagelist = t.map((v, i) => new MPVImage(v, i + 1, this));
return imagelist;
} else return this.#imagelist;
}
get meta_script() {
if (this.#meta_script === undefined) {
const c = this.doc.getElementsByTagName("script");
for (const e of c) {
const t = e.innerHTML.trim();
if (t.startsWith("var ")) {
this.#meta_script = t;
return t;
}
}
throw Error("Failed to locate meta script.");
} else return this.#meta_script;
}
get mpvkey() {
if (this.#mpvkey === undefined) {
const mpvkey: string = eval(`${this.meta_script};mpvkey`);
this.#mpvkey = mpvkey;
return mpvkey;
} else return this.#mpvkey;
}
get pagecount() {
if (this.#pagecount === undefined) {
const pagecount: number = eval(`${this.meta_script};pagecount`);
this.#pagecount = pagecount;
return pagecount;
} else return this.#pagecount;
}
}
export async function load_mpv_page(html: string, client: Client) {
await initDOMParser();
return new MPVPage(html, client);
}

26
page/MPVPage_test.ts Normal file
View File

@@ -0,0 +1,26 @@
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
import { Client } from "../client.ts";
import { load_settings } from "../config.ts";
import { API_PERMISSION } from "../test_base.ts";
import { assert } from "https://deno.land/[email protected]/_util/asserts.ts";
Deno.test({
name: "GalleryPage_test",
permissions: API_PERMISSION,
}, async () => {
const cfg = await load_settings("./config.json");
const client = new Client(cfg);
const re = await client.fetchMPVPage(1473589, "b1f3c60a95");
assertEquals(re.gid, 1473589);
assertEquals(re.pagecount, 8);
assertEquals(re.imagelist.length, re.pagecount);
const p1 = re.imagelist[0];
assertEquals(p1.index, 1);
assertEquals(p1.name, "1.png");
assertEquals(p1.page_token, "7ffd92a751");
await p1.load();
assert(p1.data);
assertEquals(p1.xres, 2449);
assertEquals(p1.yres, 3427);
console.log(p1.data.i);
});

View File

@@ -2,7 +2,12 @@ import { exists } from "std/fs/exists.ts";
export const API_PERMISSION: Deno.PermissionOptions = {
read: ["./config.json"],
net: ["e-hentai.org", "exhentai.org"],
net: [
"e-hentai.org",
"exhentai.org",
"api.e-hentai.org",
"api.exhentai.org",
],
};
export async function remove_if_exists(f: string) {