-
Notifications
You must be signed in to change notification settings - Fork 12
/
work_crawler.js
849 lines (738 loc) · 30.4 KB
/
work_crawler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
/**
* @name CeL function for downloading online works (novels, comics).
*
* @fileoverview 本檔案包含了批量下載網路作品(小說、漫畫)的函式庫。 WWW work crawler library.
*
* <code>
TODO:
將設定儲存在系統預設的設定目錄
Windows: %APPDATA%\work_crawler\
UNIX: $HOME/.work_crawler/
搜尋已下載作品
save cookie @ CLI
建造可以自動生成index/說明的工具。
自動判別網址所需要使用的下載工具,輸入網址自動揀選所需的工具檔案。
從其他的資料來源網站尋找,以獲取作品以及章節的資訊。
自動記得某個作品要從哪些網站下載。
GUI開啟錯誤紀錄
增加版本上報
漫畫下載流程教學
CLI progress bar
下載完畢後作繁簡轉換。
在單一/全部任務完成後執行的外部檔+等待單一任務腳本執行的時間(秒數)
用安全一點的 eval()
Runs untrusted code securely https://github.com/patriksimek/vm2
parse 圖像。
拼接長圖之後重新分割:以整個橫切全部都是同一顏色白色為界,並且可以省略掉相同顏色的區塊。 using .epub
處理每張圖片被分割成多個小圖的情況 add .image_indexes[] ?
檢核章節內容。
考慮 search_URL 搜尋的頁數,當搜索獲得太多結果時也要包含所有結果
detect encoded data:
https://gchq.github.io/CyberChef/
</code>
*/
// More examples: See 各網站工具檔.js: https://github.com/kanasimi/work_crawler
'use strict';
// 'use asm';
// --------------------------------------------------------------------------------------------
if (typeof CeL === 'function') {
// 忽略沒有 Windows Component Object Model 的錯誤。
CeL.env.ignore_COM_error = true;
CeL.run({
// module name
name : 'application.net.work_crawler',
// .includes() @ CeL.data.code.compatibility
require : 'data.code.compatibility.'
// .between() @ CeL.data.native
// .append() @ CeL.data.native
// .pad() @ CeL.data.native
// display_align() @ CeL.data.native
+ '|data.native.'
// for CeL.to_file_name()
+ '|application.net.'
// for CeL.env.arg_hash, CeL.fs_read()
+ '|application.platform.nodejs.|application.storage.'
// for CeL.storage.file.file_type()
+ '|application.storage.file.'
// for HTML_to_Unicode()
+ '|interact.DOM.'
// for Date.prototype.format(), String.prototype.to_Date(),
// .to_millisecond()
+ '|data.date.'
// CeL.character.load(), 僅在要設定 this.charset 時才需要載入。
+ '|data.character.'
// gettext(), and for .detect_HTML_language(), .time_zone_of_language()
+ '|application.locale.gettext'
// guess_text_language()
+ '|application.locale.encoding.'
// storage.archive()
+ '|application.storage.archive.',
// 設定不匯出的子函式。
no_extend : '*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
}
function module_code(library_namespace) {
// requiring
var
// library_namespace.locale.gettext
gettext = this.r('gettext'),
/** node.js file system module */
node_fs = library_namespace.platform.nodejs && require('fs');
// --------------------------------------------------------------------------------------------
function Work_crawler(configurations) {
Object.assign(this, configurations);
// 預設自動匯入 .env.arg_hash
if (this.auto_import_args)
this.import_args();
// 在crawler=new CeL.work_crawler({})的情況下可能沒辦法得到準確的檔案路徑,因此這個路徑僅供參考。
var main_script_path = library_namespace.get_script_base_path(/\.js/i,
module);
if (main_script_path)
this.main_script = main_script_path;
// this.id 之後將提供給 this.site_id 使用。
// 在使用gui_electron含入檔案的情況下,this.id應該稍後在設定。
if (!this.id) {
this.id = this.main_script
// **1** require.main.filename: 如 require('./site_id.js')
// **2** 如 node site_id.js work_id
&& this.main_script
// 去掉 path
.replace(/^[\s\S]*[\\\/]([^\\\/]+)$/, '$1')
// 去掉 file extension
.replace(/\.*[^.]+$/, '')
// NOT require('./site_id.js'). 如 node site_id.js work_id
|| this.main_directory.replace(/\.*[\\\/]+$/, '')
// **3** others: unnormal
|| this.base_URL.match(/\/\/([^\/]+)/)[1].toLowerCase().split('.')
//
.reverse().some(function(token, index) {
if (index === 0) {
// 頂級域名
return false;
}
if (token !== 'www') {
this.id = token;
}
if (token.length > 3 || index > 1) {
// e.g., www.[id].co.jp
return true;
}
}, this);
if (!this.id && !(this.id = this.id.match(/[^\\\/]*$/)[0])) {
library_namespace.error({
// gettext_config:{"id":"cannot-detect-work-id-from-url-$1"}
T : [ '無法從網址擷取作品 id:%1', this.base_URL ]
});
}
}
// gettext_config:{"id":"starting-$1"}
process.title = gettext('Starting %1', this.id);
if (library_namespace.is_digits(this.baidu_cse)) {
if (!this.parse_search_result) {
// for 百度站内搜索工具。非百度搜索系統得要自己撰寫。
this.parse_search_result = 'baidu';
}
// baidu cse id 百度站内搜索工具。
if (!this.search_URL) {
this.search_URL = {
URL : 'http://zhannei.baidu.com/cse/search?s='
// &ie=utf-8 &isNeedCheckDomain=1&jump=1 &entry=1
+ this.baidu_cse + '&q=',
charset : 'UTF-8'
};
}
}
if (typeof this.parse_search_result === 'string') {
if (crawler_namespace.parse_search_result_set[this.parse_search_result]) {
this.parse_search_result = crawler_namespace.parse_search_result_set[this.parse_search_result];
} else {
this.onerror('Work_crawler: No this parse_search_result: '
+ this.parse_search_result, work_data);
return Work_crawler.THROWED;
}
}
// 設定預設可容許的最小圖像大小。
if (!(this.MIN_LENGTH >= 0)) {
// 先設定一個,預防到最後都沒有被設定到。
this.setup_value('MIN_LENGTH', 'default');
}
// @see function this_get_URL() @ CeL.application.net.work_crawler.task
this.get_URL_options = {
// start_time : Date.now(),
no_protocol_warn : true,
headers : Object.assign({
// Referer will set @ start_downloading()
// Referer : this.base_URL
}, this.headers)
};
this.setup_value('timeout', this.timeout);
this.setup_value('user_agent', this.user_agent
|| crawler_namespace.regenerate_user_agent(this));
// console.log(this.get_URL_options);
this.default_agent = this.setup_agent();
}
// @inner static functions
var crawler_namespace = Object.create(null);
Work_crawler.crawler_namespace = crawler_namespace;
// ------------------------------------------
var KEY_converted_text = 'converted_text';
// return needing to wait language converted
// var promise_language = this.cache_converted_text(text_list);
// if (promise_language) { return promise_language.then(); }
function cache_converted_text(text_list, options) {
if (!this.convert_to_language)
return;
var initializated = this.convert_text_language_using
&& this.convert_to_language_using === this.convert_to_language;
if (initializated && !this.convert_text_language_using.is_asynchronous) {
// 無須 cache,直接用 this.convert_text_language(text) 取得繁簡轉換過的文字即可。
return;
}
if (!this.converted_text_cache) {
this.converted_text_cache = Object.create(null);
this.converted_text_cache_persisted = Object.create(null);
}
if (!Array.isArray(text_list))
text_list = [ text_list ];
var _this = this, promise_list = [];
text_list = text_list.filter(function(text) {
// @see function convert_text_language(text, options)
if (!text || !text.trim())
return false;
if (text in _this.converted_text_cache) {
_this.converted_text_cache[text].requiring_thread_count++;
if (!_this.converted_text_cache[text][KEY_converted_text]) {
promise_list.push(
//
_this.converted_text_cache[text].promise);
}
// 先前已要求過要轉換這段文字。不需要再要求一次。
return false;
} else {
// 正常情況: 首次要求轉換這段文字。
if (initializated) {
_this.converted_text_cache[text] = {
requiring_thread_count : 1
};
}
// 尚未初始化的情況下,還是必須 return true 以讓 text_list.length > 0 並執行初始化。
return true;
}
});
// console.trace(initializated, text_list, promise_list);
if (text_list.length === 0) {
// !promise_list: Already cached all text needed.
return promise_list.length > 0 && Promise.all(promise_list);
}
if (false) {
console.trace(library_namespace.string_digest(text_list));
}
if (initializated) {
// 初始化後正常的程序。
if (false) {
console.trace('Convert text:', library_namespace
.string_digest(text_list));
}
var promise = this.convert_text_language_using(text_list, options);
// console.trace(promise);
// assert: .convert_text_language_using() return thenable
promise = promise
//
.then(function set_text_list(converted_text_list) {
if (false) {
console.trace('Set converted cache:',
//
library_namespace.string_digest(text_list),
//
library_namespace.string_digest(converted_text_list));
}
text_list.forEach(function(text, index) {
// free
delete _this.converted_text_cache[text].promise;
_this.converted_text_cache[text][KEY_converted_text]
// assert: {Object}_this.converted_text_cache[text]
// && !!converted_text_list[index] === true
= converted_text_list[index];
});
// console.trace(_this.converted_text_cache);
if (false) {
return [ text_list, converted_text_list ];
}
});
text_list.forEach(function(text) {
_this.converted_text_cache[text].promise = promise;
});
promise_list.push(promise);
if (false) {
console.trace(promise_list);
}
return Promise.all(promise_list);
}
// console.trace('cache_converted_text: 初始化 initialization');
// 僅有初始化時會執行一次。
return Promise.resolve(library_namespace.using_CeCC({
// e.g., @ function create_ebook()
skip_server_test : options.skip_server_test,
// 結巴中文分詞還太過粗糙,不適合依此做繁簡轉換。
try_LTP_server : true
})).then(function() {
_this.convert_to_language_using = _this.convert_to_language;
_this.convert_text_language_using
// setup this.convert_text_language_using
= _this.convert_to_language === 'TW'
// library_namespace.extension.zh_conversion.CN_to_TW();
? library_namespace.CN_to_TW : library_namespace.TW_to_CN;
if (false) {
console.trace('cache_converted_text: 初始化完畢。');
}
}).then(cache_converted_text.bind(this, text_list, options));
}
// Release memory. 釋放被占用的記憶體。
function clear_converted_text_cache(options) {
if (!this.convert_to_language)
return;
// console.trace(options);
if (options === true) {
options = {
including_persistence : true
};
} else {
options = library_namespace.setup_options(options);
}
// ('text' in options)
if (typeof options.text === 'string') {
if (false) {
if (!this.converted_text_cache[options.text]) {
console.trace(this.converted_text_cache);
console.trace(this);
console.trace(options);
}
console.trace('Free cache of '
//
+ library_namespace.string_digest(options.text)
//
+ ') requiring_thread_count='
//
+ (this.converted_text_cache[options.text]
//
&& this.converted_text_cache[options.text]
//
.requiring_thread_count));
}
// @see function cache_converted_text(text_list, options)
if (options.text && options.text.trim()
//
&& --this.converted_text_cache[options.text]
// 採用 .requiring_thread_count 以避免要求轉換相同文字,後來的取用時已被刪除。
// 若相同操作會呼叫兩次 cache_converted_text(),例如初始化,則此法會出問題。
.requiring_thread_count === 0) {
if (false) {
console.trace('clear_converted_text_cache: Delete '
+ library_namespace.string_digest(options.text));
}
delete this.converted_text_cache[options.text];
}
} else {
// console.trace(options);
if (false) {
console.trace('clear_converted_text_cache: Clear all cache');
// 剩下的大多是章節名稱。
var text_list = Object.keys(this.converted_text_cache);
if (text_list.length > 0) {
console.trace('clear_converted_text_cache: keys lift: '
+ text_list.join(', '));
}
}
delete this.converted_text_cache;
}
if (options.including_persistence)
delete this.converted_text_cache_persisted;
}
function convert_text_language(text, options) {
// @see function cache_converted_text(text_list, options)
if (!text || !text.trim() || !this.convert_to_language)
return text;
if (!this.convert_text_language_using.is_asynchronous)
return this.convert_text_language_using(text);
// 當無法取得文章內容時,可能出現 this.converted_text_cache === undefined
var converted_text_data = this.converted_text_cache[text];
if (converted_text_data && converted_text_data[KEY_converted_text]) {
if (false && text.length !== converted_text_data[KEY_converted_text].length) {
throw new Error('Different length:\n' + text + '\n'
+ converted_text_data[KEY_converted_text]);
}
if (options && options.persistence)
this.converted_text_cache_persisted[text] = converted_text_data;
return converted_text_data[KEY_converted_text];
}
if ((text in this.converted_text_cache_persisted)
&& this.converted_text_cache_persisted[text][KEY_converted_text]) {
return this.converted_text_cache_persisted[text][KEY_converted_text];
}
if (options && options.allow_non_cache) {
return text;
}
// console.log(this.converted_text_cache);
console.trace(library_namespace.string_digest(text, 200));
console.trace(this);
throw new Error(
// 照理不該到這邊。
'You should run `this.cache_converted_text(text_list)` first!');
}
// --------------------------------------------------------------------------------------------
// 這邊放的是一些會在 Work_crawler_prototype 中被運算到的數值。
/** {Natural}重試次數:下載失敗、出錯時重新嘗試下載的次數。同一檔案錯誤超過此數量則跳出。若值太小,在某些網站很容易出現圖片壞掉的問題。 */
Work_crawler.MAX_ERROR_RETRY = 4;
Work_crawler.HTML_extension = 'htm';
// 數值規範設定於 import_arg_hash @ CeL.application.net.work_crawler.arguments
var Work_crawler_prototype = {
// 所有的子檔案要修訂注解說明時,應該都要順便更改在CeL.application.net.work_crawler中Work_crawler.prototype內的母comments,並以其為主體。
// 下載檔案儲存目錄路徑。
// 圖片檔與紀錄檔的下載位置。下載網路網站的作品檔案後,將儲存於此目錄下。
// 這個目錄會在 work_crawler_loader.js 裡面被 setup_crawler() 之
// global.data_directory 覆寫。
main_directory : library_namespace.storage
// 決定預設的主要下載目錄 default_main_directory。
.determin_download_directory(true),
// id : '',
// site_id is also crower_id.
// <meta name="generator" content="site_id" />
// site_id : '',
// base_URL : '',
// charset : 'GBK',
// 預設自動匯入 .env.arg_hash
auto_import_args : true,
// 本工具下載時預設的使用者代理為 Chrome,所以下載的檔案格式基本上依循用 Chrome 瀏覽時的檔案格式。
// https://github.com/kanasimi/work_crawler/issues/548
// 下載每個作品更換一次 user agent。
// regenerate_user_agent : 'work',
default_user_agent : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36',
// default directory_name_pattern 預設作品目錄名稱模式。
directory_name_pattern : '${id_title}${directory_name_extension}',
/**
* {Natural|String}timeout for get_URL()
* 下載網頁或圖片的逾時等待時間。若逾時時間太小(如10秒),下載大檔案容易失敗。
*
* 注意: 因為 this.get_URL_options 在 constructor 中建構完畢,因此 timeout
* 會在一開始就設定。之後必須以 `this.setup_value('timeout', this.timeout);`
* 設定,否則沒有效果。
*/
timeout : '30s',
// 本站速度頗慢,必須等待較久否則容易中斷。
// timeout : '60s',
/** {Natural}重試次數:下載失敗、出錯時重新嘗試下載的次數。同一檔案錯誤超過此數量則跳出。若值太小,在某些網站很容易出現圖片壞掉的問題。 */
MAX_ERROR_RETRY : Work_crawler.MAX_ERROR_RETRY,
/** {Natural}圖片下載未完全,出現 EOI (end of image) 錯誤時重新嘗試的次數。 */
MAX_EOI_ERROR : Math.min(3, Work_crawler.MAX_ERROR_RETRY),
// {Natural}MIN_LENGTH:最小容許圖片檔案大小 (bytes)。
// 若值太小,傳輸到一半壞掉的圖片可能被當作正常圖片而不會出現錯誤。
// 因為當前尚未能 parse 圖像,而 jpeg 檔案可能在檔案中間出現 End Of Image mark;
// 因此當圖像檔案過小,即使偵測到以 End Of Image mark 作結,依然有壞檔疑慮。
//
// 對於極少出現錯誤的網站,可以設定一個比較小的數值,並且設定.allow_EOI_error=false。因為這類型的網站要不是無法獲取檔案,要不就是能夠獲取完整的檔案;要得到破損檔案,並且已通過EOI測試的機會比較少。
// MIN_LENGTH : 4e3,
// 對於有些圖片只有一條細橫桿的情況。
// MIN_LENGTH : 130,
// {Natural}預設所容許的章節最短內容字數。最少應該要容許一句話的長度。
MIN_CHAPTER_SIZE : 200,
// {String}預設的圖片類別/圖片延伸檔名/副檔名/檔案類別/image filename extension。
default_image_extension : 'jpg',
// cache directory below this.main_directory.
// 必須以 path separator 作結。
cache_directory_name : library_namespace.append_path_separator('cache'),
// archive directory below this.main_directory for ebook / old comics.
// 封存舊電子書、舊漫畫用的目錄。
// 必須以 path separator 作結。
archive_directory_name : library_namespace
.append_path_separator('archive'),
// log directory below this.main_directory 必須以 path separator 作結。
log_directory_name : library_namespace.append_path_separator('log'),
// 錯誤記錄檔案: 記錄無法下載的圖檔。
error_log_file : 'error_files.txt',
// 當從頭開始檢查時,會重新設定錯誤記錄檔案。此時會把舊的記錄檔改名成為這個檔案。
// 移動完之後這個值會被設定為空,以防被覆寫。
error_log_file_backup : 'error_files.'
+ (new Date).format('%Y%2m%2dT%2H%2M%2S') + '.txt',
// last updated date, latest update date. 最後更新日期時間。
// latest_chapter_url → latest_chapter_url
// latest_chapter_name, last_update_chapter → latest_chapter
// update_time, latest_update → last_update
// 這些值會被複製到記錄報告中,並用在 show_search_result() @ gui_electron_functions.js。
last_update_status_keys : 'latest_chapter,last_update_chapter,latest_chapter,latest_chapter_name,latest_chapter_url,last_update,update_time'
.split(','),
// 記錄報告檔案/日誌的路徑。
report_file : 'report.' + (new Date).format('%Y%2m%2dT%2H%2M%2S') + '.'
+ Work_crawler.HTML_extension,
report_file_JSON : 'report.json',
backup_file_extension : 'bak',
// default start chapter index: 1.
// 將開始/接續下載的章節編號。對已下載過的章節,必須配合 .recheck。
// 若是 start_chapter 在之前下載過的最後一個章節之前的話,那麼就必須要設定 recheck 才會有效。
// 之前下載到第8章且未設定 recheck,則指定 start_chapter=9 **有**效。
// 之前下載到第8章且未設定 recheck,則指定 start_chapter=7 **無**效。必須設定 recheck。
// start_chapter : 1,
start_chapter_NO : 1,
// 是否重新獲取每個所檢測的章節內容 chapter_page。
// 警告: reget_chapter=false 僅適用於小說之類不獲取圖片的情形,
// 因為若有圖片(parse_chapter_data()會回傳chapter_data.image_list),將把chapter_page寫入僅能從chapter_URL獲取名稱的於目錄中。
reget_chapter : true,
// 是否保留 chapter page。false: 明確指定不保留,將刪除已存在的 chapter page。
// 注意: 若是沒有設定 .reget_chapter,則 preserve_chapter_page 不應發生效用。
preserve_chapter_page : false,
// 是否保留作品資料 cache 於 this.cache_directory_name 下。
preserve_work_page : false,
// 是否保留損壞圖檔。
preserve_bad_image : true,
// 是否保留 cache
// preserve_cache : true,
// 當新獲取的檔案比較大時,覆寫舊的檔案。
// https://github.com/kanasimi/work_crawler/issues/242
overwrite_old_file : true,
// recheck:從頭檢測所有作品之所有章節與所有圖片。不會重新擷取圖片。對漫畫應該僅在偶爾需要從頭檢查時開啟此選項。default:false
// 每次預設會從上一次中斷的章節接續下載,不用特地指定 recheck。
// 有些漫畫作品分區分單行本、章節與外傳,當章節數量改變、添加新章節時就需要重新檢查/掃描。
// recheck='changed': 若是已變更,例如有新的章節,則重新下載/檢查所有章節內容。否則只會自上次下載過的章節接續下載。
// recheck='multi_parts_changed': 當有多個分部的時候才重新檢查。
// recheck : true,
// recheck=false:明確指定自上次下載過的章節接續下載。
// recheck : false,
//
// 當無法獲取 chapter 資料時,直接嘗試下一章節。在手動+監視下 recheck 時可併用此項。 default:false
// skip_chapter_data_error : true,
// 重新搜尋。default:false
// search_again : false,
// TODO: .heif
image_types : {
jpg : true,
jpeg : true,
// 抓取到非JPG圖片
png : true,
gif : true,
webp : true,
bmp : true
},
// 漫畫下載完畢後壓縮每個章節的圖像檔案。
archive_images : true,
// 完全沒有出現錯誤才壓縮圖片檔案。
// archive_all_good_images_only : true,
// 壓縮圖片檔案之後,刪掉原先的圖片檔案。 請注意:必須先安裝 7-Zip **18.01 以上的版本**。
remove_images_after_archive : true,
// or .cbz
images_archive_extension : 'zip',
// 由文章狀態/進程獲取用在作品完結的措辭。
finished_words : finished_words,
is_finished : is_finished,
full_URL : full_URL_of_path,
convert_text_language : convert_text_language,
cache_converted_text : cache_converted_text,
clear_converted_text_cache : clear_converted_text_cache,
// work_data properties to reset. do not inherit
// 設定不繼承哪些作品資訊。
reset_work_data_properties : {
limited : true,
// work_data.recheck
recheck : true,
download_chapter_NO_list : true,
// work_data.last_download
last_download : true,
start_chapter_NO_next_time : true,
error_images : true,
chapter_count : true,
image_count : true
}
};
Object.assign(Work_crawler.prototype, Work_crawler_prototype);
// Release memory. 釋放被占用的記憶體。
Work_crawler_prototype = null;
// --------------------------------------------------------------------------------------------
/**
* 重設瀏覽器識別 navigator.userAgent
*
* CeL.work_crawler.regenerate_user_agent(crawler)
*
* @return {String}瀏覽器識別
*/
function regenerate_user_agent(crawler) {
// 模擬 Chrome。
crawler.user_agent = crawler.default_user_agent
// 並且每次更改不同的 user agent。
.replace(/( Chrome\/\d+\.\d+\.)(\d+)/,
//
function(all, main_ver, sub_ver) {
return main_ver + (Math.random() * 1e4 | 0);
});
return crawler.user_agent;
}
// --------------------------------------------------------------------------------------------
// node.innerText
function get_label(html) {
return html ? library_namespace.HTML_to_Unicode(
html.replace(/<!--[\s\S]*?-->/g, '').replace(
/<(script|style)[^<>]*>[\s\S]*?<\/\1>/g, '').replace(
/\s*<br(?:[^\w<>][^<>]*)?>[\r\n]*/ig, '\n').replace(
/<\/?[a-z][^<>]*>/g, '')
// incase 以"\r"為主。 e.g., 起点中文网
.replace(/\r\n?/g, '\n')).trim().replace(
// \u2060: word joiner (WJ). /^\s$/.test('\uFEFF')
/[\s\u200B\u200E\u200F\u2060]+$|^[\s\u200B\u200E\u200F\u2060]+/g, '')
// .replace(/\s{2,}/g, ' ').replace(/\s?\n+/g, '\n')
// .replace(/[\t\n]/g, ' ').replace(/ {3,}/g, ' ' + ' ')
: '';
}
// modify from CeL.application.net
// 本函式將使用之 encodeURIComponent(),包含對 charset 之處理。
// @see function_placeholder() @ module.js
crawler_namespace.encode_URI_component = function encode_URI_component(
string, encoding) {
if (library_namespace.character) {
library_namespace.debug('採用 ' + library_namespace.Class
// 有則用之。 use CeL.data.character.encode_URI_component()
+ '.character.encode_URI_component', 1, module_name);
crawler_namespace.encode_URI_component = library_namespace.character.encode_URI_component;
return crawler_namespace.encode_URI_component(string, encoding);
}
return encodeURIComponent(string);
};
function full_URL_of_path(url, base_data, base_data_2) {
if (typeof url === 'function') {
url = url.call(this, base_data, base_data_2);
} else if (base_data) {
base_data = crawler_namespace.encode_URI_component(
String(base_data), url.charset || this.charset);
if (url.URL) {
url.URL += base_data
} else {
// assert: typeof url === 'string'
url += base_data;
}
}
if (!url) {
// error occurred: 未能解析出網址
return url;
}
// combine urls
if (typeof url === 'string' && !url.includes('://')) {
if (/^https?:\/\//.test(url)) {
return url;
}
if (url.startsWith('/')) {
if (url.startsWith('//')) {
// 借用 base_URL 之 protocol。
return this.base_URL.match(/^(https?:)\/\//)[1] + url;
}
// url = url.replace(/^[\\\/]+/g, '');
// 只留存 base_URL 之網域名稱。
return this.base_URL.match(/^https?:\/\/[^\/]+/)[0] + url;
} else {
// 去掉開頭的 "./"
url = url.replace(/^\.\//, '');
}
if (url.startsWith('.')) {
library_namespace.warn([ 'full_URL_of_path: ', {
// gettext_config:{"id":"invalid-url-$1"}
T : [ '網址無效:%1', url ]
} ]);
}
url = this.base_URL + url;
} else if (url.URL) {
url.URL = this.full_URL(url.URL);
}
return url;
}
// ----------------------------------------------------------------------------
function finished_words(status) {
status = String(status);
// e.g., https://syosetu.org/?mode=ss_detail&nid=33378
if (/^[(\[]?(?:完[結结成]?|Completed)[)\]]?$/i.test(status))
return status;
// e.g., 连载中, 連載中, 已完结, 已完成, 已完結作品, 已連載完畢, 已完/未完
// 已載完: https://www.cartoonmad.com/comic/1029.html
var matched = status.match(/(?:^|已)完(?:[結结成]|$)/);
if (matched)
return matched[0];
// 完本: http://book.qidian.com/
if ('完結済|完本|読み切り'.split('|').some(function(word) {
return status.includes(word);
})) {
return status;
}
// ck101: 全文完, 全書完
// MAGCOMI: 連載終了作品
// comico_jp: 更新終了
if (/全[文書]完|終了/.test(status)) {
return status;
}
// 已停更
}
function is_finished(work_data) {
if (!work_data)
return;
if ('is_finished' in work_data) {
return work_data.is_finished;
}
var status_list = library_namespace.is_Object(work_data) ? work_data.status
// treat work_data as status
: work_data, date;
if (!status_list) {
if (!this.no_checking_of_long_time_no_updated
// 檢查是否久未更新。
&& this.recheck
&& this.recheck !== 'force'
&& !work_data.recheck
&& library_namespace.is_Object(work_data)
&& (Date.now()
//
- (date = crawler_namespace.set_last_update_Date(work_data)))
// 因為沒有明確記載作品是否完結,10年沒更新就不再重新下載。
/ library_namespace.to_millisecond('1D') > (work_data.recheck_days || 10 * 366)) {
library_namespace.info([ 'is_finished: ', {
// gettext_config:{"id":"«$1»-has-not-been-updated.-$2-is-no-longer-forced-to-re-download.-it-will-only-be-re-downloaded-if-the-number-of-chapters-changes"}
T : [ '《%1》已 %2 沒有更新,時間過久不再強制重新下載,僅在章節數量有變化時才重新下載。'
//
, work_data.title, library_namespace.age_of(date) ]
} ]);
work_data.recheck = 'changed';
}
return status_list;
}
// {String|Array}status_list
if (!Array.isArray(status_list)) {
return this.finished_words(status_list);
}
var finished;
if (status_list.some(function(status) {
return finished = this.finished_words(status);
}, this)) {
return finished;
}
}
// --------------------------------------------------------------------------------------------
// export 導出.
// includes sub-modules
var module_name = this.id;
this.finish = function(name_space, waiting) {
library_namespace.run(
// @see work_crawler/*.js
'arguments,task,search,work,chapter,image,ebook'.split(',')
//
.map(function(name) {
return module_name + '.' + name;
}), waiting);
return waiting;
};
// @inner
Object.assign(crawler_namespace, {
// @see CeL.application.net.wiki
PATTERN_non_CJK : /^[\u0000-\u2E7F]*$/i,
get_label : get_label,
regenerate_user_agent : regenerate_user_agent,
// Simulates an XMLHttpRequest response.
// 模擬 XMLHttpRequest response。
null_XMLHttp : {
responseText : ''
}
});
return Work_crawler;
}