wp-epub-gen 0.2.3 → 0.4.0
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/.vscode/settings.json +26 -0
- package/README.md +261 -75
- package/build/downloadImage.d.ts +0 -5
- package/build/errors.d.ts +0 -1
- package/build/generateTempFile.d.ts +0 -5
- package/build/index.cjs +1279 -0
- package/build/index.d.ts +3 -7
- package/build/index.js +927 -425
- package/build/libs/safeFineName.d.ts +0 -1
- package/build/libs/utils.d.ts +1 -7
- package/build/logger.d.ts +40 -0
- package/build/makeCover.d.ts +0 -5
- package/build/parseContent.d.ts +0 -5
- package/build/parseContent.test.d.ts +1 -0
- package/build/render.d.ts +0 -5
- package/build/templates.d.ts +11 -0
- package/build/types.d.ts +9 -1
- package/eslint.config.js +33 -0
- package/package.json +37 -35
- package/templates/epub3/toc.xhtml.ejs +1 -1
- package/tsconfig.json +8 -7
- package/vite.config.ts +76 -3
- package/vitest.config.ts +15 -0
- package/.eslintrc.js +0 -24
- package/.mocharc.json +0 -3
package/build/index.cjs
ADDED
|
@@ -0,0 +1,1279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const url = require("url");
|
|
6
|
+
const uuid = require("uuid");
|
|
7
|
+
const cheerio = require("cheerio");
|
|
8
|
+
const diacritics = require("diacritics");
|
|
9
|
+
const uslug = require("uslug");
|
|
10
|
+
const archiver = require("archiver");
|
|
11
|
+
const fs$1 = require("fs-extra");
|
|
12
|
+
const request = require("superagent");
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const util = require("util");
|
|
15
|
+
const ejs = require("ejs");
|
|
16
|
+
const entities = require("entities");
|
|
17
|
+
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
18
|
+
function _interopNamespaceDefault(e) {
|
|
19
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
20
|
+
if (e) {
|
|
21
|
+
for (const k in e) {
|
|
22
|
+
if (k !== "default") {
|
|
23
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
24
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: () => e[k]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
n.default = e;
|
|
32
|
+
return Object.freeze(n);
|
|
33
|
+
}
|
|
34
|
+
const cheerio__namespace = /* @__PURE__ */ _interopNamespaceDefault(cheerio);
|
|
35
|
+
const entities__namespace = /* @__PURE__ */ _interopNamespaceDefault(entities);
|
|
36
|
+
const errors = {
|
|
37
|
+
no_output_path: "No output path!",
|
|
38
|
+
no_title: "Title is required.",
|
|
39
|
+
no_content: "Content is required."
|
|
40
|
+
};
|
|
41
|
+
class GlobalLogger {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.customLogger = null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 获取 Logger 单例实例
|
|
47
|
+
*/
|
|
48
|
+
static getInstance() {
|
|
49
|
+
if (!GlobalLogger.instance) {
|
|
50
|
+
GlobalLogger.instance = new GlobalLogger();
|
|
51
|
+
}
|
|
52
|
+
return GlobalLogger.instance;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 设置自定义 logger
|
|
56
|
+
* @param logger 自定义 logger 实例
|
|
57
|
+
*/
|
|
58
|
+
setLogger(logger2) {
|
|
59
|
+
this.customLogger = logger2;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 获取当前使用的 logger
|
|
63
|
+
*/
|
|
64
|
+
getLogger() {
|
|
65
|
+
return this.customLogger || {
|
|
66
|
+
log: (msg) => console.log(msg),
|
|
67
|
+
info: (msg) => console.info(msg),
|
|
68
|
+
error: (msg) => console.error(msg),
|
|
69
|
+
warn: (msg) => console.warn(msg)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 输出普通日志
|
|
74
|
+
*/
|
|
75
|
+
log(msg) {
|
|
76
|
+
this.getLogger().log(msg);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 输出信息日志
|
|
80
|
+
*/
|
|
81
|
+
info(msg) {
|
|
82
|
+
this.getLogger().info(msg);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 输出错误日志
|
|
86
|
+
*/
|
|
87
|
+
error(msg) {
|
|
88
|
+
this.getLogger().error(msg);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 输出警告日志
|
|
92
|
+
*/
|
|
93
|
+
warn(msg) {
|
|
94
|
+
this.getLogger().warn(msg);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const logger = GlobalLogger.getInstance();
|
|
98
|
+
function safeFineName(name) {
|
|
99
|
+
return name.replace(/[/\\?%*:|"<>\t\r\n]/g, "_");
|
|
100
|
+
}
|
|
101
|
+
const mimeModule$1 = require("mime/lite");
|
|
102
|
+
const mime$1 = mimeModule$1.default || mimeModule$1;
|
|
103
|
+
const ALLOWED_ATTRIBUTES = [
|
|
104
|
+
"about",
|
|
105
|
+
"accesskey",
|
|
106
|
+
"alt",
|
|
107
|
+
"aria-activedescendant",
|
|
108
|
+
"aria-atomic",
|
|
109
|
+
"aria-autocomplete",
|
|
110
|
+
"aria-busy",
|
|
111
|
+
"aria-checked",
|
|
112
|
+
"aria-controls",
|
|
113
|
+
"aria-describedat",
|
|
114
|
+
"aria-describedby",
|
|
115
|
+
"aria-disabled",
|
|
116
|
+
"aria-dropeffect",
|
|
117
|
+
"aria-expanded",
|
|
118
|
+
"aria-flowto",
|
|
119
|
+
"aria-grabbed",
|
|
120
|
+
"aria-haspopup",
|
|
121
|
+
"aria-hidden",
|
|
122
|
+
"aria-invalid",
|
|
123
|
+
"aria-label",
|
|
124
|
+
"aria-labelledby",
|
|
125
|
+
"aria-level",
|
|
126
|
+
"aria-live",
|
|
127
|
+
"aria-multiline",
|
|
128
|
+
"aria-multiselectable",
|
|
129
|
+
"aria-orientation",
|
|
130
|
+
"aria-owns",
|
|
131
|
+
"aria-posinset",
|
|
132
|
+
"aria-pressed",
|
|
133
|
+
"aria-readonly",
|
|
134
|
+
"aria-relevant",
|
|
135
|
+
"aria-required",
|
|
136
|
+
"aria-selected",
|
|
137
|
+
"aria-setsize",
|
|
138
|
+
"aria-sort",
|
|
139
|
+
"aria-valuemax",
|
|
140
|
+
"aria-valuemin",
|
|
141
|
+
"aria-valuenow",
|
|
142
|
+
"aria-valuetext",
|
|
143
|
+
"class",
|
|
144
|
+
"colspan",
|
|
145
|
+
"content",
|
|
146
|
+
// 去除重复
|
|
147
|
+
"contenteditable",
|
|
148
|
+
"contextmenu",
|
|
149
|
+
"datatype",
|
|
150
|
+
"dir",
|
|
151
|
+
"draggable",
|
|
152
|
+
"dropzone",
|
|
153
|
+
"epub:prefix",
|
|
154
|
+
"epub:type",
|
|
155
|
+
"hidden",
|
|
156
|
+
"href",
|
|
157
|
+
"hreflang",
|
|
158
|
+
"id",
|
|
159
|
+
// 去除重复
|
|
160
|
+
"inlist",
|
|
161
|
+
"itemid",
|
|
162
|
+
"itemref",
|
|
163
|
+
"itemscope",
|
|
164
|
+
"itemtype",
|
|
165
|
+
"lang",
|
|
166
|
+
"media",
|
|
167
|
+
"ns1:type",
|
|
168
|
+
"ns2:alphabet",
|
|
169
|
+
"ns2:ph",
|
|
170
|
+
"onabort",
|
|
171
|
+
"onblur",
|
|
172
|
+
"oncanplay",
|
|
173
|
+
"oncanplaythrough",
|
|
174
|
+
"onchange",
|
|
175
|
+
"onclick",
|
|
176
|
+
"oncontextmenu",
|
|
177
|
+
"ondblclick",
|
|
178
|
+
"ondrag",
|
|
179
|
+
"ondragend",
|
|
180
|
+
"ondragenter",
|
|
181
|
+
"ondragleave",
|
|
182
|
+
"ondragover",
|
|
183
|
+
"ondragstart",
|
|
184
|
+
"ondrop",
|
|
185
|
+
"ondurationchange",
|
|
186
|
+
"onemptied",
|
|
187
|
+
"onended",
|
|
188
|
+
"onerror",
|
|
189
|
+
"onfocus",
|
|
190
|
+
"oninput",
|
|
191
|
+
"oninvalid",
|
|
192
|
+
"onkeydown",
|
|
193
|
+
"onkeypress",
|
|
194
|
+
"onkeyup",
|
|
195
|
+
"onload",
|
|
196
|
+
"onloadeddata",
|
|
197
|
+
"onloadedmetadata",
|
|
198
|
+
"onloadstart",
|
|
199
|
+
"onmousedown",
|
|
200
|
+
"onmousemove",
|
|
201
|
+
"onmouseout",
|
|
202
|
+
"onmouseover",
|
|
203
|
+
"onmouseup",
|
|
204
|
+
"onmousewheel",
|
|
205
|
+
"onpause",
|
|
206
|
+
"onplay",
|
|
207
|
+
"onplaying",
|
|
208
|
+
"onprogress",
|
|
209
|
+
"onratechange",
|
|
210
|
+
"onreadystatechange",
|
|
211
|
+
"onreset",
|
|
212
|
+
"onscroll",
|
|
213
|
+
"onseeked",
|
|
214
|
+
"onseeking",
|
|
215
|
+
"onselect",
|
|
216
|
+
"onshow",
|
|
217
|
+
"onstalled",
|
|
218
|
+
"onsubmit",
|
|
219
|
+
"onsuspend",
|
|
220
|
+
"ontimeupdate",
|
|
221
|
+
"onvolumechange",
|
|
222
|
+
"onwaiting",
|
|
223
|
+
"prefix",
|
|
224
|
+
"property",
|
|
225
|
+
"rel",
|
|
226
|
+
"resource",
|
|
227
|
+
"rev",
|
|
228
|
+
"role",
|
|
229
|
+
"rowspan",
|
|
230
|
+
"spellcheck",
|
|
231
|
+
"src",
|
|
232
|
+
"style",
|
|
233
|
+
"tabindex",
|
|
234
|
+
"target",
|
|
235
|
+
"title",
|
|
236
|
+
// 去除重复
|
|
237
|
+
"type",
|
|
238
|
+
"typeof",
|
|
239
|
+
"vocab",
|
|
240
|
+
"xml:base",
|
|
241
|
+
"xml:lang",
|
|
242
|
+
"xml:space"
|
|
243
|
+
];
|
|
244
|
+
const ALLOWED_XHTML11_TAGS = [
|
|
245
|
+
"a",
|
|
246
|
+
"abbr",
|
|
247
|
+
"acronym",
|
|
248
|
+
"address",
|
|
249
|
+
"applet",
|
|
250
|
+
"b",
|
|
251
|
+
"bar",
|
|
252
|
+
"basefont",
|
|
253
|
+
"bdo",
|
|
254
|
+
"big",
|
|
255
|
+
"blockquote",
|
|
256
|
+
"br",
|
|
257
|
+
"caption",
|
|
258
|
+
"center",
|
|
259
|
+
"cite",
|
|
260
|
+
"code",
|
|
261
|
+
"col",
|
|
262
|
+
"colgroup",
|
|
263
|
+
"dd",
|
|
264
|
+
"del",
|
|
265
|
+
"dfn",
|
|
266
|
+
"div",
|
|
267
|
+
"dl",
|
|
268
|
+
"dt",
|
|
269
|
+
"em",
|
|
270
|
+
"embed",
|
|
271
|
+
"font",
|
|
272
|
+
"h1",
|
|
273
|
+
"h2",
|
|
274
|
+
"h3",
|
|
275
|
+
"h4",
|
|
276
|
+
"h5",
|
|
277
|
+
"h6",
|
|
278
|
+
"hr",
|
|
279
|
+
"i",
|
|
280
|
+
"iframe",
|
|
281
|
+
"img",
|
|
282
|
+
"ins",
|
|
283
|
+
"kbd",
|
|
284
|
+
"li",
|
|
285
|
+
"map",
|
|
286
|
+
"noscript",
|
|
287
|
+
"ns:svg",
|
|
288
|
+
"object",
|
|
289
|
+
// 去除重复
|
|
290
|
+
"ol",
|
|
291
|
+
"p",
|
|
292
|
+
"param",
|
|
293
|
+
"pre",
|
|
294
|
+
"q",
|
|
295
|
+
"s",
|
|
296
|
+
"samp",
|
|
297
|
+
"script",
|
|
298
|
+
"small",
|
|
299
|
+
"span",
|
|
300
|
+
"strike",
|
|
301
|
+
"strong",
|
|
302
|
+
"sub",
|
|
303
|
+
"sup",
|
|
304
|
+
"table",
|
|
305
|
+
"tbody",
|
|
306
|
+
"td",
|
|
307
|
+
"tfoot",
|
|
308
|
+
"th",
|
|
309
|
+
"thead",
|
|
310
|
+
"tr",
|
|
311
|
+
"tt",
|
|
312
|
+
"u",
|
|
313
|
+
"ul",
|
|
314
|
+
"var"
|
|
315
|
+
];
|
|
316
|
+
const ALLOWED_ATTRIBUTES_SET = new Set(ALLOWED_ATTRIBUTES);
|
|
317
|
+
const ALLOWED_XHTML11_TAGS_SET = new Set(ALLOWED_XHTML11_TAGS);
|
|
318
|
+
const SELF_CLOSING_TAGS = /* @__PURE__ */ new Set(["img", "br", "hr"]);
|
|
319
|
+
function initializeChapterInfo(content, index, epubConfigs) {
|
|
320
|
+
const chapter = { ...content };
|
|
321
|
+
let { filename } = chapter;
|
|
322
|
+
if (!filename) {
|
|
323
|
+
let titleSlug = uslug(diacritics.remove(chapter.title || "no title"));
|
|
324
|
+
titleSlug = titleSlug.replace(/[/\\]/g, "_");
|
|
325
|
+
chapter.href = `${index}_${titleSlug}.xhtml`;
|
|
326
|
+
chapter.filePath = path.join(epubConfigs.dir, "OEBPS", chapter.href);
|
|
327
|
+
} else {
|
|
328
|
+
filename = safeFineName(filename);
|
|
329
|
+
const is_xhtml = filename.endsWith(".xhtml");
|
|
330
|
+
chapter.href = is_xhtml ? filename : `${filename}.xhtml`;
|
|
331
|
+
if (is_xhtml) {
|
|
332
|
+
chapter.filePath = path.join(epubConfigs.dir, "OEBPS", filename);
|
|
333
|
+
} else {
|
|
334
|
+
chapter.filePath = path.join(epubConfigs.dir, "OEBPS", `${filename}.xhtml`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
chapter.id = `item_${index}`;
|
|
338
|
+
chapter.dir = path.dirname(chapter.filePath);
|
|
339
|
+
chapter.excludeFromToc = chapter.excludeFromToc || false;
|
|
340
|
+
chapter.beforeToc = chapter.beforeToc || false;
|
|
341
|
+
return chapter;
|
|
342
|
+
}
|
|
343
|
+
function normalizeAuthorInfo(chapter) {
|
|
344
|
+
if (chapter.author && typeof chapter.author === "string") {
|
|
345
|
+
chapter.author = [chapter.author];
|
|
346
|
+
} else if (!chapter.author || !Array.isArray(chapter.author)) {
|
|
347
|
+
chapter.author = [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function getAllowedAttributes() {
|
|
351
|
+
return ALLOWED_ATTRIBUTES;
|
|
352
|
+
}
|
|
353
|
+
function getAllowedXhtml11Tags() {
|
|
354
|
+
return ALLOWED_XHTML11_TAGS;
|
|
355
|
+
}
|
|
356
|
+
function loadAndProcessHtml(data) {
|
|
357
|
+
if (!data || typeof data !== "string") {
|
|
358
|
+
throw new Error("Invalid HTML data: data must be a non-empty string");
|
|
359
|
+
}
|
|
360
|
+
const trimmedData = data.trim();
|
|
361
|
+
if (trimmedData.length === 0) {
|
|
362
|
+
throw new Error("Invalid HTML data: data cannot be empty or whitespace only");
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
let $ = cheerio__namespace.load(trimmedData, {
|
|
366
|
+
xml: {
|
|
367
|
+
lowerCaseTags: true,
|
|
368
|
+
recognizeSelfClosing: true
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
const body = $("body");
|
|
372
|
+
if (body.length) {
|
|
373
|
+
const html = body.html();
|
|
374
|
+
if (html) {
|
|
375
|
+
$ = cheerio__namespace.load(html, {
|
|
376
|
+
xml: {
|
|
377
|
+
lowerCaseTags: true,
|
|
378
|
+
recognizeSelfClosing: true
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return $;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`Failed to parse HTML content: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function processHtmlElements($, allowedAttributes, allowedXhtml11Tags, epubConfigs, index) {
|
|
391
|
+
const allowedAttrsSet = ALLOWED_ATTRIBUTES_SET;
|
|
392
|
+
const allowedTagsSet = ALLOWED_XHTML11_TAGS_SET;
|
|
393
|
+
const selfClosingTags = SELF_CLOSING_TAGS;
|
|
394
|
+
$($("*").get().reverse()).each(function(elemIndex, elem) {
|
|
395
|
+
const attrs = elem.attribs || {};
|
|
396
|
+
const $elem = $(elem);
|
|
397
|
+
const tagName = elem.name;
|
|
398
|
+
if (selfClosingTags.has(tagName)) {
|
|
399
|
+
if (tagName === "img" && !$elem.attr("alt")) {
|
|
400
|
+
$elem.attr("alt", "image-placeholder");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const attrsToRemove = [];
|
|
404
|
+
for (const [attrName] of Object.entries(attrs)) {
|
|
405
|
+
if (allowedAttrsSet.has(attrName)) {
|
|
406
|
+
if (attrName === "type" && tagName !== "script") {
|
|
407
|
+
attrsToRemove.push(attrName);
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
attrsToRemove.push(attrName);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
for (const attrName of attrsToRemove) {
|
|
414
|
+
$elem.removeAttr(attrName);
|
|
415
|
+
}
|
|
416
|
+
if (epubConfigs.version === 2) {
|
|
417
|
+
if (!allowedTagsSet.has(tagName)) {
|
|
418
|
+
if (epubConfigs.verbose) {
|
|
419
|
+
logger.warn(
|
|
420
|
+
`Warning (content[${index}]): ${tagName} tag isn't allowed on EPUB 2/XHTML 1.1 DTD.`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
const child = $elem.html();
|
|
424
|
+
$elem.replaceWith($("<div>" + child + "</div>"));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
function processImages($, chapter, epubConfigs) {
|
|
430
|
+
$("img").each((index, elem) => {
|
|
431
|
+
const url2 = $(elem).attr("src") || "";
|
|
432
|
+
if (!url2 || url2.trim().length === 0) {
|
|
433
|
+
logger.warn(`Image at index ${index} in chapter has empty src attribute, removing element`);
|
|
434
|
+
$(elem).remove();
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const trimmedUrl = url2.trim();
|
|
438
|
+
try {
|
|
439
|
+
if (!trimmedUrl.match(/^(https?:\/\/|data:|\.\/|\/)/)) {
|
|
440
|
+
logger.warn(`Image URL "${trimmedUrl}" appears to be invalid, but processing anyway`);
|
|
441
|
+
}
|
|
442
|
+
} catch (error) {
|
|
443
|
+
logger.error(`Error validating image URL "${trimmedUrl}": ${error}`);
|
|
444
|
+
}
|
|
445
|
+
const image = epubConfigs.images.find((el) => el.url === trimmedUrl);
|
|
446
|
+
let id;
|
|
447
|
+
let extension;
|
|
448
|
+
if (image) {
|
|
449
|
+
id = image.id;
|
|
450
|
+
extension = image.extension;
|
|
451
|
+
} else {
|
|
452
|
+
id = uuid.v4();
|
|
453
|
+
let mediaType = "";
|
|
454
|
+
try {
|
|
455
|
+
const cleanUrl = trimmedUrl.replace(/\?.*/, "");
|
|
456
|
+
mediaType = mime$1.getType(cleanUrl) || "";
|
|
457
|
+
if (!mediaType) {
|
|
458
|
+
const urlExtension = cleanUrl.split(".").pop()?.toLowerCase();
|
|
459
|
+
if (urlExtension && ["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(urlExtension)) {
|
|
460
|
+
mediaType = `image/${urlExtension === "jpg" ? "jpeg" : urlExtension}`;
|
|
461
|
+
logger.warn(
|
|
462
|
+
`Could not determine MIME type for "${trimmedUrl}", inferred as "${mediaType}"`
|
|
463
|
+
);
|
|
464
|
+
} else {
|
|
465
|
+
logger.warn(
|
|
466
|
+
`Could not determine MIME type for "${trimmedUrl}", defaulting to image/jpeg`
|
|
467
|
+
);
|
|
468
|
+
mediaType = "image/jpeg";
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} catch (error) {
|
|
472
|
+
logger.error(`Error determining MIME type for "${trimmedUrl}": ${error}`);
|
|
473
|
+
mediaType = "image/jpeg";
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
extension = mime$1.getExtension(mediaType) || "jpg";
|
|
477
|
+
} catch (error) {
|
|
478
|
+
logger.error(`Error getting extension for MIME type "${mediaType}": ${error}`);
|
|
479
|
+
extension = "jpg";
|
|
480
|
+
}
|
|
481
|
+
const dir = chapter.dir || "";
|
|
482
|
+
const img = { id, url: trimmedUrl, dir, mediaType, extension };
|
|
483
|
+
epubConfigs.images.push(img);
|
|
484
|
+
if (epubConfigs.verbose) {
|
|
485
|
+
logger.info(`Added image: ${trimmedUrl} -> images/${id}.${extension} (${mediaType})`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
$(elem).attr("src", `images/${id}.${extension}`);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
logger.error(`Error setting src attribute for image ${id}: ${error}`);
|
|
492
|
+
$(elem).remove();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
function extractAndCleanHtmlContent($, originalData) {
|
|
497
|
+
let data;
|
|
498
|
+
if ($("body").length) {
|
|
499
|
+
data = $("body").html() || "";
|
|
500
|
+
} else {
|
|
501
|
+
data = $.root().html() || "";
|
|
502
|
+
}
|
|
503
|
+
if (!originalData) {
|
|
504
|
+
return data.replace(
|
|
505
|
+
/<(br|hr|img|input|meta|area|base|col|embed|link|source|track|wbr)([^>]*?)><\/\1>/gi,
|
|
506
|
+
"<$1$2/>"
|
|
507
|
+
).replace(
|
|
508
|
+
new RegExp("<(br|hr|img|input|meta|area|base|col|embed|link|source|track|wbr)([^>]*?)(?<!\\/)>", "gi"),
|
|
509
|
+
"<$1$2/>"
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
513
|
+
const entityRegex = /&[a-zA-Z][a-zA-Z0-9]*;|&#[0-9]+;|&#x[0-9a-fA-F]+;/g;
|
|
514
|
+
const matches = Array.from(originalData.matchAll(entityRegex));
|
|
515
|
+
let processedOriginal = originalData;
|
|
516
|
+
const timestamp = Date.now();
|
|
517
|
+
const randomId = Math.random().toString(36).substring(2, 8);
|
|
518
|
+
const placeholderPrefix = `__ENTITY_${timestamp}_${randomId}_`;
|
|
519
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
520
|
+
const match = matches[i];
|
|
521
|
+
const placeholder = `${placeholderPrefix}${i}__`;
|
|
522
|
+
entityMap.set(placeholder, match[0]);
|
|
523
|
+
processedOriginal = processedOriginal.substring(0, match.index) + placeholder + processedOriginal.substring(match.index + match[0].length);
|
|
524
|
+
}
|
|
525
|
+
const $temp = cheerio__namespace.load(processedOriginal, {
|
|
526
|
+
xmlMode: false
|
|
527
|
+
});
|
|
528
|
+
let tempData;
|
|
529
|
+
if ($temp("body").length) {
|
|
530
|
+
tempData = $temp("body").html() || "";
|
|
531
|
+
} else {
|
|
532
|
+
tempData = $temp.root().html() || "";
|
|
533
|
+
}
|
|
534
|
+
for (const [placeholder, entity] of entityMap) {
|
|
535
|
+
tempData = tempData.replace(new RegExp(placeholder, "g"), entity);
|
|
536
|
+
}
|
|
537
|
+
return tempData.replace(
|
|
538
|
+
/<(br|hr|img|input|meta|area|base|col|embed|link|source|track|wbr)([^>]*?)><\/\1>/gi,
|
|
539
|
+
"<$1$2/>"
|
|
540
|
+
).replace(
|
|
541
|
+
new RegExp("<(br|hr|img|input|meta|area|base|col|embed|link|source|track|wbr)([^>]*?)(?<!\\/)>", "gi"),
|
|
542
|
+
"<$1$2/>"
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
function processChildrenChapters(chapter, index, epubConfigs) {
|
|
546
|
+
if (Array.isArray(chapter.children)) {
|
|
547
|
+
chapter.children = chapter.children.map(
|
|
548
|
+
(content, idx) => parseContent(content, `${index}_${idx}`, epubConfigs)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function parseContent(content, index, epubConfigs) {
|
|
553
|
+
if (!content) {
|
|
554
|
+
throw new Error("Content cannot be null or undefined");
|
|
555
|
+
}
|
|
556
|
+
if (!content.data) {
|
|
557
|
+
logger.warn(`Chapter at index ${index} has no data, using empty string`);
|
|
558
|
+
content.data = "";
|
|
559
|
+
}
|
|
560
|
+
const chapter = initializeChapterInfo(content, index, epubConfigs);
|
|
561
|
+
normalizeAuthorInfo(chapter);
|
|
562
|
+
const allowedAttributes = getAllowedAttributes();
|
|
563
|
+
const allowedXhtml11Tags = getAllowedXhtml11Tags();
|
|
564
|
+
if (!chapter.data || chapter.data.trim().length === 0) {
|
|
565
|
+
logger.warn(`Chapter at index ${index} has empty data, setting empty content`);
|
|
566
|
+
chapter.data = "";
|
|
567
|
+
} else {
|
|
568
|
+
let $;
|
|
569
|
+
try {
|
|
570
|
+
$ = loadAndProcessHtml(chapter.data);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
logger.error(`Failed to process HTML for chapter ${index}: ${error}`);
|
|
573
|
+
$ = cheerio__namespace.load(`<div>${chapter.data}</div>`);
|
|
574
|
+
}
|
|
575
|
+
processHtmlElements($, allowedAttributes, allowedXhtml11Tags, epubConfigs, index);
|
|
576
|
+
processImages($, chapter, epubConfigs);
|
|
577
|
+
chapter.data = extractAndCleanHtmlContent($, content.data);
|
|
578
|
+
}
|
|
579
|
+
processChildrenChapters(chapter, index, epubConfigs);
|
|
580
|
+
return chapter;
|
|
581
|
+
}
|
|
582
|
+
util.promisify(fs.readFile);
|
|
583
|
+
const writeFile = util.promisify(fs.writeFile);
|
|
584
|
+
const USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36";
|
|
585
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
586
|
+
async function fileIsStable(filename, max_wait = 3e4) {
|
|
587
|
+
const start_time = (/* @__PURE__ */ new Date()).getTime();
|
|
588
|
+
let last_size = fs.statSync(filename).size;
|
|
589
|
+
while ((/* @__PURE__ */ new Date()).getTime() - start_time <= max_wait) {
|
|
590
|
+
await wait(1e3);
|
|
591
|
+
const size = fs.statSync(filename).size;
|
|
592
|
+
if (size === last_size) return true;
|
|
593
|
+
last_size = size;
|
|
594
|
+
}
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
function simpleMinifier(xhtml) {
|
|
598
|
+
xhtml = xhtml.replace(/\s+<\/a>/gi, "</a>").replace(/\n\s+</g, "\n<").replace(/\n+/g, "\n");
|
|
599
|
+
return xhtml;
|
|
600
|
+
}
|
|
601
|
+
const downloadImage = async (epubData, options) => {
|
|
602
|
+
const { url: url2 } = options;
|
|
603
|
+
const { log } = epubData;
|
|
604
|
+
const epub_dir = epubData.dir;
|
|
605
|
+
if (!url2) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const image_dir = path.join(epub_dir, "OEBPS", "images");
|
|
609
|
+
fs$1.ensureDirSync(image_dir);
|
|
610
|
+
const filename = path.join(image_dir, options.id + "." + options.extension);
|
|
611
|
+
if (url2.startsWith("file://") || url2.startsWith("/")) {
|
|
612
|
+
let aux_path = url2.replace(/^file:\/\//i, "");
|
|
613
|
+
try {
|
|
614
|
+
aux_path = decodeURIComponent(aux_path);
|
|
615
|
+
} catch {
|
|
616
|
+
log(`[URL Decode Warning] Failed to decode path: ${aux_path}`);
|
|
617
|
+
}
|
|
618
|
+
if (process.platform === "win32") {
|
|
619
|
+
if (aux_path.match(/^\/[a-zA-Z]:/)) {
|
|
620
|
+
aux_path = aux_path.replace(/^\//, "");
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
log(`[Copy 1] '${aux_path}' to '${filename}'`);
|
|
624
|
+
if (fs$1.existsSync(aux_path)) {
|
|
625
|
+
try {
|
|
626
|
+
fs$1.copySync(aux_path, filename);
|
|
627
|
+
} catch (e) {
|
|
628
|
+
log("[Copy 1 Error] " + e.message);
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
log(`[Copy 1 Fail] '${url2}' not exists!`);
|
|
632
|
+
}
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
let requestAction;
|
|
636
|
+
if (url2.startsWith("http")) {
|
|
637
|
+
requestAction = request.get(url2).set({ "User-Agent": USER_AGENT });
|
|
638
|
+
requestAction.pipe(fs$1.createWriteStream(filename));
|
|
639
|
+
} else {
|
|
640
|
+
log(`[Copy 2] '${url2}' to '${filename}'`);
|
|
641
|
+
requestAction = fs$1.createReadStream(path.join(options.dir || "", url2));
|
|
642
|
+
requestAction.pipe(fs$1.createWriteStream(filename));
|
|
643
|
+
}
|
|
644
|
+
return new Promise((resolve, _reject) => {
|
|
645
|
+
requestAction.on("error", (err) => {
|
|
646
|
+
log("[Download Error] Error while downloading: " + url2);
|
|
647
|
+
log(err);
|
|
648
|
+
fs$1.unlinkSync(filename);
|
|
649
|
+
resolve();
|
|
650
|
+
});
|
|
651
|
+
requestAction.on("end", () => {
|
|
652
|
+
log("[Download Success] " + url2);
|
|
653
|
+
resolve();
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
};
|
|
657
|
+
const downloadAllImages = async (epubData) => {
|
|
658
|
+
const { images } = epubData;
|
|
659
|
+
if (images.length === 0) return;
|
|
660
|
+
fs$1.ensureDirSync(path.join(epubData.dir, "OEBPS", "images"));
|
|
661
|
+
for (const image of images) {
|
|
662
|
+
await downloadImage(epubData, image);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
const epub2_content_opf_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
666
|
+
<package xmlns="http://www.idpf.org/2007/opf"
|
|
667
|
+
version="2.0"
|
|
668
|
+
unique-identifier="BookId">
|
|
669
|
+
|
|
670
|
+
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
671
|
+
xmlns:opf="http://www.idpf.org/2007/opf">
|
|
672
|
+
|
|
673
|
+
<dc:identifier id="BookId" opf:scheme="URN"><%= id %></dc:identifier>
|
|
674
|
+
<dc:title><%= title %></dc:title>
|
|
675
|
+
<dc:description><%= description %></dc:description>
|
|
676
|
+
<dc:publisher><%= publisher || "anonymous" %></dc:publisher>
|
|
677
|
+
<dc:creator opf:role="aut" opf:file-as="<%= author.length ? author.join(",") : author %>"><%= author.length ? author.join(",") : author %></dc:creator>
|
|
678
|
+
<dc:date opf:event="modification"><%= date %></dc:date>
|
|
679
|
+
<dc:language><%= lang || "en" %></dc:language>
|
|
680
|
+
<meta name="cover" content="image_cover"/>
|
|
681
|
+
<meta name="generator" content="epub-gen"/>
|
|
682
|
+
|
|
683
|
+
</metadata>
|
|
684
|
+
|
|
685
|
+
<manifest>
|
|
686
|
+
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
|
|
687
|
+
<item id="toc" href="toc.xhtml" media-type="application/xhtml+xml"/>
|
|
688
|
+
<item id="css" href="style.css" media-type="text/css"/>
|
|
689
|
+
|
|
690
|
+
<% if(locals.cover) { %>
|
|
691
|
+
<item id="image_cover" href="cover.<%= _coverExtension %>" media-type="<%= _coverMediaType %>"/>
|
|
692
|
+
<% } %>
|
|
693
|
+
|
|
694
|
+
<% images.forEach(function(image, index){ %>
|
|
695
|
+
<item id="image_<%= index %>" href="images/<%= image.id %>.<%= image.extension %>" media-type="<%= image.mediaType %>"/>
|
|
696
|
+
<% }) %>
|
|
697
|
+
|
|
698
|
+
<% content.forEach(function(content, index){ %>
|
|
699
|
+
<item id="content_<%= index %>_<%= content.id %>" href="<%= content.href %>" media-type="application/xhtml+xml"/>
|
|
700
|
+
<% }) %>
|
|
701
|
+
|
|
702
|
+
<% fonts.forEach(function(font, index) { %>
|
|
703
|
+
<item id="font_<%= index %>" href="fonts/<%= font %>" media-type="application/x-font-ttf"/>
|
|
704
|
+
<% }) %>
|
|
705
|
+
</manifest>
|
|
706
|
+
|
|
707
|
+
<spine toc="ncx">
|
|
708
|
+
<% content.forEach(function(content, index){ %>
|
|
709
|
+
<% if(content.beforeToc && !content.excludeFromToc){ %>
|
|
710
|
+
<itemref idref="content_<%= index %>_<%= content.id %>"/>
|
|
711
|
+
<% } %>
|
|
712
|
+
<% }) %>
|
|
713
|
+
<itemref idref="toc"/>
|
|
714
|
+
<% content.forEach(function(content, index){ %>
|
|
715
|
+
<% if(!content.beforeToc && !content.excludeFromToc){ %>
|
|
716
|
+
<itemref idref="content_<%= index %>_<%= content.id %>"/>
|
|
717
|
+
<% } %>
|
|
718
|
+
<% }) %>
|
|
719
|
+
</spine>
|
|
720
|
+
<guide/>
|
|
721
|
+
</package>
|
|
722
|
+
`;
|
|
723
|
+
const epub2_toc_xhtml_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
724
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
725
|
+
<html xml:lang="<%- lang %>" xmlns="http://www.w3.org/1999/xhtml">
|
|
726
|
+
<head>
|
|
727
|
+
<title><%= title %></title>
|
|
728
|
+
<meta charset="UTF-8"/>
|
|
729
|
+
<link rel="stylesheet" type="text/css" href="style.css"/>
|
|
730
|
+
</head>
|
|
731
|
+
<body>
|
|
732
|
+
<h1 class="h1"><%= tocTitle %></h1>
|
|
733
|
+
<% content.forEach(function(content, index){ %>
|
|
734
|
+
<% if(!content.excludeFromToc){ %>
|
|
735
|
+
<p class="table-of-content">
|
|
736
|
+
<a href="<%= content.href %>"><%= (1 + index) + ". " + (content.title || "Chapter " + (1 + index)) %>
|
|
737
|
+
<% if(content.author.length){ %>
|
|
738
|
+
- <small class="toc-author"><%= content.author.join(",") %></small>
|
|
739
|
+
<% } %>
|
|
740
|
+
<% if(content.url){ %><span class="toc-link"><%= content.url %></span>
|
|
741
|
+
<% }else{ %><span class="toc-link"></span>
|
|
742
|
+
<% } %>
|
|
743
|
+
</a>
|
|
744
|
+
</p>
|
|
745
|
+
<% } %>
|
|
746
|
+
<% }) %>
|
|
747
|
+
</body>
|
|
748
|
+
</html>
|
|
749
|
+
`;
|
|
750
|
+
const epub3_content_opf_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
751
|
+
<package xmlns="http://www.idpf.org/2007/opf"
|
|
752
|
+
version="3.0"
|
|
753
|
+
unique-identifier="BookId"
|
|
754
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
755
|
+
xmlns:dcterms="http://purl.org/dc/terms/"
|
|
756
|
+
xml:lang="en"
|
|
757
|
+
xmlns:media="http://www.idpf.org/epub/vocab/overlays/#"
|
|
758
|
+
prefix="ibooks: http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/">
|
|
759
|
+
|
|
760
|
+
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
|
|
761
|
+
|
|
762
|
+
<dc:identifier id="BookId"><%= id %></dc:identifier>
|
|
763
|
+
<meta refines="#BookId" property="identifier-type" scheme="onix:codelist5">22</meta>
|
|
764
|
+
<meta property="dcterms:identifier" id="meta-identifier">BookId</meta>
|
|
765
|
+
<dc:title><%= title %></dc:title>
|
|
766
|
+
<meta property="dcterms:title" id="meta-title"><%= title %></meta>
|
|
767
|
+
<dc:language><%= lang || "en" %></dc:language>
|
|
768
|
+
<meta property="dcterms:language" id="meta-language"><%= lang || "en" %></meta>
|
|
769
|
+
<meta property="dcterms:modified"><%= (new Date()).toISOString().split(".")[0] + "Z" %></meta>
|
|
770
|
+
<dc:creator id="creator"><%= author.length ? author.join(",") : author %></dc:creator>
|
|
771
|
+
<meta refines="#creator" property="file-as"><%= author.length ? author.join(",") : author %></meta>
|
|
772
|
+
<meta property="dcterms:publisher"><%= publisher || "anonymous" %></meta>
|
|
773
|
+
<dc:publisher><%= publisher || "anonymous" %></dc:publisher>
|
|
774
|
+
<% var date = new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var stringDate = "" + year + "-" + month + "-" + day; %>
|
|
775
|
+
<meta property="dcterms:date"><%= stringDate %></meta>
|
|
776
|
+
<dc:date><%= stringDate %></dc:date>
|
|
777
|
+
<meta property="dcterms:rights">All rights reserved</meta>
|
|
778
|
+
<dc:rights>Copyright © <%= (new Date()).getFullYear() %> by <%= publisher || "anonymous" %></dc:rights>
|
|
779
|
+
<% if(locals.cover) { %>
|
|
780
|
+
<meta name="cover" content="image_cover" />
|
|
781
|
+
<% } %>
|
|
782
|
+
<meta name="generator" content="epub-gen" />
|
|
783
|
+
<meta property="ibooks:specified-fonts">true</meta>
|
|
784
|
+
</metadata>
|
|
785
|
+
|
|
786
|
+
<manifest>
|
|
787
|
+
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
|
|
788
|
+
<item id="toc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav" />
|
|
789
|
+
<item id="css" href="style.css" media-type="text/css" />
|
|
790
|
+
|
|
791
|
+
<% if(locals.cover) { %>
|
|
792
|
+
<item id="image_cover" href="cover.<%= _coverExtension %>" media-type="<%= _coverMediaType %>" />
|
|
793
|
+
<% } %>
|
|
794
|
+
|
|
795
|
+
<% images.forEach(function(image, index){ %>
|
|
796
|
+
<item id="image_<%= index %>" href="images/<%= image.id %>.<%= image.extension %>" media-type="<%= image.mediaType %>" />
|
|
797
|
+
<% }) %>
|
|
798
|
+
|
|
799
|
+
<% function renderContentItem(content) { %>
|
|
800
|
+
<% content.forEach(function(content){ %>
|
|
801
|
+
<item id="content_<%= content.id %>" href="<%= content.href %>" media-type="application/xhtml+xml" />
|
|
802
|
+
<% if (Array.isArray(content.children)) { %>
|
|
803
|
+
<% renderContentItem(content.children) %>
|
|
804
|
+
<% } %>
|
|
805
|
+
<% }) %>
|
|
806
|
+
<% } %>
|
|
807
|
+
<% renderContentItem(content) %>
|
|
808
|
+
|
|
809
|
+
<% fonts.forEach(function(font, index){ %>
|
|
810
|
+
<item id="font_<%= index %>" href="fonts/<%= font %>" media-type="application/x-font-ttf" />
|
|
811
|
+
<% }) %>
|
|
812
|
+
</manifest>
|
|
813
|
+
|
|
814
|
+
<spine toc="ncx">
|
|
815
|
+
<% var nodes_1 = content.filter(item => !item.excludeFromToc && item.beforeToc) %>
|
|
816
|
+
<% var nodes_2 = content.filter(item => !item.excludeFromToc && !item.beforeToc) %>
|
|
817
|
+
<% function renderToc(nodes) { %>
|
|
818
|
+
<% nodes.forEach(function(content){ %>
|
|
819
|
+
<itemref idref="content_<%= content.id %>" />
|
|
820
|
+
<% if (Array.isArray(content.children)) { %>
|
|
821
|
+
<% renderToc(content.children) %>
|
|
822
|
+
<% } %>
|
|
823
|
+
<% }) %>
|
|
824
|
+
<% } %>
|
|
825
|
+
<% renderToc(nodes_1) %>
|
|
826
|
+
<itemref idref="toc" />
|
|
827
|
+
<% renderToc(nodes_2) %>
|
|
828
|
+
</spine>
|
|
829
|
+
<guide>
|
|
830
|
+
<reference type="text" title="Table of Content" href="toc.xhtml" />
|
|
831
|
+
</guide>
|
|
832
|
+
</package>
|
|
833
|
+
`;
|
|
834
|
+
const epub3_toc_xhtml_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
835
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
836
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="<%- lang %>"
|
|
837
|
+
lang="<%- lang %>">
|
|
838
|
+
<head>
|
|
839
|
+
<title><%= title %></title>
|
|
840
|
+
<meta charset="UTF-8" />
|
|
841
|
+
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
842
|
+
</head>
|
|
843
|
+
<body>
|
|
844
|
+
<h1 class="h1"><%= tocTitle %></h1>
|
|
845
|
+
<nav id="toc" class="TOC" epub:type="toc">
|
|
846
|
+
<% var nodes_1 = content.filter(item => !item.excludeFromToc && item.beforeToc) %>
|
|
847
|
+
<% var nodes_2 = content.filter(item => !item.excludeFromToc && !item.beforeToc) %>
|
|
848
|
+
<% function renderToc(nodes, indent = 0) { %>
|
|
849
|
+
<ol>
|
|
850
|
+
<% nodes.forEach(function(content, index){ %>
|
|
851
|
+
<li class="table-of-content">
|
|
852
|
+
<a href="<%= content.href %>"><%= (content.title || "Chapter " + (1 + index)) %>
|
|
853
|
+
<% if(content.author.length){ %> - <small
|
|
854
|
+
class="toc-author"><%= content.author.join(",") %></small>
|
|
855
|
+
<% } %>
|
|
856
|
+
<% if(content.url){ %><span class="toc-link"><%= content.url %></span>
|
|
857
|
+
<% } %>
|
|
858
|
+
</a>
|
|
859
|
+
<% if (Array.isArray(content.children) && content.children.length > 0) { %>
|
|
860
|
+
<% renderToc(content.children, indent + 1) %>
|
|
861
|
+
<% } %>
|
|
862
|
+
</li>
|
|
863
|
+
<% }) %>
|
|
864
|
+
</ol>
|
|
865
|
+
<% } %>
|
|
866
|
+
<% renderToc(nodes_1) %>
|
|
867
|
+
<% renderToc(nodes_2) %>
|
|
868
|
+
</nav>
|
|
869
|
+
|
|
870
|
+
</body>
|
|
871
|
+
</html>
|
|
872
|
+
`;
|
|
873
|
+
const template_css = `.epub-author {
|
|
874
|
+
color: #555;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.epub-link {
|
|
878
|
+
margin-bottom: 30px;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.epub-link a {
|
|
882
|
+
color: #666;
|
|
883
|
+
font-size: 90%;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.toc-author {
|
|
887
|
+
font-size: 90%;
|
|
888
|
+
color: #555;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.toc-link {
|
|
892
|
+
color: #999;
|
|
893
|
+
font-size: 85%;
|
|
894
|
+
display: block;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
hr {
|
|
898
|
+
border: 0;
|
|
899
|
+
border-bottom: 1px solid #dedede;
|
|
900
|
+
margin: 60px 10%;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.TOC > ol {
|
|
904
|
+
margin: 0;
|
|
905
|
+
padding: 0;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.TOC > ol ol {
|
|
909
|
+
padding: 0;
|
|
910
|
+
margin-left: 2em;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.TOC li {
|
|
914
|
+
font-size: 16px;
|
|
915
|
+
list-style: none;
|
|
916
|
+
margin: 0 auto;
|
|
917
|
+
padding: 0;
|
|
918
|
+
}
|
|
919
|
+
`;
|
|
920
|
+
const toc_ncx_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
921
|
+
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
|
|
922
|
+
<head>
|
|
923
|
+
<meta name="dtb:uid" content="<%= id %>"/>
|
|
924
|
+
<meta name="dtb:generator" content="epub-gen"/>
|
|
925
|
+
<meta name="dtb:depth" content="<%= (toc_depth || 1)%>"/>
|
|
926
|
+
<meta name="dtb:totalPageCount" content="0"/>
|
|
927
|
+
<meta name="dtb:maxPageNumber" content="0"/>
|
|
928
|
+
</head>
|
|
929
|
+
<docTitle>
|
|
930
|
+
<text><%= title %></text>
|
|
931
|
+
</docTitle>
|
|
932
|
+
<docAuthor>
|
|
933
|
+
<text><%= author %></text>
|
|
934
|
+
</docAuthor>
|
|
935
|
+
<navMap>
|
|
936
|
+
<% var _index = 1; %>
|
|
937
|
+
<% var nodes_1 = content.filter(c => !c.excludeFromToc && c.beforeToc) %>
|
|
938
|
+
<% var nodes_2 = content.filter(c => !c.excludeFromToc && !c.beforeToc) %>
|
|
939
|
+
<% function renderToc(nodes) { %>
|
|
940
|
+
<% nodes.forEach(function(content, index){ %>
|
|
941
|
+
<navPoint id="content_<%= content.id %>" playOrder="<%= _index++ %>" class="chapter">
|
|
942
|
+
<navLabel>
|
|
943
|
+
<text><%= (tocAutoNumber ? ((1 + index) + ". ") : "") + (content.title || "Chapter " + (1 + index)) %></text>
|
|
944
|
+
</navLabel>
|
|
945
|
+
<content src="<%= content.href %>"/>
|
|
946
|
+
<% if (Array.isArray(content.children)) { %>
|
|
947
|
+
<% renderToc(content.children) %>
|
|
948
|
+
<% } %>
|
|
949
|
+
</navPoint>
|
|
950
|
+
<% }) %>
|
|
951
|
+
<% } %>
|
|
952
|
+
|
|
953
|
+
<% renderToc(nodes_1) %>
|
|
954
|
+
|
|
955
|
+
<navPoint id="toc" playOrder="<%= _index++ %>" class="chapter">
|
|
956
|
+
<navLabel>
|
|
957
|
+
<text><%= tocTitle %></text>
|
|
958
|
+
</navLabel>
|
|
959
|
+
<content src="toc.xhtml"/>
|
|
960
|
+
</navPoint>
|
|
961
|
+
|
|
962
|
+
<% renderToc(nodes_2) %>
|
|
963
|
+
</navMap>
|
|
964
|
+
</ncx>
|
|
965
|
+
`;
|
|
966
|
+
const generateTempFile = async (epubData) => {
|
|
967
|
+
const { log } = epubData;
|
|
968
|
+
const oebps_dir = path.join(epubData.dir, "OEBPS");
|
|
969
|
+
await fs$1.ensureDir(oebps_dir);
|
|
970
|
+
epubData.css = epubData.css || template_css;
|
|
971
|
+
await writeFile(path.join(oebps_dir, "style.css"), epubData.css, "utf-8");
|
|
972
|
+
if (epubData.fonts?.length) {
|
|
973
|
+
const fonts_dir = path.join(oebps_dir, "fonts");
|
|
974
|
+
await fs$1.ensureDir(fonts_dir);
|
|
975
|
+
epubData.fonts = epubData.fonts.map((font) => {
|
|
976
|
+
const filename = path.basename(font);
|
|
977
|
+
if (!fs$1.existsSync(font)) {
|
|
978
|
+
log(`Custom font not found at '${font}'.`);
|
|
979
|
+
} else {
|
|
980
|
+
fs$1.copySync(font, path.join(fonts_dir, filename));
|
|
981
|
+
}
|
|
982
|
+
return filename;
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
const isAppendTitle = (global_append, local_append) => {
|
|
986
|
+
if (typeof local_append === "boolean") return local_append;
|
|
987
|
+
return !!global_append;
|
|
988
|
+
};
|
|
989
|
+
const saveContentToFile = (content) => {
|
|
990
|
+
const title = entities__namespace.encodeXML(content.title || "");
|
|
991
|
+
let html = `${epubData.docHeader}
|
|
992
|
+
<head>
|
|
993
|
+
<meta charset="UTF-8" />
|
|
994
|
+
<title>${title}</title>
|
|
995
|
+
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
996
|
+
</head>
|
|
997
|
+
<body>
|
|
998
|
+
`;
|
|
999
|
+
if (content.title && isAppendTitle(epubData.appendChapterTitles, content.appendChapterTitle)) {
|
|
1000
|
+
html += `<h1>${title}</h1>`;
|
|
1001
|
+
}
|
|
1002
|
+
html += content.title && content.author && content.author?.length ? `<p class='epub-author'>${entities__namespace.encodeXML(content.author.join(", "))}</p>` : "";
|
|
1003
|
+
html += content.title && content.url ? `<p class="epub-link"><a href="${content.url}">${content.url}</a></p>` : "";
|
|
1004
|
+
html += `${content.data}`;
|
|
1005
|
+
html += "\n</body>\n</html>";
|
|
1006
|
+
fs$1.ensureDirSync(path.dirname(content.filePath));
|
|
1007
|
+
fs$1.writeFileSync(content.filePath, html, "utf-8");
|
|
1008
|
+
if (Array.isArray(content.children)) {
|
|
1009
|
+
content.children.map(saveContentToFile);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
epubData.content.map(saveContentToFile);
|
|
1013
|
+
const metainf_dir = path.join(epubData.dir, "META-INF");
|
|
1014
|
+
fs$1.ensureDirSync(metainf_dir);
|
|
1015
|
+
fs$1.writeFileSync(
|
|
1016
|
+
path.join(metainf_dir, "container.xml"),
|
|
1017
|
+
`<?xml version="1.0" encoding="UTF-8" ?>
|
|
1018
|
+
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
|
1019
|
+
<rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles>
|
|
1020
|
+
</container>`,
|
|
1021
|
+
"utf-8"
|
|
1022
|
+
);
|
|
1023
|
+
if (epubData.version === 2) {
|
|
1024
|
+
const fn = path.join(metainf_dir, "com.apple.ibooks.display-options.xml");
|
|
1025
|
+
fs$1.writeFileSync(
|
|
1026
|
+
fn,
|
|
1027
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1028
|
+
<display_options>
|
|
1029
|
+
<platform name="*">
|
|
1030
|
+
<option name="specified-fonts">true</option>
|
|
1031
|
+
</platform>
|
|
1032
|
+
</display_options>
|
|
1033
|
+
`,
|
|
1034
|
+
"utf-8"
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
let opfTemplate;
|
|
1038
|
+
let ncxTocTemplate;
|
|
1039
|
+
let htmlTocTemplate;
|
|
1040
|
+
if (epubData.customOpfTemplatePath && fs$1.existsSync(epubData.customOpfTemplatePath)) {
|
|
1041
|
+
const { readFile } = require("./libs/utils");
|
|
1042
|
+
opfTemplate = await readFile(epubData.customOpfTemplatePath, "utf-8");
|
|
1043
|
+
} else {
|
|
1044
|
+
opfTemplate = epubData.version === 2 ? epub2_content_opf_ejs : epub3_content_opf_ejs;
|
|
1045
|
+
}
|
|
1046
|
+
if (epubData.customNcxTocTemplatePath && fs$1.existsSync(epubData.customNcxTocTemplatePath)) {
|
|
1047
|
+
const { readFile } = require("./libs/utils");
|
|
1048
|
+
ncxTocTemplate = await readFile(epubData.customNcxTocTemplatePath, "utf-8");
|
|
1049
|
+
} else {
|
|
1050
|
+
ncxTocTemplate = toc_ncx_ejs;
|
|
1051
|
+
}
|
|
1052
|
+
if (epubData.customHtmlTocTemplatePath && fs$1.existsSync(epubData.customHtmlTocTemplatePath)) {
|
|
1053
|
+
const { readFile } = require("./libs/utils");
|
|
1054
|
+
htmlTocTemplate = await readFile(epubData.customHtmlTocTemplatePath, "utf-8");
|
|
1055
|
+
} else {
|
|
1056
|
+
htmlTocTemplate = epubData.version === 2 ? epub2_toc_xhtml_ejs : epub3_toc_xhtml_ejs;
|
|
1057
|
+
}
|
|
1058
|
+
const toc_depth = 1;
|
|
1059
|
+
fs$1.writeFileSync(path.join(oebps_dir, "content.opf"), ejs.render(opfTemplate, epubData), "utf-8");
|
|
1060
|
+
fs$1.writeFileSync(
|
|
1061
|
+
path.join(oebps_dir, "toc.ncx"),
|
|
1062
|
+
ejs.render(ncxTocTemplate, { ...epubData, toc_depth }),
|
|
1063
|
+
"utf-8"
|
|
1064
|
+
);
|
|
1065
|
+
fs$1.writeFileSync(
|
|
1066
|
+
path.join(oebps_dir, "toc.xhtml"),
|
|
1067
|
+
simpleMinifier(ejs.render(htmlTocTemplate, epubData)),
|
|
1068
|
+
"utf-8"
|
|
1069
|
+
);
|
|
1070
|
+
};
|
|
1071
|
+
async function makeCover(data) {
|
|
1072
|
+
const { cover, _coverExtension, log } = data;
|
|
1073
|
+
if (!cover) return;
|
|
1074
|
+
const destPath = path.join(data.dir, "OEBPS", `cover.${_coverExtension}`);
|
|
1075
|
+
let writeStream = null;
|
|
1076
|
+
if (cover.startsWith("http")) {
|
|
1077
|
+
writeStream = request.get(cover).set({ "User-Agent": USER_AGENT });
|
|
1078
|
+
writeStream.pipe(fs$1.createWriteStream(destPath));
|
|
1079
|
+
} else {
|
|
1080
|
+
if (!fs$1.existsSync(cover)) return;
|
|
1081
|
+
log("local cover image: " + cover);
|
|
1082
|
+
writeStream = fs$1.createReadStream(cover);
|
|
1083
|
+
writeStream.pipe(fs$1.createWriteStream(destPath));
|
|
1084
|
+
}
|
|
1085
|
+
return new Promise((resolve) => {
|
|
1086
|
+
writeStream.on("end", () => {
|
|
1087
|
+
log("[Success] cover image saved.");
|
|
1088
|
+
resolve();
|
|
1089
|
+
});
|
|
1090
|
+
writeStream.on("error", (e) => {
|
|
1091
|
+
log("[Error] cover image error: " + e.message);
|
|
1092
|
+
log("destPath: " + destPath);
|
|
1093
|
+
if (fs$1.existsSync(destPath)) {
|
|
1094
|
+
try {
|
|
1095
|
+
fs$1.unlinkSync(destPath);
|
|
1096
|
+
log("destPath removed.");
|
|
1097
|
+
} catch (e2) {
|
|
1098
|
+
log("[Error] remove cover image error: " + e2.message);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
resolve(e);
|
|
1102
|
+
});
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
async function render(data) {
|
|
1106
|
+
const { log } = data;
|
|
1107
|
+
log("Generating Template Files...");
|
|
1108
|
+
await generateTempFile(data);
|
|
1109
|
+
log("Downloading Images...");
|
|
1110
|
+
await downloadAllImages(data);
|
|
1111
|
+
log("Making Cover...");
|
|
1112
|
+
await makeCover(data);
|
|
1113
|
+
log("Generating Epub Files...");
|
|
1114
|
+
await genEpub(data);
|
|
1115
|
+
if (fs$1.existsSync(data.output)) {
|
|
1116
|
+
log("Output: " + data.output);
|
|
1117
|
+
log("Done.");
|
|
1118
|
+
} else {
|
|
1119
|
+
log("Output fail!");
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
async function genEpub(epubData) {
|
|
1123
|
+
const { log, dir, output } = epubData;
|
|
1124
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1125
|
+
const outputStream = fs$1.createWriteStream(epubData.output);
|
|
1126
|
+
log("Zipping temp dir to " + output);
|
|
1127
|
+
return new Promise((resolve, reject) => {
|
|
1128
|
+
archive.on("end", async () => {
|
|
1129
|
+
log("Done zipping, clearing temp dir...");
|
|
1130
|
+
const stable = await fileIsStable(epubData.output);
|
|
1131
|
+
if (!stable) {
|
|
1132
|
+
log("Output epub file is not stable!");
|
|
1133
|
+
}
|
|
1134
|
+
try {
|
|
1135
|
+
fs$1.removeSync(dir);
|
|
1136
|
+
resolve();
|
|
1137
|
+
} catch (e) {
|
|
1138
|
+
log("[Error] " + e.message);
|
|
1139
|
+
reject(e);
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
archive.on("close", () => {
|
|
1143
|
+
log("Zip close..");
|
|
1144
|
+
});
|
|
1145
|
+
archive.on("finish", () => {
|
|
1146
|
+
log("Zip finish..");
|
|
1147
|
+
});
|
|
1148
|
+
archive.on("error", (err) => {
|
|
1149
|
+
log("[Archive Error] " + err.message);
|
|
1150
|
+
reject(err);
|
|
1151
|
+
});
|
|
1152
|
+
archive.pipe(outputStream);
|
|
1153
|
+
archive.append("application/epub+zip", { store: true, name: "mimetype" });
|
|
1154
|
+
archive.directory(path.join(dir, "META-INF"), "META-INF");
|
|
1155
|
+
archive.directory(path.join(dir, "OEBPS"), "OEBPS");
|
|
1156
|
+
archive.finalize();
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
const mimeModule = require("mime/lite");
|
|
1160
|
+
const mime = mimeModule.default || mimeModule;
|
|
1161
|
+
const __filename$1 = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
|
|
1162
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
1163
|
+
const baseDir = __dirname$1;
|
|
1164
|
+
function result(success, message, options) {
|
|
1165
|
+
if (options && options.verbose) {
|
|
1166
|
+
if (!success) {
|
|
1167
|
+
logger.error(new Error(message));
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const out = {
|
|
1171
|
+
success
|
|
1172
|
+
};
|
|
1173
|
+
if (typeof message === "string") {
|
|
1174
|
+
out.message = message;
|
|
1175
|
+
}
|
|
1176
|
+
if (options) {
|
|
1177
|
+
out.options = options;
|
|
1178
|
+
}
|
|
1179
|
+
return out;
|
|
1180
|
+
}
|
|
1181
|
+
function check(options) {
|
|
1182
|
+
if (!options.output) {
|
|
1183
|
+
return result(false, errors.no_output_path, options);
|
|
1184
|
+
}
|
|
1185
|
+
if (!options.title) {
|
|
1186
|
+
return result(false, errors.no_title, options);
|
|
1187
|
+
}
|
|
1188
|
+
if (!options.content) {
|
|
1189
|
+
return result(false, errors.no_content, options);
|
|
1190
|
+
}
|
|
1191
|
+
return result(true, void 0, options);
|
|
1192
|
+
}
|
|
1193
|
+
function parseOptions(options) {
|
|
1194
|
+
const tmpDir = options.tmpDir || os.tmpdir();
|
|
1195
|
+
const id = uuid.v4();
|
|
1196
|
+
const data = {
|
|
1197
|
+
description: options.title,
|
|
1198
|
+
publisher: "anonymous",
|
|
1199
|
+
author: ["anonymous"],
|
|
1200
|
+
tocTitle: "Table Of Contents",
|
|
1201
|
+
appendChapterTitles: true,
|
|
1202
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1203
|
+
lang: "en",
|
|
1204
|
+
fonts: [],
|
|
1205
|
+
version: 3,
|
|
1206
|
+
verbose: true,
|
|
1207
|
+
timeoutSeconds: 15 * 60,
|
|
1208
|
+
// 15 min
|
|
1209
|
+
tocAutoNumber: false,
|
|
1210
|
+
...options,
|
|
1211
|
+
id,
|
|
1212
|
+
tmpDir,
|
|
1213
|
+
dir: path.resolve(tmpDir, id),
|
|
1214
|
+
baseDir,
|
|
1215
|
+
docHeader: "",
|
|
1216
|
+
images: [],
|
|
1217
|
+
content: [],
|
|
1218
|
+
log: (msg) => options.verbose && logger.log(msg)
|
|
1219
|
+
};
|
|
1220
|
+
if (data.version === 2) {
|
|
1221
|
+
data.docHeader = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1222
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
1223
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="#{self.options.lang}">
|
|
1224
|
+
`;
|
|
1225
|
+
} else {
|
|
1226
|
+
data.docHeader = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1227
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
1228
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="#{self.options.lang}">
|
|
1229
|
+
`;
|
|
1230
|
+
}
|
|
1231
|
+
if (Array.isArray(data.author) && data.author.length === 0) {
|
|
1232
|
+
data.author = ["anonymous"];
|
|
1233
|
+
}
|
|
1234
|
+
if (typeof data.author === "string") {
|
|
1235
|
+
data.author = [data.author];
|
|
1236
|
+
}
|
|
1237
|
+
data.content = options.content.map((content, index) => parseContent(content, index, data));
|
|
1238
|
+
if (data.cover) {
|
|
1239
|
+
data._coverMediaType = mime.getType(data.cover) || "";
|
|
1240
|
+
data._coverExtension = mime.getExtension(data._coverMediaType) || "";
|
|
1241
|
+
}
|
|
1242
|
+
return data;
|
|
1243
|
+
}
|
|
1244
|
+
async function epubGen(options, configs) {
|
|
1245
|
+
if (configs?.logger) {
|
|
1246
|
+
logger.setLogger(configs.logger);
|
|
1247
|
+
}
|
|
1248
|
+
options = { ...options };
|
|
1249
|
+
const o = check(options);
|
|
1250
|
+
const verbose = options.verbose !== false;
|
|
1251
|
+
if (!o.success) {
|
|
1252
|
+
if (verbose) logger.error(o.message);
|
|
1253
|
+
return o;
|
|
1254
|
+
}
|
|
1255
|
+
let t;
|
|
1256
|
+
try {
|
|
1257
|
+
const data = parseOptions(options);
|
|
1258
|
+
const timeoutSeconds = data.timeoutSeconds || 0;
|
|
1259
|
+
if (timeoutSeconds > 0) {
|
|
1260
|
+
if (verbose) logger.log(`TIMEOUT: ${timeoutSeconds}s`);
|
|
1261
|
+
t = setTimeout(() => {
|
|
1262
|
+
throw new Error("timeout!");
|
|
1263
|
+
}, timeoutSeconds * 1e3);
|
|
1264
|
+
} else {
|
|
1265
|
+
if (verbose) logger.log(`TIMEOUT: N/A`);
|
|
1266
|
+
}
|
|
1267
|
+
await render(data);
|
|
1268
|
+
return result(true, void 0, data);
|
|
1269
|
+
} catch (e) {
|
|
1270
|
+
if (verbose) logger.error(e);
|
|
1271
|
+
return result(false, e.message, options);
|
|
1272
|
+
} finally {
|
|
1273
|
+
clearTimeout(t);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
const gen = epubGen;
|
|
1277
|
+
exports.epubGen = epubGen;
|
|
1278
|
+
exports.errors = errors;
|
|
1279
|
+
exports.gen = gen;
|