@wzyjs/utils 0.3.31 → 0.3.36
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/dist/common/base/string.d.ts +1 -0
- package/dist/node/cors.d.ts +14 -0
- package/dist/node/crawl/fanqie/book/categories.d.ts +3 -0
- package/dist/node/crawl/fanqie/book/detail.d.ts +2 -0
- package/dist/node/crawl/fanqie/book/index.d.ts +5 -0
- package/dist/node/crawl/fanqie/book/library.d.ts +5 -0
- package/dist/node/crawl/fanqie/book/normalize.d.ts +22 -0
- package/dist/node/crawl/fanqie/book/search.d.ts +11 -0
- package/dist/node/crawl/fanqie/chapter/directory.d.ts +2 -0
- package/dist/node/crawl/fanqie/chapter/index.d.ts +1 -0
- package/dist/node/crawl/fanqie/content/index.d.ts +1 -0
- package/dist/node/crawl/fanqie/content/sidecar.d.ts +2 -0
- package/dist/node/crawl/fanqie/index.d.ts +4 -0
- package/dist/node/crawl/fanqie/types.d.ts +74 -0
- package/dist/node/crawl/fanqie/utils/book-source.d.ts +12 -0
- package/dist/node/crawl/fanqie/utils/consts.d.ts +40 -0
- package/dist/node/crawl/fanqie/utils/font-map.d.ts +2 -0
- package/dist/node/crawl/fanqie/utils/http.d.ts +1 -0
- package/dist/node/crawl/fanqie/utils/index.d.ts +2 -0
- package/dist/node/crawl/fanqie/utils/normalize.d.ts +2 -0
- package/dist/node/crawl/index.d.ts +1 -0
- package/dist/node/file/index.d.ts +16 -3
- package/dist/node.cjs.js +1126 -57
- package/dist/node.d.ts +2 -0
- package/dist/node.esm.js +1126 -57
- package/dist/web.cjs.js +7 -0
- package/dist/web.esm.js +7 -0
- package/package.json +4 -3
package/dist/node.esm.js
CHANGED
|
@@ -10240,6 +10240,12 @@ var replaceByVariables = (prompt, variables) => {
|
|
|
10240
10240
|
return variables[p1] ?? match;
|
|
10241
10241
|
});
|
|
10242
10242
|
};
|
|
10243
|
+
var getSafeCallbackPath = (value, fallback) => {
|
|
10244
|
+
if (!value || !value.startsWith("/") || value.startsWith("//")) {
|
|
10245
|
+
return fallback;
|
|
10246
|
+
}
|
|
10247
|
+
return value;
|
|
10248
|
+
};
|
|
10243
10249
|
var getType = (value) => {
|
|
10244
10250
|
return Object.prototype.toString.call(value).slice(8, -1);
|
|
10245
10251
|
};
|
|
@@ -10553,6 +10559,36 @@ var Timezone;
|
|
|
10553
10559
|
Timezone2["EuropeBerlin"] = "Europe/Berlin";
|
|
10554
10560
|
})(Timezone ||= {});
|
|
10555
10561
|
var dayjs_default = dayjs;
|
|
10562
|
+
// src/node/cors.ts
|
|
10563
|
+
var exports_cors = {};
|
|
10564
|
+
__export(exports_cors, {
|
|
10565
|
+
createCorsPreflightResponse: () => createCorsPreflightResponse,
|
|
10566
|
+
createCorsHeaders: () => createCorsHeaders,
|
|
10567
|
+
applyCorsHeaders: () => applyCorsHeaders
|
|
10568
|
+
});
|
|
10569
|
+
var DEFAULT_ALLOW_HEADERS = ["Authorization", "Content-Type", "X-TRPC-Source"];
|
|
10570
|
+
var DEFAULT_ALLOW_METHODS = ["GET", "POST", "OPTIONS"];
|
|
10571
|
+
var DEFAULT_MAX_AGE = "86400";
|
|
10572
|
+
var createCorsHeaders = (request, options = {}) => {
|
|
10573
|
+
const origin = request.headers.get("origin") ?? "*";
|
|
10574
|
+
return {
|
|
10575
|
+
"Access-Control-Allow-Headers": (options.allowHeaders ?? DEFAULT_ALLOW_HEADERS).join(", "),
|
|
10576
|
+
"Access-Control-Allow-Methods": (options.allowMethods ?? DEFAULT_ALLOW_METHODS).join(", "),
|
|
10577
|
+
"Access-Control-Allow-Origin": origin,
|
|
10578
|
+
"Access-Control-Max-Age": options.maxAge ?? DEFAULT_MAX_AGE,
|
|
10579
|
+
Vary: "Origin"
|
|
10580
|
+
};
|
|
10581
|
+
};
|
|
10582
|
+
var applyCorsHeaders = (request, response, options) => {
|
|
10583
|
+
Object.entries(createCorsHeaders(request, options)).forEach(([key2, value]) => {
|
|
10584
|
+
response.headers.set(key2, value);
|
|
10585
|
+
});
|
|
10586
|
+
return response;
|
|
10587
|
+
};
|
|
10588
|
+
var createCorsPreflightResponse = (request, options) => new Response(null, {
|
|
10589
|
+
headers: createCorsHeaders(request, options),
|
|
10590
|
+
status: 204
|
|
10591
|
+
});
|
|
10556
10592
|
// src/node/oss/index.ts
|
|
10557
10593
|
var exports_oss = {};
|
|
10558
10594
|
__export(exports_oss, {
|
|
@@ -10721,7 +10757,11 @@ var exports_file = {};
|
|
|
10721
10757
|
__export(exports_file, {
|
|
10722
10758
|
replaceContentInFile: () => replaceContentInFile,
|
|
10723
10759
|
processFile: () => processFile,
|
|
10760
|
+
parseBase64File: () => parseBase64File,
|
|
10761
|
+
normalizeDataUrlBase64: () => normalizeDataUrlBase64,
|
|
10724
10762
|
getMimeType: () => getMimeType,
|
|
10763
|
+
getExtFromMimeType: () => getExtFromMimeType,
|
|
10764
|
+
getExtFromFileName: () => getExtFromFileName,
|
|
10725
10765
|
downloadFile: () => downloadFile
|
|
10726
10766
|
});
|
|
10727
10767
|
import fs from "fs-extra";
|
|
@@ -10729,53 +10769,62 @@ import axios6 from "axios";
|
|
|
10729
10769
|
import url2 from "url";
|
|
10730
10770
|
import * as path2 from "path";
|
|
10731
10771
|
import crypto2 from "crypto";
|
|
10772
|
+
var mimeExtMap = {
|
|
10773
|
+
"image/png": ["png"],
|
|
10774
|
+
"image/jpeg": ["jpg", "jpeg"],
|
|
10775
|
+
"image/gif": ["gif"],
|
|
10776
|
+
"image/webp": ["webp"],
|
|
10777
|
+
"image/svg+xml": ["svg"],
|
|
10778
|
+
"video/mp4": ["mp4"],
|
|
10779
|
+
"audio/mpeg": ["mp3"],
|
|
10780
|
+
"application/pdf": ["pdf"],
|
|
10781
|
+
"application/msword": ["doc"],
|
|
10782
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ["docx"],
|
|
10783
|
+
"application/vnd.ms-excel": ["xls"],
|
|
10784
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["xlsx"],
|
|
10785
|
+
"application/vnd.ms-powerpoint": ["ppt"],
|
|
10786
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ["pptx"],
|
|
10787
|
+
"text/plain": ["txt"],
|
|
10788
|
+
"text/html": ["html"],
|
|
10789
|
+
"text/css": ["css"],
|
|
10790
|
+
"application/javascript": ["js"],
|
|
10791
|
+
"application/json": ["json"],
|
|
10792
|
+
"application/xml": ["xml"]
|
|
10793
|
+
};
|
|
10794
|
+
var getExtFromFileName = (fileName) => {
|
|
10795
|
+
return fileName ? path2.extname(fileName).toLowerCase() : "";
|
|
10796
|
+
};
|
|
10797
|
+
var getExtFromMimeType = (mimeType) => {
|
|
10798
|
+
const ext = mimeType ? mimeExtMap[mimeType]?.[0] : "";
|
|
10799
|
+
return ext ? `.${ext}` : "";
|
|
10800
|
+
};
|
|
10732
10801
|
var getMimeType = (fileName) => {
|
|
10733
|
-
const ext = fileName.
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
10760
|
-
case "ppt":
|
|
10761
|
-
return "application/vnd.ms-powerpoint";
|
|
10762
|
-
case "pptx":
|
|
10763
|
-
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
10764
|
-
case "txt":
|
|
10765
|
-
return "text/plain";
|
|
10766
|
-
case "html":
|
|
10767
|
-
return "text/html";
|
|
10768
|
-
case "css":
|
|
10769
|
-
return "text/css";
|
|
10770
|
-
case "js":
|
|
10771
|
-
return "application/javascript";
|
|
10772
|
-
case "json":
|
|
10773
|
-
return "application/json";
|
|
10774
|
-
case "xml":
|
|
10775
|
-
return "application/xml";
|
|
10776
|
-
default:
|
|
10777
|
-
return "application/octet-stream";
|
|
10778
|
-
}
|
|
10802
|
+
const ext = getExtFromFileName(fileName).replace(/^\./, "");
|
|
10803
|
+
const matched = Object.entries(mimeExtMap).find(([, extensions]) => extensions.includes(ext));
|
|
10804
|
+
return matched?.[0] || "application/octet-stream";
|
|
10805
|
+
};
|
|
10806
|
+
var generateUniqueFileName = (originalName = "") => {
|
|
10807
|
+
const ext = path2.extname(originalName);
|
|
10808
|
+
const nameWithoutExt = path2.basename(originalName, ext);
|
|
10809
|
+
const finalFileName = `${nameWithoutExt ? `${nameWithoutExt}_` : ""}${Date.now()}_${Math.random().toString(36).slice(2)}${ext}`;
|
|
10810
|
+
return {
|
|
10811
|
+
finalFileName,
|
|
10812
|
+
ext: ext.toLowerCase()
|
|
10813
|
+
};
|
|
10814
|
+
};
|
|
10815
|
+
var normalizeDataUrlBase64 = (value) => {
|
|
10816
|
+
const matched = value.match(/^data:([^;]+);base64,(.*)$/);
|
|
10817
|
+
return {
|
|
10818
|
+
base64: matched?.[2] || value,
|
|
10819
|
+
mimeType: matched?.[1]
|
|
10820
|
+
};
|
|
10821
|
+
};
|
|
10822
|
+
var parseBase64File = (input) => {
|
|
10823
|
+
const parsed = normalizeDataUrlBase64(input.fileBase64);
|
|
10824
|
+
return {
|
|
10825
|
+
buffer: Buffer.from(parsed.base64, "base64"),
|
|
10826
|
+
mimeType: input.mimeType || parsed.mimeType
|
|
10827
|
+
};
|
|
10779
10828
|
};
|
|
10780
10829
|
var replaceContentInFile = async (filePath, targetContent, replacement) => {
|
|
10781
10830
|
const data = await fs.readFile(filePath, "utf8");
|
|
@@ -10789,8 +10838,8 @@ var replaceContentInFile = async (filePath, targetContent, replacement) => {
|
|
|
10789
10838
|
};
|
|
10790
10839
|
var downloadFile = async (httpUrl, outputPath) => {
|
|
10791
10840
|
try {
|
|
10792
|
-
|
|
10793
|
-
|
|
10841
|
+
const parsedUrl = url2.parse(httpUrl);
|
|
10842
|
+
const fileName = path2.basename(parsedUrl.pathname);
|
|
10794
10843
|
if (!outputPath) {
|
|
10795
10844
|
if (!fs.existsSync(".tmp")) {
|
|
10796
10845
|
fs.mkdirSync(".tmp", { recursive: true });
|
|
@@ -10814,15 +10863,6 @@ var downloadFile = async (httpUrl, outputPath) => {
|
|
|
10814
10863
|
throw error;
|
|
10815
10864
|
}
|
|
10816
10865
|
};
|
|
10817
|
-
var generateUniqueFileName = (originalName = "") => {
|
|
10818
|
-
const ext = path2.extname(originalName);
|
|
10819
|
-
const nameWithoutExt = path2.basename(originalName, ext);
|
|
10820
|
-
const finalFileName = `${nameWithoutExt ? `${nameWithoutExt}_` : ""}${Date.now()}_${Math.random().toString(36).slice(2)}${ext}`;
|
|
10821
|
-
return {
|
|
10822
|
-
finalFileName,
|
|
10823
|
-
ext: ext.toLowerCase()
|
|
10824
|
-
};
|
|
10825
|
-
};
|
|
10826
10866
|
var processFile = async (params) => {
|
|
10827
10867
|
const { file, type } = params;
|
|
10828
10868
|
let buffer;
|
|
@@ -10885,7 +10925,1033 @@ var processFile = async (params) => {
|
|
|
10885
10925
|
ext
|
|
10886
10926
|
};
|
|
10887
10927
|
};
|
|
10928
|
+
// src/node/crawl/index.ts
|
|
10929
|
+
var exports_crawl = {};
|
|
10930
|
+
__export(exports_crawl, {
|
|
10931
|
+
fanqie: () => exports_fanqie
|
|
10932
|
+
});
|
|
10933
|
+
|
|
10934
|
+
// src/node/crawl/fanqie/index.ts
|
|
10935
|
+
var exports_fanqie = {};
|
|
10936
|
+
__export(exports_fanqie, {
|
|
10937
|
+
fanqieContent: () => exports_content,
|
|
10938
|
+
fanqieChapter: () => exports_chapter,
|
|
10939
|
+
fanqieBook: () => exports_book
|
|
10940
|
+
});
|
|
10941
|
+
|
|
10942
|
+
// src/node/crawl/fanqie/book/index.ts
|
|
10943
|
+
var exports_book = {};
|
|
10944
|
+
__export(exports_book, {
|
|
10945
|
+
searchFanqieBooks: () => searchFanqieBooks,
|
|
10946
|
+
resolveFanqieLibraryCategoryId: () => resolveFanqieLibraryCategoryId,
|
|
10947
|
+
normalizeFanqieSearchResults: () => normalizeFanqieSearchResults,
|
|
10948
|
+
normalizeFanqieOfficialLibraryItem: () => normalizeFanqieOfficialLibraryItem,
|
|
10949
|
+
normalizeFanqieBookTags: () => normalizeFanqieBookTags,
|
|
10950
|
+
normalizeFanqieBookStatus: () => normalizeFanqieBookStatus,
|
|
10951
|
+
normalizeFanqieBookSearchItem: () => normalizeFanqieBookSearchItem,
|
|
10952
|
+
normalizeFanqieBookDetailItem: () => normalizeFanqieBookDetailItem,
|
|
10953
|
+
listFanqieLibraryBooks: () => listFanqieLibraryBooks,
|
|
10954
|
+
getFanqieLibraryFilters: () => getFanqieLibraryFilters,
|
|
10955
|
+
getFanqieBookDetailById: () => getFanqieBookDetailById,
|
|
10956
|
+
createFanqieSearchUrl: () => createFanqieSearchUrl
|
|
10957
|
+
});
|
|
10958
|
+
|
|
10959
|
+
// src/node/crawl/fanqie/utils/consts.ts
|
|
10960
|
+
var FANQIE_LIBRARY_LIST_URL = "https://fanqienovel.com/api/author/library/book_list/v0/";
|
|
10961
|
+
var FANQIE_LIBRARY_CATEGORY_URL = "https://fanqienovel.com/api/author/book/category_list/v0/";
|
|
10962
|
+
var FANQIE_BOOK_SOURCE_SEARCH_PATH = "/search";
|
|
10963
|
+
var FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_SOURCE = "番茄";
|
|
10964
|
+
var FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_TAB = "小说";
|
|
10965
|
+
var FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_DISABLED_SOURCES = "0";
|
|
10966
|
+
var FANQIE_BOOK_SOURCE_CATALOG_PATH = "/catalog";
|
|
10967
|
+
var FANQIE_BOOK_SOURCE_CATALOG_SOURCE = "番茄";
|
|
10968
|
+
var FANQIE_BOOK_SOURCE_CATALOG_TAB = "小说";
|
|
10969
|
+
var FANQIE_BOOK_SOURCE_CATALOG_VARIABLE = { custom: "" };
|
|
10970
|
+
var FANQIE_BOOK_SOURCE_DETAIL_PATH = "/detail";
|
|
10971
|
+
var FANQIE_BOOK_SOURCE_DETAIL_SOURCE = "番茄";
|
|
10972
|
+
var FANQIE_BOOK_SOURCE_DETAIL_TAB = "小说";
|
|
10973
|
+
var FANQIE_BOOK_SOURCE_DETAIL_VARIABLE = { custom: "" };
|
|
10974
|
+
var DEFAULT_FANQIE_BOOK_SOURCE_BASE_URLS = [
|
|
10975
|
+
"https://api.langge.cf",
|
|
10976
|
+
"https://v2.czyl.cf",
|
|
10977
|
+
"http://219.154.201.122:5006"
|
|
10978
|
+
];
|
|
10979
|
+
var FANQIE_PAGE_HEADERS = {
|
|
10980
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
10981
|
+
Referer: "https://fanqienovel.com/"
|
|
10982
|
+
};
|
|
10983
|
+
var FANQIE_BOOK_SOURCE_SEARCH_HEADERS = {
|
|
10984
|
+
"user-agent": "Mozilla/5.0"
|
|
10985
|
+
};
|
|
10986
|
+
var FANQIE_BOOK_SOURCE_CATALOG_HEADERS = {
|
|
10987
|
+
"content-type": "application/json",
|
|
10988
|
+
"user-agent": "Mozilla/5.0"
|
|
10989
|
+
};
|
|
10990
|
+
var FANQIE_BOOK_SOURCE_DETAIL_HEADERS = {
|
|
10991
|
+
"content-type": "application/json",
|
|
10992
|
+
"user-agent": "Mozilla/5.0"
|
|
10993
|
+
};
|
|
10994
|
+
var DEFAULT_FANQIE_OFFICIAL_SIDECAR_BASE_URL = "http://127.0.0.1:18423";
|
|
10995
|
+
var DEFAULT_FANQIE_OFFICIAL_SIDECAR_CONTAINER = "tomato-novel-webui";
|
|
10996
|
+
var DEFAULT_FANQIE_OFFICIAL_SIDECAR_JOB_TIMEOUT_MS = 5 * 60 * 1000;
|
|
10997
|
+
var DEFAULT_FANQIE_OFFICIAL_SIDECAR_JOB_POLL_INTERVAL_MS = 1500;
|
|
10998
|
+
var DEFAULT_FANQIE_OFFICIAL_SIDECAR_STATUS_TIMEOUT_MS = 15 * 1000;
|
|
10999
|
+
var DEFAULT_FANQIE_OFFICIAL_SIDECAR_STATUS_POLL_INTERVAL_MS = 1000;
|
|
11000
|
+
|
|
11001
|
+
// src/node/crawl/fanqie/utils/http.ts
|
|
11002
|
+
var fetchFanqieJson = async (url3, init) => {
|
|
11003
|
+
const response = await fetch(url3, {
|
|
11004
|
+
...init,
|
|
11005
|
+
cache: "no-store"
|
|
11006
|
+
});
|
|
11007
|
+
if (!response.ok) {
|
|
11008
|
+
throw new Error(`番茄请求失败: ${response.status} ${url3}`);
|
|
11009
|
+
}
|
|
11010
|
+
return await response.json();
|
|
11011
|
+
};
|
|
11012
|
+
|
|
11013
|
+
// src/node/crawl/fanqie/utils/font-map.ts
|
|
11014
|
+
var FANQIE_OBFUSCATED_FONT_MAP = {
|
|
11015
|
+
"": "D",
|
|
11016
|
+
"": "在",
|
|
11017
|
+
"": "主",
|
|
11018
|
+
"": "特",
|
|
11019
|
+
"": "家",
|
|
11020
|
+
"": "军",
|
|
11021
|
+
"": "然",
|
|
11022
|
+
"": "表",
|
|
11023
|
+
"": "场",
|
|
11024
|
+
"": "4",
|
|
11025
|
+
"": "要",
|
|
11026
|
+
"": "只",
|
|
11027
|
+
"": "v",
|
|
11028
|
+
"": "和",
|
|
11029
|
+
"": "6",
|
|
11030
|
+
"": "别",
|
|
11031
|
+
"": "还",
|
|
11032
|
+
"": "g",
|
|
11033
|
+
"": "现",
|
|
11034
|
+
"": "儿",
|
|
11035
|
+
"": "岁",
|
|
11036
|
+
"": "此",
|
|
11037
|
+
"": "象",
|
|
11038
|
+
"": "月",
|
|
11039
|
+
"": "3",
|
|
11040
|
+
"": "出",
|
|
11041
|
+
"": "战",
|
|
11042
|
+
"": "工",
|
|
11043
|
+
"": "相",
|
|
11044
|
+
"": "o",
|
|
11045
|
+
"": "男",
|
|
11046
|
+
"": "直",
|
|
11047
|
+
"": "失",
|
|
11048
|
+
"": "世",
|
|
11049
|
+
"": "F",
|
|
11050
|
+
"": "都",
|
|
11051
|
+
"": "平",
|
|
11052
|
+
"": "文",
|
|
11053
|
+
"": "什",
|
|
11054
|
+
"": "V",
|
|
11055
|
+
"": "O",
|
|
11056
|
+
"": "将",
|
|
11057
|
+
"": "真",
|
|
11058
|
+
"": "T",
|
|
11059
|
+
"": "那",
|
|
11060
|
+
"": "当",
|
|
11061
|
+
"": "会",
|
|
11062
|
+
"": "立",
|
|
11063
|
+
"": "些",
|
|
11064
|
+
"": "u",
|
|
11065
|
+
"": "是",
|
|
11066
|
+
"": "十",
|
|
11067
|
+
"": "张",
|
|
11068
|
+
"": "学",
|
|
11069
|
+
"": "气",
|
|
11070
|
+
"": "大",
|
|
11071
|
+
"": "爱",
|
|
11072
|
+
"": "两",
|
|
11073
|
+
"": "命",
|
|
11074
|
+
"": "全",
|
|
11075
|
+
"": "后",
|
|
11076
|
+
"": "东",
|
|
11077
|
+
"": "性",
|
|
11078
|
+
"": "通",
|
|
11079
|
+
"": "被",
|
|
11080
|
+
"": "1",
|
|
11081
|
+
"": "它",
|
|
11082
|
+
"": "乐",
|
|
11083
|
+
"": "接",
|
|
11084
|
+
"": "而",
|
|
11085
|
+
"": "感",
|
|
11086
|
+
"": "车",
|
|
11087
|
+
"": "山",
|
|
11088
|
+
"": "公",
|
|
11089
|
+
"": "了",
|
|
11090
|
+
"": "常",
|
|
11091
|
+
"": "以",
|
|
11092
|
+
"": "何",
|
|
11093
|
+
"": "可",
|
|
11094
|
+
"": "话",
|
|
11095
|
+
"": "先",
|
|
11096
|
+
"": "p",
|
|
11097
|
+
"": "i",
|
|
11098
|
+
"": "叫",
|
|
11099
|
+
"": "轻",
|
|
11100
|
+
"": "M",
|
|
11101
|
+
"": "士",
|
|
11102
|
+
"": "w",
|
|
11103
|
+
"": "着",
|
|
11104
|
+
"": "变",
|
|
11105
|
+
"": "尔",
|
|
11106
|
+
"": "快",
|
|
11107
|
+
"": "l",
|
|
11108
|
+
"": "个",
|
|
11109
|
+
"": "说",
|
|
11110
|
+
"": "少",
|
|
11111
|
+
"": "色",
|
|
11112
|
+
"": "里",
|
|
11113
|
+
"": "安",
|
|
11114
|
+
"": "花",
|
|
11115
|
+
"": "远",
|
|
11116
|
+
"": "7",
|
|
11117
|
+
"": "难",
|
|
11118
|
+
"": "师",
|
|
11119
|
+
"": "放",
|
|
11120
|
+
"": "t",
|
|
11121
|
+
"": "报",
|
|
11122
|
+
"": "认",
|
|
11123
|
+
"": "面",
|
|
11124
|
+
"": "道",
|
|
11125
|
+
"": "S",
|
|
11126
|
+
"": "克",
|
|
11127
|
+
"": "地",
|
|
11128
|
+
"": "度",
|
|
11129
|
+
"": "I",
|
|
11130
|
+
"": "好",
|
|
11131
|
+
"": "机",
|
|
11132
|
+
"": "U",
|
|
11133
|
+
"": "民",
|
|
11134
|
+
"": "写",
|
|
11135
|
+
"": "把",
|
|
11136
|
+
"": "万",
|
|
11137
|
+
"": "同",
|
|
11138
|
+
"": "水",
|
|
11139
|
+
"": "新",
|
|
11140
|
+
"": "没",
|
|
11141
|
+
"": "书",
|
|
11142
|
+
"": "电",
|
|
11143
|
+
"": "吃",
|
|
11144
|
+
"": "像",
|
|
11145
|
+
"": "斯",
|
|
11146
|
+
"": "5",
|
|
11147
|
+
"": "为",
|
|
11148
|
+
"": "y",
|
|
11149
|
+
"": "白",
|
|
11150
|
+
"": "几",
|
|
11151
|
+
"": "日",
|
|
11152
|
+
"": "教",
|
|
11153
|
+
"": "看",
|
|
11154
|
+
"": "但",
|
|
11155
|
+
"": "第",
|
|
11156
|
+
"": "加",
|
|
11157
|
+
"": "候",
|
|
11158
|
+
"": "作",
|
|
11159
|
+
"": "上",
|
|
11160
|
+
"": "拉",
|
|
11161
|
+
"": "住",
|
|
11162
|
+
"": "有",
|
|
11163
|
+
"": "法",
|
|
11164
|
+
"": "r",
|
|
11165
|
+
"": "事",
|
|
11166
|
+
"": "应",
|
|
11167
|
+
"": "位",
|
|
11168
|
+
"": "利",
|
|
11169
|
+
"": "你",
|
|
11170
|
+
"": "声",
|
|
11171
|
+
"": "身",
|
|
11172
|
+
"": "国",
|
|
11173
|
+
"": "问",
|
|
11174
|
+
"": "马",
|
|
11175
|
+
"": "女",
|
|
11176
|
+
"": "他",
|
|
11177
|
+
"": "Y",
|
|
11178
|
+
"": "比",
|
|
11179
|
+
"": "父",
|
|
11180
|
+
"": "x",
|
|
11181
|
+
"": "A",
|
|
11182
|
+
"": "H",
|
|
11183
|
+
"": "N",
|
|
11184
|
+
"": "s",
|
|
11185
|
+
"": "X",
|
|
11186
|
+
"": "边",
|
|
11187
|
+
"": "美",
|
|
11188
|
+
"": "对",
|
|
11189
|
+
"": "所",
|
|
11190
|
+
"": "金",
|
|
11191
|
+
"": "活",
|
|
11192
|
+
"": "回",
|
|
11193
|
+
"": "意",
|
|
11194
|
+
"": "到",
|
|
11195
|
+
"": "z",
|
|
11196
|
+
"": "从",
|
|
11197
|
+
"": "j",
|
|
11198
|
+
"": "知",
|
|
11199
|
+
"": "又",
|
|
11200
|
+
"": "内",
|
|
11201
|
+
"": "因",
|
|
11202
|
+
"": "点",
|
|
11203
|
+
"": "Q",
|
|
11204
|
+
"": "三",
|
|
11205
|
+
"": "定",
|
|
11206
|
+
"": "8",
|
|
11207
|
+
"": "R",
|
|
11208
|
+
"": "b",
|
|
11209
|
+
"": "正",
|
|
11210
|
+
"": "或",
|
|
11211
|
+
"": "夫",
|
|
11212
|
+
"": "向",
|
|
11213
|
+
"": "德",
|
|
11214
|
+
"": "听",
|
|
11215
|
+
"": "更",
|
|
11216
|
+
"": "得",
|
|
11217
|
+
"": "告",
|
|
11218
|
+
"": "并",
|
|
11219
|
+
"": "本",
|
|
11220
|
+
"": "q",
|
|
11221
|
+
"": "过",
|
|
11222
|
+
"": "记",
|
|
11223
|
+
"": "L",
|
|
11224
|
+
"": "让",
|
|
11225
|
+
"": "打",
|
|
11226
|
+
"": "f",
|
|
11227
|
+
"": "人",
|
|
11228
|
+
"": "就",
|
|
11229
|
+
"": "者",
|
|
11230
|
+
"": "去",
|
|
11231
|
+
"": "原",
|
|
11232
|
+
"": "满",
|
|
11233
|
+
"": "体",
|
|
11234
|
+
"": "做",
|
|
11235
|
+
"": "经",
|
|
11236
|
+
"": "K",
|
|
11237
|
+
"": "走",
|
|
11238
|
+
"": "如",
|
|
11239
|
+
"": "孩",
|
|
11240
|
+
"": "c",
|
|
11241
|
+
"": "G",
|
|
11242
|
+
"": "给",
|
|
11243
|
+
"": "使",
|
|
11244
|
+
"": "物",
|
|
11245
|
+
"": "最",
|
|
11246
|
+
"": "笑",
|
|
11247
|
+
"": "部",
|
|
11248
|
+
"": "员",
|
|
11249
|
+
"": "等",
|
|
11250
|
+
"": "受",
|
|
11251
|
+
"": "k",
|
|
11252
|
+
"": "行",
|
|
11253
|
+
"": "一",
|
|
11254
|
+
"": "条",
|
|
11255
|
+
"": "果",
|
|
11256
|
+
"": "动",
|
|
11257
|
+
"": "光",
|
|
11258
|
+
"": "门",
|
|
11259
|
+
"": "头",
|
|
11260
|
+
"": "见",
|
|
11261
|
+
"": "往",
|
|
11262
|
+
"": "自",
|
|
11263
|
+
"": "解",
|
|
11264
|
+
"": "成",
|
|
11265
|
+
"": "处",
|
|
11266
|
+
"": "天",
|
|
11267
|
+
"": "能",
|
|
11268
|
+
"": "于",
|
|
11269
|
+
"": "名",
|
|
11270
|
+
"": "其",
|
|
11271
|
+
"": "发",
|
|
11272
|
+
"": "总",
|
|
11273
|
+
"": "母",
|
|
11274
|
+
"": "的",
|
|
11275
|
+
"": "死",
|
|
11276
|
+
"": "手",
|
|
11277
|
+
"": "入",
|
|
11278
|
+
"": "路",
|
|
11279
|
+
"": "进",
|
|
11280
|
+
"": "心",
|
|
11281
|
+
"": "来",
|
|
11282
|
+
"": "h",
|
|
11283
|
+
"": "时",
|
|
11284
|
+
"": "力",
|
|
11285
|
+
"": "多",
|
|
11286
|
+
"": "开",
|
|
11287
|
+
"": "已",
|
|
11288
|
+
"": "许",
|
|
11289
|
+
"": "d",
|
|
11290
|
+
"": "至",
|
|
11291
|
+
"": "由",
|
|
11292
|
+
"": "很",
|
|
11293
|
+
"": "界",
|
|
11294
|
+
"": "n",
|
|
11295
|
+
"": "小",
|
|
11296
|
+
"": "与",
|
|
11297
|
+
"": "Z",
|
|
11298
|
+
"": "想",
|
|
11299
|
+
"": "代",
|
|
11300
|
+
"": "么",
|
|
11301
|
+
"": "分",
|
|
11302
|
+
"": "生",
|
|
11303
|
+
"": "口",
|
|
11304
|
+
"": "再",
|
|
11305
|
+
"": "妈",
|
|
11306
|
+
"": "望",
|
|
11307
|
+
"": "次",
|
|
11308
|
+
"": "西",
|
|
11309
|
+
"": "风",
|
|
11310
|
+
"": "种",
|
|
11311
|
+
"": "带",
|
|
11312
|
+
"": "J",
|
|
11313
|
+
"": "实",
|
|
11314
|
+
"": "情",
|
|
11315
|
+
"": "才",
|
|
11316
|
+
"": "这",
|
|
11317
|
+
"": "E",
|
|
11318
|
+
"": "我",
|
|
11319
|
+
"": "神",
|
|
11320
|
+
"": "格",
|
|
11321
|
+
"": "长",
|
|
11322
|
+
"": "觉",
|
|
11323
|
+
"": "间",
|
|
11324
|
+
"": "年",
|
|
11325
|
+
"": "眼",
|
|
11326
|
+
"": "无",
|
|
11327
|
+
"": "不",
|
|
11328
|
+
"": "亲",
|
|
11329
|
+
"": "关",
|
|
11330
|
+
"": "结",
|
|
11331
|
+
"": "0",
|
|
11332
|
+
"": "友",
|
|
11333
|
+
"": "信",
|
|
11334
|
+
"": "下",
|
|
11335
|
+
"": "却",
|
|
11336
|
+
"": "重",
|
|
11337
|
+
"": "己",
|
|
11338
|
+
"": "老",
|
|
11339
|
+
"": "2",
|
|
11340
|
+
"": "音",
|
|
11341
|
+
"": "字",
|
|
11342
|
+
"": "m",
|
|
11343
|
+
"": "呢",
|
|
11344
|
+
"": "明",
|
|
11345
|
+
"": "之",
|
|
11346
|
+
"": "前",
|
|
11347
|
+
"": "高",
|
|
11348
|
+
"": "P",
|
|
11349
|
+
"": "B",
|
|
11350
|
+
"": "目",
|
|
11351
|
+
"": "太",
|
|
11352
|
+
"": "e",
|
|
11353
|
+
"": "9",
|
|
11354
|
+
"": "起",
|
|
11355
|
+
"": "稜",
|
|
11356
|
+
"": "她",
|
|
11357
|
+
"": "也",
|
|
11358
|
+
"": "W",
|
|
11359
|
+
"": "用",
|
|
11360
|
+
"": "方",
|
|
11361
|
+
"": "子",
|
|
11362
|
+
"": "英",
|
|
11363
|
+
"": "每",
|
|
11364
|
+
"": "理",
|
|
11365
|
+
"": "便",
|
|
11366
|
+
"": "四",
|
|
11367
|
+
"": "数",
|
|
11368
|
+
"": "期",
|
|
11369
|
+
"": "中",
|
|
11370
|
+
"": "C",
|
|
11371
|
+
"": "外",
|
|
11372
|
+
"": "样",
|
|
11373
|
+
"": "a",
|
|
11374
|
+
"": "海",
|
|
11375
|
+
"": "们",
|
|
11376
|
+
"": "任"
|
|
11377
|
+
};
|
|
11378
|
+
var restoreFanqieObfuscatedText = (content) => {
|
|
11379
|
+
return Array.from(content).map((char) => FANQIE_OBFUSCATED_FONT_MAP[char] || char).join("");
|
|
11380
|
+
};
|
|
11381
|
+
|
|
11382
|
+
// src/node/crawl/fanqie/utils/normalize.ts
|
|
11383
|
+
var normalizeFanqieText = (value) => {
|
|
11384
|
+
if (value === null || value === undefined) {
|
|
11385
|
+
return null;
|
|
11386
|
+
}
|
|
11387
|
+
const normalized = restoreFanqieObfuscatedText(String(value)).trim();
|
|
11388
|
+
return normalized || null;
|
|
11389
|
+
};
|
|
11390
|
+
var normalizeFanqieCoverUrl = (value) => {
|
|
11391
|
+
const url3 = value ? String(value).trim() : "";
|
|
11392
|
+
if (!url3) {
|
|
11393
|
+
return null;
|
|
11394
|
+
}
|
|
11395
|
+
return url3.replace(/^http:\/\//, "https://");
|
|
11396
|
+
};
|
|
11397
|
+
|
|
11398
|
+
// src/node/crawl/fanqie/book/categories.ts
|
|
11399
|
+
var getCategoryGroupKey = (label) => {
|
|
11400
|
+
if (label === "主题") {
|
|
11401
|
+
return "topic";
|
|
11402
|
+
}
|
|
11403
|
+
if (label === "角色") {
|
|
11404
|
+
return "role";
|
|
11405
|
+
}
|
|
11406
|
+
if (label === "情节") {
|
|
11407
|
+
return "plot";
|
|
11408
|
+
}
|
|
11409
|
+
return null;
|
|
11410
|
+
};
|
|
11411
|
+
var createEmptyLibraryFilters = () => ({
|
|
11412
|
+
topic: [],
|
|
11413
|
+
role: [],
|
|
11414
|
+
plot: []
|
|
11415
|
+
});
|
|
11416
|
+
var normalizeFanqieLibraryFilters = (payload) => {
|
|
11417
|
+
const list = Array.isArray(payload) ? payload : [];
|
|
11418
|
+
const groups = createEmptyLibraryFilters();
|
|
11419
|
+
list.forEach((item) => {
|
|
11420
|
+
const label = String(item?.label || "").trim();
|
|
11421
|
+
const groupKey = getCategoryGroupKey(label);
|
|
11422
|
+
const categoryId = Number(item?.category_id);
|
|
11423
|
+
const name = normalizeFanqieText(item?.name);
|
|
11424
|
+
if (!groupKey || !Number.isInteger(categoryId) || !name) {
|
|
11425
|
+
return;
|
|
11426
|
+
}
|
|
11427
|
+
groups[groupKey].push({
|
|
11428
|
+
categoryId,
|
|
11429
|
+
label,
|
|
11430
|
+
name,
|
|
11431
|
+
coverUrl: normalizeFanqieCoverUrl(item?.cover_uri)
|
|
11432
|
+
});
|
|
11433
|
+
});
|
|
11434
|
+
return groups;
|
|
11435
|
+
};
|
|
11436
|
+
var getFanqieLibraryFilters = async (gender) => {
|
|
11437
|
+
const url3 = new URL(FANQIE_LIBRARY_CATEGORY_URL);
|
|
11438
|
+
url3.searchParams.set("gender", String(gender));
|
|
11439
|
+
const data = await fetchFanqieJson(url3.toString(), {
|
|
11440
|
+
headers: FANQIE_PAGE_HEADERS
|
|
11441
|
+
});
|
|
11442
|
+
return normalizeFanqieLibraryFilters(data?.data);
|
|
11443
|
+
};
|
|
11444
|
+
var resolveFanqieLibraryCategoryId = async ({
|
|
11445
|
+
gender,
|
|
11446
|
+
categoryGroup,
|
|
11447
|
+
categoryId
|
|
11448
|
+
}) => {
|
|
11449
|
+
if (categoryGroup === "all") {
|
|
11450
|
+
return -1;
|
|
11451
|
+
}
|
|
11452
|
+
const groups = await getFanqieLibraryFilters(gender);
|
|
11453
|
+
const currentGroup = groups[categoryGroup];
|
|
11454
|
+
if (currentGroup.length === 0) {
|
|
11455
|
+
return -1;
|
|
11456
|
+
}
|
|
11457
|
+
const matchedItem = typeof categoryId === "number" ? currentGroup.find((item) => item.categoryId === categoryId) : null;
|
|
11458
|
+
return matchedItem?.categoryId || currentGroup[0]?.categoryId || -1;
|
|
11459
|
+
};
|
|
11460
|
+
// src/node/crawl/fanqie/utils/book-source.ts
|
|
11461
|
+
var getFanqieBookSourceBaseUrls = () => {
|
|
11462
|
+
const raw = process.env.FANQIE_BOOK_SOURCE_BASE_URLS?.trim() || process.env.FANQIE_BOOK_SOURCE_DETAIL_BASE_URLS?.trim();
|
|
11463
|
+
if (!raw) {
|
|
11464
|
+
return [...DEFAULT_FANQIE_BOOK_SOURCE_BASE_URLS];
|
|
11465
|
+
}
|
|
11466
|
+
const list = raw.split(",").map((item) => item.trim()).filter(Boolean);
|
|
11467
|
+
return list.length > 0 ? list : [...DEFAULT_FANQIE_BOOK_SOURCE_BASE_URLS];
|
|
11468
|
+
};
|
|
11469
|
+
var encodeFanqieBookSourceBookId = (fanqieBookId) => {
|
|
11470
|
+
return Buffer.from(fanqieBookId, "utf8").toString("base64");
|
|
11471
|
+
};
|
|
11472
|
+
var decodeFanqieBookSourceBookId = (encodedBookId) => {
|
|
11473
|
+
const raw = encodedBookId ? String(encodedBookId).trim() : "";
|
|
11474
|
+
if (!raw) {
|
|
11475
|
+
return "";
|
|
11476
|
+
}
|
|
11477
|
+
try {
|
|
11478
|
+
const normalized = raw.replace(/-/g, "+").replace(/_/g, "/").padEnd(raw.length + (4 - raw.length % 4) % 4, "=");
|
|
11479
|
+
const decoded = Buffer.from(normalized, "base64").toString("utf8").trim();
|
|
11480
|
+
return /^\d+$/.test(decoded) ? decoded : raw;
|
|
11481
|
+
} catch {
|
|
11482
|
+
return raw;
|
|
11483
|
+
}
|
|
11484
|
+
};
|
|
11485
|
+
var createFanqieBookSourceUrl = (baseUrl, path3, searchParams) => {
|
|
11486
|
+
const url3 = new URL(path3, baseUrl);
|
|
11487
|
+
Object.entries(searchParams).forEach(([key2, value]) => {
|
|
11488
|
+
url3.searchParams.set(key2, value);
|
|
11489
|
+
});
|
|
11490
|
+
return url3.toString();
|
|
11491
|
+
};
|
|
11492
|
+
var requestFanqieBookSourceJson = async ({
|
|
11493
|
+
path: path3,
|
|
11494
|
+
searchParams,
|
|
11495
|
+
init,
|
|
11496
|
+
baseUrl,
|
|
11497
|
+
errorPrefix,
|
|
11498
|
+
validate
|
|
11499
|
+
}) => {
|
|
11500
|
+
const baseUrls = baseUrl ? [baseUrl] : getFanqieBookSourceBaseUrls();
|
|
11501
|
+
const errors2 = [];
|
|
11502
|
+
for (const currentBaseUrl of baseUrls) {
|
|
11503
|
+
try {
|
|
11504
|
+
const url3 = createFanqieBookSourceUrl(currentBaseUrl, path3, searchParams);
|
|
11505
|
+
const response = await fetchFanqieJson(url3, init);
|
|
11506
|
+
validate?.(response);
|
|
11507
|
+
return response;
|
|
11508
|
+
} catch (error) {
|
|
11509
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11510
|
+
errors2.push(`${currentBaseUrl}: ${message}`);
|
|
11511
|
+
}
|
|
11512
|
+
}
|
|
11513
|
+
throw new Error(`${errorPrefix}: ${errors2.join(" | ")}`);
|
|
11514
|
+
};
|
|
11515
|
+
|
|
11516
|
+
// src/node/crawl/fanqie/book/normalize.ts
|
|
11517
|
+
var normalizeFanqieSearchTitle = (value) => {
|
|
11518
|
+
const normalized = normalizeFanqieText(value);
|
|
11519
|
+
if (!normalized) {
|
|
11520
|
+
return "";
|
|
11521
|
+
}
|
|
11522
|
+
return normalized.replace(/(别名:.*?)/g, "").trim();
|
|
11523
|
+
};
|
|
11524
|
+
var normalizeFanqieBookStatus = (value) => {
|
|
11525
|
+
const normalized = normalizeFanqieText(value);
|
|
11526
|
+
if (!normalized) {
|
|
11527
|
+
return null;
|
|
11528
|
+
}
|
|
11529
|
+
if (normalized.includes("完结")) {
|
|
11530
|
+
return 1;
|
|
11531
|
+
}
|
|
11532
|
+
if (normalized.includes("连载")) {
|
|
11533
|
+
return 0;
|
|
11534
|
+
}
|
|
11535
|
+
return null;
|
|
11536
|
+
};
|
|
11537
|
+
var normalizeFanqieBookTags = (value) => {
|
|
11538
|
+
const normalized = normalizeFanqieText(value);
|
|
11539
|
+
if (!normalized) {
|
|
11540
|
+
return [];
|
|
11541
|
+
}
|
|
11542
|
+
return normalized.split(/[,,、\s]+/).map((item) => item.trim()).filter(Boolean);
|
|
11543
|
+
};
|
|
11544
|
+
var createFanqieBookItemBase = (payload) => ({
|
|
11545
|
+
author: normalizeFanqieText(payload.author),
|
|
11546
|
+
score: normalizeFanqieText(payload.score),
|
|
11547
|
+
coverUrl: normalizeFanqieCoverUrl(payload.thumb_url),
|
|
11548
|
+
description: normalizeFanqieText(payload.abstract),
|
|
11549
|
+
category: normalizeFanqieText(payload.category),
|
|
11550
|
+
tags: normalizeFanqieBookTags(payload.tags),
|
|
11551
|
+
wordCount: normalizeFanqieText(payload.word_number),
|
|
11552
|
+
readCount: normalizeFanqieText(payload.read_count),
|
|
11553
|
+
readCountAll: normalizeFanqieText(payload.read_count_all),
|
|
11554
|
+
creationStatus: normalizeFanqieBookStatus(payload.status),
|
|
11555
|
+
lastChapterTime: normalizeFanqieText(payload.last_chapter_update_time),
|
|
11556
|
+
rawTitle: payload.book_name ? String(payload.book_name) : null,
|
|
11557
|
+
rawAuthor: payload.author ? String(payload.author) : null,
|
|
11558
|
+
rawDescription: payload.abstract ? String(payload.abstract) : null,
|
|
11559
|
+
rawCategory: payload.category ? String(payload.category) : null,
|
|
11560
|
+
rawScore: payload.score ? String(payload.score) : null,
|
|
11561
|
+
rawWordCount: payload.word_number ? String(payload.word_number) : null,
|
|
11562
|
+
rawReadCount: payload.read_count ? String(payload.read_count) : null,
|
|
11563
|
+
rawReadCountAll: payload.read_count_all ? String(payload.read_count_all) : null
|
|
11564
|
+
});
|
|
11565
|
+
var normalizeFanqieBookSearchItem = (payload) => {
|
|
11566
|
+
const id = decodeFanqieBookSourceBookId(payload.book_id);
|
|
11567
|
+
const title = normalizeFanqieSearchTitle(payload.book_name);
|
|
11568
|
+
if (!id || !title) {
|
|
11569
|
+
return null;
|
|
11570
|
+
}
|
|
11571
|
+
return {
|
|
11572
|
+
id,
|
|
11573
|
+
title,
|
|
11574
|
+
...createFanqieBookItemBase(payload)
|
|
11575
|
+
};
|
|
11576
|
+
};
|
|
11577
|
+
var normalizeFanqieBookDetailItem = (fanqieBookId, payload) => {
|
|
11578
|
+
const title = normalizeFanqieText(payload.book_name);
|
|
11579
|
+
if (!title) {
|
|
11580
|
+
throw new Error("书源详情缺少书名");
|
|
11581
|
+
}
|
|
11582
|
+
return {
|
|
11583
|
+
id: fanqieBookId,
|
|
11584
|
+
title,
|
|
11585
|
+
...createFanqieBookItemBase(payload)
|
|
11586
|
+
};
|
|
11587
|
+
};
|
|
11588
|
+
var normalizeFanqieOfficialLibraryItem = (payload) => {
|
|
11589
|
+
const id = payload?.book_id ? String(payload.book_id).trim() : "";
|
|
11590
|
+
const title = normalizeFanqieText(payload?.book_name);
|
|
11591
|
+
if (!id || !title) {
|
|
11592
|
+
return null;
|
|
11593
|
+
}
|
|
11594
|
+
return {
|
|
11595
|
+
id,
|
|
11596
|
+
title,
|
|
11597
|
+
author: normalizeFanqieText(payload?.author),
|
|
11598
|
+
coverUrl: normalizeFanqieCoverUrl(payload?.thumb_url),
|
|
11599
|
+
description: normalizeFanqieText(payload?.abstract),
|
|
11600
|
+
category: normalizeFanqieText(payload?.category),
|
|
11601
|
+
tags: [],
|
|
11602
|
+
wordCount: normalizeFanqieText(payload?.word_count),
|
|
11603
|
+
readCount: normalizeFanqieText(payload?.read_count),
|
|
11604
|
+
readCountAll: null,
|
|
11605
|
+
creationStatus: payload?.creation_status === 0 || payload?.creation_status === 1 ? payload.creation_status : null,
|
|
11606
|
+
lastChapterTime: payload?.last_chapter_time ? String(payload.last_chapter_time) : null,
|
|
11607
|
+
rawTitle: payload?.book_name ? String(payload.book_name) : null,
|
|
11608
|
+
rawAuthor: payload?.author ? String(payload.author) : null,
|
|
11609
|
+
rawDescription: payload?.abstract ? String(payload.abstract) : null,
|
|
11610
|
+
rawCategory: payload?.category ? String(payload.category) : null,
|
|
11611
|
+
rawScore: null,
|
|
11612
|
+
rawWordCount: payload?.word_count ? String(payload.word_count) : null,
|
|
11613
|
+
rawReadCount: payload?.read_count ? String(payload.read_count) : null,
|
|
11614
|
+
rawReadCountAll: null
|
|
11615
|
+
};
|
|
11616
|
+
};
|
|
10888
11617
|
|
|
11618
|
+
// src/node/crawl/fanqie/book/detail.ts
|
|
11619
|
+
var getFanqieBookDetailById = async (fanqieBookId) => {
|
|
11620
|
+
const response = await requestFanqieBookSourceJson({
|
|
11621
|
+
path: FANQIE_BOOK_SOURCE_DETAIL_PATH,
|
|
11622
|
+
searchParams: {
|
|
11623
|
+
book_id: encodeFanqieBookSourceBookId(fanqieBookId),
|
|
11624
|
+
source: FANQIE_BOOK_SOURCE_DETAIL_SOURCE,
|
|
11625
|
+
tab: FANQIE_BOOK_SOURCE_DETAIL_TAB,
|
|
11626
|
+
variable: JSON.stringify(FANQIE_BOOK_SOURCE_DETAIL_VARIABLE)
|
|
11627
|
+
},
|
|
11628
|
+
init: {
|
|
11629
|
+
method: "POST",
|
|
11630
|
+
headers: FANQIE_BOOK_SOURCE_DETAIL_HEADERS,
|
|
11631
|
+
body: JSON.stringify({ html: "" })
|
|
11632
|
+
},
|
|
11633
|
+
errorPrefix: "番茄书籍详情获取失败",
|
|
11634
|
+
validate: (data) => {
|
|
11635
|
+
if (data?.code !== 0 || !data.data) {
|
|
11636
|
+
throw new Error(data?.msg || "书源详情返回为空");
|
|
11637
|
+
}
|
|
11638
|
+
}
|
|
11639
|
+
});
|
|
11640
|
+
return normalizeFanqieBookDetailItem(fanqieBookId, response.data);
|
|
11641
|
+
};
|
|
11642
|
+
// src/node/crawl/fanqie/book/library.ts
|
|
11643
|
+
var normalizeFanqieLibraryResults = (payload) => {
|
|
11644
|
+
const list = Array.isArray(payload) ? payload : [];
|
|
11645
|
+
return list.flatMap((item) => {
|
|
11646
|
+
const result = normalizeFanqieOfficialLibraryItem(item);
|
|
11647
|
+
return result ? [result] : [];
|
|
11648
|
+
});
|
|
11649
|
+
};
|
|
11650
|
+
var listFanqieLibraryBooks = async ({
|
|
11651
|
+
gender,
|
|
11652
|
+
categoryId,
|
|
11653
|
+
creationStatus,
|
|
11654
|
+
wordCount,
|
|
11655
|
+
sort,
|
|
11656
|
+
pageIndex,
|
|
11657
|
+
pageSize
|
|
11658
|
+
}) => {
|
|
11659
|
+
const url3 = new URL(FANQIE_LIBRARY_LIST_URL);
|
|
11660
|
+
url3.searchParams.set("page_count", String(pageSize));
|
|
11661
|
+
url3.searchParams.set("page_index", String(pageIndex));
|
|
11662
|
+
url3.searchParams.set("gender", String(gender));
|
|
11663
|
+
url3.searchParams.set("category_id", String(categoryId));
|
|
11664
|
+
url3.searchParams.set("creation_status", String(creationStatus));
|
|
11665
|
+
url3.searchParams.set("word_count", String(wordCount));
|
|
11666
|
+
url3.searchParams.set("book_type", "-1");
|
|
11667
|
+
url3.searchParams.set("sort", String(sort));
|
|
11668
|
+
const data = await fetchFanqieJson(url3.toString(), {
|
|
11669
|
+
headers: FANQIE_PAGE_HEADERS
|
|
11670
|
+
});
|
|
11671
|
+
return {
|
|
11672
|
+
results: normalizeFanqieLibraryResults(data?.data?.book_list),
|
|
11673
|
+
total: Number(data?.data?.total_count || 0)
|
|
11674
|
+
};
|
|
11675
|
+
};
|
|
11676
|
+
// src/node/crawl/fanqie/book/search.ts
|
|
11677
|
+
var createFanqieBookSourceSearchUrl = (baseUrl, query, options = {}) => {
|
|
11678
|
+
return createFanqieBookSourceUrl(baseUrl, FANQIE_BOOK_SOURCE_SEARCH_PATH, {
|
|
11679
|
+
title: query,
|
|
11680
|
+
tab: options.tab || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_TAB,
|
|
11681
|
+
source: options.source || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_SOURCE,
|
|
11682
|
+
page: String(options.page || 1),
|
|
11683
|
+
disabled_sources: options.disabledSources || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_DISABLED_SOURCES
|
|
11684
|
+
});
|
|
11685
|
+
};
|
|
11686
|
+
var normalizeFanqieSearchResults = (payload) => {
|
|
11687
|
+
const list = Array.isArray(payload) ? payload : [];
|
|
11688
|
+
return list.flatMap((item) => {
|
|
11689
|
+
const result = normalizeFanqieBookSearchItem(item);
|
|
11690
|
+
return result ? [result] : [];
|
|
11691
|
+
});
|
|
11692
|
+
};
|
|
11693
|
+
var createFanqieSearchUrl = (query, options = {}) => {
|
|
11694
|
+
const baseUrl = options.baseUrl || getFanqieBookSourceBaseUrls()[0];
|
|
11695
|
+
return createFanqieBookSourceSearchUrl(baseUrl, query, options);
|
|
11696
|
+
};
|
|
11697
|
+
var searchFanqieBooks = async (query, options = {}) => {
|
|
11698
|
+
const response = await requestFanqieBookSourceJson({
|
|
11699
|
+
path: FANQIE_BOOK_SOURCE_SEARCH_PATH,
|
|
11700
|
+
baseUrl: options.baseUrl,
|
|
11701
|
+
searchParams: {
|
|
11702
|
+
title: query,
|
|
11703
|
+
tab: options.tab || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_TAB,
|
|
11704
|
+
source: options.source || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_SOURCE,
|
|
11705
|
+
page: String(options.page || 1),
|
|
11706
|
+
disabled_sources: options.disabledSources || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_DISABLED_SOURCES
|
|
11707
|
+
},
|
|
11708
|
+
init: {
|
|
11709
|
+
method: "GET",
|
|
11710
|
+
headers: FANQIE_BOOK_SOURCE_SEARCH_HEADERS
|
|
11711
|
+
},
|
|
11712
|
+
errorPrefix: "番茄书籍搜索失败",
|
|
11713
|
+
validate: (data) => {
|
|
11714
|
+
if (data?.code !== 0) {
|
|
11715
|
+
throw new Error(data?.msg || "书源搜索失败");
|
|
11716
|
+
}
|
|
11717
|
+
}
|
|
11718
|
+
});
|
|
11719
|
+
return normalizeFanqieSearchResults(response.data);
|
|
11720
|
+
};
|
|
11721
|
+
// src/node/crawl/fanqie/chapter/index.ts
|
|
11722
|
+
var exports_chapter = {};
|
|
11723
|
+
__export(exports_chapter, {
|
|
11724
|
+
getFanqieChapterDirectory: () => getFanqieChapterDirectory
|
|
11725
|
+
});
|
|
11726
|
+
|
|
11727
|
+
// src/node/crawl/fanqie/chapter/directory.ts
|
|
11728
|
+
var extractVolumeName = (value) => {
|
|
11729
|
+
const normalized = normalizeFanqieText(value);
|
|
11730
|
+
if (!normalized) {
|
|
11731
|
+
return null;
|
|
11732
|
+
}
|
|
11733
|
+
const [volumeName] = normalized.split("|").map((item) => item.trim());
|
|
11734
|
+
return volumeName || null;
|
|
11735
|
+
};
|
|
11736
|
+
var normalizeFanqieBookSourceCatalog = (payload) => {
|
|
11737
|
+
const list = Array.isArray(payload) ? payload : [];
|
|
11738
|
+
return list.map((item, index) => ({
|
|
11739
|
+
fanqieChapterId: item?.item_id ? String(item.item_id).trim() : "",
|
|
11740
|
+
title: normalizeFanqieText(item?.title) || `第${index + 1}章`,
|
|
11741
|
+
chapterOrder: index + 1,
|
|
11742
|
+
volumeName: extractVolumeName(item?.first_pass_time)
|
|
11743
|
+
})).filter((item) => Boolean(item.fanqieChapterId));
|
|
11744
|
+
};
|
|
11745
|
+
var getFanqieChapterDirectory = async (fanqieBookId) => {
|
|
11746
|
+
const response = await requestFanqieBookSourceJson({
|
|
11747
|
+
path: FANQIE_BOOK_SOURCE_CATALOG_PATH,
|
|
11748
|
+
searchParams: {
|
|
11749
|
+
book_id: encodeFanqieBookSourceBookId(fanqieBookId),
|
|
11750
|
+
source: FANQIE_BOOK_SOURCE_CATALOG_SOURCE,
|
|
11751
|
+
tab: FANQIE_BOOK_SOURCE_CATALOG_TAB,
|
|
11752
|
+
variable: JSON.stringify(FANQIE_BOOK_SOURCE_CATALOG_VARIABLE)
|
|
11753
|
+
},
|
|
11754
|
+
init: {
|
|
11755
|
+
method: "POST",
|
|
11756
|
+
headers: FANQIE_BOOK_SOURCE_CATALOG_HEADERS,
|
|
11757
|
+
body: JSON.stringify({ html: "" })
|
|
11758
|
+
},
|
|
11759
|
+
errorPrefix: "番茄章节目录获取失败",
|
|
11760
|
+
validate: (data) => {
|
|
11761
|
+
if (data?.code !== 0) {
|
|
11762
|
+
throw new Error(data?.msg || "章节目录接口返回异常");
|
|
11763
|
+
}
|
|
11764
|
+
}
|
|
11765
|
+
});
|
|
11766
|
+
return normalizeFanqieBookSourceCatalog(response.data);
|
|
11767
|
+
};
|
|
11768
|
+
// src/node/crawl/fanqie/content/index.ts
|
|
11769
|
+
var exports_content = {};
|
|
11770
|
+
__export(exports_content, {
|
|
11771
|
+
getFanqieChapterContents: () => getFanqieChapterContents
|
|
11772
|
+
});
|
|
11773
|
+
|
|
11774
|
+
// src/node/crawl/fanqie/content/sidecar.ts
|
|
11775
|
+
import { execFile } from "node:child_process";
|
|
11776
|
+
import { promisify } from "node:util";
|
|
11777
|
+
var execFileAsync = promisify(execFile);
|
|
11778
|
+
var sleep = async (ms) => {
|
|
11779
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
11780
|
+
};
|
|
11781
|
+
var assertFanqieNumericId = (value, label) => {
|
|
11782
|
+
if (!/^\d+$/.test(value.trim())) {
|
|
11783
|
+
throw new Error(`${label} 格式不正确`);
|
|
11784
|
+
}
|
|
11785
|
+
};
|
|
11786
|
+
var resolveSidecarOptions = (options = {}) => ({
|
|
11787
|
+
baseUrl: options.baseUrl || process.env.FANQIE_OFFICIAL_SIDECAR_BASE_URL || DEFAULT_FANQIE_OFFICIAL_SIDECAR_BASE_URL,
|
|
11788
|
+
container: options.container || process.env.FANQIE_OFFICIAL_SIDECAR_CONTAINER || DEFAULT_FANQIE_OFFICIAL_SIDECAR_CONTAINER,
|
|
11789
|
+
password: options.password ?? process.env.FANQIE_OFFICIAL_SIDECAR_PASSWORD ?? "",
|
|
11790
|
+
jobTimeoutMs: options.jobTimeoutMs || DEFAULT_FANQIE_OFFICIAL_SIDECAR_JOB_TIMEOUT_MS,
|
|
11791
|
+
jobPollIntervalMs: options.jobPollIntervalMs || DEFAULT_FANQIE_OFFICIAL_SIDECAR_JOB_POLL_INTERVAL_MS,
|
|
11792
|
+
statusTimeoutMs: options.statusTimeoutMs || DEFAULT_FANQIE_OFFICIAL_SIDECAR_STATUS_TIMEOUT_MS,
|
|
11793
|
+
statusPollIntervalMs: options.statusPollIntervalMs || DEFAULT_FANQIE_OFFICIAL_SIDECAR_STATUS_POLL_INTERVAL_MS
|
|
11794
|
+
});
|
|
11795
|
+
var getOfficialSidecarHeaders = (password) => {
|
|
11796
|
+
if (!password) {
|
|
11797
|
+
return {};
|
|
11798
|
+
}
|
|
11799
|
+
return {
|
|
11800
|
+
"x-tomato-password": password
|
|
11801
|
+
};
|
|
11802
|
+
};
|
|
11803
|
+
var officialSidecarFetchJson = async (path3, options, init) => {
|
|
11804
|
+
const response = await fetch(`${options.baseUrl}${path3}`, {
|
|
11805
|
+
...init,
|
|
11806
|
+
headers: {
|
|
11807
|
+
...getOfficialSidecarHeaders(options.password),
|
|
11808
|
+
...init?.headers || {}
|
|
11809
|
+
},
|
|
11810
|
+
cache: "no-store"
|
|
11811
|
+
});
|
|
11812
|
+
if (!response.ok) {
|
|
11813
|
+
throw new Error(`官方正文服务请求失败: ${response.status} ${path3}`);
|
|
11814
|
+
}
|
|
11815
|
+
return await response.json();
|
|
11816
|
+
};
|
|
11817
|
+
var submitOfficialSidecarChoice = async (jobId, type, value, options) => {
|
|
11818
|
+
await officialSidecarFetchJson(`/api/jobs/${jobId}/${type}`, options, {
|
|
11819
|
+
method: "POST",
|
|
11820
|
+
headers: {
|
|
11821
|
+
"content-type": "application/json"
|
|
11822
|
+
},
|
|
11823
|
+
body: JSON.stringify({ value })
|
|
11824
|
+
});
|
|
11825
|
+
};
|
|
11826
|
+
var createOfficialSidecarJob = async (fanqieBookId, rangeEnd, options) => {
|
|
11827
|
+
return await officialSidecarFetchJson("/api/jobs", options, {
|
|
11828
|
+
method: "POST",
|
|
11829
|
+
headers: {
|
|
11830
|
+
"content-type": "application/json"
|
|
11831
|
+
},
|
|
11832
|
+
body: JSON.stringify({
|
|
11833
|
+
book_id: fanqieBookId,
|
|
11834
|
+
range_start: 1,
|
|
11835
|
+
range_end: rangeEnd
|
|
11836
|
+
})
|
|
11837
|
+
});
|
|
11838
|
+
};
|
|
11839
|
+
var pollOfficialSidecarJobUntilDone = async (jobId, options) => {
|
|
11840
|
+
const startedAt = Date.now();
|
|
11841
|
+
while (Date.now() - startedAt < options.jobTimeoutMs) {
|
|
11842
|
+
const response = await officialSidecarFetchJson(`/api/jobs?id=${jobId}`, options);
|
|
11843
|
+
const job = response.items[0];
|
|
11844
|
+
if (!job) {
|
|
11845
|
+
throw new Error("官方正文服务任务不存在");
|
|
11846
|
+
}
|
|
11847
|
+
if (job.book_name_options?.length) {
|
|
11848
|
+
await submitOfficialSidecarChoice(jobId, "book_name", job.book_name_options[0]?.value || "", options);
|
|
11849
|
+
await sleep(300);
|
|
11850
|
+
continue;
|
|
11851
|
+
}
|
|
11852
|
+
if (job.format_options?.length) {
|
|
11853
|
+
await submitOfficialSidecarChoice(jobId, "format", job.format_options[0]?.value || "", options);
|
|
11854
|
+
await sleep(300);
|
|
11855
|
+
continue;
|
|
11856
|
+
}
|
|
11857
|
+
if (job.state === "done") {
|
|
11858
|
+
return;
|
|
11859
|
+
}
|
|
11860
|
+
if (job.state === "failed") {
|
|
11861
|
+
throw new Error(job.message || "官方正文服务任务失败");
|
|
11862
|
+
}
|
|
11863
|
+
if (job.state === "canceled") {
|
|
11864
|
+
throw new Error("官方正文服务任务已取消");
|
|
11865
|
+
}
|
|
11866
|
+
await sleep(options.jobPollIntervalMs);
|
|
11867
|
+
}
|
|
11868
|
+
throw new Error("官方正文服务任务超时");
|
|
11869
|
+
};
|
|
11870
|
+
var deleteOfficialSidecarJob = async (jobId, options) => {
|
|
11871
|
+
try {
|
|
11872
|
+
await fetch(`${options.baseUrl}/api/jobs/${jobId}`, {
|
|
11873
|
+
method: "DELETE",
|
|
11874
|
+
headers: getOfficialSidecarHeaders(options.password),
|
|
11875
|
+
cache: "no-store"
|
|
11876
|
+
});
|
|
11877
|
+
} catch {}
|
|
11878
|
+
};
|
|
11879
|
+
var tryReadOfficialSidecarStatusJson = async (fanqieBookId, options) => {
|
|
11880
|
+
assertFanqieNumericId(fanqieBookId, "书籍 ID");
|
|
11881
|
+
const shell = [
|
|
11882
|
+
'folder=""',
|
|
11883
|
+
`for candidate in /app/${fanqieBookId}_*; do [ -f "$candidate/status.json" ] && folder="$candidate" && break; done`,
|
|
11884
|
+
'[ -n "$folder" ] || exit 11',
|
|
11885
|
+
'cat "$folder/status.json"'
|
|
11886
|
+
].join("; ");
|
|
11887
|
+
try {
|
|
11888
|
+
const { stdout } = await execFileAsync("docker", ["exec", options.container, "sh", "-lc", shell], {
|
|
11889
|
+
maxBuffer: 200 * 1024 * 1024
|
|
11890
|
+
});
|
|
11891
|
+
return JSON.parse(stdout);
|
|
11892
|
+
} catch (error) {
|
|
11893
|
+
const execError = error;
|
|
11894
|
+
if (execError.code === 11) {
|
|
11895
|
+
return null;
|
|
11896
|
+
}
|
|
11897
|
+
throw new Error(execError.stderr?.trim() || execError.message || "读取官方正文服务状态失败");
|
|
11898
|
+
}
|
|
11899
|
+
};
|
|
11900
|
+
var readOfficialSidecarStatusJson = async (fanqieBookId, options) => {
|
|
11901
|
+
const startedAt = Date.now();
|
|
11902
|
+
let lastError = null;
|
|
11903
|
+
while (Date.now() - startedAt < options.statusTimeoutMs) {
|
|
11904
|
+
try {
|
|
11905
|
+
const status = await tryReadOfficialSidecarStatusJson(fanqieBookId, options);
|
|
11906
|
+
if (status) {
|
|
11907
|
+
return status;
|
|
11908
|
+
}
|
|
11909
|
+
} catch (error) {
|
|
11910
|
+
lastError = error instanceof Error ? error : new Error(error?.message || "读取官方正文服务状态失败");
|
|
11911
|
+
}
|
|
11912
|
+
await sleep(options.statusPollIntervalMs);
|
|
11913
|
+
}
|
|
11914
|
+
if (lastError) {
|
|
11915
|
+
throw lastError;
|
|
11916
|
+
}
|
|
11917
|
+
throw new Error("官方正文服务任务已完成,但未找到 status.json 结果文件");
|
|
11918
|
+
};
|
|
11919
|
+
var getFanqieChapterContents = async (fanqieBookId, chapterIds, sidecarOptions) => {
|
|
11920
|
+
assertFanqieNumericId(fanqieBookId, "书籍 ID");
|
|
11921
|
+
const normalizedChapterIds = chapterIds.map((item) => item.trim()).filter(Boolean);
|
|
11922
|
+
if (normalizedChapterIds.length === 0) {
|
|
11923
|
+
return new Map;
|
|
11924
|
+
}
|
|
11925
|
+
const options = resolveSidecarOptions(sidecarOptions);
|
|
11926
|
+
let jobId = null;
|
|
11927
|
+
try {
|
|
11928
|
+
const job = await createOfficialSidecarJob(fanqieBookId, normalizedChapterIds.length, options);
|
|
11929
|
+
jobId = job.id;
|
|
11930
|
+
await pollOfficialSidecarJobUntilDone(job.id, options);
|
|
11931
|
+
const status = await readOfficialSidecarStatusJson(fanqieBookId, options);
|
|
11932
|
+
const downloaded = status.downloaded || {};
|
|
11933
|
+
const result = new Map;
|
|
11934
|
+
normalizedChapterIds.forEach((chapterId, index) => {
|
|
11935
|
+
const item = downloaded[chapterId];
|
|
11936
|
+
if (!item?.[1]) {
|
|
11937
|
+
return;
|
|
11938
|
+
}
|
|
11939
|
+
result.set(chapterId, {
|
|
11940
|
+
title: item[0] || `第${index + 1}章`,
|
|
11941
|
+
content: item[1],
|
|
11942
|
+
chapterOrder: index + 1
|
|
11943
|
+
});
|
|
11944
|
+
});
|
|
11945
|
+
if (result.size === 0) {
|
|
11946
|
+
throw new Error("官方正文服务未返回可用章节内容");
|
|
11947
|
+
}
|
|
11948
|
+
return result;
|
|
11949
|
+
} finally {
|
|
11950
|
+
if (jobId) {
|
|
11951
|
+
await deleteOfficialSidecarJob(jobId, options);
|
|
11952
|
+
}
|
|
11953
|
+
}
|
|
11954
|
+
};
|
|
10889
11955
|
// src/node.ts
|
|
10890
11956
|
var _ = import_lodash.default;
|
|
10891
11957
|
export {
|
|
@@ -10948,6 +12014,7 @@ export {
|
|
|
10948
12014
|
getType,
|
|
10949
12015
|
getStrLength,
|
|
10950
12016
|
getSliceStr,
|
|
12017
|
+
getSafeCallbackPath,
|
|
10951
12018
|
getRandomString,
|
|
10952
12019
|
getRandomColor,
|
|
10953
12020
|
getPublicUrl,
|
|
@@ -10972,6 +12039,8 @@ export {
|
|
|
10972
12039
|
dateType as date,
|
|
10973
12040
|
custom,
|
|
10974
12041
|
exports_cron as cron,
|
|
12042
|
+
exports_crawl as crawl,
|
|
12043
|
+
exports_cors as cors,
|
|
10975
12044
|
default4 as consola,
|
|
10976
12045
|
coerce,
|
|
10977
12046
|
cheerio,
|