@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/node.cjs.js CHANGED
@@ -5539,6 +5539,7 @@ __export(exports_node, {
5539
5539
  getType: () => getType,
5540
5540
  getStrLength: () => getStrLength,
5541
5541
  getSliceStr: () => getSliceStr,
5542
+ getSafeCallbackPath: () => getSafeCallbackPath,
5542
5543
  getRandomString: () => getRandomString,
5543
5544
  getRandomColor: () => getRandomColor,
5544
5545
  getPublicUrl: () => getPublicUrl,
@@ -5563,6 +5564,8 @@ __export(exports_node, {
5563
5564
  date: () => dateType,
5564
5565
  custom: () => custom,
5565
5566
  cron: () => exports_cron,
5567
+ crawl: () => exports_crawl,
5568
+ cors: () => exports_cors,
5566
5569
  consola: () => import_consola.default,
5567
5570
  coerce: () => coerce,
5568
5571
  cheerio: () => cheerio,
@@ -10412,6 +10415,12 @@ var replaceByVariables = (prompt, variables) => {
10412
10415
  return variables[p1] ?? match;
10413
10416
  });
10414
10417
  };
10418
+ var getSafeCallbackPath = (value, fallback) => {
10419
+ if (!value || !value.startsWith("/") || value.startsWith("//")) {
10420
+ return fallback;
10421
+ }
10422
+ return value;
10423
+ };
10415
10424
  var getType = (value) => {
10416
10425
  return Object.prototype.toString.call(value).slice(8, -1);
10417
10426
  };
@@ -10725,6 +10734,36 @@ var Timezone;
10725
10734
  Timezone2["EuropeBerlin"] = "Europe/Berlin";
10726
10735
  })(Timezone ||= {});
10727
10736
  var dayjs_default = import_dayjs.default;
10737
+ // src/node/cors.ts
10738
+ var exports_cors = {};
10739
+ __export(exports_cors, {
10740
+ createCorsPreflightResponse: () => createCorsPreflightResponse,
10741
+ createCorsHeaders: () => createCorsHeaders,
10742
+ applyCorsHeaders: () => applyCorsHeaders
10743
+ });
10744
+ var DEFAULT_ALLOW_HEADERS = ["Authorization", "Content-Type", "X-TRPC-Source"];
10745
+ var DEFAULT_ALLOW_METHODS = ["GET", "POST", "OPTIONS"];
10746
+ var DEFAULT_MAX_AGE = "86400";
10747
+ var createCorsHeaders = (request, options = {}) => {
10748
+ const origin = request.headers.get("origin") ?? "*";
10749
+ return {
10750
+ "Access-Control-Allow-Headers": (options.allowHeaders ?? DEFAULT_ALLOW_HEADERS).join(", "),
10751
+ "Access-Control-Allow-Methods": (options.allowMethods ?? DEFAULT_ALLOW_METHODS).join(", "),
10752
+ "Access-Control-Allow-Origin": origin,
10753
+ "Access-Control-Max-Age": options.maxAge ?? DEFAULT_MAX_AGE,
10754
+ Vary: "Origin"
10755
+ };
10756
+ };
10757
+ var applyCorsHeaders = (request, response, options) => {
10758
+ Object.entries(createCorsHeaders(request, options)).forEach(([key2, value]) => {
10759
+ response.headers.set(key2, value);
10760
+ });
10761
+ return response;
10762
+ };
10763
+ var createCorsPreflightResponse = (request, options) => new Response(null, {
10764
+ headers: createCorsHeaders(request, options),
10765
+ status: 204
10766
+ });
10728
10767
  // src/node/oss/index.ts
10729
10768
  var exports_oss = {};
