@wzyjs/utils 0.3.31 → 0.3.32

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