diff --git a/client.ts b/client.ts index d0d02f8..196d2de 100644 --- a/client.ts +++ b/client.ts @@ -4,7 +4,7 @@ import { load_gallery_page } from "./page/GalleryPage.ts"; import { load_home_overview_page } from "./page/HomeOverviewPage.ts"; import { load_mpv_page } from "./page/MPVPage.ts"; import { load_single_page } from "./page/SinglePage.ts"; -import { RecoverableError, TimeoutError } from "./utils.ts"; +import { RecoverableError, TimeoutError, toJSON } from "./utils.ts"; export type GID = [number, string]; @@ -172,7 +172,7 @@ export class Client { if (gids.length > 25) throw Error("Load limiting is reached."); const data = { method: "gdata", gidlist: gids, namespace: 1 }; const re = await this.post(`https://${this.host}/api.php`, { - body: JSON.stringify(data), + body: toJSON(data), headers: { "content-type": "application/json" }, }); if (re.status != 200) { diff --git a/config.ts b/config.ts index 47a16cf..73880b6 100644 --- a/config.ts +++ b/config.ts @@ -1,6 +1,6 @@ import { exists } from "@std/fs/exists"; import { JsonValue, parse } from "@std/jsonc"; -import { isDocker } from "./utils.ts"; +import { isDocker, toJSON } from "./utils.ts"; export type ConfigType = { cookies: boolean; @@ -252,5 +252,5 @@ export async function load_settings(path: string) { } export function save_settings(path: string, cfg: Config, signal?: AbortSignal) { - return Deno.writeTextFile(path, JSON.stringify(cfg._data), { signal }); + return Deno.writeTextFile(path, toJSON(cfg._data), { signal }); } diff --git a/db.ts b/db.ts index decb8f4..127c92b 100644 --- a/db.ts +++ b/db.ts @@ -9,7 +9,13 @@ import { join, resolve } from "@std/path"; import { SqliteError } from "sqlite/mod.ts"; import { SqliteError as Sqlite3Error } from "sqlite3"; import { Status } from "sqlite/src/constants.ts"; -import { parse_bool, sleep, sure_dir_sync, try_remove_sync } from "./utils.ts"; +import { + parse_bool, + sleep, + sure_dir_sync, + toJSON, + try_remove_sync, +} from "./utils.ts"; import { Task, TaskType } from "./task.ts"; import { generate as randomstring } from "randomstring"; import type { GalleryMetadataSingle } from "./page/GalleryMetadata.ts"; @@ -575,7 +581,7 @@ export class EhDb { add_ehmeta(data: GalleryMetadataSingle) { this.db.query( "INSERT OR REPLACE INTO ehmeta VALUES (?, ?, ?);", - [data.gid, JSON.stringify(data), new Date()], + [data.gid, toJSON(data), new Date()], ); } add_gmeta(gmeta: GMeta) { diff --git a/page/MPVPage.ts b/page/MPVPage.ts index 633f994..ee73316 100644 --- a/page/MPVPage.ts +++ b/page/MPVPage.ts @@ -1,7 +1,7 @@ import { DOMParser } from "deno_dom/wasm-noinit"; import { extname } from "@std/path"; import { Client } from "../client.ts"; -import { initDOMParser } from "../utils.ts"; +import { initDOMParser, toJSON } from "../utils.ts"; import type { EhFile, PMeta } from "../db.ts"; export type MPVRawImage = { @@ -255,7 +255,7 @@ class MPVPage { }; if (nl) param.nl = nl; const re = await this.client.post(this.api_url, { - body: JSON.stringify(param), + body: toJSON(param), headers: { "content-type": "application/json" }, }); if (re.status != 200) { diff --git a/routes/api/config.ts b/routes/api/config.ts index 24848b5..143008b 100644 --- a/routes/api/config.ts +++ b/routes/api/config.ts @@ -9,6 +9,7 @@ import { parse_bool } from "../../server/parse_form.ts"; import { return_json } from "../../server/utils.ts"; import { ExitTarget } from "../../signal_handler.ts"; import type { User } from "../../db.ts"; +import { toJSON } from "../../utils.ts"; const UNSAFE_TYPE: (keyof ConfigType)[] = [ "base", @@ -39,7 +40,7 @@ export const handler: Handlers = { }; const sendMessage = (mes: ConfigSeverSocketData) => { if (socket.readyState === socket.OPEN) { - socket.send(JSON.stringify(mes)); + socket.send(toJSON(mes)); } }; const close_handle = () => { diff --git a/routes/api/task.ts b/routes/api/task.ts index 95b5b7c..b9d4493 100644 --- a/routes/api/task.ts +++ b/routes/api/task.ts @@ -11,6 +11,7 @@ import { return_data, return_error } from "../../server/utils.ts"; import type { DownloadConfig } from "../../tasks/download.ts"; import type { ExportZipConfig } from "../../tasks/export_zip.ts"; import { User, UserPermission } from "../../db.ts"; +import { toJSON } from "../../utils.ts"; export const handler: Handlers = { GET(req, ctx) { @@ -24,7 +25,7 @@ export const handler: Handlers = { e: CustomEvent, ) => { if (socket.readyState === socket.OPEN) { - socket.send(JSON.stringify({ type: e.type, detail: e.detail })); + socket.send(toJSON({ type: e.type, detail: e.detail })); } }; const close_handle = () => { @@ -42,7 +43,7 @@ export const handler: Handlers = { }; function sendMessage(mes: TaskServerSocketData) { if (socket.readyState === socket.OPEN) { - socket.send(JSON.stringify(mes)); + socket.send(toJSON(mes)); } } const interval = setInterval(() => { diff --git a/server/utils.ts b/server/utils.ts index eecbc07..0aec2c0 100644 --- a/server/utils.ts +++ b/server/utils.ts @@ -1,3 +1,5 @@ +import { toJSON } from "../utils.ts"; + export function get_ws_host() { const protocol = document.location.protocol === "https:" ? "wss:" : "ws:"; return `${protocol}//${document.location.host}`; @@ -23,7 +25,7 @@ function gen_response( } const h = new Headers(headers); h.set("Content-Type", "application/json; charset=UTF-8"); - return new Response(JSON.stringify(d), { status, headers: h }); + return new Response(toJSON(d), { status, headers: h }); } export function return_error( @@ -55,7 +57,7 @@ export function gen_error( } export function return_json(data: T, status = 200) { - return new Response(JSON.stringify(data), { + return new Response(toJSON(data), { status, headers: { "Content-Type": "application/json; chatset=UTF-8" }, }); diff --git a/task_manager.ts b/task_manager.ts index 2d916f0..ab816c5 100644 --- a/task_manager.ts +++ b/task_manager.ts @@ -23,6 +23,7 @@ import { PromiseStatus, RecoverableError, sleep, + toJSON, } from "./utils.ts"; export class AlreadyClosedError extends Error { @@ -124,7 +125,7 @@ export class TaskManager extends EventTarget { id: 0, pid: Deno.pid, type: TaskType.Download, - details: cfg ? JSON.stringify(cfg) : null, + details: cfg ? toJSON(cfg) : null, }; return await this.#add_task(task); } @@ -135,7 +136,7 @@ export class TaskManager extends EventTarget { id: 0, pid: Deno.pid, type: TaskType.ExportZip, - details: JSON.stringify(cfg || DEFAULT_EXPORT_ZIP_CONFIG), + details: toJSON(cfg || DEFAULT_EXPORT_ZIP_CONFIG), }; return await this.#add_task(task); } diff --git a/utils.ts b/utils.ts index 1d9d4d5..235dc26 100644 --- a/utils.ts +++ b/utils.ts @@ -281,3 +281,11 @@ export function isDocker() { } return _isDocker; } + +export function toJSON(obj: unknown) { + return JSON.stringify( + obj, + (_, value) => + typeof value === "bigint" ? parseInt(value.toString()) : value, + ); +} diff --git a/utils_test.ts b/utils_test.ts index 017f139..eeef861 100644 --- a/utils_test.ts +++ b/utils_test.ts @@ -12,6 +12,7 @@ import { PromiseStatus, sleep, sure_dir, + toJSON, } from "./utils.ts"; import { md5 } from "lifegpc-md5"; @@ -158,3 +159,11 @@ Deno.test("map_test", () => { }, d); assertEquals(re, [3, 4]); }); + +Deno.test("toJSON_test", () => { + assertEquals(toJSON({ a: 3n }), '{"a":3}'); + assertEquals( + toJSON([1099511627776n, { a: 45n }]), + '[1099511627776,{"a":45}]', + ); +});