10730
10769
  __export(exports_oss, {
@@ -10893,7 +10932,11 @@ var exports_file = {};
10893
10932
  __export(exports_file, {
10894
10933
  replaceContentInFile: () => replaceContentInFile,
10895
10934
  processFile: () => processFile,
10935
+ parseBase64File: () => parseBase64File,
10936
+ normalizeDataUrlBase64: () => normalizeDataUrlBase64,
10896
10937
  getMimeType: () => getMimeType,
10938
+ getExtFromMimeType: () => getExtFromMimeType,
10939
+ getExtFromFileName: () => getExtFromFileName,
10897
10940
  downloadFile: () => downloadFile
10898
10941
  });
10899
10942
  var import_fs_extra = __toESM(require("fs-extra"));
@@ -10901,53 +10944,62 @@ var import_axios9 = __toESM(require("axios"));
10901
10944
  var import_url = __toESM(require("url"));
10902
10945
  var path2 = __toESM(require("path"));
10903
10946
  var import_crypto = __toESM(require("crypto"));
10947
+ var mimeExtMap = {
10948
+ "image/png": ["png"],
10949
+ "image/jpeg": ["jpg", "jpeg"],
10950
+ "image/gif": ["gif"],
10951
+ "image/webp": ["webp"],
10952
+ "image/svg+xml": ["svg"],
10953
+ "video/mp4": ["mp4"],
10954
+ "audio/mpeg": ["mp3"],
10955
+ "application/pdf": ["pdf"],
10956
+ "application/msword": ["doc"],
10957
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ["docx"],
10958
+ "application/vnd.ms-excel": ["xls"],
10959
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["xlsx"],
10960
+ "application/vnd.ms-powerpoint": ["ppt"],
10961
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": ["pptx"],
10962
+ "text/plain": ["txt"],
10963
+ "text/html": ["html"],
10964
+ "text/css": ["css"],
10965
+ "application/javascript": ["js"],
10966
+ "application/json": ["json"],
10967
+ "application/xml": ["xml"]
10968
+ };
10969
+ var getExtFromFileName = (fileName) => {
10970
+ return fileName ? path2.extname(fileName).toLowerCase() : "";
10971
+ };
10972
+ var getExtFromMimeType = (mimeType) => {
10973
+ const ext = mimeType ? mimeExtMap[mimeType]?.[0] : "";
10974
+ return ext ? `.${ext}` : "";
10975
+ };
10904
10976
  var getMimeType = (fileName) => {
10905
- const ext = fileName.split(".").pop()?.toLowerCase();
10906
- switch (ext) {
10907
- case "png":
10908
- return "image/png";
10909
- case "jpg":
10910
- case "jpeg":
10911
- return "image/jpeg";
10912
- case "gif":
10913
- return "image/gif";
10914
- case "webp":
10915
- return "image/webp";
10916
- case "svg":
10917
- return "image/svg+xml";
10918
- case "mp4":
10919
- return "video/mp4";
10920
- case "mp3":
10921
- return "audio/mpeg";
10922
- case "pdf":
10923
- return "application/pdf";
10924
- case "doc":
10925
- return "application/msword";
10926
- case "docx":
10927
- return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
10928
- case "xls":
10929
- return "application/vnd.ms-excel";
10930
- case "xlsx":
10931
- return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
10932
- case "ppt":
10933
- return "application/vnd.ms-powerpoint";
10934
- case "pptx":
10935
- return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
10936
- case "txt":
10937
- return "text/plain";
10938
- case "html":
10939
- return "text/html";
10940
- case "css":
10941
- return "text/css";
10942
- case "js":
10943
- return "application/javascript";
10944
- case "json":
10945
- return "application/json";
10946
- case "xml":
10947
- return "application/xml";
10948
- default:
10949
- return "application/octet-stream";
10950
- }
10977
+ const ext = getExtFromFileName(fileName).replace(/^\./, "");
10978
+ const matched = Object.entries(mimeExtMap).find(([, extensions]) => extensions.includes(ext));
10979
+ return matched?.[0] || "application/octet-stream";
10980
+ };
10981
+ var generateUniqueFileName = (originalName = "") => {
10982
+ const ext = path2.extname(originalName);
10983
+ const nameWithoutExt = path2.basename(originalName, ext);
10984
+ const finalFileName = `${nameWithoutExt ? `${nameWithoutExt}_` : ""}${Date.now()}_${Math.random().toString(36).slice(2)}${ext}`;
10985
+ return {
10986
+ finalFileName,
10987
+ ext: ext.toLowerCase()
10988
+ };
10989
+ };
10990
+ var normalizeDataUrlBase64 = (value) => {
10991
+ const matched = value.match(/^data:([^;]+);base64,(.*)$/);
10992
+ return {
10993
+ base64: matched?.[2] || value,
10994
+ mimeType: matched?.[1]
10995
+ };
10996
+ };
10997
+ var parseBase64File = (input) => {
10998
+ const parsed = normalizeDataUrlBase64(input.fileBase64);
10999
+ return {
11000
+ buffer: Buffer.from(parsed.base64, "base64"),
11001
+ mimeType: input.mimeType || parsed.mimeType
11002
+ };
10951
11003
  };
10952
11004
  var replaceContentInFile = async (filePath, targetContent, replacement) => {
10953
11005
  const data = await import_fs_extra.default.readFile(filePath, "utf8");
@@ -10961,8 +11013,8 @@ var replaceContentInFile = async (filePath, targetContent, replacement) => {
10961
11013
  };
10962
11014
  var downloadFile = async (httpUrl, outputPath) => {
10963
11015
  try {
10964
- let parsedUrl = import_url.default.parse(httpUrl);
10965
- let fileName = path2.basename(parsedUrl.pathname);
11016
+ const parsedUrl = import_url.default.parse(httpUrl);
11017
+ const fileName = path2.basename(parsedUrl.pathname);
10966
11018
  if (!outputPath) {
10967
11019
  if (!import_fs_extra.default.existsSync(".tmp")) {
10968
11020
  import_fs_extra.default.mkdirSync(".tmp", { recursive: true });
@@ -10986,15 +11038,6 @@ var downloadFile = async (httpUrl, outputPath) => {
10986
11038
  throw error;
10987
11039
  }
10988
11040
  };
10989
- var generateUniqueFileName = (originalName = "") => {
10990
- const ext = path2.extname(originalName);
10991
- const nameWithoutExt = path2.basename(originalName, ext);
10992
- const finalFileName = `${nameWithoutExt ? `${nameWithoutExt}_` : ""}${Date.now()}_${Math.random().toString(36).slice(2)}${ext}`;
10993
- return {
10994
- finalFileName,
10995
- ext: ext.toLowerCase()
10996
- };
10997
- };
10998
11041
  var processFile = async (params) => {
10999
11042
  const { file, type } = params;
11000
11043
  let buffer;
@@ -11057,6 +11100,1032 @@ var processFile = async (params) => {
11057
11100
  ext
11058
11101
  };
11059
11102
  };
11103
+ // src/node/crawl/index.ts
11104
+ var exports_crawl = {};
11105
+ __export(exports_crawl, {
11106
+ fanqie: () => exports_fanqie
11107
+ });
11108
+
11109
+ // src/node/crawl/fanqie/index.ts
11110
+ var exports_fanqie = {};
11111
+ __export(exports_fanqie, {
11112
+ fanqieContent: () => exports_content,
11113
+ fanqieChapter: () => exports_chapter,
11114
+ fanqieBook: () => exports_book
11115
+ });
11116
+
11117
+ // src/node/crawl/fanqie/book/index.ts
11118
+ var exports_book = {};
11119
+ __export(exports_book, {
11120
+ searchFanqieBooks: () => searchFanqieBooks,
11121
+ resolveFanqieLibraryCategoryId: () => resolveFanqieLibraryCategoryId,
11122
+ normalizeFanqieSearchResults: () => normalizeFanqieSearchResults,
11123
+ normalizeFanqieOfficialLibraryItem: () => normalizeFanqieOfficialLibraryItem,
11124
+ normalizeFanqieBookTags: () => normalizeFanqieBookTags,
11125
+ normalizeFanqieBookStatus: () => normalizeFanqieBookStatus,
11126
+ normalizeFanqieBookSearchItem: () => normalizeFanqieBookSearchItem,
11127
+ normalizeFanqieBookDetailItem: () => normalizeFanqieBookDetailItem,
11128
+ listFanqieLibraryBooks: () => listFanqieLibraryBooks,
11129
+ getFanqieLibraryFilters: () => getFanqieLibraryFilters,
11130
+ getFanqieBookDetailById: () => getFanqieBookDetailById,
11131
+ createFanqieSearchUrl: () => createFanqieSearchUrl
11132
+ });
11133
+
11134
+ // src/node/crawl/fanqie/utils/consts.ts
11135
+ var FANQIE_LIBRARY_LIST_URL = "https://fanqienovel.com/api/author/library/book_list/v0/";
11136
+ var FANQIE_LIBRARY_CATEGORY_URL = "https://fanqienovel.com/api/author/book/category_list/v0/";
11137
+ var FANQIE_BOOK_SOURCE_SEARCH_PATH = "/search";
11138
+ var FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_SOURCE = "番茄";
11139
+ var FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_TAB = "小说";
11140
+ var FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_DISABLED_SOURCES = "0";
11141
+ var FANQIE_BOOK_SOURCE_CATALOG_PATH = "/catalog";
11142
+ var FANQIE_BOOK_SOURCE_CATALOG_SOURCE = "番茄";
11143
+ var FANQIE_BOOK_SOURCE_CATALOG_TAB = "小说";
11144
+ var FANQIE_BOOK_SOURCE_CATALOG_VARIABLE = { custom: "" };
11145
+ var FANQIE_BOOK_SOURCE_DETAIL_PATH = "/detail";
11146
+ var FANQIE_BOOK_SOURCE_DETAIL_SOURCE = "番茄";
11147
+ var FANQIE_BOOK_SOURCE_DETAIL_TAB = "小说";
11148
+ var FANQIE_BOOK_SOURCE_DETAIL_VARIABLE = { custom: "" };
11149
+ var DEFAULT_FANQIE_BOOK_SOURCE_BASE_URLS = [
11150
+ "https://api.langge.cf",
11151
+ "https://v2.czyl.cf",
11152
+ "http://219.154.201.122:5006"
11153
+ ];
11154
+ var FANQIE_PAGE_HEADERS = {
11155
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
11156
+ Referer: "https://fanqienovel.com/"
11157
+ };
11158
+ var FANQIE_BOOK_SOURCE_SEARCH_HEADERS = {
11159
+ "user-agent": "Mozilla/5.0"
11160
+ };
11161
+ var FANQIE_BOOK_SOURCE_CATALOG_HEADERS = {
11162
+ "content-type": "application/json",
11163
+ "user-agent": "Mozilla/5.0"
11164
+ };
11165
+ var FANQIE_BOOK_SOURCE_DETAIL_HEADERS = {
11166
+ "content-type": "application/json",
11167
+ "user-agent": "Mozilla/5.0"
11168
+ };
11169
+ var DEFAULT_FANQIE_OFFICIAL_SIDECAR_BASE_URL = "http://127.0.0.1:18423";
11170
+ var DEFAULT_FANQIE_OFFICIAL_SIDECAR_CONTAINER = "tomato-novel-webui";
11171
+ var DEFAULT_FANQIE_OFFICIAL_SIDECAR_JOB_TIMEOUT_MS = 5 * 60 * 1000;
11172
+ var DEFAULT_FANQIE_OFFICIAL_SIDECAR_JOB_POLL_INTERVAL_MS = 1500;
11173
+ var DEFAULT_FANQIE_OFFICIAL_SIDECAR_STATUS_TIMEOUT_MS = 15 * 1000;
11174
+ var DEFAULT_FANQIE_OFFICIAL_SIDECAR_STATUS_POLL_INTERVAL_MS = 1000;
11175
+
11176
+ // src/node/crawl/fanqie/utils/http.ts
11177
+ var fetchFanqieJson = async (url3, init) => {
11178
+ const response = await fetch(url3, {
11179
+ ...init,
11180
+ cache: "no-store"
11181
+ });
11182
+ if (!response.ok) {
11183
+ throw new Error(`番茄请求失败: ${response.status} ${url3}`);
11184
+ }
11185
+ return await response.json();
11186
+ };
11187
+
11188
+ // src/node/crawl/fanqie/utils/font-map.ts
11189
+ var FANQIE_OBFUSCATED_FONT_MAP = {
11190
+ "": "D",
11191
+ "": "在",
11192
+ "": "主",
11193
+ "": "特",
11194
+ "": "家",
11195
+ "": "军",
11196
+ "": "然",
11197
+ "": "表",
11198
+ "": "场",
11199
+ "": "4",
11200
+ "": "要",
11201
+ "": "只",
11202
+ "": "v",
11203
+ "": "和",
11204
+ "": "6",
11205
+ "": "别",
11206
+ "": "还",
11207
+ "": "g",
11208
+ "": "现",
11209
+ "": "儿",
11210
+ "": "岁",
11211
+ "": "此",
11212
+ "": "象",
11213
+ "": "月",
11214
+ "": "3",
11215
+ "": "出",
11216
+ "": "战",
11217
+ "": "工",
11218
+ "": "相",
11219
+ "": "o",
11220
+ "": "男",
11221
+ "": "直",
11222
+ "": "失",
11223
+ "": "世",
11224
+ "": "F",
11225
+ "": "都",
11226
+ "": "平",
11227
+ "": "文",
11228
+ "": "什",
11229
+ "": "V",
11230
+ "": "O",
11231
+ "": "将",
11232
+ "": "真",
11233
+ "": "T",
11234
+ "": "那",
11235
+ "": "当",
11236
+ "": "会",
11237
+ "": "立",
11238
+ "": "些",
11239
+ "": "u",
11240
+ "": "是",
11241
+ "": "十",
11242
+ "": "张",
11243
+ "": "学",
11244
+ "": "气",
11245
+ "": "大",
11246
+ "": "爱",
11247
+ "": "两",
11248
+ "": "命",
11249
+ "": "全",
11250
+ "": "后",
11251
+ "": "东",
11252
+ "": "性",
11253
+ "": "通",
11254
+ "": "被",
11255
+ "": "1",
11256
+ "": "它",
11257
+ "": "乐",
11258
+ "": "接",
11259
+ "": "而",
11260
+ "": "感",
11261
+ "": "车",
11262
+ "": "山",
11263
+ "": "公",
11264
+ "": "了",
11265
+ "": "常",
11266
+ "": "以",
11267
+ "": "何",
11268
+ "": "可",
11269
+ "": "话",
11270
+ "": "先",
11271
+ "": "p",
11272
+ "": "i",
11273
+ "": "叫",
11274
+ "": "轻",
11275
+ "": "M",
11276
+ "": "士",
11277
+ "": "w",
11278
+ "": "着",
11279
+ "": "变",
11280
+ "": "尔",
11281
+ "": "快",
11282
+ "": "l",
11283
+ "": "个",
11284
+ "": "说",
11285
+ "": "少",
11286
+ "": "色",
11287
+ "": "里",
11288
+ "": "安",
11289
+ "": "花",
11290
+ "": "远",
11291
+ "": "7",
11292
+ "": "难",
11293
+ "": "师",
11294
+ "": "放",
11295
+ "": "t",
11296
+ "": "报",
11297
+ "": "认",
11298
+ "": "面",
11299
+ "": "道",
11300
+ "": "S",
11301
+ "": "克",
11302
+ "": "地",
11303
+ "": "度",
11304
+ "": "I",
11305
+ "": "好",
11306
+ "": "机",
11307
+ "": "U",
11308
+ "": "民",
11309
+ "": "写",
11310
+ "": "把",
11311
+ "": "万",
11312
+ "": "同",
11313
+ "": "水",
11314
+ "": "新",
11315
+ "": "没",
11316
+ "": "书",
11317
+ "": "电",
11318
+ "": "吃",
11319
+ "": "像",
11320
+ "": "斯",
11321
+ "": "5",
11322
+ "": "为",
11323
+ "": "y",
11324
+ "": "白",
11325
+ "": "几",
11326
+ "": "日",
11327
+ "": "教",
11328
+ "": "看",
11329
+ "": "但",
11330
+ "": "第",
11331
+ "": "加",
11332
+ "": "候",
11333
+ "": "作",
11334
+ "": "上",
11335
+ "": "拉",
11336
+ "": "住",
11337
+ "": "有",
11338
+ "": "法",
11339
+ "": "r",
11340
+ "": "事",
11341
+ "": "应",
11342
+ "": "位",
11343
+ "": "利",
11344
+ "": "你",
11345
+ "": "声",
11346
+ "": "身",
11347
+ "": "国",
11348
+ "": "问",
11349
+ "": "马",
11350
+ "": "女",
11351
+ "": "他",
11352
+ "": "Y",
11353
+ "": "比",
11354
+ "": "父",
11355
+ "": "x",
11356
+ "": "A",
11357
+ "": "H",
11358
+ "": "N",
11359
+ "": "s",
11360
+ "": "X",
11361
+ "": "边",
11362
+ "": "美",
11363
+ "": "对",
11364
+ "": "所",
11365
+ "": "金",
11366
+ "": "活",
11367
+ "": "回",
11368
+ "": "意",
11369
+ "": "到",
11370
+ "": "z",
11371
+ "": "从",
11372
+ "": "j",
11373
+ "": "知",
11374
+ "": "又",
11375
+ "": "内",
11376
+ "": "因",
11377
+ "": "点",
11378
+ "": "Q",
11379
+ "": "三",
11380
+ "": "定",
11381
+ "": "8",
11382
+ "": "R",
11383
+ "": "b",
11384
+ "": "正",
11385
+ "": "或",
11386
+ "": "夫",
11387
+ "": "向",
11388
+ "": "德",
11389
+ "": "听",
11390
+ "": "更",
11391
+ "": "得",
11392
+ "": "告",
11393
+ "": "并",
11394
+ "": "本",
11395
+ "": "q",
11396
+ "": "过",
11397
+ "": "记",
11398
+ "": "L",
11399
+ "": "让",
11400
+ "": "打",
11401
+ "": "f",
11402
+ "": "人",
11403
+ "": "就",
11404
+ "": "者",
11405
+ "": "去",
11406
+ "": "原",
11407
+ "": "满",
11408
+ "": "体",
11409
+ "": "做",
11410
+ "": "经",
11411
+ "": "K",
11412
+ "": "走",
11413
+ "": "如",
11414
+ "": "孩",
11415
+ "": "c",
11416
+ "": "G",
11417
+ "": "给",
11418
+ "": "使",
11419
+ "": "物",
11420
+ "": "最",
11421
+ "": "笑",
11422
+ "": "部",
11423
+ "": "员",
11424
+ "": "等",
11425
+ "": "受",
11426
+ "": "k",
11427
+ "": "行",
11428
+ "": "一",
11429
+ "": "条",
11430
+ "": "果",
11431
+ "": "动",
11432
+ "": "光",
11433
+ "": "门",
11434
+ "": "头",
11435
+ "": "见",
11436
+ "": "往",
11437
+ "": "自",
11438
+ "": "解",
11439
+ "": "成",
11440
+ "": "处",
11441
+ "": "天",
11442
+ "": "能",
11443
+ "": "于",
11444
+ "": "名",
11445
+ "": "其",
11446
+ "": "发",
11447
+ "": "总",
11448
+ "": "母",
11449
+ "": "的",
11450
+ "": "死",
11451
+ "": "手",
11452
+ "": "入",
11453
+ "": "路",
11454
+ "": "进",
11455
+ "": "心",
11456
+ "": "来",
11457
+ "": "h",
11458
+ "": "时",
11459
+ "": "力",
11460
+ "": "多",
11461
+ "": "开",
11462
+ "": "已",
11463
+ "": "许",
11464
+ "": "d",
11465
+ "": "至",
11466
+ "": "由",
11467
+ "": "很",
11468
+ "": "界",
11469
+ "": "n",
11470
+ "": "小",
11471
+ "": "与",
11472
+ "": "Z",
11473
+ "": "想",
11474
+ "": "代",
11475
+ "": "么",
11476
+ "": "分",
11477
+ "": "生",
11478
+ "": "口",
11479
+ "": "再",
11480
+ "": "妈",
11481
+ "": "望",
11482
+ "": "次",
11483
+ "": "西",
11484
+ "": "风",
11485
+ "": "种",
11486
+ "": "带",
11487
+ "": "J",
11488
+ "": "实",
11489
+ "": "情",
11490
+ "": "才",
11491
+ "": "这",
11492
+ "": "E",
11493
+ "": "我",
11494
+ "": "神",
11495
+ "": "格",
11496
+ "": "长",
11497
+ "": "觉",
11498
+ "": "间",
11499
+ "": "年",
11500
+ "": "眼",
11501
+ "": "无",
11502
+ "": "不",
11503
+ "": "亲",
11504
+ "": "关",
11505
+ "": "结",
11506
+ "": "0",
11507
+ "": "友",
11508
+ "": "信",
11509
+ "": "下",
11510
+ "": "却",
11511
+ "": "重",
11512
+ "": "己",
11513
+ "": "老",
11514
+ "": "2",
11515
+ "": "音",
11516
+ "": "字",
11517
+ "": "m",
11518
+ "": "呢",
11519
+ "": "明",
11520
+ "": "之",
11521
+ "": "前",
11522
+ "": "高",
11523
+ "": "P",
11524
+ "": "B",
11525
+ "": "目",
11526
+ "": "太",
11527
+ "": "e",
11528
+ "": "9",
11529
+ "": "起",
11530
+ "": "稜",
11531
+ "": "她",
11532
+ "": "也",
11533
+ "": "W",
11534
+ "": "用",
11535
+ "": "方",
11536
+ "": "子",
11537
+ "": "英",
11538
+ "": "每",
11539
+ "": "理",
11540
+ "": "便",
11541
+ "": "四",
11542
+ "": "数",
11543
+ "": "期",
11544
+ "": "中",
11545
+ "": "C",
11546
+ "": "外",
11547
+ "": "样",
11548
+ "": "a",
11549
+ "": "海",
11550
+ "": "们",
11551
+ "": "任"
11552
+ };
11553
+ var restoreFanqieObfuscatedText = (content) => {
11554
+ return Array.from(content).map((char) => FANQIE_OBFUSCATED_FONT_MAP[char] || char).join("");
11555
+ };
11556
+
11557
+ // src/node/crawl/fanqie/utils/normalize.ts
11558
+ var normalizeFanqieText = (value) => {
11559
+ if (value === null || value === undefined) {
11560
+ return null;
11561
+ }
11562
+ const normalized = restoreFanqieObfuscatedText(String(value)).trim();
11563
+ return normalized || null;
11564
+ };
11565
+ var normalizeFanqieCoverUrl = (value) => {
11566
+ const url3 = value ? String(value).trim() : "";
11567
+ if (!url3) {
11568
+ return null;
11569
+ }
11570
+ return url3.replace(/^http:\/\//, "https://");
11571
+ };
11572
+
11573
+ // src/node/crawl/fanqie/book/categories.ts
11574
+ var getCategoryGroupKey = (label) => {
11575
+ if (label === "主题") {
11576
+ return "topic";
11577
+ }
11578
+ if (label === "角色") {
11579
+ return "role";
11580
+ }
11581
+ if (label === "情节") {
11582
+ return "plot";
11583
+ }
11584
+ return null;
11585
+ };
11586
+ var createEmptyLibraryFilters = () => ({
11587
+ topic: [],
11588
+ role: [],
11589
+ plot: []
11590
+ });
11591
+ var normalizeFanqieLibraryFilters = (payload) => {
11592
+ const list = Array.isArray(payload) ? payload : [];
11593
+ const groups = createEmptyLibraryFilters();
11594
+ list.forEach((item) => {
11595
+ const label = String(item?.label || "").trim();
11596
+ const groupKey = getCategoryGroupKey(label);
11597
+ const categoryId = Number(item?.category_id);
11598
+ const name = normalizeFanqieText(item?.name);
11599
+ if (!groupKey || !Number.isInteger(categoryId) || !name) {
11600
+ return;
11601
+ }
11602
+ groups[groupKey].push({
11603
+ categoryId,
11604
+ label,
11605
+ name,
11606
+ coverUrl: normalizeFanqieCoverUrl(item?.cover_uri)
11607
+ });
11608
+ });
11609
+ return groups;
11610
+ };
11611
+ var getFanqieLibraryFilters = async (gender) => {
11612
+ const url3 = new URL(FANQIE_LIBRARY_CATEGORY_URL);
11613
+ url3.searchParams.set("gender", String(gender));
11614
+ const data = await fetchFanqieJson(url3.toString(), {
11615
+ headers: FANQIE_PAGE_HEADERS
11616
+ });
11617
+ return normalizeFanqieLibraryFilters(data?.data);
11618
+ };
11619
+ var resolveFanqieLibraryCategoryId = async ({
11620
+ gender,
11621
+ categoryGroup,
11622
+ categoryId
11623
+ }) => {
11624
+ if (categoryGroup === "all") {
11625
+ return -1;
11626
+ }
11627
+ const groups = await getFanqieLibraryFilters(gender);
11628
+ const currentGroup = groups[categoryGroup];
11629
+ if (currentGroup.length === 0) {
11630
+ return -1;
11631
+ }
11632
+ const matchedItem = typeof categoryId === "number" ? currentGroup.find((item) => item.categoryId === categoryId) : null;
11633
+ return matchedItem?.categoryId || currentGroup[0]?.categoryId || -1;
11634
+ };
11635
+ // src/node/crawl/fanqie/utils/book-source.ts
11636
+ var getFanqieBookSourceBaseUrls = () => {
11637
+ const raw = process.env.FANQIE_BOOK_SOURCE_BASE_URLS?.trim() || process.env.FANQIE_BOOK_SOURCE_DETAIL_BASE_URLS?.trim();
11638
+ if (!raw) {
11639
+ return [...DEFAULT_FANQIE_BOOK_SOURCE_BASE_URLS];
11640
+ }
11641
+ const list = raw.split(",").map((item) => item.trim()).filter(Boolean);
11642
+ return list.length > 0 ? list : [...DEFAULT_FANQIE_BOOK_SOURCE_BASE_URLS];
11643
+ };
11644
+ var encodeFanqieBookSourceBookId = (fanqieBookId) => {
11645
+ return Buffer.from(fanqieBookId, "utf8").toString("base64");
11646
+ };
11647
+ var decodeFanqieBookSourceBookId = (encodedBookId) => {
11648
+ const raw = encodedBookId ? String(encodedBookId).trim() : "";
11649
+ if (!raw) {
11650
+ return "";
11651
+ }
11652
+ try {
11653
+ const normalized = raw.replace(/-/g, "+").replace(/_/g, "/").padEnd(raw.length + (4 - raw.length % 4) % 4, "=");
11654
+ const decoded = Buffer.from(normalized, "base64").toString("utf8").trim();
11655
+ return /^\d+$/.test(decoded) ? decoded : raw;
11656
+ } catch {
11657
+ return raw;
11658
+ }
11659
+ };
11660
+ var createFanqieBookSourceUrl = (baseUrl, path3, searchParams) => {
11661
+ const url3 = new URL(path3, baseUrl);
11662
+ Object.entries(searchParams).forEach(([key2, value]) => {
11663
+ url3.searchParams.set(key2, value);
11664
+ });
11665
+ return url3.toString();
11666
+ };
11667
+ var requestFanqieBookSourceJson = async ({
11668
+ path: path3,
11669
+ searchParams,
11670
+ init,
11671
+ baseUrl,
11672
+ errorPrefix,
11673
+ validate
11674
+ }) => {
11675
+ const baseUrls = baseUrl ? [baseUrl] : getFanqieBookSourceBaseUrls();
11676
+ const errors2 = [];
11677
+ for (const currentBaseUrl of baseUrls) {
11678
+ try {
11679
+ const url3 = createFanqieBookSourceUrl(currentBaseUrl, path3, searchParams);
11680
+ const response = await fetchFanqieJson(url3, init);
11681
+ validate?.(response);
11682
+ return response;
11683
+ } catch (error) {
11684
+ const message = error instanceof Error ? error.message : String(error);
11685
+ errors2.push(`${currentBaseUrl}: ${message}`);
11686
+ }
11687
+ }
11688
+ throw new Error(`${errorPrefix}: ${errors2.join(" | ")}`);
11689
+ };
11690
+
11691
+ // src/node/crawl/fanqie/book/normalize.ts
11692
+ var normalizeFanqieSearchTitle = (value) => {
11693
+ const normalized = normalizeFanqieText(value);
11694
+ if (!normalized) {
11695
+ return "";
11696
+ }
11697
+ return normalized.replace(/(别名:.*?)/g, "").trim();
11698
+ };
11699
+ var normalizeFanqieBookStatus = (value) => {
11700
+ const normalized = normalizeFanqieText(value);
11701
+ if (!normalized) {
11702
+ return null;
11703
+ }
11704
+ if (normalized.includes("完结")) {
11705
+ return 1;
11706
+ }
11707
+ if (normalized.includes("连载")) {
11708
+ return 0;
11709
+ }
11710
+ return null;
11711
+ };
11712
+ var normalizeFanqieBookTags = (value) => {
11713
+ const normalized = normalizeFanqieText(value);
11714
+ if (!normalized) {
11715
+ return [];
11716
+ }
11717
+ return normalized.split(/[,,、\s]+/).map((item) => item.trim()).filter(Boolean);
11718
+ };
11719
+ var createFanqieBookItemBase = (payload) => ({
11720
+ author: normalizeFanqieText(payload.author),
11721
+ score: normalizeFanqieText(payload.score),
11722
+ coverUrl: normalizeFanqieCoverUrl(payload.thumb_url),
11723
+ description: normalizeFanqieText(payload.abstract),
11724
+ category: normalizeFanqieText(payload.category),
11725
+ tags: normalizeFanqieBookTags(payload.tags),
11726
+ wordCount: normalizeFanqieText(payload.word_number),
11727
+ readCount: normalizeFanqieText(payload.read_count),
11728
+ readCountAll: normalizeFanqieText(payload.read_count_all),
11729
+ creationStatus: normalizeFanqieBookStatus(payload.status),
11730
+ lastChapterTime: normalizeFanqieText(payload.last_chapter_update_time),
11731
+ rawTitle: payload.book_name ? String(payload.book_name) : null,
11732
+ rawAuthor: payload.author ? String(payload.author) : null,
11733
+ rawDescription: payload.abstract ? String(payload.abstract) : null,
11734
+ rawCategory: payload.category ? String(payload.category) : null,
11735
+ rawScore: payload.score ? String(payload.score) : null,
11736
+ rawWordCount: payload.word_number ? String(payload.word_number) : null,
11737
+ rawReadCount: payload.read_count ? String(payload.read_count) : null,
11738
+ rawReadCountAll: payload.read_count_all ? String(payload.read_count_all) : null
11739
+ });
11740
+ var normalizeFanqieBookSearchItem = (payload) => {
11741
+ const id = decodeFanqieBookSourceBookId(payload.book_id);
11742
+ const title = normalizeFanqieSearchTitle(payload.book_name);
11743
+ if (!id || !title) {
11744
+ return null;
11745
+ }
11746
+ return {
11747
+ id,
11748
+ title,
11749
+ ...createFanqieBookItemBase(payload)
11750
+ };
11751
+ };
11752
+ var normalizeFanqieBookDetailItem = (fanqieBookId, payload) => {
11753
+ const title = normalizeFanqieText(payload.book_name);
11754
+ if (!title) {
11755
+ throw new Error("书源详情缺少书名");
11756
+ }
11757
+ return {
11758
+ id: fanqieBookId,
11759
+ title,
11760
+ ...createFanqieBookItemBase(payload)
11761
+ };
11762
+ };
11763
+ var normalizeFanqieOfficialLibraryItem = (payload) => {
11764
+ const id = payload?.book_id ? String(payload.book_id).trim() : "";
11765
+ const title = normalizeFanqieText(payload?.book_name);
11766
+ if (!id || !title) {
11767
+ return null;
11768
+ }
11769
+ return {
11770
+ id,
11771
+ title,
11772
+ author: normalizeFanqieText(payload?.author),
11773
+ coverUrl: normalizeFanqieCoverUrl(payload?.thumb_url),
11774
+ description: normalizeFanqieText(payload?.abstract),
11775
+ category: normalizeFanqieText(payload?.category),
11776
+ tags: [],
11777
+ wordCount: normalizeFanqieText(payload?.word_count),
11778
+ readCount: normalizeFanqieText(payload?.read_count),
11779
+ readCountAll: null,
11780
+ creationStatus: payload?.creation_status === 0 || payload?.creation_status === 1 ? payload.creation_status : null,
11781
+ lastChapterTime: payload?.last_chapter_time ? String(payload.last_chapter_time) : null,
11782
+ rawTitle: payload?.book_name ? String(payload.book_name) : null,
11783
+ rawAuthor: payload?.author ? String(payload.author) : null,
11784
+ rawDescription: payload?.abstract ? String(payload.abstract) : null,
11785
+ rawCategory: payload?.category ? String(payload.category) : null,
11786
+ rawScore: null,
11787
+ rawWordCount: payload?.word_count ? String(payload.word_count) : null,
11788
+ rawReadCount: payload?.read_count ? String(payload.read_count) : null,
11789
+ rawReadCountAll: null
11790
+ };
11791
+ };
11060
11792
 
11793
+ // src/node/crawl/fanqie/book/detail.ts
11794
+ var getFanqieBookDetailById = async (fanqieBookId) => {
11795
+ const response = await requestFanqieBookSourceJson({
11796
+ path: FANQIE_BOOK_SOURCE_DETAIL_PATH,
11797
+ searchParams: {
11798
+ book_id: encodeFanqieBookSourceBookId(fanqieBookId),
11799
+ source: FANQIE_BOOK_SOURCE_DETAIL_SOURCE,
11800
+ tab: FANQIE_BOOK_SOURCE_DETAIL_TAB,
11801
+ variable: JSON.stringify(FANQIE_BOOK_SOURCE_DETAIL_VARIABLE)
11802
+ },
11803
+ init: {
11804
+ method: "POST",
11805
+ headers: FANQIE_BOOK_SOURCE_DETAIL_HEADERS,
11806
+ body: JSON.stringify({ html: "" })
11807
+ },
11808
+ errorPrefix: "番茄书籍详情获取失败",
11809
+ validate: (data) => {
11810
+ if (data?.code !== 0 || !data.data) {
11811
+ throw new Error(data?.msg || "书源详情返回为空");
11812
+ }
11813
+ }
11814
+ });
11815
+ return normalizeFanqieBookDetailItem(fanqieBookId, response.data);
11816
+ };
11817
+ // src/node/crawl/fanqie/book/library.ts
11818
+ var normalizeFanqieLibraryResults = (payload) => {
11819
+ const list = Array.isArray(payload) ? payload : [];
11820
+ return list.flatMap((item) => {
11821
+ const result = normalizeFanqieOfficialLibraryItem(item);
11822
+ return result ? [result] : [];
11823
+ });
11824
+ };
11825
+ var listFanqieLibraryBooks = async ({
11826
+ gender,
11827
+ categoryId,
11828
+ creationStatus,
11829
+ wordCount,
11830
+ sort,
11831
+ pageIndex,
11832
+ pageSize
11833
+ }) => {
11834
+ const url3 = new URL(FANQIE_LIBRARY_LIST_URL);
11835
+ url3.searchParams.set("page_count", String(pageSize));
11836
+ url3.searchParams.set("page_index", String(pageIndex));
11837
+ url3.searchParams.set("gender", String(gender));
11838
+ url3.searchParams.set("category_id", String(categoryId));
11839
+ url3.searchParams.set("creation_status", String(creationStatus));
11840
+ url3.searchParams.set("word_count", String(wordCount));
11841
+ url3.searchParams.set("book_type", "-1");
11842
+ url3.searchParams.set("sort", String(sort));
11843
+ const data = await fetchFanqieJson(url3.toString(), {
11844
+ headers: FANQIE_PAGE_HEADERS
11845
+ });
11846
+ return {
11847
+ results: normalizeFanqieLibraryResults(data?.data?.book_list),
11848
+ total: Number(data?.data?.total_count || 0)
11849
+ };
11850
+ };
11851
+ // src/node/crawl/fanqie/book/search.ts
11852
+ var createFanqieBookSourceSearchUrl = (baseUrl, query, options = {}) => {
11853
+ return createFanqieBookSourceUrl(baseUrl, FANQIE_BOOK_SOURCE_SEARCH_PATH, {
11854
+ title: query,
11855
+ tab: options.tab || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_TAB,
11856
+ source: options.source || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_SOURCE,
11857
+ page: String(options.page || 1),
11858
+ disabled_sources: options.disabledSources || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_DISABLED_SOURCES
11859
+ });
11860
+ };
11861
+ var normalizeFanqieSearchResults = (payload) => {
11862
+ const list = Array.isArray(payload) ? payload : [];
11863
+ return list.flatMap((item) => {
11864
+ const result = normalizeFanqieBookSearchItem(item);
11865
+ return result ? [result] : [];
11866
+ });
11867
+ };
11868
+ var createFanqieSearchUrl = (query, options = {}) => {
11869
+ const baseUrl = options.baseUrl || getFanqieBookSourceBaseUrls()[0];
11870
+ return createFanqieBookSourceSearchUrl(baseUrl, query, options);
11871
+ };
11872
+ var searchFanqieBooks = async (query, options = {}) => {
11873
+ const response = await requestFanqieBookSourceJson({
11874
+ path: FANQIE_BOOK_SOURCE_SEARCH_PATH,
11875
+ baseUrl: options.baseUrl,
11876
+ searchParams: {
11877
+ title: query,
11878
+ tab: options.tab || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_TAB,
11879
+ source: options.source || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_SOURCE,
11880
+ page: String(options.page || 1),
11881
+ disabled_sources: options.disabledSources || FANQIE_BOOK_SOURCE_SEARCH_DEFAULT_DISABLED_SOURCES
11882
+ },
11883
+ init: {
11884
+ method: "GET",
11885
+ headers: FANQIE_BOOK_SOURCE_SEARCH_HEADERS
11886
+ },
11887
+ errorPrefix: "番茄书籍搜索失败",
11888
+ validate: (data) => {
11889
+ if (data?.code !== 0) {
11890
+ throw new Error(data?.msg || "书源搜索失败");
11891
+ }
11892
+ }
11893
+ });
11894
+ return normalizeFanqieSearchResults(response.data);
11895
+ };
11896
+ // src/node/crawl/fanqie/chapter/index.ts
11897
+ var exports_chapter = {};
11898
+ __export(exports_chapter, {
11899
+ getFanqieChapterDirectory: () => getFanqieChapterDirectory
11900
+ });
11901
+
11902
+ // src/node/crawl/fanqie/chapter/directory.ts
11903
+ var extractVolumeName = (value) => {
11904
+ const normalized = normalizeFanqieText(value);
11905
+ if (!normalized) {
11906
+ return null;
11907
+ }
11908
+ const [volumeName] = normalized.split("|").map((item) => item.trim());
11909
+ return volumeName || null;
11910
+ };
11911
+ var normalizeFanqieBookSourceCatalog = (payload) => {
11912
+ const list = Array.isArray(payload) ? payload : [];
11913
+ return list.map((item, index) => ({
11914
+ fanqieChapterId: item?.item_id ? String(item.item_id).trim() : "",
11915
+ title: normalizeFanqieText(item?.title) || `第${index + 1}章`,
11916
+ chapterOrder: index + 1,
11917
+ volumeName: extractVolumeName(item?.first_pass_time)
11918
+ })).filter((item) => Boolean(item.fanqieChapterId));
11919
+ };
11920
+ var getFanqieChapterDirectory = async (fanqieBookId) => {
11921
+ const response = await requestFanqieBookSourceJson({
11922
+ path: FANQIE_BOOK_SOURCE_CATALOG_PATH,
11923
+ searchParams: {
11924
+ book_id: encodeFanqieBookSourceBookId(fanqieBookId),
11925
+ source: FANQIE_BOOK_SOURCE_CATALOG_SOURCE,
11926
+ tab: FANQIE_BOOK_SOURCE_CATALOG_TAB,
11927
+ variable: JSON.stringify(FANQIE_BOOK_SOURCE_CATALOG_VARIABLE)
11928
+ },
11929
+ init: {
11930
+ method: "POST",
11931
+ headers: FANQIE_BOOK_SOURCE_CATALOG_HEADERS,
11932
+ body: JSON.stringify({ html: "" })
11933
+ },
11934
+ errorPrefix: "番茄章节目录获取失败",
11935
+ validate: (data) => {
11936
+ if (data?.code !== 0) {
11937
+ throw new Error(data?.msg || "章节目录接口返回异常");
11938
+ }
11939
+ }
11940
+ });
11941
+ return normalizeFanqieBookSourceCatalog(response.data);
11942
+ };
11943
+ // src/node/crawl/fanqie/content/index.ts
11944
+ var exports_content = {};
11945
+ __export(exports_content, {
11946
+ getFanqieChapterContents: () => getFanqieChapterContents
11947
+ });
11948
+
11949
+ // src/node/crawl/fanqie/content/sidecar.ts
11950
+ var import_node_child_process = require("node:child_process");
11951
+ var import_node_util = require("node:util");
11952
+ var execFileAsync = import_node_util.promisify(import_node_child_process.execFile);
11953
+ var sleep = async (ms) => {
11954
+ await new Promise((resolve) => setTimeout(resolve, ms));
11955
+ };
11956
+ var assertFanqieNumericId = (value, label) => {
11957
+ if (!/^\d+$/.test(value.trim())) {
11958
+ throw new Error(`${label} 格式不正确`);
11959
+ }
11960
+ };
11961
+ var resolveSidecarOptions = (options = {}) => ({
11962
+ baseUrl: options.baseUrl || process.env.FANQIE_OFFICIAL_SIDECAR_BASE_URL || DEFAULT_FANQIE_OFFICIAL_SIDECAR_BASE_URL,
11963
+ container: options.container || process.env.FANQIE_OFFICIAL_SIDECAR_CONTAINER || DEFAULT_FANQIE_OFFICIAL_SIDECAR_CONTAINER,
11964
+ password: options.password ?? process.env.FANQIE_OFFICIAL_SIDECAR_PASSWORD ?? "",
11965
+ jobTimeoutMs: options.jobTimeoutMs || DEFAULT_FANQIE_OFFICIAL_SIDECAR_JOB_TIMEOUT_MS,
11966
+ jobPollIntervalMs: options.jobPollIntervalMs || DEFAULT_FANQIE_OFFICIAL_SIDECAR_JOB_POLL_INTERVAL_MS,
11967
+ statusTimeoutMs: options.statusTimeoutMs || DEFAULT_FANQIE_OFFICIAL_SIDECAR_STATUS_TIMEOUT_MS,
11968
+ statusPollIntervalMs: options.statusPollIntervalMs || DEFAULT_FANQIE_OFFICIAL_SIDECAR_STATUS_POLL_INTERVAL_MS
11969
+ });
11970
+ var getOfficialSidecarHeaders = (password) => {
11971
+ if (!password) {
11972
+ return {};
11973
+ }
11974
+ return {
11975
+ "x-tomato-password": password
11976
+ };
11977
+ };
11978
+ var officialSidecarFetchJson = async (path3, options, init) => {
11979
+ const response = await fetch(`${options.baseUrl}${path3}`, {
11980
+ ...init,
11981
+ headers: {
11982
+ ...getOfficialSidecarHeaders(options.password),
11983
+ ...init?.headers || {}
11984
+ },
11985
+ cache: "no-store"
11986
+ });
11987
+ if (!response.ok) {
11988
+ throw new Error(`官方正文服务请求失败: ${response.status} ${path3}`);
11989
+ }
11990
+ return await response.json();
11991
+ };
11992
+ var submitOfficialSidecarChoice = async (jobId, type, value, options) => {
11993
+ await officialSidecarFetchJson(`/api/jobs/${jobId}/${type}`, options, {
11994
+ method: "POST",
11995
+ headers: {
11996
+ "content-type": "application/json"
11997
+ },
11998
+ body: JSON.stringify({ value })
11999
+ });
12000
+ };
12001
+ var createOfficialSidecarJob = async (fanqieBookId, rangeEnd, options) => {
12002
+ return await officialSidecarFetchJson("/api/jobs", options, {
12003
+ method: "POST",
12004
+ headers: {
12005
+ "content-type": "application/json"
12006
+ },
12007
+ body: JSON.stringify({
12008
+ book_id: fanqieBookId,
12009
+ range_start: 1,
12010
+ range_end: rangeEnd
12011
+ })
12012
+ });
12013
+ };
12014
+ var pollOfficialSidecarJobUntilDone = async (jobId, options) => {
12015
+ const startedAt = Date.now();
12016
+ while (Date.now() - startedAt < options.jobTimeoutMs) {
12017
+ const response = await officialSidecarFetchJson(`/api/jobs?id=${jobId}`, options);
12018
+ const job = response.items[0];
12019
+ if (!job) {
12020
+ throw new Error("官方正文服务任务不存在");
12021
+ }
12022
+ if (job.book_name_options?.length) {
12023
+ await submitOfficialSidecarChoice(jobId, "book_name", job.book_name_options[0]?.value || "", options);
12024
+ await sleep(300);
12025
+ continue;
12026
+ }
12027
+ if (job.format_options?.length) {
12028
+ await submitOfficialSidecarChoice(jobId, "format", job.format_options[0]?.value || "", options);
12029
+ await sleep(300);
12030
+ continue;
12031
+ }
12032
+ if (job.state === "done") {
12033
+ return;
12034
+ }
12035
+ if (job.state === "failed") {
12036
+ throw new Error(job.message || "官方正文服务任务失败");
12037
+ }
12038
+ if (job.state === "canceled") {
12039
+ throw new Error("官方正文服务任务已取消");
12040
+ }
12041
+ await sleep(options.jobPollIntervalMs);
12042
+ }
12043
+ throw new Error("官方正文服务任务超时");
12044
+ };
12045
+ var deleteOfficialSidecarJob = async (jobId, options) => {
12046
+ try {
12047
+ await fetch(`${options.baseUrl}/api/jobs/${jobId}`, {
12048
+ method: "DELETE",
12049
+ headers: getOfficialSidecarHeaders(options.password),
12050
+ cache: "no-store"
12051
+ });
12052
+ } catch {}
12053
+ };
12054
+ var tryReadOfficialSidecarStatusJson = async (fanqieBookId, options) => {
12055
+ assertFanqieNumericId(fanqieBookId, "书籍 ID");
12056
+ const shell = [
12057
+ 'folder=""',
12058
+ `for candidate in /app/${fanqieBookId}_*; do [ -f "$candidate/status.json" ] && folder="$candidate" && break; done`,
12059
+ '[ -n "$folder" ] || exit 11',
12060
+ 'cat "$folder/status.json"'
12061
+ ].join("; ");
12062
+ try {
12063
+ const { stdout } = await execFileAsync("docker", ["exec", options.container, "sh", "-lc", shell], {
12064
+ maxBuffer: 200 * 1024 * 1024
12065
+ });
12066
+ return JSON.parse(stdout);
12067
+ } catch (error) {
12068
+ const execError = error;
12069
+ if (execError.code === 11) {
12070
+ return null;
12071
+ }
12072
+ throw new Error(execError.stderr?.trim() || execError.message || "读取官方正文服务状态失败");
12073
+ }
12074
+ };
12075
+ var readOfficialSidecarStatusJson = async (fanqieBookId, options) => {
12076
+ const startedAt = Date.now();
12077
+ let lastError = null;
12078
+ while (Date.now() - startedAt < options.statusTimeoutMs) {
12079
+ try {
12080
+ const status = await tryReadOfficialSidecarStatusJson(fanqieBookId, options);
12081
+ if (status) {
12082
+ return status;
12083
+ }
12084
+ } catch (error) {
12085
+ lastError = error instanceof Error ? error : new Error(error?.message || "读取官方正文服务状态失败");
12086
+ }
12087
+ await sleep(options.statusPollIntervalMs);
12088
+ }
12089
+ if (lastError) {
12090
+ throw lastError;
12091
+ }
12092
+ throw new Error("官方正文服务任务已完成,但未找到 status.json 结果文件");
12093
+ };
12094
+ var getFanqieChapterContents = async (fanqieBookId, chapterIds, sidecarOptions) => {
12095
+ assertFanqieNumericId(fanqieBookId, "书籍 ID");
12096
+ const normalizedChapterIds = chapterIds.map((item) => item.trim()).filter(Boolean);
12097
+ if (normalizedChapterIds.length === 0) {
12098
+ return new Map;
12099
+ }
12100
+ const options = resolveSidecarOptions(sidecarOptions);
12101
+ let jobId = null;
12102
+ try {
12103
+ const job = await createOfficialSidecarJob(fanqieBookId, normalizedChapterIds.length, options);
12104
+ jobId = job.id;
12105
+ await pollOfficialSidecarJobUntilDone(job.id, options);
12106
+ const status = await readOfficialSidecarStatusJson(fanqieBookId, options);
12107
+ const downloaded = status.downloaded || {};
12108
+ const result = new Map;
12109
+ normalizedChapterIds.forEach((chapterId, index) => {
12110
+ const item = downloaded[chapterId];
12111
+ if (!item?.[1]) {
12112
+ return;
12113
+ }
12114
+ result.set(chapterId, {
12115
+ title: item[0] || `第${index + 1}章`,
12116
+ content: item[1],
12117
+ chapterOrder: index + 1
12118
+ });
12119
+ });
12120
+ if (result.size === 0) {
12121
+ throw new Error("官方正文服务未返回可用章节内容");
12122
+ }
12123
+ return result;
12124
+ } finally {
12125
+ if (jobId) {
12126
+ await deleteOfficialSidecarJob(jobId, options);
12127
+ }
12128
+ }
12129
+ };
11061
12130
  // src/node.ts
11062
12131
  var _ = import_lodash.default;