dsh-plugins-jizuo-creator-video 0.3.29 → 0.3.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/node_modules/@jizuo/video-plugin/README.md +3 -3
- package/node_modules/@jizuo/video-plugin/lib/client.js +54 -2
- package/node_modules/@jizuo/video-plugin/node_modules/@jizuo/plugin/README.md +3 -3
- package/node_modules/@jizuo/video-plugin/node_modules/@jizuo/plugin/lib/client.js +0 -4
- package/node_modules/@jizuo/video-plugin/node_modules/@jizuo/plugin/lib/dream-worker.js +176 -31
- package/node_modules/@jizuo/video-plugin/node_modules/@jizuo/plugin/lib/index.js +332 -188
- package/node_modules/@jizuo/video-plugin/node_modules/@jizuo/plugin/package.json +1 -1
- package/node_modules/@jizuo/video-plugin/package.json +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ dsh-plugins-jizuo-creator-video
|
|
|
12
12
|
dsh plugin --profile desktop add dsh-plugins-jizuo-creator-video
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
这是已验证 v0.3.
|
|
15
|
+
这是已验证 v0.3.30 插件包的 npm 安装入口,内含原始插件及共享核心,无需额外安装依赖。
|
|
16
16
|
|
|
17
17
|
已有 GitHub 安装版本时,请先卸载对应的旧安装项,再安装此 npm 包,完成后重启 DSH;卸载插件不会删除作品或对话。不要同时安装同一功能的 GitHub 包与 npm 包。
|
|
18
18
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
支持 **DeepSeek Harness Desktop 0.2.0-rc.2**。在左侧「插件」→「添加插件」粘贴下面这一行,确认来源后安装:
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
|
-
https://github.com/ShikangPang/jizuo-creator-video/releases/download/v0.3.
|
|
10
|
+
https://github.com/ShikangPang/jizuo-creator-video/releases/download/v0.3.30/jizuo-video-plugin-0.3.30.tgz
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
只填安装包地址,不要填整个安装命令。安装完成后「已安装」中显示「即作视频」,可分别启用、关闭、配置或卸载。共享核心已包含在安装包中,自动加载,不需要另装即作桌面端或手动安装核心插件。保留 Harness 原有新会话、插件与工作区入口;创作项目统一显示在工作区区域,小说和视频以前置图标区分,不再提供独立的创作面板。
|
|
@@ -17,7 +17,7 @@ https://github.com/ShikangPang/jizuo-creator-video/releases/download/v0.3.29/jiz
|
|
|
17
17
|
也可使用 Desktop 菜单安装的 dsh 命令:
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
dsh plugin --profile desktop add https://github.com/ShikangPang/jizuo-creator-video/releases/download/v0.3.
|
|
20
|
+
dsh plugin --profile desktop add https://github.com/ShikangPang/jizuo-creator-video/releases/download/v0.3.30/jizuo-video-plugin-0.3.30.tgz
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
源码克隆后运行 `node scripts/install.mjs --all` 一次安装五个插件,默认 desktop;Web 用户显式传 `--profile web`。脚本严格检查宿主版本。不要用旧版全局 dsh 命令安装到新版 Desktop,也不要绕过兼容检查。
|
|
@@ -26,7 +26,7 @@ dsh plugin --profile desktop add https://github.com/ShikangPang/jizuo-creator-vi
|
|
|
26
26
|
|
|
27
27
|
启用即作写作插件后,点击工作区列表顶部的“导入小说”。支持 TXT、Markdown 和 DOCX,单文件最大 10 MB。选择文件后预览章节、修改作品名称,确认后创建新的小说项目,展开“正文”即可编辑。取消预览不会创建作品;首章前的内容保留为前言。TXT 支持 UTF-8 和 GB18030 编码。
|
|
28
28
|
|
|
29
|
-
已安装多个即作插件时,请将它们一并更新到 0.3.
|
|
29
|
+
已安装多个即作插件时,请将它们一并更新到 0.3.30,并重启 Harness,使共享核心版本一致。
|
|
30
30
|
|
|
31
31
|
## 项目与数据
|
|
32
32
|
|
|
@@ -9742,6 +9742,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9742
9742
|
"image/jpeg",
|
|
9743
9743
|
"image/webp"
|
|
9744
9744
|
]);
|
|
9745
|
+
const ASSETS_PER_PAGE = 60;
|
|
9745
9746
|
function VideoAssetsPanel({ project, episode: suppliedEpisode, remote }) {
|
|
9746
9747
|
const [selectedEpisodeId, setSelectedEpisodeId] = (0, react.useState)("");
|
|
9747
9748
|
const episode = suppliedEpisode ?? project.episodes.find((item) => !item.deletedAt && item.id === selectedEpisodeId);
|
|
@@ -9757,7 +9758,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9757
9758
|
const [targetShotId, setTargetShotId] = (0, react.useState)("");
|
|
9758
9759
|
const [search, setSearch] = (0, react.useState)("");
|
|
9759
9760
|
const [kind, setKind] = (0, react.useState)("all");
|
|
9760
|
-
const
|
|
9761
|
+
const query = search.trim().toLocaleLowerCase();
|
|
9762
|
+
const visibleAssets = project.assets.filter((asset) => (kind === "all" || asset.kind === kind) && asset.label.toLocaleLowerCase().includes(query));
|
|
9763
|
+
const pageKey = JSON.stringify([
|
|
9764
|
+
project.workId,
|
|
9765
|
+
kind,
|
|
9766
|
+
query
|
|
9767
|
+
]);
|
|
9768
|
+
const [pagination, setPagination] = (0, react.useState)({
|
|
9769
|
+
key: "",
|
|
9770
|
+
page: 0
|
|
9771
|
+
});
|
|
9772
|
+
const pageCount = Math.max(1, Math.ceil(visibleAssets.length / ASSETS_PER_PAGE));
|
|
9773
|
+
const page = Math.min(pagination.key === pageKey ? pagination.page : 0, pageCount - 1);
|
|
9774
|
+
const pageAssets = visibleAssets.slice(page * ASSETS_PER_PAGE, (page + 1) * ASSETS_PER_PAGE);
|
|
9761
9775
|
const images = project.assets.filter((asset) => asset.kind === "image");
|
|
9762
9776
|
const shots = episode?.shots.filter((shot) => !shot.archived) ?? [];
|
|
9763
9777
|
const targetShot = shots.find((shot) => shot.id === targetShotId);
|
|
@@ -9972,7 +9986,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9972
9986
|
})] }),
|
|
9973
9987
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9974
9988
|
className: "jz-video-asset-grid",
|
|
9975
|
-
children:
|
|
9989
|
+
children: pageAssets.map((asset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("figure", { children: [
|
|
9976
9990
|
asset.kind === "image" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VideoAssetPreview, {
|
|
9977
9991
|
remote,
|
|
9978
9992
|
workId: project.workId,
|
|
@@ -10016,6 +10030,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10016
10030
|
}
|
|
10017
10031
|
})
|
|
10018
10032
|
] }, asset.id))
|
|
10033
|
+
}),
|
|
10034
|
+
pageCount > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
|
|
10035
|
+
className: "jz-video-tools",
|
|
10036
|
+
"aria-label": "素材分页",
|
|
10037
|
+
children: [
|
|
10038
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10039
|
+
type: "button",
|
|
10040
|
+
"aria-label": "上一页素材",
|
|
10041
|
+
disabled: page === 0,
|
|
10042
|
+
onClick: () => setPagination({
|
|
10043
|
+
key: pageKey,
|
|
10044
|
+
page: page - 1
|
|
10045
|
+
}),
|
|
10046
|
+
children: "上一页"
|
|
10047
|
+
}),
|
|
10048
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10049
|
+
"aria-live": "polite",
|
|
10050
|
+
children: [
|
|
10051
|
+
"第 ",
|
|
10052
|
+
page + 1,
|
|
10053
|
+
" / ",
|
|
10054
|
+
pageCount,
|
|
10055
|
+
" 页,共 ",
|
|
10056
|
+
visibleAssets.length,
|
|
10057
|
+
" 项"
|
|
10058
|
+
]
|
|
10059
|
+
}),
|
|
10060
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10061
|
+
type: "button",
|
|
10062
|
+
"aria-label": "下一页素材",
|
|
10063
|
+
disabled: page + 1 >= pageCount,
|
|
10064
|
+
onClick: () => setPagination({
|
|
10065
|
+
key: pageKey,
|
|
10066
|
+
page: page + 1
|
|
10067
|
+
}),
|
|
10068
|
+
children: "下一页"
|
|
10069
|
+
})
|
|
10070
|
+
]
|
|
10019
10071
|
})
|
|
10020
10072
|
] }),
|
|
10021
10073
|
!visibleAssets.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
支持 **DeepSeek Harness Desktop 0.2.0-rc.2**。在左侧「插件」→「添加插件」粘贴下面这一行,确认来源后安装:
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
|
-
https://github.com/ShikangPang/jizuo-creator-writing/releases/download/v0.3.
|
|
10
|
+
https://github.com/ShikangPang/jizuo-creator-writing/releases/download/v0.3.30/jizuo-writing-plugin-0.3.30.tgz
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
只填安装包地址,不要填整个安装命令。安装完成后「已安装」中显示「即作写作」,可分别启用、关闭、配置或卸载。共享核心已包含在安装包中,自动加载,不需要另装即作桌面端或手动安装核心插件。保留 Harness 原有新会话、插件与工作区入口;创作项目统一显示在工作区区域,小说和视频以前置图标区分,不再提供独立的创作面板。
|
|
@@ -17,7 +17,7 @@ https://github.com/ShikangPang/jizuo-creator-writing/releases/download/v0.3.29/j
|
|
|
17
17
|
也可使用 Desktop 菜单安装的 dsh 命令:
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
dsh plugin --profile desktop add https://github.com/ShikangPang/jizuo-creator-writing/releases/download/v0.3.
|
|
20
|
+
dsh plugin --profile desktop add https://github.com/ShikangPang/jizuo-creator-writing/releases/download/v0.3.30/jizuo-writing-plugin-0.3.30.tgz
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
源码克隆后运行 `node scripts/install.mjs --all` 一次安装五个插件,默认 desktop;Web 用户显式传 `--profile web`。脚本严格检查宿主版本。不要用旧版全局 dsh 命令安装到新版 Desktop,也不要绕过兼容检查。
|
|
@@ -26,7 +26,7 @@ dsh plugin --profile desktop add https://github.com/ShikangPang/jizuo-creator-wr
|
|
|
26
26
|
|
|
27
27
|
启用即作写作插件后,点击工作区列表顶部的“导入小说”。支持 TXT、Markdown 和 DOCX,单文件最大 10 MB。选择文件后预览章节、修改作品名称,确认后创建新的小说项目,展开“正文”即可编辑。取消预览不会创建作品;首章前的内容保留为前言。TXT 支持 UTF-8 和 GB18030 编码。
|
|
28
28
|
|
|
29
|
-
已安装多个即作插件时,请将它们一并更新到 0.3.
|
|
29
|
+
已安装多个即作插件时,请将它们一并更新到 0.3.30,并重启 Harness,使共享核心版本一致。
|
|
30
30
|
|
|
31
31
|
## 项目与数据
|
|
32
32
|
|
|
@@ -10522,12 +10522,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10522
10522
|
}
|
|
10523
10523
|
};
|
|
10524
10524
|
refresh();
|
|
10525
|
-
const timer = window.setInterval(() => {
|
|
10526
|
-
refresh();
|
|
10527
|
-
}, BACKGROUND_REFRESH_INTERVAL_MS);
|
|
10528
10525
|
return () => {
|
|
10529
10526
|
active = false;
|
|
10530
|
-
window.clearInterval(timer);
|
|
10531
10527
|
};
|
|
10532
10528
|
}, [
|
|
10533
10529
|
expandedVolumeId,
|
|
@@ -4,6 +4,7 @@ import { access, link, lstat, mkdir, open, readFile, readdir, realpath, rename,
|
|
|
4
4
|
import { closeSync, constants, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
|
+
import { inflateRawSync } from "node:zlib";
|
|
7
8
|
import { isDeepStrictEqual } from "node:util";
|
|
8
9
|
import { createServer } from "node:http";
|
|
9
10
|
//#region \0rolldown/runtime.js
|
|
@@ -64627,7 +64628,7 @@ var require_underline = /* @__PURE__ */ __commonJSMin$1(((exports) => {
|
|
|
64627
64628
|
}
|
|
64628
64629
|
}));
|
|
64629
64630
|
//#endregion
|
|
64630
|
-
//#region ../work-domain/src/
|
|
64631
|
+
//#region ../work-domain/src/docxLimits.ts
|
|
64631
64632
|
var import_lib = /* @__PURE__ */ __toESM$1((/* @__PURE__ */ __commonJSMin$1(((exports) => {
|
|
64632
64633
|
var _ = (init_index_all(), __toCommonJS(index_all_exports));
|
|
64633
64634
|
var docxReader = require_docx_reader();
|
|
@@ -64679,6 +64680,64 @@ var import_lib = /* @__PURE__ */ __toESM$1((/* @__PURE__ */ __commonJSMin$1(((ex
|
|
|
64679
64680
|
});
|
|
64680
64681
|
}
|
|
64681
64682
|
})))(), 1);
|
|
64683
|
+
const MAX_ENTRIES = 2048;
|
|
64684
|
+
const MAX_ENTRY_BYTES = 16777216;
|
|
64685
|
+
const MAX_TOTAL_BYTES = 33554432;
|
|
64686
|
+
const invalid$1 = () => new JizuoError("validation_error", "无法读取 Word 文档,请确认文件为有效的 DOCX 格式后重试");
|
|
64687
|
+
const oversized = () => new JizuoError("validation_error", "Word 文档解压内容超过安全限制,请拆分后再导入");
|
|
64688
|
+
/** Check both ZIP metadata and bounded actual inflation before handing bytes to Mammoth. */
|
|
64689
|
+
function validateDocxArchive(source) {
|
|
64690
|
+
let end = -1;
|
|
64691
|
+
for (let at = source.length - 22; at >= Math.max(0, source.length - 22 - 65535); at -= 1) if (source.readUInt32LE(at) === 101010256 && at + 22 + source.readUInt16LE(at + 20) === source.length) {
|
|
64692
|
+
end = at;
|
|
64693
|
+
break;
|
|
64694
|
+
}
|
|
64695
|
+
if (end < 0) throw invalid$1();
|
|
64696
|
+
const count = source.readUInt16LE(end + 10);
|
|
64697
|
+
if (count > MAX_ENTRIES) throw new JizuoError("validation_error", "Word 文档包含过多文件条目,请简化文档后再导入");
|
|
64698
|
+
if (source.readUInt32LE(end + 4) !== 0 || count !== source.readUInt16LE(end + 8)) throw invalid$1();
|
|
64699
|
+
const centralSize = source.readUInt32LE(end + 12);
|
|
64700
|
+
const centralStart = source.readUInt32LE(end + 16);
|
|
64701
|
+
if (centralStart + centralSize !== end) throw invalid$1();
|
|
64702
|
+
let cursor = centralStart;
|
|
64703
|
+
let total = 0;
|
|
64704
|
+
const names = /* @__PURE__ */ new Set();
|
|
64705
|
+
for (let index = 0; index < count; index += 1) {
|
|
64706
|
+
if (cursor + 46 > end || source.readUInt32LE(cursor) !== 33639248) throw invalid$1();
|
|
64707
|
+
const flags = source.readUInt16LE(cursor + 8);
|
|
64708
|
+
const method = source.readUInt16LE(cursor + 10);
|
|
64709
|
+
const compressedSize = source.readUInt32LE(cursor + 20);
|
|
64710
|
+
const size = source.readUInt32LE(cursor + 24);
|
|
64711
|
+
const nameSize = source.readUInt16LE(cursor + 28);
|
|
64712
|
+
const next = cursor + 46 + nameSize + source.readUInt16LE(cursor + 30) + source.readUInt16LE(cursor + 32);
|
|
64713
|
+
const local = source.readUInt32LE(cursor + 42);
|
|
64714
|
+
if (next > end || flags & 1 || method !== 0 && method !== 8 || source.readUInt16LE(cursor + 34) !== 0) throw invalid$1();
|
|
64715
|
+
if (size > MAX_ENTRY_BYTES || total + size > MAX_TOTAL_BYTES) throw oversized();
|
|
64716
|
+
const name = source.subarray(cursor + 46, cursor + 46 + nameSize);
|
|
64717
|
+
const key = name.toString("utf8");
|
|
64718
|
+
if (names.has(key) || key.split("/").includes("..")) throw invalid$1();
|
|
64719
|
+
names.add(key);
|
|
64720
|
+
if (local + 30 > centralStart || source.readUInt32LE(local) !== 67324752 || source.readUInt16LE(local + 8) !== method || source.readUInt16LE(local + 6) !== flags) throw invalid$1();
|
|
64721
|
+
const localNameSize = source.readUInt16LE(local + 26);
|
|
64722
|
+
const start = local + 30 + localNameSize + source.readUInt16LE(local + 28);
|
|
64723
|
+
if (start + compressedSize > centralStart || !source.subarray(local + 30, local + 30 + localNameSize).equals(name)) throw invalid$1();
|
|
64724
|
+
const compressed = source.subarray(start, start + compressedSize);
|
|
64725
|
+
let expandedSize = compressedSize;
|
|
64726
|
+
if (method === 8) try {
|
|
64727
|
+
expandedSize = inflateRawSync(compressed, { maxOutputLength: Math.min(MAX_ENTRY_BYTES, MAX_TOTAL_BYTES - total) || 1 }).length;
|
|
64728
|
+
} catch (error) {
|
|
64729
|
+
if (error.code === "ERR_BUFFER_TOO_LARGE") throw oversized();
|
|
64730
|
+
throw invalid$1();
|
|
64731
|
+
}
|
|
64732
|
+
if (expandedSize > MAX_ENTRY_BYTES || total + expandedSize > MAX_TOTAL_BYTES) throw oversized();
|
|
64733
|
+
if (expandedSize !== size) throw invalid$1();
|
|
64734
|
+
total += expandedSize;
|
|
64735
|
+
cursor = next;
|
|
64736
|
+
}
|
|
64737
|
+
if (cursor !== end) throw invalid$1();
|
|
64738
|
+
}
|
|
64739
|
+
//#endregion
|
|
64740
|
+
//#region ../work-domain/src/importer.ts
|
|
64682
64741
|
function isChapterTitle(title) {
|
|
64683
64742
|
return /^第[\d零〇一二三四五六七八九十百千万两]+章(?:$|[\s::、..—-])/u.test(title.trim());
|
|
64684
64743
|
}
|
|
@@ -64697,7 +64756,8 @@ function splitMarkdown(markdown, fallbackTitle) {
|
|
|
64697
64756
|
const plainChapters = splitText(markdown, fallbackTitle);
|
|
64698
64757
|
if (plainChapters.some((chapter) => isChapterTitle(chapter.title))) return plainChapters;
|
|
64699
64758
|
}
|
|
64700
|
-
const
|
|
64759
|
+
const deepestLevel = headings.reduce((level, heading) => Math.max(level, heading.level), 0);
|
|
64760
|
+
const selected = chapterHeadings.length > 0 ? chapterHeadings : headings.filter((heading) => heading.level === deepestLevel);
|
|
64701
64761
|
if (selected.length === 0) return [{
|
|
64702
64762
|
title: fallbackTitle,
|
|
64703
64763
|
content: markdown.trim()
|
|
@@ -64751,18 +64811,42 @@ function decodeHtml(value) {
|
|
|
64751
64811
|
function htmlToMarkdown(html) {
|
|
64752
64812
|
return html.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/giu, (_match, level, content) => `\n${"#".repeat(Number(level))} ${decodeHtml(content).trim()}\n`).replace(/<p[^>]*>([\s\S]*?)<\/p>/giu, (_match, content) => `\n${decodeHtml(content)}\n`).replace(/<li[^>]*>([\s\S]*?)<\/li>/giu, (_match, content) => `\n- ${decodeHtml(content)}\n`).replace(/<[^>]+>/gu, "").replace(/\n{3,}/gu, "\n\n").trim();
|
|
64753
64813
|
}
|
|
64814
|
+
function checkSourceSize(size) {
|
|
64815
|
+
if (size > 10485760) throw new JizuoError("validation_error", "文件超过 10 MB,请拆分后再导入");
|
|
64816
|
+
}
|
|
64817
|
+
async function readLocalSource(path) {
|
|
64818
|
+
const file = await open(path, "r");
|
|
64819
|
+
try {
|
|
64820
|
+
const stat = await file.stat();
|
|
64821
|
+
if (!stat.isFile()) throw new JizuoError("validation_error", "请选择有效的小说文件");
|
|
64822
|
+
checkSourceSize(stat.size);
|
|
64823
|
+
const chunks = [];
|
|
64824
|
+
let size = 0;
|
|
64825
|
+
while (true) {
|
|
64826
|
+
const chunk = Buffer.alloc(Math.min(65536, 10485761 - size));
|
|
64827
|
+
const { bytesRead } = await file.read(chunk);
|
|
64828
|
+
if (bytesRead === 0) return Buffer.concat(chunks, size);
|
|
64829
|
+
size += bytesRead;
|
|
64830
|
+
checkSourceSize(size);
|
|
64831
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
64832
|
+
}
|
|
64833
|
+
} finally {
|
|
64834
|
+
await file.close();
|
|
64835
|
+
}
|
|
64836
|
+
}
|
|
64754
64837
|
async function previewImport(rawInput) {
|
|
64755
64838
|
const input = PreviewImportInput.parse(rawInput);
|
|
64756
|
-
const source = input.sourceBase64 === void 0 ? await
|
|
64839
|
+
const source = input.sourceBase64 === void 0 ? await readLocalSource(input.sourcePath) : Buffer.from(input.sourceBase64, "base64");
|
|
64757
64840
|
if (input.sourceBase64 !== void 0) {
|
|
64758
64841
|
if (source.toString("base64") !== input.sourceBase64) throw new JizuoError("validation_error", "文件内容无效,请重新选择小说文件");
|
|
64759
|
-
if (source.length > 10485760) throw new JizuoError("validation_error", "文件超过 10 MB,请拆分后再导入");
|
|
64760
64842
|
}
|
|
64843
|
+
checkSourceSize(source.length);
|
|
64761
64844
|
if (source.length === 0) throw new JizuoError("validation_error", "文件为空,请选择有正文的小说文件");
|
|
64762
64845
|
const suggestedTitle = basename(input.sourcePath, extname(input.sourcePath));
|
|
64763
64846
|
let chapters;
|
|
64764
64847
|
const warnings = [];
|
|
64765
64848
|
if (input.format === "docx") {
|
|
64849
|
+
validateDocxArchive(source);
|
|
64766
64850
|
let converted;
|
|
64767
64851
|
try {
|
|
64768
64852
|
converted = await import_lib.convertToHtml({ buffer: source });
|
|
@@ -70434,6 +70518,12 @@ const DEFAULT_MAX_RESPONSE_BYTES = 262144;
|
|
|
70434
70518
|
const CALLBACK_MAX_BODY_BYTES = 65536;
|
|
70435
70519
|
const LOGIN_TTL_MS = 18e4;
|
|
70436
70520
|
const SESSION_TTL_MS = 6048e5;
|
|
70521
|
+
const vaultOperations = /* @__PURE__ */ new WeakMap();
|
|
70522
|
+
function withVault(vault, action) {
|
|
70523
|
+
const pending = (vaultOperations.get(vault) ?? Promise.resolve()).then(action);
|
|
70524
|
+
vaultOperations.set(vault, pending.catch(() => {}));
|
|
70525
|
+
return pending;
|
|
70526
|
+
}
|
|
70437
70527
|
const NspoxUser = object$1({
|
|
70438
70528
|
id: string().trim().min(1).max(160),
|
|
70439
70529
|
name: string().trim().min(1).max(160),
|
|
@@ -70521,6 +70611,7 @@ var AccountGateway = class {
|
|
|
70521
70611
|
now;
|
|
70522
70612
|
randomBytes;
|
|
70523
70613
|
maxResponseBytes;
|
|
70614
|
+
requestTimeoutMs;
|
|
70524
70615
|
vault;
|
|
70525
70616
|
pendingLogin;
|
|
70526
70617
|
constructor(options = {}) {
|
|
@@ -70528,8 +70619,10 @@ var AccountGateway = class {
|
|
|
70528
70619
|
this.now = options.now ?? Date.now;
|
|
70529
70620
|
this.randomBytes = options.randomBytes ?? (() => globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)));
|
|
70530
70621
|
this.maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
|
|
70622
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 15e3;
|
|
70531
70623
|
this.vault = options.vault ?? new MemoryTokenVault();
|
|
70532
70624
|
if (!Number.isSafeInteger(this.maxResponseBytes) || this.maxResponseBytes < 128) throw new JizuoError("validation_error", "平台响应大小上限无效");
|
|
70625
|
+
if (!Number.isSafeInteger(this.requestTimeoutMs) || this.requestTimeoutMs < 1 || this.requestTimeoutMs > 12e4) throw new JizuoError("validation_error", "平台请求超时时间无效");
|
|
70533
70626
|
}
|
|
70534
70627
|
async beginBrowserLogin() {
|
|
70535
70628
|
this.closePendingLogin();
|
|
@@ -70630,7 +70723,7 @@ var AccountGateway = class {
|
|
|
70630
70723
|
}
|
|
70631
70724
|
async logout() {
|
|
70632
70725
|
this.closePendingLogin();
|
|
70633
|
-
await this.vault.clear();
|
|
70726
|
+
await withVault(this.vault, () => this.vault.clear());
|
|
70634
70727
|
}
|
|
70635
70728
|
async handleCallback(server, request, response) {
|
|
70636
70729
|
const pending = this.pendingLogin;
|
|
@@ -70672,7 +70765,7 @@ var AccountGateway = class {
|
|
|
70672
70765
|
...refreshToken === "" ? {} : { refreshToken }
|
|
70673
70766
|
};
|
|
70674
70767
|
try {
|
|
70675
|
-
await this.vault.set(tokens);
|
|
70768
|
+
await withVault(this.vault, () => this.vault.set(tokens));
|
|
70676
70769
|
} catch {
|
|
70677
70770
|
pending.consumed = false;
|
|
70678
70771
|
sendHtml(response, 500, callbackPage(false));
|
|
@@ -70694,14 +70787,31 @@ var AccountGateway = class {
|
|
|
70694
70787
|
pending.server.close();
|
|
70695
70788
|
}
|
|
70696
70789
|
async requireAccessToken() {
|
|
70697
|
-
|
|
70698
|
-
|
|
70699
|
-
|
|
70700
|
-
|
|
70701
|
-
|
|
70702
|
-
|
|
70790
|
+
return withVault(this.vault, async () => {
|
|
70791
|
+
const tokens = await this.vault.get();
|
|
70792
|
+
if (tokens === void 0 || tokens.expiresAt <= this.now()) {
|
|
70793
|
+
await this.vault.clear();
|
|
70794
|
+
throw new JizuoError("credential_required", "请先通过浏览器登录 NSPOX 账号");
|
|
70795
|
+
}
|
|
70796
|
+
return tokens.accessToken;
|
|
70797
|
+
});
|
|
70703
70798
|
}
|
|
70704
70799
|
async requestJson(path, options) {
|
|
70800
|
+
const controller = new AbortController();
|
|
70801
|
+
let timeout;
|
|
70802
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
70803
|
+
timeout = setTimeout(() => {
|
|
70804
|
+
reject(new JizuoError("runtime_unavailable", "NSPOX 账号服务响应超时,请稍后重试"));
|
|
70805
|
+
controller.abort();
|
|
70806
|
+
}, this.requestTimeoutMs);
|
|
70807
|
+
});
|
|
70808
|
+
try {
|
|
70809
|
+
return await Promise.race([this.readResponse(path, options, controller.signal), deadline]);
|
|
70810
|
+
} finally {
|
|
70811
|
+
clearTimeout(timeout);
|
|
70812
|
+
}
|
|
70813
|
+
}
|
|
70814
|
+
async readResponse(path, options, signal) {
|
|
70705
70815
|
const url = validateProductionUrl(new URL(path, PRODUCTION_ORIGIN).href);
|
|
70706
70816
|
const headers = new Headers({
|
|
70707
70817
|
accept: "application/json",
|
|
@@ -70712,30 +70822,65 @@ var AccountGateway = class {
|
|
|
70712
70822
|
response = await this.fetchImpl(url, {
|
|
70713
70823
|
method: "GET",
|
|
70714
70824
|
headers,
|
|
70715
|
-
redirect: "manual"
|
|
70825
|
+
redirect: "manual",
|
|
70826
|
+
signal
|
|
70716
70827
|
});
|
|
70717
70828
|
} catch {
|
|
70718
70829
|
throw new JizuoError("runtime_unavailable", "无法连接 NSPOX 账号服务");
|
|
70719
70830
|
}
|
|
70720
|
-
if (response.url !== "") validateProductionUrl(response.url);
|
|
70721
|
-
if (response.status >= 300 && response.status < 400) {
|
|
70722
|
-
const location = response.headers.get("location");
|
|
70723
|
-
if (location !== null) validateProductionUrl(new URL(location, url).href);
|
|
70724
|
-
throw new JizuoError("denied", "平台响应发生了未允许的重定向");
|
|
70725
|
-
}
|
|
70726
|
-
if (response.status === 401) {
|
|
70727
|
-
await this.vault.clear();
|
|
70728
|
-
throw new JizuoError("credential_required", "NSPOX 登录状态已失效");
|
|
70729
|
-
}
|
|
70730
|
-
if (!response.ok) throw new JizuoError("runtime_unavailable", "NSPOX 账号服务暂时不可用", { status: response.status });
|
|
70731
|
-
const declaredSize = Number(response.headers.get("content-length"));
|
|
70732
|
-
if (Number.isFinite(declaredSize) && declaredSize > this.maxResponseBytes) throw new JizuoError("validation_error", "平台响应超过安全大小限制");
|
|
70733
|
-
const text = await response.text();
|
|
70734
|
-
if (new TextEncoder().encode(text).byteLength > this.maxResponseBytes) throw new JizuoError("validation_error", "平台响应超过安全大小限制");
|
|
70735
70831
|
try {
|
|
70736
|
-
|
|
70737
|
-
|
|
70738
|
-
|
|
70832
|
+
signal.throwIfAborted();
|
|
70833
|
+
if (response.url !== "") validateProductionUrl(response.url);
|
|
70834
|
+
if (response.status >= 300 && response.status < 400) {
|
|
70835
|
+
const location = response.headers.get("location");
|
|
70836
|
+
if (location !== null) validateProductionUrl(new URL(location, url).href);
|
|
70837
|
+
throw new JizuoError("denied", "平台响应发生了未允许的重定向");
|
|
70838
|
+
}
|
|
70839
|
+
if (response.status === 401) {
|
|
70840
|
+
await withVault(this.vault, async () => {
|
|
70841
|
+
if ((await this.vault.get())?.accessToken === options.accessToken) await this.vault.clear();
|
|
70842
|
+
});
|
|
70843
|
+
throw new JizuoError("credential_required", "NSPOX 登录状态已失效");
|
|
70844
|
+
}
|
|
70845
|
+
if (!response.ok) throw new JizuoError("runtime_unavailable", "NSPOX 账号服务暂时不可用", { status: response.status });
|
|
70846
|
+
const declaredSize = Number(response.headers.get("content-length"));
|
|
70847
|
+
if (Number.isFinite(declaredSize) && declaredSize > this.maxResponseBytes) throw new JizuoError("validation_error", "平台响应超过安全大小限制");
|
|
70848
|
+
const text = await this.readBoundedBody(response, signal);
|
|
70849
|
+
try {
|
|
70850
|
+
return JSON.parse(text);
|
|
70851
|
+
} catch {
|
|
70852
|
+
throw new JizuoError("validation_error", "平台响应不是有效 JSON");
|
|
70853
|
+
}
|
|
70854
|
+
} finally {
|
|
70855
|
+
if (response.body !== null && !response.body.locked) response.body.cancel().catch(() => {});
|
|
70856
|
+
}
|
|
70857
|
+
}
|
|
70858
|
+
async readBoundedBody(response, signal) {
|
|
70859
|
+
if (response.body === null) return "";
|
|
70860
|
+
const reader = response.body.getReader();
|
|
70861
|
+
const cancel = () => {
|
|
70862
|
+
reader.cancel().catch(() => {});
|
|
70863
|
+
};
|
|
70864
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
70865
|
+
const chunks = [];
|
|
70866
|
+
let size = 0;
|
|
70867
|
+
try {
|
|
70868
|
+
while (true) {
|
|
70869
|
+
signal.throwIfAborted();
|
|
70870
|
+
const { done, value } = await reader.read();
|
|
70871
|
+
signal.throwIfAborted();
|
|
70872
|
+
if (done) return new TextDecoder().decode(Buffer.concat(chunks, size));
|
|
70873
|
+
size += value.byteLength;
|
|
70874
|
+
if (size > this.maxResponseBytes) throw new JizuoError("validation_error", "平台响应超过安全大小限制");
|
|
70875
|
+
chunks.push(value);
|
|
70876
|
+
}
|
|
70877
|
+
} catch (error) {
|
|
70878
|
+
if (error instanceof JizuoError) throw error;
|
|
70879
|
+
throw new JizuoError("runtime_unavailable", "读取 NSPOX 账号响应失败,请稍后重试");
|
|
70880
|
+
} finally {
|
|
70881
|
+
signal.removeEventListener("abort", cancel);
|
|
70882
|
+
cancel();
|
|
70883
|
+
reader.releaseLock();
|
|
70739
70884
|
}
|
|
70740
70885
|
}
|
|
70741
70886
|
};
|