mirror of
https://github.com/lifegpc/eh-downloader.git
synced 2026-07-08 01:31:00 +08:00
Add export zip task
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { Component, ContextType } from "preact";
|
||||
import Checkbox from "preact-material-components/Checkbox";
|
||||
import { StateUpdater } from "preact/hooks";
|
||||
import { BCtx } from "./BContext.tsx";
|
||||
|
||||
type Props = {
|
||||
@@ -8,7 +7,7 @@ type Props = {
|
||||
checked: boolean;
|
||||
name?: string;
|
||||
description?: string;
|
||||
set_value?: StateUpdater<boolean>;
|
||||
set_value?: (v: boolean) => void;
|
||||
};
|
||||
|
||||
export default class BCheckbox extends Component<Props, unknown> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, ComponentChildren, ContextType } from "preact";
|
||||
import TextField from "preact-material-components/TextField";
|
||||
import { Ref, StateUpdater, useRef } from "preact/hooks";
|
||||
import { Ref, useRef } from "preact/hooks";
|
||||
import { BCtx } from "./BContext.tsx";
|
||||
|
||||
interface TextType {
|
||||
@@ -33,6 +33,7 @@ type Props<T extends keyof TextType> = {
|
||||
max?: DataType[T];
|
||||
outlined?: boolean;
|
||||
id?: string;
|
||||
list?: string;
|
||||
};
|
||||
|
||||
export default class BTextField<T extends keyof TextType>
|
||||
@@ -134,6 +135,7 @@ export default class BTextField<T extends keyof TextType>
|
||||
min={this.props.min}
|
||||
max={this.props.max}
|
||||
outlined={this.props.outlined}
|
||||
list={this.props.list}
|
||||
/>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
||||
44
components/GidDataList.tsx
Normal file
44
components/GidDataList.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Component } from "preact";
|
||||
import { GMeta } from "../db.ts";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { GalleryListResult } from "../server/gallery.ts";
|
||||
|
||||
type Props = {
|
||||
/**@default {false}*/
|
||||
jpn_title?: boolean;
|
||||
id: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
data?: GMeta[];
|
||||
};
|
||||
|
||||
export default class GidDataList extends Component<Props, State> {
|
||||
render() {
|
||||
const fetchData = async () => {
|
||||
const re = await fetch(
|
||||
"/api/gallery/list?all=1&fields=gid,title,title_jpn",
|
||||
);
|
||||
const d: GalleryListResult = await re.json();
|
||||
if (d.ok) {
|
||||
this.setState({ data: d.data });
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchData().catch((e) => console.error(e));
|
||||
}, []);
|
||||
return (
|
||||
<datalist id={this.props.id}>
|
||||
{this.state.data
|
||||
? this.state.data.map((d) => {
|
||||
let title = d.title;
|
||||
if (this.props.jpn_title && d.title_jpn) {
|
||||
title = d.title_jpn;
|
||||
}
|
||||
return <option value={d.gid}>{title}</option>;
|
||||
})
|
||||
: null}
|
||||
</datalist>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,11 @@ import { StateUpdater, useRef, useState } from "preact/hooks";
|
||||
import { TaskType } from "../task.ts";
|
||||
import BTextField from "./BTextField.tsx";
|
||||
import { parseUrl, UrlType } from "../url.ts";
|
||||
import { generate_download_cfg } from "../server/cfg.ts";
|
||||
import {
|
||||
cfg,
|
||||
generate_download_cfg,
|
||||
generate_export_zip_cfg,
|
||||
} from "../server/cfg.ts";
|
||||
import BCheckbox from "./BCheckbox.tsx";
|
||||
import BContext from "./BContext.tsx";
|
||||
import Button from "preact-material-components/Button";
|
||||
@@ -17,6 +21,8 @@ import { sendTaskMessage } from "../islands/TaskManager.tsx";
|
||||
import Snackbar from "preact-material-components/Snackbar";
|
||||
import { GalleryResult } from "../server/gallery.ts";
|
||||
import { tw } from "twind";
|
||||
import { ExportZipConfig } from "../tasks/export_zip.ts";
|
||||
import GidDataList from "./GidDataList.tsx";
|
||||
|
||||
export type NewTaskProps = {
|
||||
show: boolean;
|
||||
@@ -46,6 +52,8 @@ export default class NewTask extends Component<NewTaskProps> {
|
||||
const [ezgid, set_ezgid1] = useState<number>();
|
||||
const [ginfo, set_ginfo] = useState<GalleryResult>();
|
||||
const [abort, set_abort] = useState<AbortController>();
|
||||
const [ezcfg, set_ezcfg1] = useState(generate_export_zip_cfg());
|
||||
const [overwrite_ezcfg, set_overwrite_ezcfg] = useState(false);
|
||||
if (task_type === TaskType.Download) {
|
||||
const set_url: StateUpdater<string> = (u) => {
|
||||
const n = typeof u === "string" ? u : u(url || "");
|
||||
@@ -70,10 +78,9 @@ export default class NewTask extends Component<NewTaskProps> {
|
||||
set_url1(`https://e-hentai.org/g/${dgid}/${n}/`);
|
||||
}
|
||||
};
|
||||
const set_overwrite_cfg: StateUpdater<boolean> = (u) => {
|
||||
const n = typeof u === "boolean" ? u : u(overwrite_cfg);
|
||||
const set_overwrite_cfg = (n: boolean) => {
|
||||
set_overwrite_cfg1(n);
|
||||
if (n === true) {
|
||||
if (n) {
|
||||
set_dcfg(generate_download_cfg());
|
||||
}
|
||||
};
|
||||
@@ -176,6 +183,14 @@ export default class NewTask extends Component<NewTaskProps> {
|
||||
};
|
||||
}
|
||||
} else if (task_type === TaskType.ExportZip) {
|
||||
const export_ad = overwrite_ezcfg
|
||||
? ezcfg.export_ad || false
|
||||
: false;
|
||||
const jpn_title = overwrite_ezcfg
|
||||
? ezcfg.jpn_title || false
|
||||
: cfg.value
|
||||
? cfg.value.export_zip_jpn_title
|
||||
: false;
|
||||
const fetch_ginfo = (gid: number) => {
|
||||
set_abort(new AbortController());
|
||||
fetch(`/api/gallery/${gid}`).then(async (res) => {
|
||||
@@ -201,15 +216,22 @@ export default class NewTask extends Component<NewTaskProps> {
|
||||
};
|
||||
let ginfo_div = null;
|
||||
if (ginfo?.ok) {
|
||||
let title = ginfo.data.meta.title;
|
||||
if (jpn_title && ginfo.data.meta.title_jpn) {
|
||||
title = ginfo.data.meta.title_jpn;
|
||||
}
|
||||
const count = export_ad
|
||||
? ginfo.data.pages.length
|
||||
: ginfo.data.pages.reduce((p, c) => c.is_ad ? p : p + 1, 0);
|
||||
ginfo_div = (
|
||||
<div>
|
||||
<div>
|
||||
{t("task.gallery_title")}
|
||||
{ginfo.data.meta.title}
|
||||
{title}
|
||||
</div>
|
||||
<div>
|
||||
{t("task.gallery_page")}
|
||||
{ginfo.data.pages.length}
|
||||
{count}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -220,17 +242,71 @@ export default class NewTask extends Component<NewTaskProps> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const set_ezcfg: StateUpdater<ExportZipConfig> = (v) => {
|
||||
set_ezcfg1(v);
|
||||
this.forceUpdate();
|
||||
};
|
||||
const set_overwrite_cfg = (v: boolean) => {
|
||||
set_overwrite_ezcfg(v);
|
||||
if (v) {
|
||||
set_ezcfg(Object.assign(ezcfg, generate_export_zip_cfg()));
|
||||
}
|
||||
};
|
||||
let cfg_div = null;
|
||||
if (overwrite_ezcfg) {
|
||||
cfg_div = (
|
||||
<div>
|
||||
<BContext set_value={set_ezcfg}>
|
||||
<BCheckbox
|
||||
id="ez-jpn_title"
|
||||
name="jpn_title"
|
||||
checked={ezcfg.jpn_title || false}
|
||||
description={t("task.ezcfg_jpn_title")}
|
||||
/>
|
||||
<BCheckbox
|
||||
id="ez-export_ad"
|
||||
name="export_ad"
|
||||
checked={ezcfg.export_ad || false}
|
||||
description={t("task.ezcfg_export_ad")}
|
||||
/>
|
||||
<BTextField
|
||||
name="output"
|
||||
value={ezcfg.output}
|
||||
description={t("task.ezcfg_output")}
|
||||
type="text"
|
||||
outlined={true}
|
||||
/>
|
||||
<BTextField
|
||||
name="max_length"
|
||||
value={ezcfg.max_length}
|
||||
description={t("task.ezcfg_max_length")}
|
||||
type="number"
|
||||
outlined={true}
|
||||
min={0}
|
||||
/>
|
||||
</BContext>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
config_div = (
|
||||
<div class="export_zip">
|
||||
<GidDataList id="gid-list" jpn_title={jpn_title} />
|
||||
<BTextField
|
||||
id="ezgid"
|
||||
value={ezgid}
|
||||
description={t("task.gallery_id")}
|
||||
type="number"
|
||||
outlined={true}
|
||||
set_value={set_ezgid}
|
||||
list="gid-list"
|
||||
/>
|
||||
{ginfo_div}
|
||||
<BCheckbox
|
||||
id="ez-cfg"
|
||||
checked={overwrite_ezcfg}
|
||||
description={t("task.overwrite_cfg")}
|
||||
set_value={set_overwrite_cfg}
|
||||
/>
|
||||
{cfg_div}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
14
db.ts
14
db.ts
@@ -659,6 +659,7 @@ export class EhDb {
|
||||
}
|
||||
convert_gmeta(m: GMetaRaw[]): GMeta[] {
|
||||
return m.map((m) => {
|
||||
if (m.expunged === undefined) return <GMeta> <unknown> m;
|
||||
const b = m.expunged !== 0;
|
||||
const t = <GMeta> <unknown> m;
|
||||
t.expunged = b;
|
||||
@@ -752,6 +753,19 @@ export class EhDb {
|
||||
[limit, offset],
|
||||
).map((n) => n[0]);
|
||||
}
|
||||
get_gmetas(offset = 0, limit = 20, fields = "*") {
|
||||
return this.convert_gmeta(
|
||||
this.db.queryEntries<GMetaRaw>(
|
||||
`SELECT ${fields} FROM gmeta LIMIT ? OFFSET ?;`,
|
||||
[limit, offset],
|
||||
),
|
||||
);
|
||||
}
|
||||
get_gmetas_all(fields = "*") {
|
||||
return this.convert_gmeta(
|
||||
this.db.queryEntries<GMetaRaw>(`SELECT ${fields} FROM gmeta;`),
|
||||
);
|
||||
}
|
||||
get_gmeta_by_gid(gid: number) {
|
||||
const s = this.convert_gmeta(
|
||||
this.db.queryEntries<GMetaRaw>(
|
||||
|
||||
18
fresh.gen.ts
18
fresh.gen.ts
@@ -14,10 +14,11 @@ import * as $8 from "./routes/api/filemeta.ts";
|
||||
import * as $9 from "./routes/api/filemeta/[token].ts";
|
||||
import * as $10 from "./routes/api/files/[token].ts";
|
||||
import * as $11 from "./routes/api/gallery/[gid].ts";
|
||||
import * as $12 from "./routes/api/status.ts";
|
||||
import * as $13 from "./routes/api/task.ts";
|
||||
import * as $14 from "./routes/api/thumbnail/[id].ts";
|
||||
import * as $15 from "./routes/index.tsx";
|
||||
import * as $12 from "./routes/api/gallery/list.ts";
|
||||
import * as $13 from "./routes/api/status.ts";
|
||||
import * as $14 from "./routes/api/task.ts";
|
||||
import * as $15 from "./routes/api/thumbnail/[id].ts";
|
||||
import * as $16 from "./routes/index.tsx";
|
||||
import * as $$0 from "./islands/Container.tsx";
|
||||
import * as $$1 from "./islands/Settings.tsx";
|
||||
import * as $$2 from "./islands/TaskManager.tsx";
|
||||
@@ -36,10 +37,11 @@ const manifest = {
|
||||
"./routes/api/filemeta/[token].ts": $9,
|
||||
"./routes/api/files/[token].ts": $10,
|
||||
"./routes/api/gallery/[gid].ts": $11,
|
||||
"./routes/api/status.ts": $12,
|
||||
"./routes/api/task.ts": $13,
|
||||
"./routes/api/thumbnail/[id].ts": $14,
|
||||
"./routes/index.tsx": $15,
|
||||
"./routes/api/gallery/list.ts": $12,
|
||||
"./routes/api/status.ts": $13,
|
||||
"./routes/api/task.ts": $14,
|
||||
"./routes/api/thumbnail/[id].ts": $15,
|
||||
"./routes/index.tsx": $16,
|
||||
},
|
||||
islands: {
|
||||
"./islands/Container.tsx": $$0,
|
||||
|
||||
46
routes/api/gallery/list.ts
Normal file
46
routes/api/gallery/list.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Handlers } from "$fresh/server.ts";
|
||||
import { get_task_manager } from "../../../server.ts";
|
||||
import { parse_bool, parse_int } from "../../../server/parse_form.ts";
|
||||
import { return_data, return_error } from "../../../server/utils.ts";
|
||||
|
||||
const ALLOW_FIELDS = [
|
||||
"gid",
|
||||
"token",
|
||||
"title",
|
||||
"title_jpn",
|
||||
"category",
|
||||
"uploader",
|
||||
"posted",
|
||||
"filecount",
|
||||
"filesize",
|
||||
"expunged",
|
||||
"rating",
|
||||
"parent_gid",
|
||||
"parent_key",
|
||||
"first_gid",
|
||||
"first_key",
|
||||
];
|
||||
|
||||
export const handler: Handlers = {
|
||||
async GET(req, _ctx) {
|
||||
const u = new URL(req.url);
|
||||
const t = get_task_manager();
|
||||
const all = await parse_bool(u.searchParams.get("all"), false);
|
||||
const offset = await parse_int(u.searchParams.get("offset"), 0);
|
||||
const limit = await parse_int(u.searchParams.get("limit"), 20);
|
||||
const fields = u.searchParams.get("fields") || "*";
|
||||
if (fields !== "*") {
|
||||
const fs = fields.split(",");
|
||||
const ok = fs.every((d) => {
|
||||
const c = d.trim();
|
||||
return ALLOW_FIELDS.includes(c);
|
||||
});
|
||||
if (!ok) return return_error(1, "Some fields not allowed.");
|
||||
}
|
||||
if (all) {
|
||||
return return_data(t.db.get_gmetas_all(fields));
|
||||
} else {
|
||||
return return_data(t.db.get_gmetas(offset, limit, fields));
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -3,6 +3,10 @@ import { ConfigType } from "../config.ts";
|
||||
import { get_ws_host } from "./utils.ts";
|
||||
import { ConfigClientSocketData, ConfigSeverSocketData } from "./config.ts";
|
||||
import { DEFAULT_DOWNLOAD_CONFIG, DownloadConfig } from "../tasks/download.ts";
|
||||
import {
|
||||
DEFAULT_EXPORT_ZIP_CONFIG,
|
||||
ExportZipConfig,
|
||||
} from "../tasks/export_zip.ts";
|
||||
|
||||
export const cfg = signal<ConfigType | undefined>(undefined);
|
||||
|
||||
@@ -36,3 +40,11 @@ export function generate_download_cfg(): DownloadConfig {
|
||||
remove_previous_gallery: c.remove_previous_gallery,
|
||||
};
|
||||
}
|
||||
|
||||
export function generate_export_zip_cfg(): ExportZipConfig {
|
||||
if (!cfg.value) return DEFAULT_EXPORT_ZIP_CONFIG;
|
||||
const c = cfg.value;
|
||||
return {
|
||||
jpn_title: c.export_zip_jpn_title,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,3 +8,5 @@ export type GalleryData = {
|
||||
};
|
||||
|
||||
export type GalleryResult = JSONResult<GalleryData>;
|
||||
|
||||
export type GalleryListResult = JSONResult<GMeta[]>;
|
||||
|
||||
@@ -251,7 +251,7 @@ div.new_task>div.container {
|
||||
height: calc(100% - 5% - 64px);
|
||||
min-width: 400px;
|
||||
width: calc(100% - 20%);
|
||||
margin-top: 8rem;
|
||||
margin-top: 2.5%;
|
||||
}
|
||||
|
||||
div.new_task .top {
|
||||
|
||||
@@ -15,5 +15,9 @@
|
||||
"overwrite_cfg": "Overwrite default config.",
|
||||
"submit_failed": "Failed to submit task.",
|
||||
"gallery_title": "Gallery title: ",
|
||||
"gallery_page": "Number of pages: "
|
||||
"gallery_page": "Number of pages: ",
|
||||
"ezcfg_output": "The path to output file: ",
|
||||
"ezcfg_jpn_title": "Use japanese title first.",
|
||||
"ezcfg_export_ad": "Export pages which marked as ads.",
|
||||
"ezcfg_max_length": "Maximum length of filenames in Zip files: "
|
||||
}
|
||||
|
||||
@@ -15,5 +15,9 @@
|
||||
"overwrite_cfg": "覆盖默认设置。",
|
||||
"submit_failed": "提交任务失败",
|
||||
"gallery_title": "画廊标题:",
|
||||
"gallery_page": "页数:"
|
||||
"gallery_page": "页数:",
|
||||
"ezcfg_output": "输出文件的位置:",
|
||||
"ezcfg_jpn_title": "优先使用日语标题。",
|
||||
"ezcfg_export_ad": "导出标记为广告的页面。",
|
||||
"ezcfg_max_length": "Zip文件中文件名的最大长度:"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user