mirror of
https://github.com/lifegpc/eh-downloader.git
synced 2026-06-06 05:38:44 +08:00
Add support to upload custom file
This commit is contained in:
@@ -28,6 +28,7 @@ export type ConfigType = {
|
||||
flutter_frontend?: string;
|
||||
fetch_timeout: number;
|
||||
download_timeout: number;
|
||||
ffprobe_path: string;
|
||||
};
|
||||
|
||||
export enum ThumbnailMethod {
|
||||
@@ -172,6 +173,9 @@ export class Config {
|
||||
get download_timeout() {
|
||||
return this._return_number("download_timeout") || 10000;
|
||||
}
|
||||
get ffprobe_path() {
|
||||
return this._return_string("ffprobe_path") || "ffprobe";
|
||||
}
|
||||
to_json(): ConfigType {
|
||||
return {
|
||||
cookies: typeof this.cookies === "string",
|
||||
@@ -200,6 +204,7 @@ export class Config {
|
||||
flutter_frontend: this.flutter_frontend,
|
||||
fetch_timeout: this.fetch_timeout,
|
||||
download_timeout: this.download_timeout,
|
||||
ffprobe_path: this.ffprobe_path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
78
fresh.gen.ts
78
fresh.gen.ts
@@ -10,27 +10,30 @@ import * as $4 from "./routes/api/exit.ts";
|
||||
import * as $5 from "./routes/api/export/gallery/zip/[gid].ts";
|
||||
import * as $6 from "./routes/api/file/[id].ts";
|
||||
import * as $7 from "./routes/api/file/random.ts";
|
||||
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/gallery/list.ts";
|
||||
import * as $13 from "./routes/api/status.ts";
|
||||
import * as $14 from "./routes/api/tag/[id].ts";
|
||||
import * as $15 from "./routes/api/tag/rows.ts";
|
||||
import * as $16 from "./routes/api/task.ts";
|
||||
import * as $17 from "./routes/api/thumbnail/[id].ts";
|
||||
import * as $18 from "./routes/api/token.ts";
|
||||
import * as $19 from "./routes/api/user.ts";
|
||||
import * as $20 from "./routes/file/[id].ts";
|
||||
import * as $21 from "./routes/file/_middleware.ts";
|
||||
import * as $22 from "./routes/index.tsx";
|
||||
import * as $23 from "./routes/manifest.json.ts";
|
||||
import * as $24 from "./routes/thumbnail/[id].ts";
|
||||
import * as $25 from "./routes/thumbnail/_middleware.ts";
|
||||
import * as $8 from "./routes/api/file/upload.ts";
|
||||
import * as $9 from "./routes/api/filemeta.ts";
|
||||
import * as $10 from "./routes/api/filemeta/[token].ts";
|
||||
import * as $11 from "./routes/api/files/[token].ts";
|
||||
import * as $12 from "./routes/api/gallery/[gid].ts";
|
||||
import * as $13 from "./routes/api/gallery/list.ts";
|
||||
import * as $14 from "./routes/api/status.ts";
|
||||
import * as $15 from "./routes/api/tag/[id].ts";
|
||||
import * as $16 from "./routes/api/tag/rows.ts";
|
||||
import * as $17 from "./routes/api/task.ts";
|
||||
import * as $18 from "./routes/api/thumbnail/[id].ts";
|
||||
import * as $19 from "./routes/api/token.ts";
|
||||
import * as $20 from "./routes/api/user.ts";
|
||||
import * as $21 from "./routes/file/[id].ts";
|
||||
import * as $22 from "./routes/file/_middleware.ts";
|
||||
import * as $23 from "./routes/index.tsx";
|
||||
import * as $24 from "./routes/manifest.json.ts";
|
||||
import * as $25 from "./routes/thumbnail/[id].ts";
|
||||
import * as $26 from "./routes/thumbnail/_middleware.ts";
|
||||
import * as $27 from "./routes/upload.tsx";
|
||||
import * as $$0 from "./islands/Container.tsx";
|
||||
import * as $$1 from "./islands/Settings.tsx";
|
||||
import * as $$2 from "./islands/TaskManager.tsx";
|
||||
import * as $$3 from "./islands/Upload.tsx";
|
||||
|
||||
const manifest = {
|
||||
routes: {
|
||||
@@ -42,29 +45,32 @@ const manifest = {
|
||||
"./routes/api/export/gallery/zip/[gid].ts": $5,
|
||||
"./routes/api/file/[id].ts": $6,
|
||||
"./routes/api/file/random.ts": $7,
|
||||
"./routes/api/filemeta.ts": $8,
|
||||
"./routes/api/filemeta/[token].ts": $9,
|
||||
"./routes/api/files/[token].ts": $10,
|
||||
"./routes/api/gallery/[gid].ts": $11,
|
||||
"./routes/api/gallery/list.ts": $12,
|
||||
"./routes/api/status.ts": $13,
|
||||
"./routes/api/tag/[id].ts": $14,
|
||||
"./routes/api/tag/rows.ts": $15,
|
||||
"./routes/api/task.ts": $16,
|
||||
"./routes/api/thumbnail/[id].ts": $17,
|
||||
"./routes/api/token.ts": $18,
|
||||
"./routes/api/user.ts": $19,
|
||||
"./routes/file/[id].ts": $20,
|
||||
"./routes/file/_middleware.ts": $21,
|
||||
"./routes/index.tsx": $22,
|
||||
"./routes/manifest.json.ts": $23,
|
||||
"./routes/thumbnail/[id].ts": $24,
|
||||
"./routes/thumbnail/_middleware.ts": $25,
|
||||
"./routes/api/file/upload.ts": $8,
|
||||
"./routes/api/filemeta.ts": $9,
|
||||
"./routes/api/filemeta/[token].ts": $10,
|
||||
"./routes/api/files/[token].ts": $11,
|
||||
"./routes/api/gallery/[gid].ts": $12,
|
||||
"./routes/api/gallery/list.ts": $13,
|
||||
"./routes/api/status.ts": $14,
|
||||
"./routes/api/tag/[id].ts": $15,
|
||||
"./routes/api/tag/rows.ts": $16,
|
||||
"./routes/api/task.ts": $17,
|
||||
"./routes/api/thumbnail/[id].ts": $18,
|
||||
"./routes/api/token.ts": $19,
|
||||
"./routes/api/user.ts": $20,
|
||||
"./routes/file/[id].ts": $21,
|
||||
"./routes/file/_middleware.ts": $22,
|
||||
"./routes/index.tsx": $23,
|
||||
"./routes/manifest.json.ts": $24,
|
||||
"./routes/thumbnail/[id].ts": $25,
|
||||
"./routes/thumbnail/_middleware.ts": $26,
|
||||
"./routes/upload.tsx": $27,
|
||||
},
|
||||
islands: {
|
||||
"./islands/Container.tsx": $$0,
|
||||
"./islands/Settings.tsx": $$1,
|
||||
"./islands/TaskManager.tsx": $$2,
|
||||
"./islands/Upload.tsx": $$3,
|
||||
},
|
||||
baseUrl: import.meta.url,
|
||||
};
|
||||
|
||||
44
islands/Upload.tsx
Normal file
44
islands/Upload.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import t, { i18n_map, I18NMap } from "../server/i18n.ts";
|
||||
|
||||
export type UploaderProps = {
|
||||
i18n?: I18NMap;
|
||||
lang?: string;
|
||||
};
|
||||
|
||||
export default function Uploader(props: UploaderProps) {
|
||||
if (props.i18n) i18n_map.value = props.i18n;
|
||||
return (
|
||||
<form
|
||||
action="/api/file/upload"
|
||||
method="post"
|
||||
encType="multipart/form-data"
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
required={true}
|
||||
accept=".jpg,.jpeg,.png"
|
||||
/>
|
||||
<br />
|
||||
{t("upload.filename")}{" "}
|
||||
<input
|
||||
type="text"
|
||||
name="filename"
|
||||
placeholder={t("upload.filename")}
|
||||
/>
|
||||
<br />
|
||||
<input id="is_original" type="checkbox" name="is_original" />{" "}
|
||||
<label for="is_original">{t("upload.is_original")}</label>
|
||||
<br />
|
||||
<input
|
||||
id="token"
|
||||
type="text"
|
||||
name="token"
|
||||
placeholder={t("upload.token")}
|
||||
required={true}
|
||||
/>
|
||||
<br />
|
||||
<input type="submit" value={t("upload.upload")} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
75
routes/api/file/upload.ts
Normal file
75
routes/api/file/upload.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Handlers } from "$fresh/server.ts";
|
||||
import type { EhFile } from "../../../db.ts";
|
||||
import { get_task_manager } from "../../../server.ts";
|
||||
import { return_data, return_error } from "../../../server/utils.ts";
|
||||
import { get_string, parse_bool } from "../../../server/parse_form.ts";
|
||||
import { fb_get_size } from "../../../thumbnail/ffmpeg_binary.ts";
|
||||
import { sure_dir } from "../../../utils.ts";
|
||||
import mime from "mime";
|
||||
import { extname, join, resolve } from "std/path/mod.ts";
|
||||
|
||||
export const handler: Handlers = {
|
||||
async POST(req, _ctx) {
|
||||
const m = get_task_manager();
|
||||
try {
|
||||
const form = await req.formData();
|
||||
const file = form.get("file");
|
||||
if (!file) {
|
||||
return return_error(1, "Missing file.");
|
||||
}
|
||||
const mext = typeof file === "string"
|
||||
? null
|
||||
: `.${mime.getExtension(file.type)}`;
|
||||
const filename = (await get_string(form.get("filename"))) ||
|
||||
(typeof file === "string" ? null : file.name);
|
||||
if (!filename) {
|
||||
return return_error(2, "Missing filename.");
|
||||
}
|
||||
const fext = extname(filename);
|
||||
const fn = mext == fext
|
||||
? filename
|
||||
: `${filename.slice(0, filename.length - fext.length)}${mext}`;
|
||||
const dir = (await get_string(form.get("dir"))) ||
|
||||
join(m.cfg.base, "uploaded");
|
||||
const is_original = await parse_bool(
|
||||
form.get("is_original"),
|
||||
false,
|
||||
);
|
||||
const token = await get_string(form.get("token"));
|
||||
if (!token) {
|
||||
return return_error(3, "Missing token.");
|
||||
}
|
||||
const path = join(dir, fn);
|
||||
await sure_dir(dir);
|
||||
try {
|
||||
if (typeof file === "string") {
|
||||
await Deno.writeTextFile(path, file);
|
||||
} else {
|
||||
await Deno.writeFile(path, file.stream());
|
||||
}
|
||||
const size = await fb_get_size(path);
|
||||
if (!size) {
|
||||
await Deno.remove(path);
|
||||
return return_error(4, "Failed to get file size.");
|
||||
}
|
||||
const rpath = resolve(path);
|
||||
const f = {
|
||||
id: 0,
|
||||
path: rpath,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
is_original,
|
||||
token,
|
||||
} as EhFile;
|
||||
const nf = m.db.add_file(f, false);
|
||||
return return_data(nf);
|
||||
} catch (e) {
|
||||
await Deno.remove(path);
|
||||
throw e;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return return_error(500, "Internal Server Error.");
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -26,6 +26,9 @@ export const handler: Handlers = {
|
||||
m.cfg.ffmpeg_path,
|
||||
);
|
||||
const ffmpeg_api_enabled = await check_ffmpeg_api();
|
||||
const ffprobe_binary_enabled = await check_ffmpeg_binary(
|
||||
m.cfg.ffprobe_path,
|
||||
);
|
||||
const meilisearch_enabled = m.meilisearch !== undefined;
|
||||
let meilisearch;
|
||||
if (
|
||||
@@ -50,6 +53,7 @@ export const handler: Handlers = {
|
||||
return return_data<StatusData>({
|
||||
ffmpeg_api_enabled,
|
||||
ffmpeg_binary_enabled,
|
||||
ffprobe_binary_enabled,
|
||||
meilisearch_enabled,
|
||||
meilisearch,
|
||||
no_user,
|
||||
|
||||
31
routes/upload.tsx
Normal file
31
routes/upload.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||
import GlobalContext from "../components/GlobalContext.tsx";
|
||||
import Uploader from "../islands/Upload.tsx";
|
||||
import { get_i18nmap, i18n_handle_request } from "../server/i18ns.ts";
|
||||
|
||||
type Props = {
|
||||
lang: string;
|
||||
};
|
||||
|
||||
export const handler: Handlers<Props> = {
|
||||
GET(req, ctx) {
|
||||
const re = i18n_handle_request(req);
|
||||
if (typeof re === "string") {
|
||||
return ctx.render({
|
||||
lang: re,
|
||||
});
|
||||
}
|
||||
return re;
|
||||
},
|
||||
};
|
||||
|
||||
export default function Upload({ data }: PageProps<Props>) {
|
||||
const i18n = get_i18nmap(data.lang, "upload");
|
||||
return (
|
||||
<body>
|
||||
<GlobalContext>
|
||||
<Uploader i18n={i18n} lang={data.lang} />
|
||||
</GlobalContext>
|
||||
</body>
|
||||
);
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import { get_host } from "./utils.ts";
|
||||
|
||||
const whole_maps = new Map<string, I18NMap>();
|
||||
const LANGUAGES = ["zh-cn"];
|
||||
type MODULE = "common" | "settings" | "task" | "user";
|
||||
const MODULES: MODULE[] = ["common", "settings", "task", "user"];
|
||||
type MODULE = "common" | "settings" | "task" | "upload" | "user";
|
||||
const MODULES: MODULE[] = ["common", "settings", "task", "upload", "user"];
|
||||
|
||||
export async function load_translation(signal?: AbortSignal) {
|
||||
let base = import.meta.resolve("../translation").slice(7);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type StatusData = {
|
||||
ffmpeg_api_enabled: boolean;
|
||||
ffmpeg_binary_enabled: boolean;
|
||||
ffprobe_binary_enabled: boolean;
|
||||
meilisearch_enabled: boolean;
|
||||
meilisearch?: {
|
||||
host: string;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { RecoverableError, TaskManager } from "../task_manager.ts";
|
||||
import {
|
||||
add_suffix_to_path,
|
||||
asyncEvery,
|
||||
asyncFilter,
|
||||
promiseState,
|
||||
PromiseStatus,
|
||||
@@ -235,11 +236,13 @@ export async function download_task(
|
||||
async function download_task(names: Record<string, number>, i: Image) {
|
||||
const ofiles = db.get_files(i.page_token);
|
||||
if (ofiles.length) {
|
||||
const t = ofiles[0];
|
||||
if (
|
||||
(t.is_original || !download_original_img) &&
|
||||
(await exists(t.path))
|
||||
) {
|
||||
const need = await asyncEvery(
|
||||
ofiles,
|
||||
async (t) =>
|
||||
(!t.is_original && download_original_img) ||
|
||||
(!await exists(t.path)),
|
||||
);
|
||||
if (!need) {
|
||||
const p = db.get_pmeta_by_index(task.gid, i.index);
|
||||
if (!p) {
|
||||
const op = db.get_pmeta_by_token(
|
||||
|
||||
@@ -11,6 +11,34 @@ export async function check_ffmpeg_binary(p: string) {
|
||||
return o.code === 0;
|
||||
}
|
||||
|
||||
export async function fb_get_size(i: string) {
|
||||
const cmd = new Deno.Command("ffprobe", {
|
||||
stdout: "piped",
|
||||
stderr: "piped",
|
||||
args: [
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v:0",
|
||||
"-show_entries",
|
||||
"stream=width,height",
|
||||
"-of",
|
||||
"csv=s=x:p=0",
|
||||
i,
|
||||
],
|
||||
});
|
||||
const c = cmd.spawn();
|
||||
const o = await c.output();
|
||||
if (o.code !== 0) {
|
||||
return null;
|
||||
}
|
||||
const s = (new TextDecoder()).decode(o.stdout).trim().split("x");
|
||||
return {
|
||||
width: parseInt(s[0]),
|
||||
height: parseInt(s[1]),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fb_generate_thumbnail(
|
||||
p: string,
|
||||
i: string,
|
||||
|
||||
6
translation/en/upload.jsonc
Normal file
6
translation/en/upload.jsonc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"upload": "Upload file",
|
||||
"filename": "Filename",
|
||||
"is_original": "Marked as original file",
|
||||
"token": "Token"
|
||||
}
|
||||
6
translation/zh-cn/upload.jsonc
Normal file
6
translation/zh-cn/upload.jsonc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"upload": "上传文件",
|
||||
"filename": "文件名",
|
||||
"is_original": "标记为原始文件",
|
||||
"token": "Token"
|
||||
}
|
||||
Reference in New Issue
Block a user